다오의 개발일지

Redis 야무지게 사용하기 본문

개발지식

Redis 야무지게 사용하기

다오__ 2024. 3. 19. 02:14

Redis 캐시로 사용하기

캐시란? 사용자 입장에서 원래의 소스보다 더욱 빠르게 엑세스 할 수 있는 임시 데이터 저장소를 뜻한다. 대부분의 애플리케이션에서 속도 향상을 위해 캐시를 사용하고 있다.

 

  1. Key - Value 구조 : 어떠한 데이터도 쉽게 저장할 수 있다.
  2. In-memory 데이터 저장소(RAM) : 굉장히 빠른 속도
  3. 빠른성능
    1. 평균 작업속도 < 1ms
    2. 초당 수백만 건의 작업 가능

 

캐싱 전략(Caching Strategies)

레디스를 사용할 때, 시스템에 어떻게 배치하는지에 따라 성능이 달라지게 된다. 이를 효과적으로 배치하기 위한 전략이다.

데이터의 유형과 데이터에 대한 엑세스 패턴을 잘 고려하여 선택해야 한다.

 

읽기 전략

1. 애플리케이션은 데이터를 찾을 때 우선 Cache를 찾는다.

2. 데이터가 없다면 DB에서 데이터를 가져온 뒤, 레디스에 저장한다.

3. 따라서 캐시가 없을때만 DB를 조회하기 때문에 이를 지연로딩이라고도 한다.

 

Redis가 다운되더라도 장애로 이어지지 않고 DB에서 데이터를 가져올 수 있는 장점이 있다. 하지만 캐시로 붙어있던 커넥션이 많았다면, 커넥션이 전부 DB로 붙기 때문에, DB로 부하가 많이 생길 수가 있다.

 

캐시를 새로 투입하거나, DB에만 새로운 데이터를 저장했다면 처음에 Cache Miss가 발생해서 성능 저하가 올 수 있다.

이럴때에는, 미리 DB에서 캐시로 데이터를 밀어넣어주는 작업을 할 수 있다. 이를 Cache Warming이라고 한다.

 

티켓링크에서는 상품 오픈 전, 상품의 정보를 DB에서 캐시로 올려주는 작업을 매번 진행하고 있다고 한다.

 

 

쓰기 전략

Write-Around : DB에만 데이터를 저장하는 방식, 모든 데이터는 DB에 저장되고, Cache-Miss가 발생한 경우 캐시에 있던 데이터를 끌어오게 된다. 이 경우, 캐시내의 데이터와 DB내의 데이터가 다를 수 있다는 단점이 있다.

 

Write-Through : DB와 캐시 둘다 데이터를 함께 저장하는 방식, 캐시는 항상 최신정보를 가지고 있다는 장점이 있지만, 데이터를 저장할 때마다 2단계를 거쳐야 하므로, 상대적으로 속도가 느리다. 그리고 저장된 데이터가 재 사용되지 않을 수 있기 때문에, 리소스 낭비가 될 수도 있다. 따라서 데이터를 몇분, 몇시간만 보관할지를 정하는 Expire_Time을 설정하는 것이 좋다. 하지만 이것을 어떻게 관리하느냐에 따라 장애가 발생할 수도 있다.

 

Redis의 데이터 타입

 

  • Strings : 제일 기본적인 문자열 데이터타입, setCommand로 저장하는 데이터는 모두 Strings로 저장한다.
  • Bitmaps : Strings의 변형이라도 볼 수 있고, 비트연산이 가능하다.
  • Lists : 데이터를 순서대로 저장하는 리스트는 Queue로 사용하기 적절하다.
  • Hashes : 하나의 KEY 안에 또 다시 KEY-VALUE 쌍으로 데이터를 저장한다.
  • Sets : 중복되지 않는 문자열의 집합이다.
  • Sorted Sets : Sets처럼 중복되지 않는 값을 저장하지만, 모든 데이터는 score라는 숫자값으로 정렬된다. 데이터가 저장될 때부터 score순으로 정렬되며, 만약 score가 같을때에는 사전순으로 정렬되어 저장된다.
  • HyperLogLogs : 많은 데이터를 다룰때 주로 쓰이며, 중복되지 않는 값의 개수를 카운트할 때 사용된다.
  • Streams : 로그를 저장하기 가장 좋은 자료구조이다.

 

사용 사례로 데이터타입의 특징을 파악하기

Counting

레디스에서 카운팅을 하기 쉬운 방법은, KEY하나를 만들어서, 카운팅 시마다 값을 1씩 추가하는 것이다.

Strings.incr (=increment) 함수를 사용하면 쉽게 구현 가능하다.

  1. score:a 라는 key에 10을 저장한다.
  2. INCR 함수를 쓰면 11이 되는 것을 확인가능하다.
  3. INCRBY 함수를 사용해 얼만큼 증가시킬지 입력할 수 있다.

두번째는 비트연산을 사용한다.

 

비트연산은 저장 공간을 많이 절약할 수 있다.

 

우리 서비스에 오늘 접속 한 유저 수를 세고 싶다면,

  1. 날짜 KEY 하나를 만들고, 유저 ID에 해당하는 bit를 1로 올려준다.
  2. 한 개의 bit가 한 명을 의미하므로, 천만명의 유저는 천만 개의 bit로 표현할 수가 있는데, 1.2메가바이트정도면 충분하다.
  3. 모든 데이터를 정수로 표현 할 수 있어야 한다. user_id의 값이 0이상의 정수 값일 때에만 카운팅이 가능하다.
  4. SETBIT, BITCOUNT

 

  • HyperLogLogs는 모든 String 데이터값을 unique하게 구분할 수 있다.
  • 다시 확인이 불가능 하므로, 데이터를 보호하기 위한 목적으로도 사용할 수 있다.
  • 방문한 유저의 IP의 개수, 하루종일 크롤링한 url의 개수가 몇개인지, 검색엔진에서 검색된 단어가 몇개인지 등
  • PFADD, PFCOUNT, PFMERGE

Messaging

Redis의 Lists는 메시지 큐로 사용하기 적절하다. 특히 자체적으로 blocking 기능을 제공하기 때문에, 적절히 사용하여 불필요한 polling을 막을 수가 있다.

 

 

  1. Client A가 BRPOP myqueue 0 데이터를 꺼내오려고 하는데 데이터가 없다.
  2. Client B가 LPUSH myqueue 에 "hi" 데이터를 넣었다.
  3. 바로 Client A에게 데이터가 전달되었다.

키가 있을 때에만 데이터 저장 가능하다는 것은 이미 사용했던 Key를 의미하기 때문에, 사용했던 Queue에만 메시지를 전달할 수 있기도 하다. 따라서 비효율적인 데이터 이동을 막을 수 있다.

 

인스타그램, 페이스북 SNS에는 유저별로 타임라인이 존재하고, 내가 팔로우 한 사람들의 데이터가 나오는데, 트위터에서는 각 타임라인에 뜨는 트윗을 캐싱하기 위해 레디스의 Lists를 사용하는데, 이때 RPUSHX 커맨드를 사용한다. 이를 이용해서 트위터를 자주 이용하던 유저의 타임라인에만 새로운 데이터를 미리 캐시해 놓을 수 있으며, 자주 사용하지 않는 유저는 caching key 자체가 존재하지 않기 때문에, 그런 유저들을 위해 데이터를 미리 쌓아놓는 것과 같은 비효율적인 작업을 방지할 수 있게 된다.

 

 

append-only : 쓰기(write) 연산에 대한 로그를 모두 남겨서 유지하여 시스템 장애가 발생하거나 시스템이 재시작되었을 때 데이터 재구성을 돕는다. 모든 연산을 순차적으로 저장하여 로그를 통해 상태를 복구할 수 있도록 돕는다.

 

XADD mystream * sensor-id 1234 temperature 19.8

* : ID를 입력하는 부분이지만 *로 입력하면 Redis가 자동으로 id를 만들어서 저장한다. "1634823241307-0"

자동으로 저장된 ID는 데이터가 저장된 시간을 의미한다.

 

ID값을 이용해 시간 대역대로 저장된 값을 검색할 수도 있고, 실제 서버에서 로그를 읽을 때 tail-f를 사용하는 것처럼 새로 들어오는 데이터만 읽을 수도 있다.

 

 

Redis에서 데이터를 영구 저장하려면?

(RDB vs AOF)

 

Redis는 In-memory 데이터 스토어

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

AOF : 데이터를 변경하는 커맨드가 들어오면, 그대로 모두 저장해 파일로 남긴다.

RDB : 스냅샷 방식으로 동작, 저장 당시 메모리에 있는 데이터 그대로를 사진찍듯 그대로 저장해 파일로 남긴다.

 

RDB의 경우 key1에 apple이 들어있는 것만 확인 가능한것에 비해, AOF는 중간에 key2가 b로, 그리고 지워진것까지 전부 기록되어 있어 확인할 수가 있다.

데이터가 추가 되기만 하기 때문에, 용량이 커지게 된다. AOF는 주기적으로 압축해서 재작성되는 과정을 거쳐야 한다.

 

자동 / 수동 파일 저장 방법

 

RDB

  • 자동 : redis.conf 파일에서 SAVE 옵션(시간을 기준으로 설정가능)
  • 수동 : BGSAVE 커맨트를 이용해 cli 창에서 수동으로 RDB 파일 저장
    • SAVE 커맨드는 절대 사용 X

AOF

  • 자동 : redis.conf 파일에서 auth-aof-rewrite-percentage 옵션(크기를 기준으로 압축시점  설정 가능)
  • 수동 : BGRERITEAOF 커맨드를 이용해 cli 창에서  수동으로 AOF 파일 재작성

RDB vs AOF 선택 기준

Reids를 캐시로만 사용한다면 이 기준은 필요없다.

  • 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우
    • RDB 단독 사용
    • redis.conf 파일에서 SAVE 옵션을 적절히
    • 사용 예)SAVE 900 1 = 900초 동안 1개 이상의 key가 변경되었을 경우 RDB파일을 재 작성
  • 장애 상황 직전까지의 모든 데이터가 보장 되어야 할 경우
    • AOF 사용(appendonly yes)
    • APPENDFSYNC 옵션이 everysec인 경우 최대 1초 사이의 데이터 유실 가능(기본 설정)
  • 제일 강력한 내구성이 필요한 경우
    • 두가지 전부 사용

Redis 아키텍처 선택 노하우

 

Replication  : 마스터와 복제가 한개씩 존재

Sentinel : 마스터와 복제가 존재 추가로 센티널 노드가 필요하다. 센티널은 일반 노드들을 모니터링 한다.

Cluster : 최소 3대의 마스터가 필요하며 샤딩 기능을 제공한다.

 

Replication 구성

단순한 복제 연결

  • replicaof 커맨드를 이용해 간단하게 복제 연결
  • 비동기식 복제
  • HA 기능이 없으므로 마스터에 장애 상황 시 수동 복구
    • 복제에 접속해서 노드를 끊어야 하고 애플리케이션에서 연결 정보를 변경하여 배포해야한다.

Sentinel 구성

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

  • 센티널 노드가 다른 노드 감시
    • 마스터가 비정상 상태일 때 자동으로 페일오버하여 복제가 마스터가 된다.
  • 연결 정보 변경 필요 없음
    • 어플리케이션에서는 센티널 노드만 알고 있으면 되고, 변경된 마스터의 연결 정보로 자동 연결 시켜준다.
  • 센티널 프로세스를 추가로 띄어야 하는데 센티널 노드는 항상 3대 이상의 홀수로 존재해야 함
    • 과반수 이상의 센티널이 동의 해야 페일오버 진행

 

Cluster 구성

스케일 아웃과 HA 구성

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

 

 

 

사용하면 안되는 커맨드

Redis는 Single Thread로 동작

  • keys * -> scan으로 대체 (속도개선)
    • 오래걸리는 작업을 진행한다면, 다른 사용자들은 대기해야만 한다.
  • Hash나 Sorted Set 등 자료구조는 내부에 여러개의 아이템을 저장할 수 있다.
    • 키 나누기(최대 100만개),
      • 키 내부에 아이템이 많아질 수록 성능 저하가 발생하기 때문에 100만개를 넘기지 말아야한다.
    • hgetall -> hscan
    • del -> unlink
      • 키를 background로 제거해줌

 

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

STOP-WRITES-ON-BGSAVE-ERROR = NO

  • yes(default)
  • RDB 파일 저장 실패 시 redis로의 모든 write 불가능
  • Redis 서버에 대해 모니터링이 적절하다면 이 기능을 끄는게 좋다.

 

 

 

MAXMEMORY-POLICY = ALLKEY-LRU

  • redis를 캐시로 사용할 때 Expire Time 설정 권장, 하지 않을 시 메모리가 금방 차게 찬다.
  • 메모리가 가득 찼을 때 MAXMEMORY-POLICY 정책에 의해 키 관리
    • noeviction(default) : 삭제 안함
    • volatile-lru : 가장 최근에 사용하지 않았던 것부터 제거, 하지만 expire 설정 된 키만 제거되기 때문에 1번째와 같은 결과가 나올 수도 있다.
    • allkeys-lru : 가장 최근에 사용하지 않았던 것 부터 제거, 모든 key에 해당한다.

 

TTL(Time-To-Live)을 넉넉하게 두어야 한다. 그렇지 않으면 Cache Stampede가 발생하여 장애가 일어날 수 있다.

Cache Stampede 현상 : Look-Aside(Lazy-Loading)를 통해 DB에 있는 데이터를 캐시에 저장한다. 하지만 Key가 만료 되는 순간, 많은 서버에서 이 Key를 같이 보고 있었다면,  모든 애플리케이션 서버들이 DB에 동시접근해서 같은 데이터를 찾게 되는 Duplicate Read가 발생하게 된다. 또한 읽어온 값을 각각 Redis에 쓰는 Duplicate Write또한 발생한다.

 

 

MaxMemory 값 설정

 

 

Redis의 데이터를 파일로 저장할 때 fork()를 이용해서 자식 프로세스를 생성한다. 자식 프로세스로 background에서는 데이터를 파일로 저장하고 있지만,

기본 프로세스는 일반적인 요청을 받아 데이터를 처리하고 있다. 이것이 가능한 이유는 Copy-on-Write라는 기능으로 메모리를 복사해서 사용하기 때문인데, 이로 인해 서버에서 사용하는 메모리가 2배로 증가하게 된다. 

 

복제 연결을 처음 시도하거나, 연결이 끊겨 재시도를 할 때에 새로 RDB 파일을 저장하는 과정을 거치기 때문이다.

이런 경우 MaxMemory 값은 실제 메모리의 절반을 설정해주는것이 낫다.

 

 

Memory 관리 가장 중요!

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

  • used_memory: 논리적으로 Redis가 사용하는 메모리
  • used_memory_rss: OS가 Redis에 할당하기 위해 사용한 물리적 메모리, 더 중요!
  • 삭제되는 키가 많으면 fragmentation 증가
    • 특정 시점에 피크를 찍고 다시 삭제되는 경우
    • TTL로 인한 eviction이 많이 발생하는 경우

 


https://www.youtube.com/watch?v=92NizoBL4uA

 

 

캐싱

채팅을 위한 pub/sub

영상 스트리밍

로그인기록저장

etc

'개발지식' 카테고리의 다른 글

Database 선택 가이드  (0) 2024.03.19
TDD란? (Test-Driven Development)  (0) 2024.03.05