콘텐츠로 이동

Data Structure

Redis는 다양한 자료구조를 지원하며, 각 자료구조의 내부 동작 방식과 시간 복잡도를 이해하는 것은 Redis의 성능을 최적화하는 데 중요하다.

레디스의 각 자료구조는 외부로 보이는 추상 타입(String·List·Hash 등)은 동일하지만, 내부적으로는 원소 개수와 값 크기에 따라 서로 다른 물리 구현(인코딩)으로 저장된다.

  • 인코딩 전환 이유
    • 원소가 적고 작은 컬렉션은 메모리 지역성이 높은 컴팩트 포맷(listpack/intset 등)에 저장해 메모리 사용량을 줄이고 CPU 캐시 친화성을 높임
    • 규모가 커지면 O(logN)/O(1) 연산을 유지하기 위해 해시 테이블·스킵 리스트 같은 구조로 자동 전환
  • 전환 방향: 한 번 큰 인코딩으로 승격되면 원소가 다시 줄어들어도 대부분 되돌아가지 않음(CONFIG SET이나 재생성 필요)
  • 현재 인코딩은 OBJECT ENCODING key로 확인 가능

Redis에서 가장 기본적인 자료구조로, key 하나에 value 하나가 대응되는 구조다.

  • 문자열뿐만 아니라 이미지, JSON, 직렬화된 객체 등 다양한 데이터 저장 가능
  • 메모리 제한: 최대 512MB까지의 문자열 저장 가능
  • 활용 사례: 간단한 캐시 / 세션 관리 / 카운터(예: 방문자 수, 좋아요 수 등) 등
  • 내부 인코딩
    • int: 값이 long 범위의 정수로 표현 가능할 때(포인터 자리에 정수를 그대로 저장)
    • embstr: 44바이트 이하 짧은 문자열. 문자열 헤더와 데이터를 한 번의 메모리 할당으로 연속 배치
    • raw: 45바이트 이상 긴 문자열. 헤더와 데이터를 분리 할당
명령어기능예시
INCR / INCRBY / DECR / DECRBY숫자 값을 원자적으로 증가 또는 감소INCR counter
INCRBY counter 5
NX 옵션지정한 키가 없을 때만 저장SET key2 "value2" NX
XX 옵션지정한 키가 있을 때만 값 업데이트SET key1 "new_value" XX
MSET여러 키의 값을 한 번에 설정MSET key1 "value1" key2 "value2"
MGET여러 키의 값을 한 번에 조회MGET key1 key2
APPEND기존 값에 데이터를 추가APPEND key1 "suffix"
GETRANGE문자열의 부분을 가져오기 (서브스트링)GETRANGE key1 0 3
SETRANGE문자열의 특정 위치부터 값을 설정SETRANGE key1 6 "world"

순서를 가지는 문자열 목록으로, 양 끝 삽입·제거와 인덱스 접근을 지원한다.

  • 메모리 제한: 하나의 리스트에 최대 42억여 개의 아이템 저장 가능
  • 활용 사례: 메시지 큐 / 대기열 관리 / 채팅 로그 저장 / 이벤트 스트리밍 등
  • 시간 복잡도
    • LPUSH / RPUSH / LPOP / RPOP: O(1)
    • 인덱스나 데이터를 이용한 중간 데이터 조회: O(N)
  • 내부적으로는 quicklist로 구현 → 여러 개의 listpack 노드를 양방향 연결 리스트로 이은 구조
    • 작은 단위(listpack)에 모아 메모리 지역성을 유지하면서, 노드 간에는 연결 리스트라 양 끝 O(1) 연산이 가능
    • list-max-listpack-size(기본 -2, 노드당 8KB 상한)로 노드 크기 조절
명령어기능예시
LPUSH / RPUSH리스트의 왼쪽 또는 오른쪽에 아이템 추가LPUSH mylist "first"
RPUSH mylist "second"
LPOP / RPOP리스트의 왼쪽 또는 오른쪽에서 아이템 제거LPOP mylist
LRANGE리스트의 범위를 조회LRANGE mylist 0 -1
LINSERT리스트의 특정 위치에 아이템 삽입LINSERT mylist BEFORE "second" "middle"
LINDEX / LSET리스트의 인덱스로 아이템 조회 및 수정LINDEX mylist 1
LSET mylist 0 "new_value"
LTRIM리스트의 범위를 잘라내어 저장 (자르기)LTRIM mylist 0 99
BLPOP / BRPOP리스트의 왼쪽 또는 오른쪽에서 아이템 블로킹 제거BLPOP mylist 0
RPUSHX리스트가 있을 때만 추가RPUSHX mylist "value"

기존 RPOP / LPOP 명령어에 블로킹 기능을 추가한 명령어로, 리스트에 아이템이 추가될 때까지 대기하다가 아이템이 추가되면 해당 아이템을 반환한다.

  • BLPOP / BRPOP key [key …] timeout
    • key: 대상 리스트의 키
    • timeout: 블로킹 시간(초)
  • 대기 시간 초과 시 nil 반환
  • 리스트의 키와 아이템 값 반환
    • 동시에 여러 리스트에서 가져올 수 있기 때문에, 가져온 값을 구분하기 위해 리스트의 키를 함께 반환

키 안에 또 다른 필드-값 쌍으로 구성된 데이터를 저장하는 자료구조로, 작은 객체나 테이블 데이터를 저장하는 데 적합하다.

  • 활용 사례: 사용자 프로필 / 상품 정보 저장
  • 내부 인코딩
    • listpack: 필드 수와 값 길이가 모두 작을 때 사용 → 포인터 없이 연속 메모리로 저장해 메모리 오버헤드 최소화
    • hashtable: 임계값 초과 시 일반 해시 테이블로 전환 → O(1) 필드 접근 유지
    • 전환 임계값: hash-max-listpack-entries, hash-max-listpack-value(기본 64바이트) 중 하나라도 초과하면 hashtable로 승격
명령어기능예시
HSET필드-값 쌍 저장HSET user:1000 name "John"
HGET필드 값 가져오기HGET user:1000 name
HMSET여러 필드-값 쌍 설정HMSET user:1001 name "Alice" age 30
HDEL필드 삭제HDEL user:1000 age
HEXISTS필드 존재 여부 확인HEXISTS user:1000 name
HGETALL모든 필드-값 쌍 조회HGETALL user:1000

순서가 없고, 중복된 데이터를 허용하지 않는 문자열의 집합을 저장하는 자료구조다.

  • 활용 사례: 태그 저장 / 고유 사용자 저장
  • 내부 인코딩(삼단 전환)
    • intset: 모든 원소가 정수이고 개수가 set-max-intset-entries(기본 512) 이하일 때. 정렬된 배열로 이진 탐색 O(logN), 최소 메모리
    • listpack: 원소 중 하나라도 문자열이고 크기가 작을 때(Redis 7.2+). set-max-listpack-entries(기본 128), set-max-listpack-value(기본 64바이트)
    • hashtable: 위 조건을 벗어나면 해시 테이블로 승격 → O(1) 멤버십 조회 유지
명령어기능예시
SADD값 추가SADD myset "apple" "banana"
SREM값 제거SREM myset "apple"
SINTER두 집합의 교집합SINTER myset yourset
SUNION두 집합의 합집합SUNION myset yourset
SISMEMBER값이 집합에 존재하는지 확인SISMEMBER myset "apple"
SMEMBERS집합의 모든 값 조회SMEMBERS myset

Set과 유사하지만, 각 요소에 점수(score)를 부여해 자동으로 정렬된 상태를 유지하는 자료구조다.

  • 활용 사례: 리더보드 / 순위 계산 / 시간 기반 정렬된 이벤트 저장
  • 시간 복잡도
    • 특정 데이터: 해시 테이블 구조로 O(1)
    • 점수 범위 조회: 스킵 리스트 구조로 O(logN)
  • 내부 인코딩
    • listpack: 원소 수와 값 크기가 작을 때. zset-max-listpack-entries(기본 128), zset-max-listpack-value(기본 64바이트) 이내
    • skiplist + dict(hash table): 임계값 초과 시 사용. 정렬 순회와 범위 조회는 스킵 리스트가, 단일 원소 점수 조회는 해시 테이블이 담당하는 이중 구조 → 범위 조회 O(logN), 단건 조회 O(1)
명령어기능예시
ZADD요소와 점수 삽입ZADD leaderboard 100 "player1"
ZRANGE인덱스 범위 내 요소 조회
WITHSCORES: 스코어 함께 출력 / REV: 역순 / BYLEX: 사전 순 정렬 / BYSCORES: 스코어 범위로 조회
ZRANGE leaderboard 0 -1
ZREM요소 삭제ZREM leaderboard "player1"
ZINCRBY점수 값 증가ZINCRBY leaderboard 50 "player1"
ZSCORE특정 요소의 점수 조회ZSCORE leaderboard "player1"
ZREVRANGE높은 점수 순으로 정렬된 요소 조회ZREVRANGE leaderboard 0 -1

Sorted Set은 Skip List를 사용해 데이터를 관리하기 때문에 O(logN)의 시간복잡도로 데이터를 조회할 수 있다.

  • 여러 레벨로 구성된 연결 리스트 구조
  • 각 레벨에서 일부 노드만 연결되며, 상위 레벨일수록 노드 수가 적음
  • 탐색할 때 최상위 레벨부터 시작하여, 원하는 값보다 큰 값이 나오는 지점에서 하위 레벨로 이동하여 탐색

Skip List Add Element(출처: https://en.wikipedia.org/wiki/Skip_list)

별도의 자료구조가 아니라, String 자료구조를 비트(bit) 단위로 조작할 수 있도록 하는 기능의 집합이다.

  • 하나의 키에 매우 큰 비트 배열을 저장하고, 각 비트의 인덱스(offset)를 통해 0 또는 1의 값을 설정하고 조회
  • 활용 사례: 이벤트 발생 여부 추적 / 월간 사용자 활성화 상태 / 플래그 저장
  • 메모리 제한: 2^32의 비트를 가지고 있는 비트맵 형태
명령어기능예시
SETBIT비트 설정SETBIT pageviews 1 1
GETBIT비트 값 조회GETBIT pageviews 1
BITCOUNT비트 값이 1인 비트 개수 세기BITCOUNT pageviews
BITOP비트 연산(AND, OR, XOR, NOT)BITOP AND result key1 key2

매우 적은 메모리를 사용하여 집합에 포함된 고유한 원소의 개수(Cardinality)를 추정하는 확률적 자료구조.

  • 수 십억 개의 원소에 대해서도 약 0.81%의 표준 오차 내에서 고유 원소 개수를 추정
  • 활용 사례: 대규모 사용자 방문 기록 / 이벤트 추적
  • 메모리 제한: 최대 12KB 사용 / 최대 2^64개의 원소 저장 가능
명령어기능예시
PFADD원소 추가PFADD visitors "user1" "user2"
PFCOUNT고유 원소 개수 추정PFCOUNT visitors
PFMERGE여러 HyperLogLog 병합PFMERGE total_visitors visitors1 visitors2

지리 공간 데이터(경도, 위도)를 저장하고, 특정 지점 주변의 다른 지점들을 검색하거나 두 지점 간의 거리를 계산하는 데 특화된 기능을 제공한다.

  • 내부적으로는 Sorted Set을 활용
  • 활용 사례: 근처 매장 찾기 / 배달 서비스에서의 위치 기반 검색
명령어기능예시
GEOADD좌표 추가GEOADD locations 13.361389 38.115556 "Palermo"
GEOSEARCH좌표 조회(반경)GEOSEARCH locations FROMMEMBER "Palermo" BYRADIUS 100 km
GEODIST두 지점 간 거리 계산GEODIST locations "Palermo" "Catania"

Redis Streams는 로그 데이터나 이벤트 스트리밍을 관리할 수 있는 자료구조로, 메시지 브로커로서 기능을 제공한다.

  • 특징
    • 각 메시지는 고유한 ID와 함께 저장되며, 시간순으로 정렬됨
    • 소비자 그룹(consumer group)을 통해 여러 소비자가 데이터를 효율적으로 처리할 수 있음
    • 데이터 손실 없이 대규모 메시지 스트리밍 처리 가능
    • 이벤트가 발생할 때마다 자동으로 데이터를 추가하고, 필요 시 각 소비자가 처리한 위치를 추적할 수 있음
  • 활용 사례: 실시간 로그 수집 / 이벤트 소싱 / 메시지 큐

마지막 업데이트:

Redis