본문 바로가기
Study

(Redis) Redis란? 우아한 Redis 발표영상 후기

by Developer RyanKim 2020. 7. 18.

 


https://www.youtube.com/watch?v=mPB2CZiAkKM

 

 

Cache란?
- Cache는 요청결과를 미리 저장해 두었다가 나중에 빠르게 서비스 해주는것을 의미

 

다이내믹 프로그래밍? 앞의 연산결과를 다시 사용하는 것이 핵심!
예시) factorial -> 20880!을 계산해두면 20881! 은 금방한다

 

속도
디스크 < 메모리

 

2:8 파레토 법칙 : 실제 모든 요청은 20의 사용자가 결정한다.

 

Cache 구조 1 - Look aside Cache

1. 웹 서버는 데이터가 존재하는지 Cache 먼저 확인

2. Cache에 데이터가 있으면 Cache에서 가져옴

3. Cache에 없으면 DB에서 읽어옴
4. DB에서 읽은 데이터를 Cache에 저장
    

Cache 구조 2 - Write Back

1. 웹 서버는 모든데이터를 Cache에 저장

2. Cache에 특정시간동안의 데이터가 저장됨

3. Cache있는 데이터를 DB에 저장

4. DB에 저장이 완료된 Cache의 데이터를 삭제

* insert 쿼리를 한번씩 500번 or insert 쿼리 500개를 붙혀서 한번에 뭐가더 빠른가? 붙혀서 하는게 훨씬 빠르다!

 

단점은 뭘까? 캐시는 메모리이기 때문에 리부팅되면 데이터가 사라진다!

사용예) 로그 저장. 모아서 쓰면 좋다. 또한 극단적으로 헤비한 write가 있으면 쓴다.


Collection

 

멤 캐시디 써봤나? 좋지만 컬렉션 제공 하지 않는다.
Redis는 컬렉션을 제공한다!
라이브러리로 인한 생산성 증가!

랭킹 서비스를 어떻게 구현할것인가?
- 가장 간단한 방법?

: DB에 유저의 Score 저장후 order by로 정렬하여 읽기

데이터가 많아지면 속도 문제 발생가능. 결국 디스크를 사용하므로.
-> 따라서 인메모리기준 랭킹 서버의 구현이 필요함

->Redis의 Sorted Set을 이용하면 랭킹 구현 가능.

친구 리스트를 key/value 형태로 저장해야 한다면?

현재 유저 123이 있고, 친구 KEY는 friends:123 이다. 현재친구 A가 있음.

동시에 친구추가가 일어났을때 문제 발생 가능

T1: friends:123 읽음 -> 친구 B추가 -> friends 123 쓰기

T1: friends:123 읽음 -> 친구 C추가 -> friends 123 쓰기

결과가 A,B,C나 A,C,B가 되어야하는데 하나가 덮어써져서 유실될 수 있다. (A,B / A,C)

 

-> Redis 경우 자료구조가 Atomic 하기 때문에 해당 Race Condition을 피할 수 있다! 

외부의 컬렉션을 잘 사용하면 여러 개발시간을 단축시키고 문제를 줄여줄수 있기 때문에 Collection이 중요.

Redis 사용처

- Remote Data Store : A,B,C.. 여러 서버에서 데이터를 공유하고 싶을때

- 한대에서만 필요하면 전역변수 쓰면되지 않나? : Redis는 Atomic 보장

주로 많이 쓰이는 곳들
- 인증토큰 등 저장 (String or Hash)

- Ranking 보드로 사용(Sorted Set)

- 유저 API Limit

- 좌표(list)

 

Redis Collections

- Strings

- List (중간에 데이터를 삽입하려면 느림)
- Set (중복 x)

- Sorted Set

- Hash

 

String - 단일 Key

기본 사용법
- Set <Key> <Value>

- Get <Key>

Set token:1234567 abcdefghjklmn

Get token:1234567

키를 어떻게 잡느냐에따라 분산이 달라질수 있다.

 

List
기본사용법 - 앞 혹은 뒤로 데이터를 넣고 뺄 수 있다.

Lpush <key> <A>

: Key: (A)

Rpush <key> <B>

: Key: (A,B)

Lpush <key> <C>

: Key: (C,A,B)

Rpush <key> <D,A>

: Key: (C,A,B,D,A)

 

LPOP, RPOP도 있음

 

Set : 데이터가 있는지 없는지만 체크하는 용도
기본사용법

SADD <Key> <Value> : Value가 이미 Key에 있으면 추가되지 않는다.

SMEMBERS <Key> : 모든 Value를 돌려줌 . 이런 명령들을 주의해야함 '모든'!

SISMEMBER 존재여부 확인

특정 유저를 Follow 하는 목록을 저장한다면 좋다

 

Sorted Set: 랭킹에 따라 순서가 바뀌길 바란다면

기본 사용법

ZADD <Key> <Score> <Value>

: Value가 이미 Key에 있으면 해당 Score로 변경된다.

ZRANGE <Key> <StartIndex> <EndIndex> : 해당 범위 값 모두 돌려줌

유저 랭킹 보드로 사용할 수 있음.

score는 double 타입이기때문에, 값이 정확하지 않을 수 있다!

 

Hash : Key 밑에 sub key가 존재

기본사용법 Hmset <key> <subkey1> <value1> <subkey2> <value2> ..

get 도 있음

간단한 SQL을 대체한다면?

insert into users(name,email) values('Ryan','ryan@naver.com');

-> hmset Ryan name Ryan email ryan@naver.com

 

Collection 주의사항!

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

- 10000개 이하 몇천개 수준으로 유지하는 것이 좋음

 : 예를들어 SortedSet이면 계속 소팅하려고 자원소모가 심함

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

- 즉 10000개의 아이템을 가진 Collection에 expire가 걸려있으면 그 시간후에 10000개 모두 삭제됨


Redis 운영

1. 메모리 관리를 잘하자!

2. O(N) 관련 명령어는 주의하자!

3. Replication

4. 권장설정 Tip

 

1. 메모리 관리

- Redis는 인메모리 데이터 스토어

- 물리 메모리 이상을 사용하면 문제가 발생

: Swap (디스크도 쓰는것) 이 있다면 Swap 사용으로 해당 메모리 Page 접근시마다 느려짐. 없다면? OOM 등으로 죽을 수 있음

- Maxmemory 를 설정하더라도 이보다 더 사용할 가능성이 큼. 레디스가 데이터를 지우거나해서 관리하기는 하지만.
레디스가 알고 있는 메모리이다. 모르고 쓰고있는 영역이 있을 수 있음.
(레디스는 4097만 쓰고있다고 하지만 4096 페이지 단위면 2개 할당받아야해서 4096 * 2로 쓰고있을 수 있음)

- 많은 업체가 현재 메모리를 사용해서 Swap을 쓰고있다는 것을 모르고 있음 ㅠㅠ

큰 메모리를 사용하는 instance 하나보다는 적은 메모리를 사용하는 instance 여러개가 안전함

read는 문제가 없는데 write시 포크가 일어날수 있다? 2배를 사용할 수 있음.

 

메모리 파편화가 발생할 수 있음. 4.x 대 부터 메모리 파편화를 줄이도록 jemlloc에 힌트를 주는 기능이 들어갔으나 버전에 따라 다르게 동작가능

3.x대의 경우 : 실제 used memory는 2GB로 보고되지만 11GB의 RSS를 사용하는 경우가 자주 발생

다양한 사이즈를 가지는 데이터보다는 유사한 크기의 데이터를 가지는것이 유리

 

메모리가 부족할때는?

1. 메모리가 더 많은 장비로 마이그레이션. 한 60~75면 고민해야함 메모리가 빡빡하면 마이그레이션시 포크때문에 문제발생할수있음

2. 있는 데이터 줄이기 : 데이터를 일정수문에섬나 사용하도록 특정 데이터를 줄임. swap 이미 사용중이면 프로세스 재시작해야함

 

메모리를 줄이기 위한 설정

기본적으로 Collection들은 다음과 같은 자료구조를 사용

- Hash -> HashTable을 하나 더 사용

- Sorted Set -> Skiplist와 HashTable을 이용

- Set -> HashTable 사용

Ziplist를 사용하자. (속도는 좀 느려지는데 메모리를 적게씀)

: 그냥 선형으로 저장해버림. 인메모리라서 선형탐색을 해도 빠르다. ( 몇백개 까지?)

 

2. O(N) 관련 명령어는 주의

Redis는 싱글 쓰레드.

- 레디스는 동시에 여러개의 명령을 처리할 수 있을까? 한번에 하나!

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

패킷 으로 하나의 커맨드가 완성되면 프로세스커맨드에서 실제 실행됨 (버퍼에 쌓임)

한번에 하나의 명령어를 수행하기때문에 하나에 긴 명령어를 쓰면 안됨!

 

대표적인 O(N) 명령들

Keys / Flushall, FlushDB, Delete Collections, Get All Collections

 

대표적인 실수 사례 : Key가 백만개 이상인데 확인을 위해 Keys 명령을 사용하는 경우

아이템이 몇만개든 hash, sorted set, set에서 모든 데이터를 가져오는 경우! 예전의 Spring Security oauth RedisTokenStore

Keys는 어떻게 대체할것인가?

: scan 명령으로 하나의 긴명령을 짧은 여러번의 명령으로 바꿀수 있다. (커서가 옮겨가며 수행함)

 

컬렉션의 모든 item을 가져와야할때?

일부만 가져오거나.. ( Sored Set)
큰 컬렉션을 작은 여러개의 컬렉션으로 나눠 저장

Userranks -> Userranks1 ,2 3 ...

하나당 몇천개 안쪽으로 저장하는것이 좋음

 

Spring security oauth RedisTokenStore 이슈
Access Token 저장을 List(O(N)) 자료구조를 통해 이루어짐

검색, 삭제시에 모든 item을 매번 찾아봐야함. 100만개쯤 되면 전체성능에 영향을 줌. (1초동안 멈추는 등)

현재는 Set(O(1)) 을 이용하여 검색 삭제를 하도록 수정되어있음

 

Redis Replication

Async Replication : Replication Lag 발생가능. (마스터에는 데이터 있는데 슬레이브에는 없는 등)

DBMS로 보면 statement relication이랑 유사. 쿼리를 날려서 복제하는것과 비슷하다.

 

Redis Replication시 주의할점
- 과정에서 fork가 발생하므로 메모리 부족이 발생할 수 있다! 날아가면 못찾는다 (인메모리)

AWS나 클라우드의 레디스는 fork를 안쓰고하는 경우가 있어서 안정적.

- 많은 레디스 서버가 레플리카를 두고있으면 문제 발생 위험 높아짐

 

권장 설정 Tip

  • Maxclient 설정 50000
  • RDB/AOF 설정 off 마스터에서는 무조건 off
  • 특정 commands disable
    - Keys
    - AWS의 ElasticCache는 이미 하고 있음
  • 전체 장애의 90% 이상 KEYS나 SAVE설정을 사용해서 발생
  • 적절한 ziplist 설정

Redis 데이터 분산

데이터의 특성에따라 선택할 수 있는 방법이 달라진다.

Cache 일때는 좋다. Persistent 해야하면 아니다!

 

분산 방법

- Application : Consistent Hashing (twemproxy를 사용하여 쉽게 가능), Sharding

- Redis Cluster

 

Consistent Hashing

모듈러 연산으로 나눈다면? %2

서버 1 (0) | 서버 2 (1)

1000       | 5001

11002      | 13003

15004     | 

 

이상태에서 서버 증설을 해야함. 그러면 %3으로 바꿔야한다. 데이터의 리밸런싱이 일어남!
서버가 추가되거나 삭제될 때마다 데이터의 리밸런싱이 필요하다면 장애에 매우 취약한 구조이다

 

Hash를 이용하여 나눈다면? (가장 가까운 곳으로. 30000넘으면 다시 돌아서 서버 1로)

서버 1 (10000)  |  서버 2 (20000)  | 서버 3 (30000)

1500                 |  17000                |  27000

33000

 

서버 2가 죽으면? 나머지는 모두 그대로이고 17000이 서버 3으로 이동. 변동이 해당 서버의 데이터만 생김

만약 서버 4가 추가되면 33000만 서버 4로 가면된다.

 

Sharding

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

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

 

하나의 데이터를 모든서버에서 찾으려면 비효율적.
Range

특정 범위를 정의하고 해당 범위에 속하면 거기 저장

1-10000  |  10001-20000 | 20001-30000

5001, 4    |  13002, 13003  | 20005

놀고 있는서버와 안놀고있는 서버가 극심하게 갈림.

 

modulr

모듈러를 사용하면, 확장을 2배씩했을때 기존 데이터가 분배될 곳이 바로 결정됨.

단점은 2 4 8 16 배 씩 확장해야함

 

Indexed

인덱스 서버를 따로두고 특정 키가 어디에 저장되어야할지 선택함 (균등하게 저장)

인덱스 서버가 죽으면 서비스 불가

 

Redis Cluster

Hash 기반으로 Slot 16384로 구분

마이그레이션 시 개발자가 커맨드를 사용하여 수동으로 진행해야함

 

장점: 자체적인 Primary, Secondary Failover

단점: 메모리 사용량이 많음, 마이그레이션 자체는 관리자가 시점을 결정해야함, 라이브러리 구현이 필요


Redis Failover (대체동작)

  1. 코디네이터 기반 failover
  2. VIP / DNS 기반 failover
  3. Redis Cluster 활용

코디네이터 기반

: Zookeeper, etcd, consul 등의 코디네이터 사용

: api 서버가 사용할 레디스서버를 지정해놓음. 1번 쓰기로 했는데 헬스체크해서 1번이 죽어있으면 2번에 붙혀줌

장점: 기존에 쓰고있으면 쓰던방식 그대로가능

단점: 해당 기능이용하도록 개발해야함

 

VIP 기반 (가상 IP)

API 서버는 10.0.11로만 접속 -> Redis 서버 1의 IP를 10.0.11로 할당. 헬스체크시 서버 1이 죽으면 서버 2를 10.0.11로 바꾸면 된다.

 

DNS 기반

위와 똑같은 로직으로 동작하면 된다.

 

VIP / DNS 기반

클라이언트 추가 구현 필요없음

VIP는 외부로 서비스를 제공해야하는 서비스업자에 유리 (예로 클라우드 업체)

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

- 사용하는 언어벌 DNS 캐싱정책을 잘 확인해야함

- DNS는 바꾸는것이 IP에 비해 쉬움

 

Monitoring Factor

Redis Info를 통한 정보

- RSS (레디전스 세그먼트 셋?) 실제 피지컬 메모리를 얼마나 쓰고있느냐

- Used Memory 레디스가 인식하고있는 사용메모리

- Connection 수

- 초당 처리 요청 수

System

- CPU

- Disk

- Network rx/tx

 

CPU 부하가 100% 될경우

처리량이 매우 많다면?

- 더좋은 CPU로 이전

 

O(N) 계열 특정 명령이 많은 경우 확인법

- 짧게 모니터명령을 사용하여 특정 패턴을 파악해야함

- 모니터명령을 잘못쓰면 서버에 더큰 문제 가능


결론

 

Redis는 기본적으로 매우 좋은 툴

그러나 메모리를 빡빡하게 쓸 경우 관리가 어려움

32기가 장비일때, 24기가 이상 사용하면 장비 증설을 고려하는 것이 좋음

Write가 Heavy할 경우 마이그레이션도 주의해야 함.

 

Redis를 Cache로 쓰는 경우

- 문제가 적게 발생

- Redis가 문제가 있을 때 DB등의 부하가 어느정도 증가하는지 확인 필요

- Consistent Hashing도 부하를 아주 균등하게 나누지는 않음

 

Redis를 Persistent Store로 쓰는 경우

- 무조건 Primary/Secondary 구조로 구성이 필요

- 메모리를 절대로 빡빡하게 사용하면 안됨

- 정기적인 마이그레이션 필요

가능하면 자동화 툴 만들어 사용

RDB/AOF가 필요하다면 Secondary에서만 구동

 

 

 

 

댓글