오늘의하루

[Redis 파먹기] 개념 및 자료 구조 본문

Redis

[Redis 파먹기] 개념 및 자료 구조

오늘의하루_master 2025. 4. 24. 01:38
반응형

Redis 란?

Redis는 가장 유명한 In memory DB(NoSQL)로 Key-Value 저장소입니다.
이 글에서는 Redis에서 제공하는 주요 자료 구조 및 사용 시 알아두면 좋은 주의사항에 대해 간단히 소개합니다.

1. Strings

가장 기본적인 자료형으로 단순한 문자열뿐만 아니라 숫자 값을 저장하고 다양한 비트 연산을 지원합니다.
그리고 이미지, 오디오 등 어떤 형태의 바이너리 데이터도 저장할 수 있으며 객체를 직렬화하여 문자열 형태로 저장하는 것도 가능하지만 특정 필드만 접근하거나 수정해야 하는 경우에는 Hash 자료 구조나 JSON 형태로 저장하는 것을 고려해 볼 수 있습니다.

  • String은 실무에서 가장 널리 사용되는 자료 구조 중 하나입니다.
  • 최대 크기 : 512MB
SET coupon:125071 1500원
> OK
GET coupon:125071
> 1500원

시간 복잡도

GET, SET : O(1)

  • SUBSTR, GETRANGE, SETRANGE : O(N) - N은 반환되거나 변경되는 문자열의 길이를 의미합니다.
  • 비트 연산 : O(N) - N은 비트맵의 크기를 의미합니다.

2. Lists

Linked List 자료 구조를 기반으로 구현되어 있어서 양쪽 끝에서의 삽입(push)과 삭제(pop) 연산을 O(1)의 시간 복잡도로 수행할 수 있으며 Queue (FIFO - First-In, First-Out)나 Stack (LIFO - Last-In, First-Out) 자료 구조를 구현하는 데 효과적으로 사용될 수 있습니다.

  • 최대 길이 : 4,294,967,295
LPUSH - LPOP // Stack 구조
LPUSH - RPOP || RPUSH - LPOP // Queue 구조

시간 복잡도

L(R)PUSH, L(R)POP : O(1)

  • 그 외 기능 : O(N) - N은 List의 길이를 의미합니다.

3. Sets

순서가 없는 고유한(중복되지 않는) 요소들의 컬렉션으로 Set 자료 구조를 통해 멤버십 테스트 (특정 요소가 Set에 존재하는지 확인), 교집합, 합집합, 차집합과 같은 집합 연산을 효율적으로 수행할 수 있습니다.

  • 최대 길이 : 4,294,967,295  
SADD bikes:racing:france bike:1 // bike:1
SADD bikes:racing:france bike:1 // bike:1 ( 중복 )
SADD bikes:racing:france bike:2 bike:3 // bike:1, bike:2, bike:3
SADD bikes:racing:usa bike:1 bike:4 // bike:1, bike,4

SMEMBERS bikes:racing:france // bike:1, bike:2, bike:3 ( 순서 없음 )

SINTER bikes:racing:france bikes:racing:usa // bike:1

시간 복잡도

SADD, SREM, SISMEMBER, SPOP: O(1)
SRANDMEMBER : O(N) - N은 개수를 의미합니다.

  • 그 외 기능 O(N) ~ O(N^2)

4. Hashes

Hash는 key(string) - value 쌍을 저장하는 데 사용되는 자료 구조입니다.
각 Hash는 여러 개의 필드와 그에 연결된 값을 가질 수 있으며 이는 객체를 표현하거나 관련 있는 데이터 그룹을 함께 저장하는 데 유용합니다.

  • 최대 길이 : 4,294,967,295
HSET coupon:1234 goodsId 120014 maxDiscount 1500 minPrice 1000
HGET coupon:1234 goodsId // 120014
HGETALL coupon:1234 // 1) 120014, 2) 1500, 3) 1000

시간 복잡도

HSET, HGET, HDEL, HEXISTS, HLEN, HINCRBY, HINCRBYFLOAT : O(1)

  • 그 외 기능 O(N)

5. Sorted Sets

Set과 유사하게 고유한 멤버들의 컬렉션이지만 각 멤버는 score라는 부동 소수점(Double) 숫자에 연결되어 있습니다.
멤버들은 이 score를 기준으로 정렬되며 동일한 score를 가진 멤버는 사전순으로 정렬됩니다.

부동 소수점 연산 시 발생할 수 있는 정밀도 문제를 항상 염두에 두어야 합니다.
특히 자바스크립트와 같은 언어에서 숫자를 다룰 때는 문자열 형태로 받아 처리하는 것이 안전할 수 있습니다.
ZADD ranking 100 coupon1
ZADD ranking 150 coupon2
ZADD ranking 120 coupon3

ZRANGE ranking 0 -1 WITHSCORES // 오름차순
ZREVRANGE leaderboard 0 -1 WITHSCORES // 내림차순

시간 복잡도

대부분의 연산은 O(log(n))이며, n은 멤버의 수를 의미합니다.

  • ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE : O(log(n) + m) - n은 멤버의 수, m은 반환되는 결과의 수를 의미합니다.
  • ZINTERSTORE, ZUNIONSTORE : O(n*k*log(m)) - n은 입력 Sorted Set 중 가장 큰 Set의 크기, k는 입력 Set의 개수, m은 결과 Set의 크기를 의미합니다.

6. Bitmaps

Bitmap은 String 자료형을 비트 배열로 취급하여 효율적인 비트 연산을 수행할 수 있도록 합니다.
각 비트는 특정 위치의 비트를 설정(SETBIT), 조회(GETBIT), 카운팅(BITCOUNT)하는 등의 연산을 지원합니다.

SETBIT pings:2024-01-01-00:00 123 1 // 0 - 기존에 설정이 없었기 때문입니다.
GETBIT pings:2024-01-01-00:00 123 // 1 - 이전에 설정한 값이 있기 때문입니다.
SETBIT pings:2024-01-01-00:00 456 // 0 - 이전에 설정한 값이 없습니다.

시간 복잡도

SETBIT, GETBIT: O(1)

  • BITCOUNT, BITTOP : O(N)

왜 O(N)을 피해야 할까?

Redis는 싱글 스레드 기반으로 작동하기 때문에 요청을 순차적으로 처리하며 O(N)과 같은 시간 복잡도가 높은 연산이 수행된다면 해당 요청이 완료될 때까지 다른 모든 요청이 대기하게 되어 병목 현상이 발생할 수 있습니다.

이러한 이유로 Redis에서는 O(N) 또는 그 이상의 연산을 최대한 피하는 것이 좋습니다.

물리 메모리를 초과하면 어떻게 될까?

만약 Redis가 사용하는 메모리가 RAM(물리 메모리)를 초과 하게 된다면 OS는 이를 Swap 영역(디스크 공간)을 사용하여 강제로 디스크로 내보게 됩니다.

즉 한번이라도 Swap이 발생했다면 해당 데이터에 접근할때 마다 디스크에서 읽어와야 하기 때문에 성능이 급격히 떨어지게 되며 심할 경우 서비스 장애로 이어질 수 있습니다.

MaxMemory는 진짜일까?

Redis에서 maxmemory를 설정하면 Redis는 해당 메모리 한도 내에서만 데이터를 저장하려고 시도합니다.
그러나 실제 메모리 사용량은 예상보다 더 많을 수 있습니다. (이유로는 데이터 자체, 메타 데이터, 자료구조, 복사 및 버퍼 등등)

따라서 maxmemory를 설정할 때는 이러한 오버헤드를 고려하여 여유 있게 설정하는 것이 좋습니다.

Redis의 메모리 크기를 작게 쪼개야하는 이유

읽기 작업은 상관 없지만 쓰기 작업 시 Redis는 데이터를 복사하거나 임시 공간을 확보하는 등의 이유로 일시적으로 더 많은 메모리를 사용할 수 있습니다.
이유로는 RDB 스냅샷 생성이나 AOF 재작성 등의 작업은 Copy-On-Write(COW) 메커니즘을 사용하기 때문입니다.

Collection 주의 사항

Redis는 기본적으로 TTL(Time To Live)을 key 단위로만 설정할 수 있으며 List, Set, Sorted Set, Hash 등 컬렉션 내부의 개별 요소에는 TTL을 직접 지정할 수 없습니다.

Redis 7.4부터는 Hash의 개별 필드에 대해 TTL을 설정할 수 있는 기능이 도입되었습니다.

그리고 하나의 Collection(key) 내부에 수천 개(N천 개 이하)의 요소를 유지하는 것이 성능 및 메모리 관리 측면에서 좋습니다.
너무 많은 요소를 하나의 컬렉션에 담을 경우 명령 실행 시 처리 시간이 증가하고 AOF/RDB 백업, 복제 성능에 영향을 줄 수 있으므로 적절한 분할 설계를 권장하고 있습니다.

 

반응형
Comments