
Redis로 애플리케이션을 개발하는 일은 무척 흥미롭지만, 다른 기술과 마찬가지로 설계 과정에서 몇 가지 유의해야 할 사항이 있습니다. 관계형 데이터베이스 개발 경험이 있다면 많은 부분이 익숙하겠지만, Redis는 인메모리 데이터베이스이며 대부분 단일 스레드(single-threaded)로 동작한다는 점을 기억해야 합니다. 이 글에서는 Redis 키를 다룰 때 알아두면 좋은 베스트 프랙티스를 살펴봅니다.
따라서 Redis를 사용할 때 주의해야 할 특징적인 사항들은 다음과 같습니다.
1. Redis 네임스페이스로 키 체계적으로 관리하기
데이터베이스는 데이터를 저장하지만, 개발자라면 누구나 Redis에 저장한 데이터의 일부를 놓치기 쉽습니다. 애플리케이션 요구사항이 바뀌거나 데이터 저장 방식을 변경하는 것은 자연스러운 일이며, 그 과정에서 일부 키에 EXPIRE(만료 설정)를 적용하는 것을 잊었거나, 애플리케이션의 특정 모듈이 이미 폐기되었을 수도 있습니다.
어떤 이유든, Redis 데이터베이스에는 더 이상 사용되지 않으면서 불필요하게 공간만 차지하는 데이터가 남아 있기 마련입니다. Redis는 스키마가 없는(schema-less) 구조이기 때문에, 키에 체계적인 명명 규칙을 사용하지 않으면 데이터셋의 내용을 파악하기가 매우 어렵습니다. Redis 네임스페이스를 활용한 적절한 키 명명 방식을 적용하면 데이터베이스 관리 작업이 한결 수월해집니다.
애플리케이션이나 서비스 단위로 키를 네임스페이싱할 때는 콜론(':') 문자로 키 이름의 각 부분을 구분하는 것이 일반적인 컨벤션이며, 대표적인 Redis 네임스페이스 베스트 프랙티스입니다. 이렇게 해두면 데이터 마이그레이션, 변환, 삭제, 이동 작업 시 해당 키들을 손쉽게 식별할 수 있습니다. Redis 네임스페이스와 네임스페이스 키가 바로 이러한 식별 작업을 돕습니다.
네임스페이스 외에도 Redis가 자주 활용되는 용도가 하나 더 있습니다. 대부분의 데이터는 다른 데이터베이스(예: PostgreSQL, MongoDB)에 보관하고, "핫(hot)" 데이터만 Redis에 저장하는 보조 데이터 스토어로 사용하는 방식입니다. 그런데 이 경우 개발자들이 원본 데이터 저장소에서 데이터가 옮겨진 뒤에도 Redis에서 해당 데이터를 삭제하는 것을 잊곤 합니다. 이런 교차 데이터스토어 의존성에는 연쇄 삭제(cascading delete)가 필요하며, 특정 데이터 항목의 모든 식별자를 Redis 세트(set)에 모아두면 구현할 수 있습니다. 이렇게 하면 원본 저장소에서 삭제된 후 실행되는 정리(cleanup) 절차가 해당 세트의 내용만 순회하면서 관련 복사본과 부수 데이터를 모두 제거할 수 있습니다(작업 완료 후 세트 자체까지 삭제).
2. 키 이름의 길이 관리하기
앞서 소개한 네임스페이스 활용법과 모순되어 보일 수 있지만, 키 이름 역시 메모리를 차지하기 때문에 최대한 짧게 유지하는 것이 좋습니다. 물론 수백만~수십억 개의 키로 구성된 데이터셋에서야 문제가 두드러지지만, 사실 긴 키 이름은 어떤 해시테이블에서든 비용을 발생시킵니다.
예를 들어, 32자 값(value)을 가진 100만 개의 키를 저장한다고 가정해 보겠습니다. 6자 키 이름을 사용하면 약 96MB가 소요되지만, 12자 키 이름을 사용하면 111MB까지 늘어납니다(32비트 Redis 서버 기준). 15%가 넘는 이 오버헤드는 키 수가 늘어날수록 상당히 커집니다. 참고로 Redis에서는 접두사(prefix)를 기준으로 키를 삭제하는 방식도 가능합니다.
3. 적절한 데이터 구조 선택하기
메모리 사용량이나 성능 측면에서, 때로는 어떤 데이터 구조가 다른 구조보다 데이터셋에 더 잘 맞는 경우가 있습니다. 기억해 둘 만한 베스트 프랙티스 몇 가지를 소개합니다.
수천~수백만 개의 독립적인 문자열(string) 값으로 데이터를 저장하는 대신, 관련 데이터를 해시(hash) 구조로 묶는 방식을 고려해 보세요. 해시는 매우 효율적이며 메모리 사용량을 줄여줍니다. 여기에 일부 세부 사항을 추상화해 코드 가독성을 높이는 부가적인 장점도 있습니다.
가능하다면 세트(set) 대신 리스트(list)를 사용하세요. 고유성 보장이나 멤버십 검사 같은 세트의 특성이 필요 없다면, 리스트가 메모리를 덜 사용하고 삽입 속도도 더 빠릅니다.
정렬 세트(sorted set)는 메모리 소모와 기본 연산 복잡도(예: ZADD로 새 멤버 추가) 면에서 가장 비용이 큰 데이터 구조입니다. 단순히 점수 조회만 필요하고 순서가 중요하지 않다면 해시 사용을 고려해 보세요.
Redis에서 자주 간과되는 기능으로 비트맵(bitmap), 즉 비트셋(bitset, v2.2부터 지원)이 있습니다. 비트셋을 사용하면 Redis 값에 대해 여러 비트 단위 연산을 수행할 수 있어, 대량의 데이터를 효율적으로 저장할 수 있습니다. 예를 들어 가벼운 분석(analytics) 처리에 활용할 수 있습니다.
4. SCAN을 사용하고, KEYS는 절대 사용하지 않기
SCAN 명령은 Redis v2.8부터 제공되며, 커서(cursor)를 사용해 키스페이스의 키를 조회할 수 있게 해줍니다. 이 동작 방식은 매칭되는 모든 요소를 한 번에 반환하는 KEYS 명령과 다릅니다. KEYS는 Redis 서버를 블로킹하거나 RAM 자원을 고갈시킬 수 있어 프로덕션 환경에서는 위험한 명령으로 간주됩니다. 반면 SCAN은 서버를 블로킹하거나 슬레이브(slave)에 의존할 필요 없이 안전하게 데이터를 조회할 수 있습니다.
SCAN은 이후 호출에 전달할 커서 값을 읽어야 한다는 점에 유의하세요. 또한 SCAN은 키 이름 패턴과 선택적인 count 인자를 받을 수 있습니다. SCAN과 KEYS의 또 다른 차이점은, SCAN에서는 같은 키 이름이 두 번 이상 반환될 수 있다는 점입니다.
SCAN과 함께 SSCAN, HSCAN, ZSCAN도 제공되며, 각각 세트, 해시, 정렬 세트의 내용을 순회(iterate)할 수 있게 해줍니다.
5. 서버 사이드 Lua 스크립트 활용하기
개발자 입장에서 Redis가 Lua 스크립트를 실행할 수 있다는 사실을 알게 되면 익숙한 영역에 들어선 기분일 겁니다. 배우기 가장 쉬운 언어 중 하나인 Lua를 활용하면 Redis 서버 내부에서 직접 동작하는 코드로 창의력을 발휘할 수 있습니다. 올바르게 적용하면 Lua 스크립트는 성능과 리소스 소모 측면에서 놀라운 차이를 만들어냅니다. 데이터를 (애플리케이션의) CPU 쪽으로 가져오는 대신, 스크립트를 통해 데이터 근처에서 로직을 실행함으로써 네트워크 지연과 불필요한 데이터 전송을 줄일 수 있습니다.
Lua의 극적인 효과를 보여주는 전형적인 예로, Redis에서 대량의 데이터를 가져온 뒤 애플리케이션에서 필터링하거나 집계하는 경우를 들 수 있습니다. 처리 워크플로우를 스크립트로 캡슐화하면, 스크립트를 호출하는 것만으로 훨씬 작은 결과값을 훨씬 적은 시간과 리소스로 얻을 수 있습니다.
프로 팁: Lua는 훌륭하지만, 워크플로우를 Lua로 옮기고 나면 에러 보고와 처리가 더 어려워질 수 있습니다(어차피 Redis 서버 내부에서 실행되니까요). 이를 우회하는 영리한 방법 중 하나는 Redis의 Pub/Sub을 활용해 스크립트가 "로그" 메시지를 전용 채널에 publish하도록 하는 것입니다. 그런 다음 subscriber 프로세스를 구성해 이 메시지를 수신하고 적절히 처리하면 됩니다.
Redis를 활용하는 과정에서 얻게 될 중요한 팁은 아직 많겠지만, 이 목록만으로도 가장 중요한 몇 가지를 익히며 시작하기에 충분할 것입니다. 공유하고 싶은 다른 제안이나 피드백, 질문이 있다면 언제든 편하게 알려주세요. 저는 언제나 대기 중입니다 :)