[NHN FORWARD 2021] Redis 야무지게 사용하기

DaeHoon·2023년 10월 14일
0

테크 세미나

목록 보기
1/2

1. Redis 캐시로 사용하기

What is Caching?

  • Temporary Location For Speed
    • 데이터의 원래 소스보다 더 빠르고 효율적으로 액세스할 수 있는 임시 데이터 저장소

Redis as a cache

  • 단순한 key-value 구조
  • in-memory 데이터 저장소 (RAM)
  • 빠른 성능
    • 초당 수백만 건의 작업 가능
    • 평균 작업속도 < 1ms

캐싱 전략 (Caching Strategies)

읽기 전략

  • 어플리케이션은 데이터를 찾을 때, 캐시에 먼저 확인한다.
  • 캐시에 데이터가 있으면 (Cache Hit) 캐시에서, 없으면 (Cache Miss) 데이터베이스에서 가져온다.
  • 어플리케이션은 데이터베이스에서 데이터를 가져오고, 캐시에 데이터를 저장한다. 따라서 캐시는 찾는 데이터가 없을 때만 캐시에 데이터가 insert 되기 때문에 이를 Look-Aside (Lazy Loading) 이라고 한다.
  • 만약 캐시에 붙어있는 Connection이 많았다면, Cache Miss 발생 시 모두 데이터베이스로 붙기 때문에, 갑자기 많은 부하가 몰릴 수 있다. 이럴 경우를 대비해 미리 데이터베이스에서 캐시로 데이터를 밀어놓는 작업을 할 수 있는데 이 것을 Cache Warming이라고 한다.

쓰기 전략

Write-Around

  • 모든 데이터를 DB에 저장하고, 캐시 미스가 발생한 경우에만 캐시에 데이터를 insert
  • DB와 캐시의 데이터가 상이하다는 단점 존재

Write-Through

  • DB에 데이터를 저장할 때, 캐시에도 같이 저장한다. 캐시는 항상 최신 정보를 가질 수 있음.
  • 저장할 때 마다 2단계 Step을 거쳐서 상대적으로 성능이 느리고, 저장된 데이터가 사용되지 않을 수도 있으므로 리소스를 낭비하는 경우도 있다.

2. Redis 데이터 타입 야무지게 활용하기

Redis Data Types

Redis는 자체적으로 다양한 자료구조를 제공

Best Practice - Counting

String

  • 단순 증감 연산
  • INCR / INCRBY / INCRBYFLOAT / HINCRBY / HINCRBYFLOAT / ZINCRBY
redis > SET score:a 10
“OK”
redis > INCR score:a
(integer) 11
redis > INCRBY score:a 4
(integer) 15

Bits

  • 데이터 저장공간 절약
  • 정수로 된 데이터만 카운팅 가능
  • 예를 들어, 서비스의 저장한 유저 수를 세고 싶다 할 때, 날짜 키를 만들어 놓고 유저 id에 해당하는 비트를 1로 올려준다. 1개의 비트가 1명을 의미하므로, 천만명의 유저는 천만비트로 표현할 수 있고, 이는 1.2MB 정도 차지한다.

redis > SETBIT visitors:20210817 3 1
(integer) 0
redis > SETBIT visitors:20210817 6 1
(integer) 0
redis > BITCOUNT visitors:20210817
(integer) 2

HyperLogLogs

  • 대용량 데이터를 카운팅 할 때 적절 (오차 0.81%)
  • set과 비슷하지만 저장되는 용량은 매우 작음 (12KB 고정)
  • 저장된 데이터는 다시 확인할 수 없음
  • 웹 사이트 방문 유니크한 IP 개수, 크롤링한 url 개수, 검색 단어에서 검색된 유니크한 개수 등 유니크한 값을 계산할 때 사용된다.
redis > PFADD crawled:20211024 "http://www.google.com/"
(integer) 1
redis > PFADD crawled:20171124 "http://www.redis.com/"
(integer) 1
(수백만 건 저장) ↓
redis > PFCOUNT crawled:20211024
(integer) 8278423
redis > PFMERGE crawled:all crawled:20211024 crawled:20211023 . .
(integer) 23905917 // kid를 머지해서 총 값을 띄워줌

Best Practice - Messaging

Lists

  • Blocking 기능을 이용해 Event Queue로 사용

  • Client A가 BRPOP 커맨드를 통해 myqueue에 데이터를 꺼내기 위해 Blocking 되어 있는 상

  • 이때 Client B가 "hi"라는 값을 넣어주면 Client A는 값을 확인 할 수 있다.

  • 키가 있을 때만 데이터 저장 가능 – LPUSHX / RPUSHX,

    • 키가 이미 존재 -> 예전에 사용한 큐 -> 사용했던 큐에만 메시지를 넣을 수 있음. -> 비효율적인 데이터의 이동을 막을 수 있음.

트위터 예제

  • 트위터는 각 유저의 타임라인에 보여줄 트윗을 캐싱하기 위해 Redis 리스트를 사용하는데 RPUSHX를 사용하여 트위터를 자주 이용하는 유저에게만 트윗을 캐싱할 수 있고, 자주 사용하지 않는 유저에게는 리스트에 캐싱 키 자체가 존재하지 않는다. 이를 통해 이용이 저조한 사용자의 트윗을 미리 저장하는 비효율적인 작업을 방지한다.
  • 해당 내용에 대해 더 자세하게 보고 싶으면? -> https://americanopeople.tistory.com/328

Streams

  • 로그를 저장하기 가장 적절한 자료구조
  • append-only 방식으로 저장되며, 중간에 데이터가 바뀌지 않ㄴ는다.
  • 시간 범위로 검색/ 신규 추가 데이터 수신 / 소비자별 다른 데이터 수신 (소비자 그룹) / 카프카 개념을 매우 많이 사용함. (실제로 공식문서에 메시지 브로커 역할을 간단하게 구현할 때, 카프카를 대체해서 사용 가능하다고 소개)

  • mysteam 옆에 있는 별은 아이디 값, 레디스가 알아서 저장하고 데이터가 저장된 시간을 아이디 값으로 반환한다.
  • sensor-id에 1234를, temperture에는 19.8을 저장한 것을 의미한다.

3. Redis에서 데이터를 영구 저장하려면? (RDB vs AOF)

Redis Persistence

Redis는 In-memory 데이터 스토어

  • 서버 재시작 시 모든 데이터 유실
  • 복제 기능을 사용해도 사람의 실수 발생 시 데이터 복원 불가
  • Redis를 캐시 이외의 용도로 사용한다면 적절한 데이터 백업이 필요

Redis Persistence Option

  • AOF: 데이터를 변경하는 커맨드가 들어오면 커맨드를 그대로 저장
  • RDB: 스냅샷 방식으로 동작, 저장 당시의 메모리의 데이터를 그대로 파일로 저장함

자동 / 수동 파일 저장 방법 (AOF rewrite & RDB save)

  • 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)
  • APPENDFSYNC 옵션이 everysec인 경우 최대 1초 사이의 데이터 유실 가능(기본 설정)

제일 강력한 내구성이 필요한 경우

  • RDB & AOF 동시 사용 (공식 문서)

4. Redis 아키텍처 선택 노하우 (Replication vs Sentinel vs Cluster)

Redis 아키텍처의 종류

Replication 구성

단순한 복제 연결

  • replicaof 커맨드를 이용해 간단하게 복제 연결
  • 비동기식 복제
  • HA 기능이 없으므로 장애 상황시 수동 복구
    • 레플리카 노드에 접속해서 복제를 끊어야 함 (replicaof no one 명령어로 레플리카 노드를 마스터 노드로 승격 시켜줘야 함.)
    • 어플리케이션에서 연결 정보 변경해서 배포하는 작업이 필요함.

Sentinel 구성

자동 페일오버 가능한 HA 구성(High Availability)

  • sentinel 노드가 다른 노드 감시
  • 마스터가 비정상 상태일 경우 자동으로 fail over
  • 연결 정보 변경 필요 없음
  • sentinel 노드 (Sentinel Process)는 항상 3개 이상의 홀수로 존재
    • 과반수 이상의 sentinel이 동의해야 fail over가 진행된다.

Cluster 구성

스케일 아웃과 HA 구성(High Availability)

  • 키를 여러 노드에 자동으로 분할해서 저장 (샤딩)
  • 모든 노드가 서로를 감시하여, 마스터 비정상 상태일 때 자동 페일오버
  • 최소 3대의 마스터 노드가 필요

아키텍처 선택 기준

  • HA 기능: 자동으로 fail over가 되어야 하나?
  • 샤딩 기능: 서비스의 확장을 위해 Scale Out 필요 -> Cluster, HA만 필요 -> Sentinel
  • 복제 기능: 복제 필요 -> Replication, 복제 필요 X -> Stand-Alone

5. Redis 운영 꿀팁과 장애 포인트

사용하면 안되는 커맨드

Redis는 Single Thread로 동작

  • 헤비한 커맨드 사용 시, 다른 사용자들은 Blocking 됨
  • keys 대신 scan
  • Hash나 Sorted Set 등 자료구조는 키 내부에 아이템이 많아질수록 성능이 저하 (해시 충돌)
    • 하나의 키에는 최대 100만개의 아이템
    • hgetall → hscan
    • del -> unlink

변경하면 장애를 막을 수 있는 기본 설정값

STOP-WRITES-ON-BGSAVE-ERROR = NO

  • yes(default), RDB파일이 저장되지 않았을 때, redis에 들어오는 모든 write를 차단
  • redis 서버의 모니터링이 적절할 경우, 이 기능을 꺼두는게 좋음
  • RDB 파일 저장 실패 시 redis로의 모든 write 불가능

MAXMEMORY-POLICY = ALLKEYS-LRU

  • redis를 캐시로 사용할 때 Expire Time 설정 권장
  • 메모리가 가득 찼을 때 MAXMEMORY-POLICY 정책에 의해 데이터가 삭제됨.
    • noeviction(default) : 메모리가 가득차면 레디스에 새로운 값을 저장하지 않음. -> 서비스 장애로 이뤄질 수 있음.
    • volatile-lru: 가장 최근에 사용하지 않은 키부터 삭제. -> Expire Time 설정에 있는 Key값만 삭제. -> Expire Time 설정이 없는 키값은 그대로 남아 있음 -> 마찬가지로 서비스 장애로 이뤄질 수 있음
    • allkeys-lru: 모든 키에 대해 LRU 방식으로 키를 삭제 -> 적어도 데이터가 가득참으로 인해 서비스 장애로 이뤄지지는 않음.

Cache Stampede

TTL 값을 너무 작게 설정한 경우

  • 키가 만료되는 순간 많은 서버 노드에서 키를 같이 보고 있었다면, 그 서버들이 디비에 가서 값을 찾게 되는 Duplicate Read 현상이 발생한다.
  • 그 값을 레디스에 각각 저장하게 되는 Duplicate Write 발생
  • 발생 하면 처리량이 느려 뿐 아니라, 불필요한 작업이 굉장히 늘어나기 때문에 장애로 일어날 수 있음.
  • 실제로 티켓 링크의 경우 인기있는 공연이 오픈될 경우 하나의 공연 데이터를 읽기 위해 몇 십개의 데이터 서버에서 커넥션이 연결된다.
  • 이를 해결하기 위해 TTL 시간을 늘림.

MaxMemory 값 설정

Persistence / 복제 사용시 MaxMemory 설정 주의

  • RDB 저장 & AOF rewrite 시 fork()를 통해 자식 프로세스 생성
  • 자식 프로세스는 백그라운드로 데이터를 파일로 저장하고 있지만, 부모 프로세스는 일반적인 요청을 받아 데이터를 처리하고 있음. Copy-on-Write로 인해 메모리를 복사해서 사용하기 때문에 이런 작업이 가능
  • 이로인해 서버의 메모리 사용률이 두배로 사용하는 경우 발생 가능
  • 저장하지 않더라도, 복제 기능 사용 시 처음 시도하거나 연결이 끊겨서 재시도를 할 경우 새로 RDB 파일을 저장하는 과정을 거친다.
  • Persistence / 복제 사용 시 MaxMemory는 실제 메모리의 절반으로 설정
    예) 4GB -> 2048MB

Memory 관리

물리적으로 사용되고 있는 메모리를 모니터링

  • used_memory: 논리적으로 Redis가 사용하는 메모리
  • used_memory_rss: OS가 Redis에 할당하기 위해 사용한 물리적 메모리 양
  • used_memory_rss와 used_memory의 차이가 크면 클수록 fragmentation이 크다고 말함.
  • 삭제되는 키가 많으면 fragmentation 증가
    - 특정 시점에 피크를 찍고 다시 삭제되는 경우
    - TTL로 인한 eviction이 많이 발생하는 경우
  • CONFIG SET activedefrag yes: 단편화 (fragmentation)가 많이 발생했을 경우activedefrag를 켜두는 것이 좋음. 공식문서에서도 항상 켜두지는 말고 단편화 (fragmentation)가 많이 발생했을 경우에만 켜놓을 것을 권장함.

Reference: https://www.youtube.com/watch?v=92NizoBL4uA&t=125s

profile
평범한 백엔드 개발자

0개의 댓글