이전 MSA 프로젝트에서 JWT Refresh Token의 유효성 검증 로직에 Redis를 활용했지만
도커를 활용한 환경 구성, Redis Template 기반의 구현법과 연결 등 간단한 사용법만 익힌 채로 넘어갔었다.
차후 Redis를 캐시로 활용하기 위해 보다 깊은 이해가 필요해
주말동안 백엔드 스터디에 참가해 Redis에 대해 조금 더 알아보는 시간을 가졌다.
진행 방식은 아래와 같다.
- 기술(서비스) 기업이 공개한 컨퍼런스 중에서 학습용 영상을 선정한다.
- 스터디원 중 한 명이라도 이해가 가지 않는다면 영상 진행을 멈춘다.
- 남은 스터디원들이 자신들이 이해한 내용을 설명한다.
- 설명이 부정확할 수 있기에 gpt, perplexity, google 등 매체를 가리지 않고 조사한다.
- 각자 이해한 내용을 공유하고 해당 부분의 처음으로 돌아가 다시 시청한다.
선택된 영상은 NHN에서 공개한 NHN FORWARD 2021이다.
스터디 진행은 녹화 후, 유튜브에 업로드하였으니 관심이 있다면 구경해봐도 좋다.
초반부에는 스터디 방식에 익숙해 지지 않아, 넋놓고 시청하는 시간이 길지만
차차 적극적으로 모르는 부분을 서로 언급하고, 설명하는 모습을 지켜보는 게 나름의 재미 포인트😊
- Redis 캐시로 사용
- Redis 데이터 타입 야무지게 활용하기
- Redis에서 데이터를 영구 저장하려면?
- Redis 아키텍처 선택 노하우
- Redis 운영 꿀팁 + 장애 포인트
- Temporary Location For Speed
- 원래 소스보다 더 빠르고 효율적으로 엑세스할 수 있는 임시 데이터 저장소
- Most Popular software caching solution
- 단순한 key-value 구조
- In-memory 데이터 저장소 (RAM)
- 빠른 성능
- 평균 작업속도 < 1ms
- 초당 수백만 건의 작업 가능
- 지연 시간 감소
- 캐싱 전략
- 읽기 전략
- Look-Aside(Lazy Loading)
- 레디스(캐시) 조회 → 레디스 내부에 정보가 없을 시 DB 조회
- 단점: 초기에 Cache Miss가 자주 발생할 수 있음
- 대안: 미리 DB의 데이터를 캐시에 일괄 저장
- 쓰기 전략
- Write-Around
- DB에만 저장하고 없을 때만 가져옴
- DB와 캐시의 동일성 보장 X
- Write-Through
- 값 저장 시 반드시 캐시에도 저장
- 불필요한 overhead 발생 가능
- Redis Data Types
- String
- Bitmaps
- Lists
- Hashes
- Sets
- Sorted Sets
- 사전 순으로 정렬
- 단순증감 연산
- INCR / INCRBY / INCRBYFLOAT / HINCRBY / HINCRBYFLOAT / ZINCRBY
- HyperLogLog
- 중복되지 않는 값을 저장할 때 사용
- Streams
- 로그 저장 시 활용
레디스 공식 문서에 따르면 "집합의 기수"라고 한다.
그럼 HyperLogLog는 어떻게 집합의 기수 역할을 수행할 수 있는 걸까?
1. HyperLogLog는 우선 집합의 대상이 되는 자료를 이진수로 해싱한다. (key: binary, value: 원본)
2. 해싱키의 가장 왼쪽에 존재하는 1을 기반으로 Leading Zero Count 기법을 활용해 해당 키의 패턴을 파악한다.
3. 이진수로 변환해 패턴을 파악해 기존 값들을 분류, 12KB만으로 0.81% 오차율로 여러 자료들을 식별할 수 있다.
- 대용량 데이터를 카운팅 할 때 적절 (오차 0.81%)
- String 값을 유니크하게 구분 (String → 12KB 비트로 전환)
- set과 비슷하지만 저장되는 용량은 매우 작음 (12KB 고정)
- 크고 unique한 데이터(대용량 데이터)의 수를 셀 때 적합
- 저장된 데이터는 다시 확인 X
- Best Practice - Messaging
- List
- Blocking 기능을 이용해 Event Queue로 사용
- queue가 비었을 경우, queue가 찰 때까지 클라이언트를 대기
- 키가 있을 때만 데이터 저장 가능 - LPUSHX / RPUSHX
- 키가 존재하면 이미 캐싱되어 있다고 인식
- 자주 사용하는 유저(캐싱된 유저)의 정보만 캐싱
- 사용하지 않는 유저의 정보는 업데이트 X, Blocking 하며 데이터 생성할 때까지 대기
- Stream
- 로그를 저장하기 가장 적절한 자료구조
- append-only
- 시간 범위로 검색 / 신규 추가 데이터 수신 / 소비자 별 다른 데이터 수신(소비자 그룹)
- 메시징 브로커로 사용 가능 (Kafka를 많이 참고)
- 컨슈머 그룹
- 시간 순으로 저장 가능 (영구 저장)
- ID
- Redis는 In-memory 데이터 스토어
- 휘발성 데이터기에 백업 필요
- Redis Persistence Option
- AOF: 추가될 때마다 데이터가 쌓여서 추가적으로 비워주는 작업 필요
- 자동: redis.conf 파일에서 auto-aof-rewirte-percentage 옵션(크기 기준)
- 수동: BGREWRTIEAOF 커맨드를 이용해 CLI 창에서 수동으로 AOF 파일 재작성
- RDB:
- 자동: redis.conf 파일에서 SAVE 옵션(자동 기준)
- 수동: BGSAVE 커맨드를 이용해 cli 창에서 수동으로 RDB 파일을 저장
- SAVE 커맨드는 절대 사용 X
- Redis는 요청받은 명령어를 Single Thread로 수행
- SAVE를 수행하는 동안 다른 읽기, 쓰기 작업이 불가 -> 부하로 이어질 가능성 다분
- 보완책으로
BGSAVE를 사용BGSAVE
- 저장 작업만 수행하는 자식 프로세스를 생성
- 자식 프로세스는 저장 작업만 수행, 부모 프로세스(원본)에서 로직을 수행해 정상 동작
- 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우
- RDB 단독 사용
- 장애 상황 직전까지 모든 데이터가 보장되어야 할 경우
- AOF 사용
- APPENDFSYNC 옵션이 everysec인 경우 최대 1초 사이의 데이터 유실 가능
- 성능을 고려해 명령어가 아닌 시간 기준으로 interval을 생성
- 강력한 내구성이 필요한 경우
- AOF + RDB를 동시에 사용
- AOF는 모든 명령어를 순차적으로 복구해야 하기에 속도가 느림
- RDB는 특정 시점마다 저장
- 세밀하지 못한 대신 스냅샷으로 저장하기에 부하가 적고 복구 속도가 빠름
⭐ RDB로 원하는 시점의 스냅샷을 로드, AOF로 해당 시점의 명령어를 순차 복구- Best Practice
- RDB로 특정 타이밍 전반을 복구 → AOF로 명령어를 롤백하며 세부 사항 복구
Replication
단순한 복제 연결
- replicaof 커맨드를 이용해 간단하게 복제 연결
- 비동기식 복제
- HA 기능이 없으므로 장애 상황 시 수동 복구
- replica of no one
- 애플리케이션에서 연결 정보 변경
Sentinel
자동 페일오버 가능한 HA 구성(High Availability)
- sentinal 노드가 다른 노드 감시
- 마스터가 비정상 상태일 때 자동으로 페일오버
- 연결 정보 변경 필요 없음
- sentinel 노드는 항상 3대 이상의 홀수로 존재해야 함
- 과반수 이상의 sentinel이 동의해야 페일오버 진행
- 블록체인에서 블록을 무결성을 판단하고 체인에 합류시키는 로직과 유사
Cluster 구성
스케일 아웃과 HA 구성
- 키를 여러 노드에 자동으로 분할해서 저장(샤딩)
- 모든 노드가 서로를 감시하여, 마스터가 비정상일 때 페일오버 시행
- 단일 인스턴스로 동작하는 Redis의 HA(고가용성)를 보장하기 위한 전략
- Redis의 마스터 노드가 장애가 발생했을 때, 그 역할을 대신할 새로운 마스터를 선정하고, 장애 복구를 자동으로 처리하는 과정을 의미.
- Redis Sentinel과 Redis Cluster 모두 페일오버 기능을 제공
- keys * → scan으로 대체
- keys: 모든 데이터 셋을 로드 → 성능 부하 발생 여지 높음
- scan: 점진적으로 읽어와 데이터 부하가 적음
- Hash나 sorted Set 등 자료구조
- 키 나누기 (최대 100만개)
- hgetall → hscan
- del → unlink
STOP-WRITES-ON-BGSAVE-ERROR = NO- RDB 파일 저장 실패 시 redis로의 모든 write 불가능
MAXMEMORY-POLICY = ALLKEYS-LRU
- Redis를 캐시로 사용할 때 Expire Time 설정을 확장하는 것을 권장
- TTL이 너무 짧을 경우 캐시 미스가 자주 발생
- 캐시 미스가 발생한 경우 RDB에서 파일을 조회한 후 Redis에 캐싱하는 과정이 필요(Look-Around)
- 이 과정에서 Duplicated Read 및 각종 부하가 발생할 수 있음
- 메모리가 가득 찼을 때 MAXMEMORY-POLICY 정책에 의해 키 관리
- noeviction: 삭제 X
- volatile-lru
- allkeys-lru
Persistence / Replication 사용 시 MaxMemory 설정 주의
- 실제 메모리의 절반을 최대 값으로 설정하는 걸 권장
- RDB 저장 & AOF rewrite 시 fork()로 자식 프로세스에서 저장 명령을 수행(BGSAVE)
- 생성된 자식 프로세스는 부모 프로세스와 완전히 동일, 결과적으로 두 배의 메모리를 사용
메모리 단편화는 왜 발생할까?
- 위 상황에서 Redis가 할당받은 메모리에 비해 실 사용량이 적으므로 내부 단편화에 해당
- OS에서 메모리를 할당하는 과정과 비슷하게, 짧은 생명주기를 가진 데이터가 자주 할당되면 다음과 같은 문제가 발생
- 빈 공간 발생: 메모리 블록이 자주 할당되고 해제되면 빈 공간이 발생.
- 이 빈 공간이 다른 요청에서 사용할 수 없거나, 요구하는 크기에 맞지 않아 단편화가 발생.
- NHN 레디스 야무지게 사용하기
- Redis는 정말 싱글 스레드일까?
- 외부 단편화와 내부 단편화
- ChatGPT 4o