Redis 개념 - g-market/b-shop-backend GitHub Wiki
Caching
데이터의 원래 소스보다 더 빠르고 효율적으로 액세스 할 수 있는 임시 데이터 저장소
Redis as a cache
Redis는 단순한 key-value 딕셔너리 구조입니다. 또한 In Memory 데이터 저장소(RAM)입니다. 또한 이와 더불어 빠른 성능을 보장합니다.
- 평균 작업속도 < 1ms
- 초당 수백만 건의 작업이 가능
캐싱 전략(Caching Strategies)
읽기 전략
이 구조는 레디스가 다운되더라도 바로 장애로 이어지지 않습니다. DB에서 데이터를 가져올 수 있습니다. 대신 Cache에 붙어 있던 Connection이 DB에 커넥션이 급증하여 많은 부하가 몰릴 수 있습니다.
이를 방지하기 위해 미리 캐시를 새로 투입하거나, 미리 DB에서 캐시로 밀어 넣어줄 수 있습니다. 예를 들어 상품 오픈전 상품의 정보를 DB에서 Cache로 밀어 넣는 과정을 해줍니다.
쓰기 전략
Write-Around는 DB에만 데이터를 저장합니다. 캐시 미스가 발생할 때, Cache에 데이터를 끌어옵니다. 즉 Write-Around는 Cache 내 데이터와 DB 데이터가 다르다는 점이 존재합니다.
Write-Through는 Cache와 DB 함께 저장합니다. 이에 따라 저장할 때마다 두 단계의 step을 밟기에 상대적으로 느립니다. 또한 데이터가 재사용성이 적기에 무조건 Cache에 넣기에 일종의 리소스 낭비라고 볼 수 있습니다. 따라서 캐시에 데이터를 저장할 때 Expired Time을 지정해줘야 합니다. 그러나 Expired Time 값에 관리를 잘못하면 장애가 발생할 수도 있습니다.
Redis Data Types
Redis는 자체적으로 다양한 자료구조를 제공합니다.
- String: setCommand를 이용해 저장된 데이터는 기본적으로 String으로 저장
- Bitmaps: String의 변형이자 비트 연산이 가능함
- Lists: 데이터를 순서대로 저장하고 Queue로 이용할 수 있음
- Hashes: 하나의 키로 여러개의 데이터의 Object를 저장할 수 있음
- Sets: 중복되지않은 문자열을 저장
- Sorted Sets: 모든 값은 Score라는 숫자 값으로 정렬됨, 데이터가 저장될 때부터 정렬됨, Score가 같을 때는 사전순
- HyperLogLogs: 굉장히 많은 데이터를 다룰 때 사용하며, 중복되지 않은 값을 카운트할 때 사용
- Streams: 로그를 저장하기 가장 좋은 자료구조
Counting
Strings
Couting할 때 사용할 수 있습니다. 즉 단순 증감 연산에 사용됩니다.
- INCR/INCRBY/INCRBYFLOAT/HINCRBY/HINCRBYFLOAT/ZINCRBY
Bits
오늘의 접속한 사용자를 카운트할 때 용이합니다. 비트로 표현하기 데이터 저장공간을 절약할 수 있습니다. 날짜 키를 만들고, 천만 명의 유저를 천만 개의 bit로만 표현할 수 있기에 약 1.2MB 정도 되므로 효율적으로 관리할 수 있습니다. 다만 정수일 때만 표현할 수 있습니다.
HyperLogLogs
모든 String 데이터 값을 Unique하게 구분할 수 있습니다. 이에 따라 대용량 데이터를 카운팅 할 때 적절합니다.(오차 0.81%) 이는 Set과 비슷하지만 저장되는 용량은 매우 적습니다(저장되는 데이터 값이 12KB 고정). 다만 한 번 저장된 값은 다시 불러올 수 없습니다. 예를 들어 웹사이트에 방문한 IP가 몇 개가 되는지 검색엔진에서 검색된 유니크한 단어가 몇 개 되는지를 크고 유니크한 값을 가져오는데 유리합니다. 만약 일별로 데이터를 저장했는데 일주일치를 취합해서 보고 싶다면, PFMERGE
Command를 통해 취합해서 볼 수 있습니다.
Messaging
Lists
Blocking 기능을 이용해 Event Queue로 사용 가능합니다.
또한 키가 있을 때만 List에 데이터를 추가하는데, LPUSHX / RPUSHX
Key가 이미 있다는 것은 예전에 사용했던 큐이므로 비효율적인 데이터의 이동을 막을 수 있습니다.
Streams
로그를 저장하기 가장 적절한 자료구조입니다. 모든 data는 append-only 방식으로 추가되기에, 중간에 데이터가 바뀌지 않습니다. 이에 따라 시간 범위로 검색 / 신규 추가 데이터 수신 / 소비자별 다른 데이터 수신(소비자 그룹)
Redis에서 데이터를 영구 저장하는 방법(RDB vs AOF)
Redis는 In Memory 데이터 스토어이기에 서버 재시작 시 모든 데이터가 유실됩니다. 이에 대응하기 위해 복제 기능을 사용해도 사람의 실수 발생 시 데이터 복원이 불가합니다. Redis 캐시 이외의 용도로 사용한다면 적절한 데이터 백업이 필요됩니다.
레디스는 데이터를 영구적으로 저장하는 방법을 2가지 제공합니다.
AOF는 Append Only File의 약자로 데이터를 변경하는 커맨드를 그대로 저장합니다. RDB는 스냅샷 방식으로 저장합니다. AOF 파일은 데이터 추가만 되기에 주기적으로 압축을 해서 재작성해야 합니다. AOP는 Redis Protocol, RDB는 Binary 파일로 저장됩니다.
자동 / 수동 파일 저장 방법
RDB, AOF는 커맨드로 파일을 직접 생성할 수 있으며, 원하는 시점에 파일이 자동 생성되도록 설정할 수 있습니다.
RDB
- 자동: Redis.conf 파일에서 SAVE 옵션(시간 기준)
- 수동: BGSAVE 커맨드를 이용해 CLI 창에서 수동으로 RDB 파일을 저장
- SAVE 커맨드는 절대 사용 X
AOF
- 자동: Redis.conf 파일에서 auto-aof-rewrite-percentage 옵션(크기 기준)
- 수동: BGREWRITEAOF 커맨드를 이용해 CLI 창에서 수동으로 AOF 파일을 재작성
RDB vs AOF 선택 기준
- 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우
-> RDB 단독 사용
-> redis.conf 파일에서 SAVE 옵션을 적절히 사용 예) SAVE 900 1(900초 동안 1개 이상의 키가 변경되었을 때 RDB 파일을 재작성) - 장애 상황 직전까지의 모든 데이터가 보장되어야 할 경우
-> AOF 사용(appendonly yes)
-> APPENDSYNC 옵션이 everysec인 경우 최대 1초 사이의 데이터 유실 가능(기본 설정) - 제일 강력한 내구성이 필요한 경우
-> RDB & AOF 동시 사용
Redis 아키텍처 선택 노하우
Sentinal 일반 노드들을 모니터링 합니다. Cluster 구성은 최소 3대의 master가 필요하며, shading 기능(traffic을 분산)을 제공합니다.
Replication 구성
단순한 복제 연결 구성으로, replicaof커맨드를 이용해 간단하게 복제 연결하며 비동기식으로 복제가 진행됩니다.
HA 기능이 없으므로 장애 상황 시 수동으로 복구를 진행해야 합니다.
- replicaof no one(레플리카 노드에 접속하여 직접 연결을 끊어야 함)
- 애플리케이션에서 연결 정보를 변경 후 배포
Sentinel 구성
자동 페일오버 가능한 HA 구성(High Availability)
- sentinel 노드가 다른 노드 감시
- 마스터가 비정상 상태일 때 자동으로 페일오버
- 연결 정보 변경 필요가 없음 (애플리케이션은 Sentinal 노드만 알면 됨)
- sentinel 노드는 항상 3대 이상의 홀수로 존재해야 함
- 과반수 이상의 sentinel이 동의해야 페일오버를 진행
Cluster 구성
스케일 아웃과 HA 구성(Hig Availability)
- 키를 여러 노드에 자동으로 분할해서 저장(샤딩)
- 모든 노드가 서로를 감시하여, 마스터 비정상 상태일 때 자동 페일오버
- 최소 3대의 마스터 노드가 필요함
Redis 꿀팁
Redis는 Single Thread로 동작합니다. 한 사용자가 오래 걸리는 커맨드를 실행한다면, 나머지 모든 요청들은 수행할 수 없고 대기하게 됩니다. 이로 인해 장애도 빈번하게 발생할 수 있습니다.
keys *
: 모든 key를 보여주는 커맨드인데, 시간 복잡도는 O(n)이므로 사용하지 않아야 한다. 이를 대신하는 방법은 scan 0
재귀적으로 키를 호출할 수 있습니다.
또한, Hash나 Sorted Set 등 자료구조에 내부에 아이템이 많아질수록 성능이 저하됩니다.
- 키 나누기(최대 100만 개)
hgetall
->hsacn
del
->unlink
변경하면 장애를 막을 수 있는 기본 설정값
STOP-WRITES-ON-BGSAVE-ERROR = NO
- yes(default)
- RDB 파일 저장 실패 시 redis로의 모든 write 불가능
- 모니터링을 잘하는 조건하에 NO로 설정하여 불필요한 장애를 막을 수 있습니다.
MAXMEMORY-POLICY = ALLKEYS-LRU
- redis를 캐시로 사용할 때 Expire Time 설정 권장
- 메모리가 가득 찼을 때 MAXMEMORY-POLICY 정책에 의해 키 관리
- noeviction(default): 삭제 안 함(메모리가 가득 차면 더 이상 레디스의 새로운 키를 저장하지 않음 -> 장애)
- volatile-lru(가장 오래된 키부터 삭제, expire 설정만 있는 키를 삭제함 만약 expire이 없는 키만 존재하면 -> 장애)
- allkeys-lru
Cache Stampede
TTL 값을 너무 작게 설정한 경우, 즉 키가 만료되는 순간 많은 서버에서 이 키를 같이 보고 있었다면, 모든 애플리케이션 서버들이 DB에서 같은 데이터를 찾는 Duplicated Read가 발생합니다. 또한, 읽어온 값을 Redis에 각각 write 하는 Duplicated Write발생합니다. 이는 비효율적인 상황이고 처리량도 많으며, 장애로까지 이어질 수 있습니다.
MaxMemory 값 설정
Persistence / 복제 사용 시 MaxMemory 설정을 주의해야 합니다.
- RDB 저장 & AOF rewrite 시 fork()
- Copy-On-Write로 인해 메모리를 두배로 사용하는 경우 발생 가능
- Persistence / 복제 사용 시 MaxMemory는 실제 메모리의 절반으로 설정
예) 4GB -> 2048MB
Memory 관리
물리적으로 사용되고 있는 메모리를 모니터링해야 합니다.
- used_memory: 논리적으로 Redis가 사용하는 메모리
- used_memory_rss: OS가 Redis에 할당하기 위해 사용한 물리적 메모리 양
- 삭제되는 키가 많으면 fragmentation 증가
- 특정 시점에 피크를 찍고 다시 삭제되는 경우
- TTL로 인해 eviction이 많이 발생하는 경우
- CONFIG SET activedefrag yes