Life/컨퍼런스

[우아한 Tech] 우아한 Redis

샤아이인 2022. 9. 25.

강대명 님의 우아한 Redis를 들으며 정리한 내용 입니다.

 

1. Redis 소개

  • 인메모리 데이터 저장소 오픈소스
  • String, set ,sorted-set, hashes, list 등 다양한 타입 지원

 

1 - 1) 일반적인 Cache 사용법 (Look asid Cahce)

캐시에 자료가 없으면, DB 접근 후 캐시에 갱신하는 방식

출처 - https://youtu.be/mPB2CZiAkKM

1 - 2) Write Back 방식

Write가 매우 빈번한 경우, 매번 DB에 접근하여 Write하기 보다는 모아서 Cache에 저장해 두었다가, 나중에 특정 시점에 DB에 한번에 날려준다.

출처 - https://youtu.be/mPB2CZiAkKM

하지만 이 방식은 장애가 발생하여 Cache가 날라가버릴 위험이 있다.

 

예를 들어 log를 DB에 저장해야 할 때 write back과 같은 방식을 사용한다.

 

2. Redis의 장점

memcached와 redis의 대표적인 차이점중 하나는, redis는 collection을 지원하지만 memcached는 그렇지 않다.

즉, redis는 이미 만들어진 유용한 기능을 가져다 사용할수가 있다!

 

이는 마치 C 와 Python과 유사하다. 여러 부가기능을 제공하는 Python이 C보다 생산성이 높은것은 당연하다.

 

memcached와 redis에서도 마찬가지 이다. 당연히 Collection을 지원하는 redis가 더 생산성이 높다.

 

2 - 1) Collection이 중요한 이유

  • 만약 랭킹 서버를 직접 구현한다면?
    • DB에 유저 Score를 저장하고 Order by로 정렬 후 읽어오는 방식이 있다.
    • 하지만 이 방식은 디스크에 접근함으로 성능이슈 발생 할 수 있음
    • Redis의 Soreted Set을 이용하면, 랭킹을 쉽게 구현 가능하다.
      • Replication도 쉽게 가능

 

  • 친구 리스트를 Key/Value 형태로 관리한다면?
    - 트랜잭션이 작동하는 중 동시성 이슈 발생 가능

이상적인 상황은 다음과 같을 것 이다. 트랜잭션이 순차적으로 동작하여 문제가 발생하지 않았다.

출처 - https://youtu.be/mPB2CZiAkKM

 

하지만 Race Condition이 문제가 발생할수도 있다.

출처 - https://youtu.be/mPB2CZiAkKM

Redis는 자료구조가 Atomic하기 때문에 Rece Condition을 피할 수 있다.

즉, Collection을 잘 이용하는 것으로, 여러가지 개발 시간을 단축시키고, 문제를 줄여줄 수 있다.

 

3. 어디서 사용해야 할까?

3 - 1) Remote Data Store

A, B, C 서버에서 데이터를 공유하고 싶을때!

 

3 - 2) 한대에서만 필요하다면, 전역 변수를 사용하면 되는것 아닐까?

Redis자체가 싱글스레드라 Atomic을 보장해준다.

 

일반적으로 인증토큰 저장(Strings, hash), Ranking보드, 유저 API limit

 

4. Redis Collection

4 - 1) 기본 사용

  • Strings : Key, Value 저장 방식
  • List : head, tail에 데이터 삽입시 빠르지만, 중간 삽입은 시간이 걸림
  • Set : 중복된 데이터 방지
  • Sorted Set : Score를 추가하여 순서를 보장 가능
  • Hash

보통 Strings 을 통해 저장할때는 Key의 네이밍이 중요하다.

Set token:123456 shine
Get token:123456

위와 같이 사용한 경우, key를 보면 prefix로 token을 명시해 주었다.

이런 prefix를 통해 나중에 분산이 변경될수도 있다.

 

4 - 2) 주의사항

하나의 Collection에 너무 많은 item을 담지 않는것 이 좋다. 10000개 이하 를 유지하는것이 좋다.

 

또한 Expire는 Collection의 item 개별로 걸리는 것이 아니라, Collection 자체에 걸린다.

=> 즉, Collection에 expire가 걸려 있다면 그 시간후에 Collection에 저장된 모든 아이템이 삭제된다.

 

5. Redis 운영

5 - 1) 메모리 관리를 잘하자

Redis는 In-memory data store이다. 즉, Physical Memory 이상을 사용하면 문제가 발생한다.


메모리를 다 사용했다고 해서 바로 죽는것이 아니라 Swap을 사용하게 되는데, Swap이 있다면 Swap 사용으로 해당 메모리 Page 접근시 마다 늦어짐. -> 디스크에 접근하다보니 당연함.

 

Swap이 없다면 OOM(out ot memory) 으로 죽게됨

 

Maxmemory를 설정하더라도 메모리 할당 구현체에 메모리 관리 의존, 따라서 Redis는 정확한 메모리 사용량을 알 수없어 이보다 더 사용할 가능성이 큼. => 메모리 파편화랑 관련 있음

 

이러한 사항 때문에 모니터링 도구는 필수적이다.

 

큰 메모리의 instance 하나보다 적은 메모리 instance 여러개가 안전하다.

출처 - https://youtu.be/mPB2CZiAkKM


Redis는 FORK를 하는데, copy on wirte기반이라 write가 헤비한 Redis는 최대 메모리 두배까지 쓸 수 있다.

위 사진을 보면 8GB 3개 짜리는 3개준 하나만 fork를 하면 24GB가 되는데, 24GB는 바로 48GB까지 되버린다.

 

3.x대 버전에서는 메모리 파편화가 발생이 심했다.

실제 사용중인 메모리가 2GB로 보고되지만, 실상은 11GB까지 사용되는 경우도 있었음

 

4.x대 버전부터 Jemalloc에 힌트를 줘서 파편화를 많이 줄이게 됨

 

(tip, Redis사용시 다양한 사이즈를 가지는 데이터보다는 유사한 크기의 데이터를 저장하는 경우가 유리하다)

 

메모리가 부족할 때는?

 

  • 좀 더 메모리 많은 장비로 Migration -> 메모리가 빡빡하면 Migration중에 문제가 발생 할 수도 (60~70% 사용중이면 이전 고려)
  • 기존 데이터 줄이기
  • 기본적으로 Collection 들은 다음과 같은 자료구조 사용
    • Hash -> HashTable을 하나 더 사용
    • Sorted Set -> Skiplist와 Hash Table을 이용
    • Set -> HashTable 사용
    • 위 자료구조들은 메모리들을 생각보다 많이 사용함, 그래서 Ziplist를 활용하는 방법도 있다.

자료구조를 사용할때 내부적으로 Ziplist를 사용하도록 변경하는 방식이다.

 

Ziplist는 선형으로 저장, In-Memory 특성 상, 적은 개수라면 선형탐색을 하더라도 빠르다.

 

5 - 2) O(N)명령은 주의 하자

Redis는 Single Thread라 하나의 요청이 병목되면 뒤에 요청들도 계속 기다려야한다.

 

단순한 get/set의 경우 초당 10만 TPS 이상 가능(CPU속도에 영향을 받음)

위와 같이 TCP에서 packet을 받는 상황이라고 해보자.

Packet 단위로 받아 하나의 Command가 완성되면 실행되는 구조이다.

 

▶ 대표적인 O(N) 명령

  • KEYS
  • FLUSHALL
  • FLUSHDB
  • Delete COlLECTIONS
  • Get All Collections

▶ 대표적인 사례

  • Key가 100만개 이상인데, 확인을 위해 KEYS 명령을 사용하는 경우
  • 큰 컬렉션의 데이터를 다 가져오는 경우
  • 예전 Spring security oauth RedisTokenStore(최신 버전은 패치됨)
    • Access Token 저장을 List(O(N)) 자료구조를 통해서 이루어짐 -> 삭제시에 모든 item을 찾아봐야함
    • 최근에는 Set(O(1))을 통해 가져오도록 패치됨
  • keys는 scan 명령어로 사용하는 것으로 하나의 긴명령을 짧은 여러 명령으로 변경 가능하다
    • 짧은 scan 명령 사이 시간에 대기중인 다른 명령을 처리하게 된다!
  • Collection의 모든 아이템을 가져와야 하는 경우
    • 일부분만 가져오거나
    • 큰 Collection을 작은 여러개의 Collection으로 나누어 저장하는 것 이 좋음

 

5 - 3) Redis Replication

  • Async Replication -> Replication Lag이 발생 할 수 있다.
    • A에 저장된 데이터에 변경을 가하면, B에도 변경을 하라고 명령을 쌓아두는데, 그 틈사이에 정합성이 보장되지 못함
  • DBMS로 보면 statement replication과 유사 -> 쿼리가 직접 간다.

 

▶ Replication 설정 과정

  • Secondary에 replicaof 명령 전달
  • Secondary는 Primary에 sync 명령 전달
  • Primary는 현재 메모리 상태를 저장하기 위해 Fork (요 과정에서 copy on wirte가 발생 할 수 있음)
  • Fork 한 프로세서는 현재 메모리 정보를 disk에 dump (disk 대신 메모리에서 바로 줄 수도 있음)
  • Fork 이후의 데이터를 secondary에 계속 전달
  • 이러한 Fork가 발생하면 메모리 부족이 발생할 수 있다.
  • AWS나 클라우드의 Redis는 다르게 구현되어 있어서 좀 더 안정적일 수 있다.

 

많은 대수의 Redis가 Replica를 두고 있다면, 네트워크 이슈나, 사람의 작업으로 동시에 replication이 재시도 되도록 하면 문제가 발생할 수 있다.

 

5 - 4) redis.conf 권장설정

  • Maxclient 설정 50000, Maxclient 수만큼만 접속 가능
  • RDB/AOF 설정 off
  • 특정 commands disable
  • 전체 장애의 90% 이상이 KEYS와 SAVE 설정을 사용해서 발생 -> Ziplist 사용하기

 

6. Redis 데이터 분산

크게 애플리케이션에서 어떻게 다룰지와, Redis Cluster를 쓸지에 따라 나뉘게 된다.

 

6 - 1) Application

▶ Consistent Hashing

  • 일반 모듈러 해싱 방식은 서버가 장애/추가 될 때마다 여러 서버에서 걸쳐 데이터 리밸런싱이 일어난다.
  • Consistent Hashing은 자기 해시보다 크면서 가장 근접한 해시를 가진 서버 찾아간다. 따라서 서버 장애/추가 될 때에 N분의 1만큼만 이동하게 된다.

 

▶ Sharding

데이터를 어떻게 나눌 것 인가? == 데이터를 어떻게 찾을 것 인가?

  • Range: 특정 Range를 정의하고, 해당되는 Range에 저장한다
    • 데이터가 일정 Range에 몰리거나 비어있을 수 있다.
    • 서버의 상황에 따라서 놀고있는 서버와, 일하는 서버가 극심하게 나뉜다.
    • 하지만 서버 확장에는 편함
  • Modular 연산을 사용할 경우 서버를 하나씩 추가하면 데이터 리벨린싱이 빈번하게 발생한다.
    • 추가 서버를 N배씩 늘려준다면, 원래 자기 서버 번호 +N 번으로 이동하면된다. (N은 2, 4, 8, 16, 32, 64 ... 2배씩 증가)
  • Indexed: 해당 key가 어디에 저장되어야 할 관리 서버가 따로 존재, 단 인덱스 서버가 죽으면 심각한 장애

 

6 - 2) Redis Cluster

  • Hash 기반으로 Slot 16384로 구분
  • slot = crc16(key) % 16384
  • Key가 Key{hashKey} 패턴이면, hashKey에 명시된 Slot으로 이동한다.
  • Redis 서버는 slot range를 가지고 있기때문에 slot 단위로 데이터를 다른 서버로 전달하게 된다.
  • 자기 slot 외에 데이터가 들어오면 -Moved {서버} 에러를 보낸다. 라이브러리는 이 에러를 받고 해당하는 서버로 옮겨 줘야한다.

▶ Redis Cluster 장단점

  • 장점
    • 자체적인 Primary, Secndary Failover.
    • Slot 단위의 데이터 관리
  • 단점
    • 메모리 사용량이 더 많음
    • Migration 자체는 관리자가 시점을 결정해야함
    • Library 구현이 필요함

 

7. Redis Failover

7 - 1) Coordinator 기반 Failover

처음에는 다음과 같이 Server가 사용해야 할 Redis가 1번이라고 명시해둠

그러면 1번 Redis만 사용하게 됨.

 

이후 Redis 1번에서 장애가 발생하면, 이를 Healt Checker가 인식하고 Redis 2를 Primary로 승격시키게 된다.

Healt Checker가 Coordinator에게 current Redis가 2번으로 변경되었다고 업데이트 한다.

 

  • Coordinator 기반으로 설정을 관리한다면 동일한 방식으로 관리가 가능
  • 해당 기능을 이용하도록 개발이 필요

 

7 - 2) VIP 방식

우선 Virtual IP 를 Redis 1에게 할당하여 Server가 Redis 1번에만 요청을 보내도록 한다.

 

이후 Redis 1에서 장애가 발생하면, Redis 2에 VIP를 할당하여 승격시킨다.

Health Checker는 죽은 서버의 연결을 모두 끊어 클라이언트의 재접속을 유도한다.

 

DNS 방식도 이와 유사하게 domain을 할당하면서 사용하면 된다.

 

클라이언트에 추가적인 구현이 필요없다.

 

VIP 기반은 외부로 서비스를 제공해야 하는 서비스 업자에게 유리하다. 예를 들어 AWS

 

DNS 기반은 DNS Cache TTL을 관리해야한다.

  • 클라이언트가 DNS를 캐싱해버리면 접속이 안될 수 있다.
  • VIP 기반은 외부로 서비스를 제공해야하는 서비스 업자에 유리
  • DNS 변경은 VIP 변경보다 훨씬 간단하다.

 

8. Redis Moditoring

  • Redis Info를 통한 정보
    • RSS : 피지컬 메모리를 얼마나 쓰고 있냐, OS가 보고 있는 지표
    • Used Memory: 레디스가 알고 있는 사용 메모리
    • Connection 수: 레디스는 싱글 스레드라 컨넥션을 계속 맺고 해제하면 성능이 급격하게 떨어질 수 있다.
    • 초당 처리 요청수
  • System
    • CPU: 초당 처리 요청수는 짧은 명령이라면 CPU에 영향을 받는다.
    • Disk
    • Network rt/tx: 스위치에서 너무 많은 데이터가 있으면 drop이 일어날 수 있어서 확인 필요
  • CPU가 100%를 칠 경우
  • 레디시는 싱글 스레드라 CPU 하나를 사용, 따라서 CPU 단일 성능을 높이는게 좋음
  • O(N) 계열의 특정 명령이 많은 경우
    • Monitor 명령을 통해 특정 패턴을 파악하는 것이 필요
    • Monitor 잘못쓰면 부하로 해당 서버에 더 큰 문제 발생 할 수 있음. 짧게 쓰는게 좋다

댓글