금요일

Redis WRONGTYPE 에러 완벽 해결 가이드

 🔍 검색 키워드: Redis WRONGTYPE 에러, Redis 데이터 타입 오류, Redis 자료구조, Redis 디버깅


Redis WRONGTYPE 에러 완벽 해결 가이드


증상: WRONGTYPE Operation against a key holding the wrong kind of value

Redis 명령어를 실행했는데 갑자기 이런 에러가 뜨나요?


(error) WRONGTYPE Operation against a key holding the wrong kind of value


이것이 WRONGTYPE 에러입니다. 이미 다른 자료구조로 저장된 키에 맞지 않는 명령어를 사용할 때 발생합니다.


원인 분석


원인 1: 키에 저장된 자료구조와 명령어 불일치

예를 들어 String로 저장된 키에 List 명령어(LPUSH)를 사용하면 에러가 납니다.


SET user:1:name "John"

LPUSH user:1:name "extra" 

결과: WRONGTYPE 에러 (String 키에 List 명령어 사용)


원인 2: 키 이름 재사용으로 인한 타입 충돌

여러 개발자가 같은 키 네이밍 규칙 없이 사용하다 보면 흔히 발생합니다.


원인 3: 캐시 데이터 구조 변경 후 기존 키 미정리

애플리케이션 로직이 바뀌어 자료구조를 변경했는데 기존 키가 남아있는 경우


해결 방법


방법 1: TYPE 명령어로 현재 키의 타입 확인

TYPE user:1:name

결과: string 또는 list, hash, set, zset 중 하나 반환


먼저 문제가 되는 키의 실제 타입을 확인하는 것이 첫 걸음입니다.


방법 2: 올바른 명령어로 재시도

확인된 타입에 맞는 명령어를 사용합니다.

- String이면: GET, SET, INCR

- List면: LPUSH, RPUSH, LRANGE

- Hash면: HSET, HGET, HGETALL

- Set이면: SADD, SMEMBERS

- Sorted Set이면: ZADD, ZRANGE


방법 3: 키를 삭제하고 재생성

기존 키의 데이터가 더 이상 필요 없다면 DEL로 삭제 후 새로 생성합니다.


DEL user:1:name

LPUSH user:1:name "value1" "value2"


주의: 프로덕션 환경에서는 DEL 실행 전 반드시 데이터 백업 여부를 확인해야 합니다.


방법 4: 키 네이밍 컨벤션 도입으로 예방

프로젝트 차원에서 키 이름에 자료구조를 명시하는 규칙을 도입하면 재발을 막을 수 있습니다.


예시:

- str:user:1:name (String)

- list:user:1:orders (List)

- hash:user:1:profile (Hash)

- set:user:1:tags (Set)


방법 5: 애플리케이션 코드에서 사전 타입 체크

Redis 클라이언트 라이브러리에서 명령어 실행 전 TYPE 체크 로직을 추가하면 에러를 사전에 방지할 수 있습니다. 특히 배치 작업이나 마이그레이션 스크립트에서 유용합니다.


정리

- WRONGTYPE은 키의 실제 자료구조와 명령어가 맞지 않을 때 발생

- TYPE 명령어로 먼저 확인하는 것이 가장 빠른 진단법

- 키 네이밍 컨벤션으로 예방하는 것이 근본적인 해결책

- 프로덕션에서 DEL 사용 시 반드시 백업 확인


Redis는 스키마리스 데이터베이스이기 때문에, 팀 차원의 키 관리 규칙이 없으면 이런 문제가 반복될 수 있습니다.

Prisma N+1 쿼리 문제 완벽 해결

 🔍 검색 키워드: Prisma N+1 쿼리, include relation, select 최적화, Prisma 성능 튜닝, 데이터베이스 쿼리 최적화


Prisma N+1 쿼리 문제 완벽 해결


증상: 데이터 조회 시 데이터베이스 쿼리가 너무 많다

사용자 목록 조회 후 각 사용자의 프로필을 가져오려니 쿼리가 100개 이상 날아가나요?


1. 사용자 목록 조회 (1개 쿼리)

2. 각 사용자의 프로필 조회 (N개 쿼리)

결과: 1 + N = 101개 쿼리!


이것이 N+1 쿼리 문제입니다. 성능 저하, 데이터베이스 부하 증가를 초래합니다.


원인 분석


원인 1: 관계 데이터를 include/select 없이 조회

const users = await prisma.user.findMany();

for (const user of users) {

  const profile = await prisma.profile.findUnique({

    where: { userId: user.id }

  });

}

결과: 1 + N개 쿼리


원인 2: 중첩 관계에서 include 누락

const posts = await prisma.post.findMany();

for (const post of posts) {

  const author = await prisma.user.findUnique({

    where: { id: post.authorId }

  });

  const comments = await prisma.comment.findMany({

    where: { postId: post.id }

  });

}

결과: 1 + N + N*M개 쿼리


해결 방법


✅ 방법 1: include로 관계 데이터 포함 (기본)

const users = await prisma.user.findMany({

  include: {

    profile: true

  }

});

1개 쿼리로 완료!


✅ 방법 2: select로 필요한 컬럼만 선택 (최적화)

const users = await prisma.user.findMany({

  select: {

    id: true,

    email: true,

    profile: {

      select: {

        bio: true,

        avatar: true

      }

    }

  }

});

데이터양 감소, 성능 향상


✅ 방법 3: where 조건으로 불필요한 데이터 필터링

const post = await prisma.post.findUnique({

  where: { id: 1 },

  include: {

    comments: {

      where: { approved: true },

      orderBy: { createdAt: 'desc' },

      take: 10

    }

  }

});

효율적!


결론

- include로 관계 데이터 포함 — N+1의 기본 해결

- select로 필요한 필드만 — 성능 + 보안

- where로 DB 수준 필터링 — 불필요한 데이터 전송 방지

- 중첩은 2단계까지 — 복잡도와 성능의 균형

- 쿼리 로깅으로 검증 — 개발 중 문제 조기 발견


Prisma는 ORM이지만, SQL 수준의 최적화 의식을 가져야 효율적인 쿼리를 만들 수 있습니다.

TypeScript 제네릭 타입 추론 완벽 정복

🔍 검색 키워드: TypeScript 제네릭 타입 추론, 제네릭 제약 조건, Generic extends, TypeScript 고급 타입, 타입 안전성


TypeScript 제네릭 타입 추론 완벽 정복


증상: 제네릭이 자동 추론되지 않는다

함수에 제네릭을 사용했는데 타입이 unknown이거나 명시적으로 지정해야 하나요?


이것이 제네릭 타입 추론의 복잡성입니다. 동작하지 않으면 수동 타입 지정이 필요하지만, 올바른 제약 조건을 설정하면 자동 추론이 가능합니다.


원인 분석


원인 1: 제약 조건 없는 제네릭

function processArray<T>(arr: T[]): T[] {

  return arr.sort(); // ❌ 에러: T에 sort가 있다는 보장 없음

}


원인 2: 여러 제네릭 간 관계 미정의

function transform<T, U>(input: T): U {

  return input as U; // ❌ 위험한 as 사용

}


해결 방법


✅ 방법 1: 제약 조건으로 구조 정의 (extends)

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {

  return obj[key]; // ✓ key는 T의 키만 가능

}


✅ 방법 2: infer로 중첩 타입 추출

type GetArrayElement<T> = T extends Array<infer U> ? U : never;


type NumArray = GetArrayElement<number[]>; // number ✓


✅ 방법 3: 조건부 타입으로 분기

type IsString<T> = T extends string ? true : false;


type A = IsString<'hello'>; // true ✓

type B = IsString<number>; // false ✓


결론

- 제약 조건(extends)은 필수 — 타입 안전성과 추론 향상

- infer로 타입 추출 — 제네릭 값을 유연하게 활용

- 조건부 타입으로 분기 — 복잡한 타입 관계 표현

- 오버로드로 정확하게 — 여러 입출력 조합 처리


제네릭을 마스터하면 TypeScript의 진정한 강력함을 경험할 수 있습니다.