🔍 검색 키워드: Redis WRONGTYPE 에러, Redis 데이터 타입, Redis 명령어 호환성, INCR 문자열, Redis 키 재사용
Redis WRONGTYPE 에러: 데이터 타입 불일치 해결
Redis는 강타입 데이터베이스입니다. 같은 키에 다른 타입의 데이터를 저장하려고 하면 WRONGTYPE 에러가 발생합니다. 흔히 개발 중에 같은 키명을 여러 목적으로 재사용할 때 마주치는 실수입니다. 이 글에서는 에러 원인과 해결 방법을 실제 상황으로 설명하겠습니다.
1. 증상: WRONGTYPE 에러 메시지
ERR WRONGTYPE Operation against a key holding the wrong kind of value
at Object.writeError (/app/node_modules/redis/lib/reply.js:71:15)
at Socket.onreply (/app/node_modules/redis/lib/index.js:304:17)
또는 Redis CLI에서:
> set user:1 "{name: john}"
OK
> lpush user:1 "new_value"
(error) WRONGTYPE Operation against a key holding the wrong kind of value
2. 원인 분석
Redis는 5가지 기본 데이터 타입을 가집니다:
- String:
set,get,incr등 - List:
lpush,rpush,lrange등 - Set:
sadd,scard,smembers등 - Hash:
hset,hget,hgetall등 - Sorted Set:
zadd,zscore,zrange등
원인 1: 같은 키를 다른 타입으로 사용
// 기존 코드: user:1을 String으로 사용
await redis.set('user:1', JSON.stringify({ name: 'john' }));
// 신규 코드: 같은 키를 List로 변경
await redis.lpush('user:1', 'new_value');
// ❌ WRONGTYPE 에러!
원인 2: 개발 중 타입 변경
// 처음에는 값 저장 (String)
redis.set('counter', '10');
// 나중에 증가시키려고 시도 (INCR은 String 전용)
redis.incr('counter'); // 기존 값이 JSON이면 에러
// 또는 반대로
redis.lpush('counter', 1); // 이제 List
redis.incr('counter'); // WRONGTYPE!
원인 3: TTL 만료 후 타입 재사용
redis.set('session:abc', 'token123', 'EX', 3600); // String으로 저장
// TTL이 지나 자동 삭제됨
// 몇 시간 후...
redis.hset('session:abc', 'user_id', '123'); // Hash로 저장 시도
// 만약 키가 남아있으면 WRONGTYPE!
3. 해결 방법
방법 1: 기존 키 삭제 후 재생성
// 안전한 재설정
await redis.del('user:1');
await redis.lpush('user:1', 'value1', 'value2');
방법 2: 다른 키명 사용
// AS-IS: 충돌하는 키
const key = 'user:1';
// TO-BE: 타입 명시하는 키명
const userKey = 'user:1:string'; // String용
const userListKey = 'user:1:list'; // List용
const userHashKey = 'user:1:hash'; // Hash용
// 또는 버전 붙이기
const userKeyV2 = 'user:1:v2';
방법 3: 타입 확인 후 처리
// Node.js Redis 클라이언트
async function safeSet(key, value, type = 'string') {
const existingType = await redis.type(key);
if (existingType !== 'none' && existingType !== type) {
console.warn(`키 '${key}'의 기존 타입: ${existingType}, 요청 타입: ${type}`);
await redis.del(key); // 기존 데이터 삭제
}
if (type === 'string') {
await redis.set(key, value);
} else if (type === 'list') {
await redis.lpush(key, value);
} else if (type === 'hash') {
await redis.hset(key, ...Object.entries(value).flat());
}
}
// 사용
await safeSet('user:1', { name: 'john' }, 'hash');
방법 4: Redis에서 타입 조회
# 기존 모든 키와 타입 확인
$ redis-cli KEYS '*' | while read key; do echo -n "$key: "; redis-cli TYPE "$key"; done
# 특정 패턴 확인
$ redis-cli SCAN 0 MATCH "user:*" | xargs -I {} redis-cli TYPE {}
방법 5: 마이그레이션 스크립트
// 전체 키를 새 구조로 마이그레이션
const redis = require('redis');
const client = redis.createClient();
async function migrateKeys() {
const keys = await new Promise((resolve, reject) => {
let allKeys = [];
const stream = client.scanStream();
stream.on('data', (keys) => allKeys.push(...keys));
stream.on('end', () => resolve(allKeys));
stream.on('error', reject);
});
for (const key of keys) {
if (key.startsWith('user:')) {
const type = await client.type(key);
if (type === 'string') {
const value = await client.get(key);
await client.del(key);
await client.hset(`${key}:v2`, 'data', value);
}
// 다른 타입도 처리...
}
}
console.log('마이그레이션 완료');
}
await migrateKeys();
4. 타입별 명령어 정리
| 명령어 | 대상 타입 | 예제 |
|---|---|---|
set / get |
String | set key value |
lpush / lrange |
List | lpush list_key value |
sadd / smembers |
Set | sadd set_key member |
hset / hget |
Hash | hset hash_key field value |
zadd / zrange |
Sorted Set | zadd zset_key 1 member |
incr |
String만 | incr counter |
lpop |
List만 | lpop list_key |
5. 예방 가이드
개발 단계:
- 키 설계 시 타입 명시:
{domain}:{id}:{type} - 예:
user:123:string,session:456:hash,queue:789:list
테스트 단계:
- Redis 모의 객체로 타입 검증
- 개발/스테이징 Redis 정기 초기화
배포 단계:
- 마이그레이션 스크립트로 기존 키 처리
- TTL 만료 후 재사용 금지 정책 수립
WRONGTYPE 에러는 Redis를 처음 다룰 때 흔한 실수입니다. 핵심은 같은 키에는 하나의 타입만 유지하는 것. 타입을 바꿔야 한다면 새 키를 사용하거나, 기존 데이터를 명시적으로 삭제하세요. 이를 통해 안정적인 Redis 운영이 가능합니다.
댓글 없음:
댓글 쓰기