본문 바로가기
Semina

[우아한 세미나] Redis 그리고 개인적인 탐구 내용들

by Jordy-torvalds 2021. 9. 29.
반응형

레디스 기초

레디스 소개

  • 인-메모리 자료구조 저장소
  • 오픈소스(BSD 3 License)
  • 싱글 스레드
  • C 언어로 개발
  • 지원 자료 구조
    • 문자열, 세트, 정렬된 세트, 해쉬, 리스트
    • 하이퍼 로그 로그, 비트맵, 지오스패셜 인덱스
    • 스트림
  • 지원자가 한 명!

캐시란?

나중에 요청된 결과를 미리 저장해두었다가 빠르게 제공 해주는 것

ex) 팩토리얼 이전 단계 저장

파레토 법칙

전체 요청의 80%는 20%의 사용자

캐시 구조

Look aside cache

일반적인 경우

Write Back

쓰기가 빈번한 경우(ex.로그)

모아서 처리해서 쓰기가 빠르지만.. 유실의 위험이 있다..

컬렉션

  • 데이터를 저장하는 자료 구조를 제공해줘서 개발이 편하고 난이도도 내려감.
  • 멤캐쉬는 제공해주지 않음!
  • 개발의 편의성: Sorted Set을 사용한 Ranking 서버 구현
  • 개발의 난이도: 싱글 스레드이므로 모든 트랜잭션이 원장성을 가짐
    • 경쟁 상태가 발생하지 않음

Strings

문자열 타입 value.

세션 키나 JWT 토큰 등 저장에 사용

# Setter
set $key $value
get $key

# example
set jordy-server:user-token-ID:1 1234
get jordy-server:user-token-ID:1

# multi setter
mset $key1 $value1 $key2 $value2 $key3 $value3
mget $key1 $key2 $key3

# example
mset jordy-server:user-token-ID:1 1234 jordy-server:shopping-cart:1 2 
mget jordy-server:user-token-ID:1 jordy-server:shopping-cart:1

List

# left insert
lpush $key $value1 $value2 $value3

# example
lpush jordy-server:purchase-history pen water eraser

# right insert
rpush $key $value1 $value2 $value3

# example
rpush jordy-server:purchase-history pen water eraser

Set

중복되지 않는 배열

데이터 유무만 체크

ex) sns에서 팔로우 리스트 구현시 사용

# add unique value 
sadd $key $value
## 이미 존재하는 value면 추가 되지 않음

# get all
smembers $key
## 해당 키로된 데이터 전체를 조회
## 시간 복잡도가 O(N)이므로 주의 필요

# check existing key-value
sismember $key $value
# 존재하면 1 없으면 0

Sorted Sets

중복되지 않는 정렬된 세트

score에 따라 정렬된 결과가 필요할 때 사용.

# add
zadd $key $score $value
## 이미 존재하는 value면 해당 score로 변경됨
## $score는 부동(떠서 움직이는) 소수점인 double 타입이기 때문에 정확하지 않을 수 있음.
### 컴퓨터에서는 표현이 불가능한 정수값들이 있고 이러한 값을 할당할 경우 
### 정확한 결과가 나오지 않음.

# get by range
zrange $key $startIndex $endIndex

# get by reverse range
zrevrange $key $startIndex $endIndex

# get all range
zrangebyscore $key  ($startIndex +inf

# get by all reverse range
zrevrangebyscore $key  (+inf $endIndex

Hash

Key 밑에 sub key를 둘 수가 있어 연관성이 있는 key간에 계층 구조를 만들 수 있다.

# hash multi set
hmset $key $subkey1 $value1 $subkey2 $subkey2

# get all subkey-value by key
hgetall $key
## 해당 키와 관련된 모든 서브키와 데이터를 가져옴

# get subkey by key
hget $key $subkey

# get multi subkey-value by key
hmget $key $subkey1 $subkey2 $subkey3

# example
hmset niniz name jordy species dinosaur weight 30

---
hgetall

# console
1) "name"
2) "jordy"
3) "species"
4) "dinosaur"
5) "weight"
6) "30"

---
hget niniz name

#console
1) jordy
---
hmget niniz name, species

#console
1) jordy
2) dinosaur

컬렉션 주의사항

하나의 컬렉션에 너무 많은 아이템을 담으면 좋지 않음.

  • 10,000개 이하의 수준으로 유지하는게 좋음

expire는 collection의 item 개별로 걸리지 않고 전체 collection에 대해서만 걸림

  • 10,000개의 아이템을 가진 collection에 expire가 걸려있으면 그 시간 이후에 10,000개의 아이템이 모두 삭제됨.

레디스 운영

  • 메모리 관리를 잘하자
  • O(N) 관련 명령어는 주의하자

메모리 관리

  • 물리 메모리 이상을 사용하면 문제가 발생
    • Swap이 있다면 Swap 사용으로 해당 메모리 Page 접근시 마다 늦어짐
  • Maxmemory를 설정하더라도 이보다 더 사용할 가능성이 큼
    • 초과해서 사용하면 설정에 따라 expire 목록에 있는 것을 삭제하거나 키를 랜덤으로 삭제.
  • RSS 값을 모니터링 해야함
    • 사용 메모리와 실제 메모리가 다를 수 있기 때문인데, 페이지 단위(보통 4MB)로 메모리를 할당 받기 때문에 100KB가 필요해도 4MB를 할당해줌
    • 그래서 계속해서 100KB 필요하다고 요청을 3회를 했다고 했을 때 최초에 할당된 4MB에 계속해서 쌓이도록 하는게 아니라 12MB가 할당되면서 메모리가 파편화됨
  • 메모리 할당과 반환은 jemalloc으로 이뤄지는데, 반환 했다고 하더라도 실제로는 반환되어 있지 않을 수도 있음.
  • 메모리 파편화를 줄이려면 유사한 크기의 데이터를 가지는 것이 유리.

인스턴스: 큰 메모리 1대 VS 적은 메모리 여러대

큰 메모리를 사용하는 INSTANCE 하나보단 적은 메모리를 사용하는 INSTANCE 여러 대가 안전

Copy-on-write 때문인데 읽기 작업을 할 때는 쓰레드 간에 같은 메모리를 바라봐도 문제가 없지만 변경이 발생하게 되면 해당 메모리를 동일하게 복제를 해서 작업을 한 후에 연결을 새로 맺는다. 그 복제 과정에서 메모리 양이 배로 증가할 수 있고, 메모리 양이 클 경우 이로인한 리스크 또한 증가한다.

Copy-on-write는 RDB/AOF와 관련한 작업이 필요할 때 발생한다.

메모리가 부족할 때는?

  • 더 메모리가 큰 장비로 마이그레이션이 필요
  • 메모리가 빡빡하면 마이그레이션시 문제가 발생함
  • 전체 메모리중 60%~70%를 사용하도록 유지해야하며 그 이상을 사용하면 장비 이관을 고려 해야함
  • 데이터를 일정 수준에서만 사용하도록 데이터를 줄여야 하며 이미 SWAP이 발생했으면 프로세스 재기동이 필요.

메모리를 많이 쓰는 자료구조는?

  • 기본적으로 Collection 들은 다음과 같은 자료구조를 사용
    • Hash → HashTable를 하나 더 사용
    • Sorted Set → skiplist와 hashtable를 이용
    • set → hash table 사용
    • 위 자료구조들은 메모리를 많이 사용함
  • Ziplist를 이용하자!

Ziplist 구조

인-메모리 특성상, 적은 개수라면 선형 탐색을 하더라도 빠르기 때문에 list, hash, sorted set 등을 ziplist 를 대체해서 사용하도록 하는 설정이 있음

O(N) 관련 명령어 주의

  • 레디스는 Single Thread
    • Redis는 한 번에 하나의 명령만 처리 가능
    • 단순한 set/get은 초당 10만 TPS 이상 가능
      • CPU 속도에 영향을 받음

레디스는 싱글 쓰레드

  • 명령은 분해되어 패킷 단위로 전달이 됨.
  • ProcessInputBuffer가 하나의 커맨드가 완성되는지 확인해서 완성되면 ProcessCommand로 실행
  • 한 번에 하나의 명령만 수행이 가능한데, 긴 시간을 요구하는 명령이 전달되면 해당 명령을 처리하는 동안 다른 명령은 대기하게 됨
  • 대표적인 O(N)
    • KEYS
      • scan 명령으로 긴 명령을 짧은 여러번의 명령으로 대체 가능
    • FLUSHALL, FLUSHDB
    • DELETE COLLECTION
    • GET ALL COLLECTION
  • 대표적인 실수 사례
    • KEYS가 백만개 이상인데 KEYS 명령을 사용해서 모니터링!
    • 아이템이 몇 만개든 hash, sorted-set set에서 모든 데이터를 가져오는 경우
    • 예전의 Spring security oauth RedisTokenStore → 현재는 해치됨
  • 모든 아이템을 가져와야 할 떄?
    • 컬렉션의 일부를 가져오거나 데이터를 토막 내어 저장

레디스 레플리케이션

싱글 쓰레드?

6.2.5 버전을 기준으로 레디스를 실행한 상태에서 다음 명령어를 실행해보면 아래와 같이 출력되는 것을 확인할 수 있습니다.

# COMMAND
ps H -o 'tid comm' $REDIS_PID

# RESULT
TID COMMAND
1 redis-server
2 bio_close_file
3 bio_aof_fsync
4 bio_lazy_free
5 jemalloc_bg_thd

RedisGate의 쓰레드 관련 글을 보면 아래와 같이 설명이 나온다.

  • 메인 쓰레드: 아래 3개의 Sub 쓰레드에서 처리하는 것 이외에 거의 모든 명령어 처리, 이벤트 처리 등을 한다.
  • Sub 쓰레드 1번(bio_close_file): AOF Rewrite 할 때 새 파일에 Rewrite 완료하고 기존 파일을 close 할 때 동작한다. AOF를 활성화하지 않아도 쓰레드는 생성된다.

AOF Rewrite
파일 사이즈 증가에 따라 발생할 수 있는 OS 파일 사이즈 제한, 레디스 서버 시작시 AOF 로딩 타임 등을 방지하기 위해서 파일 내 데이터를 다시 쓰는 기능이다.
동일한 키를 N번 수정한다고 했을 때, 과정은 모두 제거하고 최종 수정된 결과만 남김으로써 사이즈를 줄입니다.
ex) key가 jordy인 데이터의 value가 1에서 1000까지 set을 했을 때 1에서 999까지의 기록은 제거하고 최종 수정된 결과인 key: jordy value:1000 만 남게 됩니다.

  • Sub 쓰레드 2번(bio_aof_fsync): 1초 마다 AOF에 쓸 때 동작한다.
  • Sub 쓰레드 3번(bio_lazy_free): UNLINK, 비동기 FLUSHALL 또는 FLUSHDB 명령을 처리할 때 동작한다. 이 쓰레드는 버전 4.0에 추가되었다.

Redis DEL operations are normally blocking, so if you send Redis “DEL mykey” and your key happens to have 50 million objects, the server will block for seconds without serving anything in the meantime.
Non blocking DEL and FLUSHALL/FLUSHDB
There is a new command called UNLINK that just deletes a key reference in the database, and does the actual clean up of the allocations in a separated thread

특정 키를 삭제하는 DEL 명령어의 경우 블락킹 작업인데 이로 인해 다른 요청의 처리가 지연될 수 있다. 그래서 비동기로 우선 저장소와 키 간에 링크만 끊고 실제 처리는 분리된 쓰레드에서 정리를 한다.

 

  • 쓰레드 4번 jemalloc_bg_thd: jemalloc background thread, 버전 6.0에 추가되었다.

쓰레드 1번과 3번까지의 소스는 레디스 소스 코드 파일 중 bio.h, bio.c에서 확인할 수 있고,

jemalloc_bg_thd$REDIS_PATH/deps/jemalloc/src/background_thread.c 에서 확인 할 수 있다.

 

레퍼런스

반응형