database

Redis 자료구조

HJHStudy 2025. 6. 22. 16:51
728x90

Redis는 Key-value를 갖고 다양한 자료 구조를 갖는다. 이때 다양한 타입을 지원한다.

 

redis 자료 구조에 대해 알아보자.

레디스의 데이터 타입은 Strings, Bitmaps, Lists, Hashes, Sets, Sorted Sets, HyperLongLogs, Steams가 있다.

각각 하나씩 확인해 보자.

 

Strings

일반적인 문자열로 최대 512MB이다. string으로 될 수 있는 바이너리 데이터, JPEG 이미지도 저장이 가능하며 단순 증감 연산에 용이하다. 

String-String 매핑을 사용해 연결되는 자료를 매핑할 수 있으며 HTML 매핑도 가능하다.

단순한 key-value 캐싱 용도에 적합, INCR, DECR, APPEND 등 간단한 연산 지원

세션 토큰, JWT 저장, 카운터 (조회수, 좋아요 수 등)에 사용된다.

주의 점은 큰 문자열 (JSON 등)을 자주 수정하면 전체 재저장 되므로 비효율적이다.

SET mykey "Hello"
GET mykey         # → "Hello"
INCR counter      # counter가 없으면 0으로 간주하고 1 증가 → 1

 

 

 

Bitmaps

bitmaps는 String의 변형으로 bit 단위 연산이 가능하다. 2^32 bit인 512MB까지 가능하다.

저장 시 저장 공간 절약에 장점이 있다.

하나의 값(예: 사용자 ID 등)의 상태를 0 또는 1의 비트 단위로 표현한다.

일반적인 Boolean 값 하나를 저장하려면 최소 1바이트 (8비트)가 필요하지만 비트맵은 1개의 Boolean 값을 오직 1비트로 저장한다.

프로모션이나 쿠폰 등 참여 값을 참, 거짓으로 저장 시 공간 효율이 늘어난다.

SETBIT user:1 0 1       # 0번 비트를 1로 설정 → true
GETBIT user:1 0         # → 1
BITCOUNT user:1         # → 설정된 1의 개수 반환 (ex: 1)

주의점은 하나의 비트라도 설정되면 Redis는 전체 문자열 크기만큼 메모리 할당된다.

Redis Bitmaps는 String 자료형의 연속적인 byte 배열로 구현되며 희소 비트라도 메모리를 전체 offset만큼 할당하기 때문이다. 10억 번째 비트를 설정하면, Redis는 10억 비트를 담기 위해 10억 비트만큼의 메모리를 할당한다. 비트 하나를 멀리 떨어진 위치에 쓰는 순간 전체 공간을 연속적으로 확보해야 하는 구조이다.

 

 

 

Lists

array 형식의 데이터 구조로 데이터를 순서대로 저장한다.

추가, 삭제, 조회는 O(1)의 속도를 가지지만 중간 특정 index값을 조회 시 O(n)의 속도를 가지는 단점이 있다.

중간의 추가 및 삭제가 느리고 head-tail에서 추가와 삭제가 진행된다. 이때 연산은 push와 pop을 사용한다.

메시지 큐로 사용하기 적절하다.

 

 

 

 

 

 

LPUSH mylist "a" "b"    # → 리스트 왼쪽에 b, a 삽입 → ["a", "b"]
RPUSH mylist "c"        # → 리스트 오른쪽에 c 삽입 → ["a", "b", "c"]
LRANGE mylist 0 -1      # → ["a", "b", "c"]

간단한 메시지 큐나 작업 대기열 표현에 적합하다.

주의점은 리스트가 커질 경우 LRANGE 0 -1은 전체를 반환하므로 성능 저하를 유발한다.

 

Hashes

field-value로 구성되어 있는 해시 형태로 키 하위에 subkey를 이용해 추가적인 hash table을 제공하는 자료구조이다.

메모리가 허용하는 한 제한 없이 필드들을 넣을 수 있다.

 

 

개별 필드 접근/갱신 가능으로 효율적이다.

사용자 프로필 같은 구조에 적합하다.

HSET user:1000 name "Alice" age 30
HGET user:1000 name         # → "Alice"
HGETALL user:1000           # → 모든 필드와 값 반환

사용자 정보나 설정 정보 저장에 용이하다.

너무 많은 필드는 관리가 어렵고 성능보다 구조화에 주로 사용된다.

 

 

Sets

중복된 데이터를 담지 않기 위해 사용하는 자료구조로 유니크한 keyr값을 가지며 정렬되지 않는 집합이다.

중복된 데이터를 여러 번 저장 시 최종 한 번만 저장된다.

set 간 연산을 지원하고 교집합, 합집합, 차이를 빠른 시간 내 추출할 수 있다.

모든 데이터를 전부 가지고 올 수 있는 SMEMBERS 전체 호출은 성능에 영향을 줄 수 있다 (O(n)).

 

 

SADD myset "a" "b" "c"     # → 3 (성공적으로 추가된 개수)
SMEMBERS myset             # → {"a", "b", "c"}
SISMEMBER myset "a"        # → 1 (존재)

특정 이벤트 참여 유저 목록이나 친구 목록, 태그 시스템에 사용된다.

 

 

Sorted Sets

set에 score라는 필드가 추가된 것으로 이때 스코어는 일종의 가중치다.

일반적으로 set은 정렬이 안되고 삽입한 순서대로 들어가지만 Sorted set은 set의 특성을 가지지만 저장된 데이터들의 순서도 관리한다.

데이터가 저장될 때부터 score 순으로 정렬되며 저장된다. 이때 데이터는 오름차순으로 내부 정렬된다.

value는 중복이 불가하나 score는 중복이 가능하다. 만일 score 값이 같다면 사전 순으로 정렬돼 저장된다.

 

 

ZADD leaderboard 100 "user1" 200 "user2"
ZRANGE leaderboard 0 -1 WITHSCORES   # → ["user1", "100", "user2", "200"]
ZREVRANK leaderboard "user1"        # → 1 (점수 높은 순의 순위)

랭킹, 점수 기록 등에 사용된다.

주의점은 삭제나 갱신에 score에 따라 내부 재정렬이 발생한다.

 

 

HyperLogLogs

많은 양의 데이터를 덤프할 때 사용하고 중복되지 않는 대용량 데이터를 count 할 때 많이 사용한다. 오차 범위는 약 ±0.81%

set과 비슷하지만 저장되는 용량이 매우 작은데 저장되는 모든 값이 12kb로 고정이다.

하지만 저장된 데이터는 다시 확인할 수 없다.

  • 저장된 값은 내부적으로 해시되어 저장되며 값을 다시 꺼낼 수 없음.

엄청 크고 유니크한 값을 count 할때 사용한다.

웹 사이트 방문 ip 개수 카운팅, 하루 종일 크롤링 한 url 개수 몇 개 인지, 검색 엔진에서 검색 한 단어 몇개 인지에 보통 쓰인다.

# 오늘 방문자 기록
PFADD daily:2025-06-22 user100 user101 user102

# 내일 방문자
PFADD daily:2025-06-23 user102 user103

# 전체 기간 유니크 방문자 수
PFMERGE total:visitor daily:2025-06-22 daily:2025-06-23
PFCOUNT total:visitor
  •  

 

 

Streams

log를 저장하기 가장 좋은 자료 구조로 append-only이며 중간에 데이터가 바뀌지 않는다.

읽을 때 id 값 기반으로 시간 범위 검색을 한다.

tail -f 사용하는 것처럼 신규 추가 데이터를 수신한다.

소비자 그룹을 지원한다.

이벤트 스트림 처리에 사용되고 kafka의 대안으로 사용된다. 실시간 로그, 알림 시스템이 적합하다.

주의점은 소비자 그룹 처리를 하지 않는다면 데이터 누락이 될 수 있다.

 

XADD mystream * sensor-id 1234 temperature 19.8
XRANGE mystream - +        # → 전체 메시지 반환
XREAD COUNT 1 STREAMS mystream 0

//소비자 그룹 관련 명령어
XGROUP CREATE mystream mygroup $ MKSTREAM   # 그룹 생성 (마지막 메시지부터 시작)
XREADGROUP GROUP mygroup consumer1 STREAMS mystream >  # 그룹 소비
XACK mystream mygroup message-id            # 처리 완료 메시지 확인 (acknowledge)
//구조
[ Stream key: notifications ]
         │
         └── [ Consumer Group: alert-group ]
                     ├── [ Consumer: worker-1 ]
                     └── [ Consumer: worker-2 ]

하나의 스트림 키 아래에 여러 컨슈머 그룹이 있을 수 있고, 하나의 컨슈머 그룹에는 여러 컨슈머(worker)가 존재할 수 있다.

조사하면 들은 생각은 키로 하면 될 것 같은데 왜 컨슈머로 나누는가?이다.

메시지를 여러 작업자에게 효율적으로 분산하고, 처리 상태를 Redis가 추적하기 때문이며, 동시성과 누가 메시지를 처리했는지 알기 위해서이다. 컨슈머를 통해 메시지 분산 처리, 처리 상태 관리, 재처리 지원, 중복 방지가 가능해지기 때문이다.

 

 


활용 사례

 

Bitmaps

출석체크, 쿠폰 참여

사용자 ID에 해당하는 인덱스를 0 또는 1의 비트로 표현하여 사용자 상태를 압축 저장

하루에 출석한 유저를 기록, 10만 명 유저의 출석 여부를 저장해야 함

SETBIT attendance:2025-06-22 1001 1 # user ID 1001 출석
SETBIT attendance:2025-06-22 1055 1 # user ID 1055 출석

//집계
BITCOUNT attendance:2025-06-22 # 당일 총 출석자 수
GETBIT attendance:2025-06-22 1001 # 특정 유저의 출석 여부

 

 

Sorted Sets

score로 정렬, 순위 조회, 범위 검색에 탁월

유저가 게임 점수를 획득할 때마다 업데이트, Top 10 랭커 조회 필요

ZADD game:ranking 1500 user1
ZADD game:ranking 1600 user2
ZADD game:ranking 1400 user3

ZREVRANGE game:ranking 0 9 WITHSCORES # 점수 기준 상위 10명
ZRANK game:ranking user3 # user3의 순위
ZSCORE game:ranking user3 # user3의 점수

장점

빠른 정렬과 범위 조회 (O(log N))

실시간 랭킹 유지 가능

점수 업데이트 시 자동 정렬

 

HyperLogLog

웹사이트 일별 방문자 수 추정

하루 동안 웹사이트 방문자(로그인 사용자 또는 IP)를 수백만 건 추적, 정확한 값은 필요 없고 빠른 집계가 중요

PFADD visitors:2025-06-22 user123
PFADD visitors:2025-06-22 user456
PFADD visitors:2025-06-22 user789

//조회
PFCOUNT visitors:2025-06-22 # 2025년 6월 22일 유니크 방문자 수 추정

//병합
PFMERGE visitors:total visitors:2025-06-21 visitors:2025-06-22
PFCOUNT visitors:total # 누적 유니크 방문자 수 추정

장점

메모리 사용량 일정 (≈12KB)

대규모 트래픽에도 실시간 처리 가능

완벽히 정확한 수치가 필요 없는 경우에 최적

 

 

Streams

append-only 구조로 시간순 이벤트를 저장, 메시지는 명시적으로 확인(ACK)되기 전까지 유지됨

이벤트 기반 알림 시스템

사용자가 회원가입, 주문, 결제를 하면 알림을 전송해야 함, 비동기 처리를 위해 Redis Streams를 이벤트 버퍼로 활용

XADD notifications * userId 123 type email message "가입을 환영합니다!"
XADD notifications * userId 456 type sms message "주문이 완료되었습니다"
//*는 자동으로 timestamp + sequence로 ID 생성, 각 이벤트는 Key-Value 형태로 저장

//소비자 그룹 생성 (처음 한 번만 생성)
XGROUP CREATE notifications alert-group $ MKSTREAM
//alert-group이라는 소비자 그룹 생성
//$는 새로 들어오는 데이터부터 처리
//MKSTREAM: 스트림이 없다면 생성

//알림 처리
XREADGROUP GROUP alert-group worker-1 COUNT 10 STREAMS notifications >
//alert-group이라는 그룹에서 worker-1 소비자가 메시지 읽음
//>는 아직 소비되지 않은 새 메시지만 읽음
//메시지를 처리 후 XACK으로 확인

//처리 완료 확인(ACK)
XACK notifications alert-group 1687512200000-0

장점

Kafka처럼 내구성 있는 메시지 큐 역할 수행

소비자 그룹으로 병렬 처리 가능

메시지 손실 없이 안정적인 비동기 처리

각 소비자는 자신이 받은 메시지만 ACK 함

728x90