Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Redis 데이터 구조 완벽 가이드: 올바른 선택과 성능 최적화 전략

Redis를 처음 접하시거나 제공되는 기능을 다시 한번 정리하고 싶으신 분이라면, 이 가이드가 Redis가 지원하는 모든 데이터 구조를 이해하는 데 큰 도움이 될 것입니다.

올바른 구조 선택하기

Redis의 데이터 구조는 단순합니다. 어떤 구조도 해결하려는 문제에 완벽하게 들어맞지는 않을 수 있지만, 데이터에 적합한 초기 구조를 잘 선택하면 Redis 명령어들이 효율적인 방향으로 안내해 줍니다. 많은 Redis 명령어에는 해당 명령이 다루는 기반 데이터 구조의 유형을 나타내는 접두사(prefix)가 붙어 있습니다.

범용 데이터 구조

접두사 주요 용도 주의사항 및 흔한 오용
Hash H 객체 저장, 사전(dictionary) 객체로 처리할 수 있는 모든 데이터 Redis 자체와 마찬가지로 해시에서 사용자가 겪는 가장 큰 문제 중 하나는 키 관리입니다. 불필요한 키와 값으로 해시가 가득 차지 않도록 관리 체계를 반드시 마련하세요.
List L 큐, 스택, 순환 리스트 대용량 리스트에 대한 랜덤 액세스는 피하고, 큐 적체 발생 여부에 항상 주의하세요.
Set S 태깅 시스템, 시계열 데이터 버킷 교집합과 합집합 연산은 신중하게 사용해야 하며, 셋의 크기는 작게 유지하는 것이 좋습니다. 고유 값(unique) 카운팅에는 셋이 최선이 아니며, 대신 HyperLogLog를 사용하세요!
Sorted Set Z 스코어보드(리더보드), 사전식 검색, 랜덤 액세스 리스트 ZSet은 가장 다재다능한 구조 중 하나이지만 동시에 비용이 가장 큰 구조이기도 합니다. Big O를 항상 염두에 두세요. 자동완성 기능에 정렬된 셋을 사용한다면 사전식 검색을 처리하는 ZRANGEBYLEX를 살펴보세요(단, UTF-8 처리에는 주의가 필요합니다!).
Stream X 시계열, 큐, 메시지 분배 스트림에 크기 제한(cap)을 설정하지 않는 실수가 흔합니다. 또한 분산 메시징 시스템(특히 Apache Kafka)의 기능과 혼동하는 경우도 자주 있습니다.
String (없음) 단순 캐시, 카운터, 비트 조작 매우 큰 값에는 주의하세요. 특정 네트워크 환경이나 Redis 클라이언트에서 GET/SET 시 원인을 찾기 어려운 장애를 일으킬 수 있습니다. SETNX로 잠금(locking) 시스템을 구축하면 스스로 문제를 일으키기 쉽습니다.

특수 목적 데이터 구조

Redis는 특정 타입의 특수한 활용 사례를 다루기 위한 다양한 연산을 제공하며, 내부적으로는 위에서 소개한 범용 데이터 타입 중 하나를 기반으로 동작합니다.

접두사 기반 타입 주요 용도 주의사항 및 흔한 오용
Bitmap BIT String 공간 효율적인 플래그 저장, 분석 기능에 대한 오해, 또는 비트맵 기반 시스템에서 복잡한 계산 관리를 놓치는 경우가 많습니다.
Counter INCR, DECR String 공간 효율적인 통계 분석 64비트 부호 있는 정수 범위를 초과하면 오류가 반환됩니다.
Geohash GEO Sorted Set 매장 위치 검색, 근처 사용자 찾기 장거리 거리 계산이나 특수한 좌표계 투영, 정교한 지리 정보 함수가 필요한 경우에는 적합하지 않습니다.
HyperLogLog PF String 분석용 고유 값(unique) 카운팅 여러 HLL을 병합하는 작업은 비용이 클 수 있습니다. PFMERGE에 지정하는 파라미터 수에 상한을 두는 것이 좋습니다.
Pub/Sub PUB, SUB, PSUB (없음) 휘발성 메시지 전달 영속성 없이 단순하고 빠르게 메시지를 전달합니다. 대부분의 사용 사례에서는 Stream 타입이 더 나은 선택입니다.

Big O 활용하기

데이터 구조의 접근 패턴을 잘못 사용하는 것은 사전을 잘못 사용하는 것과 같습니다. 단어 하나를 찾으려고 모든 페이지를 넘겨서는 안 됩니다! 모든 Redis 명령어의 시간 복잡도는 redis.io에 문서화되어 있으니 참고하세요.

Big O는 어떤 대상이 "극한 상황에서 어떻게 동작하는지"를 표현하는 방법입니다. 쉽게 말해, 시스템 내 데이터 양이 증가함에 따라 연산 성능이 어떻게 변화하는지를 나타냅니다. O(1) 연산은 데이터 양과 무관하게 항상 일정한 시간 안에 완료됩니다. O(n) 연산은 데이터 양에 비례해 선형적으로 느려지고, O(n²) 연산은 데이터가 늘어날수록 기하급수적으로 느려집니다.

보다 깊이 있는 학습을 원한다면 샘플 코드와 그래프가 포함된 Big O 입문 자료를 참고해 보세요.

좋은 시스템 설계에는 기본적인 알고리즘 분석 이상의 것이 필요하지만, 기본기를 가끔씩 되새기고 애플리케이션에서 데이터 접근의 전체 복잡도를 점검해 보는 것은 그만한 가치가 있습니다.

연산 크기는 작게 유지하기

Redis는 싱글 스레드(single-threaded)로 동작합니다. 덕분에 동작을 예측하기 쉽지만, 멀티스레드 데이터 접근에만 익숙한 사람에게는 의외의 결과를 가져올 수도 있습니다. Redis에서는 오래 실행되는 연산이 다른 데이터베이스보다 더 위험합니다. Redis는 한 번에 하나의 작업만 처리하기 때문에, 길게 실행되는 연산 하나가 시스템 전체의 병목을 일으킬 수 있습니다.

데이터 접근 연산을 아주 세밀하게(granular) 나누는 것이 Redis에서 좋은 성능을 내는 핵심입니다. 10만 개 요소를 가진 컬렉션에 대한 O(N) 연산 1회보다 O(1) 연산 10만 회가, 네트워크 및 프로토콜 파싱 비용을 감안하더라도 더 나은 선택일 수 있습니다.

그렇다면 시스템의 병목 지점은 어떻게 찾을 수 있을까요? SLOWLOG를 정기적으로 확인하세요(RedisGreen 고객이라면 대시보드의 "Slow Queries" 탭만 확인하면 됩니다). 개별 연산이 오래 실행될수록 시스템이 수행하려는 다른 모든 작업이 느려지므로, 연산은 항상 짧게 유지하세요!