DB 도 내부적으로 캐시가 있긴하지만, 여러가지를 계속 접근하다보면 캐시를 계속 삭제하기 때문에 다시 또 디스크에 접근하는 경우가 발생한다.
Cache 구조 #1 - Look aside Cache
일반적으로 가장 많이 쓰는 형식
Cache 구조 #2 - Write Back
write 하는 데이터가 많은 경우에 사용한다.
쓰는 데이터가 너무 많을 경우 데이터를 Cache 에 저장해놓고 특정 시간마다 DB에 적재한다.
일반적으로 인서트 1번씩 인서트 500번을 날리는것보다, 한번에 500개를 붙여서 날리는게 말도안되게 빠르다. : 배치작업
Write Back의 단점 :
메모리에 저장되어있기 때문에, 리부팅하면 데이터 날아간다.
장애 발생할 경우 데이터 모두 날아갈 수도있다.
로그를 DB에 저장하시나요?
로그를 DB 에 저장할 때, 매번 데이터를 저장하지 않고 Write Back 을 사용하면 좋다. (
Redis에서는 Collection을 제공하는데 Collection이 왜 중요한지? 개발의 편의성, 개발의 난이도
라이브러리를 많이 제공하면 개발하기 편함
랭킹서버 등을 만들 때, Redis의 Sorted Set 을 이용하면 작업하기 편함
Redis Collections
개발할 때, 자료구조를 잘 선택하는 것이 중요하다.
Strings : 키 - 밸류로 저장
Set <key> <value>
Get <key>
List : head, tail 사용 가능하지만 중간 데이터 찾기 오래걸림
Lpush <key> <A>
Rpush <key> <B, C>
LPOP <key>
RPOP <key>
Set : 중복 데이터 저장하지 않음. 데이터가 있는지 없는지만 체크하는 용도
SADD <key> <Value>
SMEMBERS <key> : 모든 VALUE 돌려줌
SISMEMBERS <key> : 존재하면 1, 존재하지 않으면 0
Sorted Sets : Score 로 정렬 가능
ZADD <key> <Score> <value> : value 가 이미 존재하면 해당 Score 로 변경됨
ZRANGE <key> <StartIndex> <EndIndex>, ZREVRANGE <key> <StartIndex> <EndIndex> : 해당 Index 범위값 모두 돌려줌. 유저 랭킹보드로 사용 가능. score 가 double 타입이기 때문에 값이 정확하지 않을 수 있는데, 실수가 표현할 수 없는 정수값들이 존재하기 때문.
Hash : 키 - 밸류 구조 안에 서브 키 - 서브 밸류가 인서트 가능
Collection 주의 사항
하나의 컬렉션에 너무 많은 아이템을 담지 말고, 10,000개 이하 수준으로 유지하는게 좋음.
매번 몇만개씩 가져오면 성능 이슈 발생
Expire는 Collection의 item 개별로 걸리지 않고 전체 Collection에 대해서만 걸림
즉 10,000개의 아이템을 가진 Collection에 대해 expire가 걸려있다면 그 시간 이후에 모두 같이 삭제
일부만 걸리도록 사용하는 사람들도 있긴한데 이렇게 하면 메모리를 너무 많이 사용한다.
Redis 운영
메모리 관리를 잘하자. 진짜진짜 중요
Redis는 In-Memory Data Store
Physical Memory 이상을 사용하면 문제가 발생
Swap 이 있다면, Swap 사용으로 해당 메모리 Page 접근시마다 늦어짐. -> 디스크 사용
Maxmemory를 설정하더라도 이보다 더 사용할 가능성이 큼.
특정 메모리 이상으로 사용하지 말라고 선언하는 옵션
레디스는 메모리 풀을 사용하는게 아니고, 메모리 할당/해제를 jemalloc 사용. jemalloc보다 더 잘 짜서 사용할 수 있는 사람은 없겠지만, 레디스가 정확하게 어느정도 메모리를 사용하고 있는지 알 수 없음. 해제했다고 하지만 실제로는 붙잡고 있는 경우도있음. : 메모리 파편화
RSS 값을 모니터링 해야함
실제 used memory는 2GB로 보고가 되지만 1GB의 RSS를 사용하는 경우가 자주 발생
많은 업체가 현재 메모리를 사용해서 Swap을 쓰고 있다는 것을 모를때가 많음. -> 그래서 어느순간 레디스가 느려졌다고 생각함
큰 메모리를 사용하는 instance 하나보다는 적은 메모리를 사용하는 instance 여러개가 안전함.
CPU 4 Core, 32GB Memory
24GB 하나보다는, 8GB 3개가 좋다
비슷한 사이즈의 메모리를 사용하는게 좋다.
레디스는 필연적으로 마스터-슬레이브 포크를 하게 되어있음
이 때, Read가 많은 경우는 괜찮은데, Write 가 헤비하게 일어나면 메모리를 최대 2배까지도 사용한다.
처음에 포크했을 때는 메모리를 복사하지 않는데, write하면 메모리 복사가 일어남
메모리가 부족할 때는?
돈이 많으면 좀 더 메모리 많은 장비로 Migration
돈이 없으면 데이터 줄이기
기본적으로 Collection 들은 다음과 같은 자료구조 사용
Hash -> Hash Table 을 하나 더 사용
Sorted Set -> Skiplist와 HashTable 을 이용
Set -> HashTable 사용
위의 자료구조들은 메모리를 많이 사용함 (메모리 파편화, 포인터 등으로 메모리 많이 사용됨)
Ziplist를 사용하자.
In-Memory 이다보니, 적당한 사이즈 까지는 풀 서치를 하더라도 빠르게 사용할 수 잇다.
O(N) 관련 명령어는 주의하자.
Redis 는 Single Threaded
단순한 get/set의 경우, 초당 10만 TPS 이상가능(CPU속도에 영향받음)
하나라도 긴 시간을 요하는 명령이 걸리면 문제가 생김
대표적인 O(N)명령 : KEYS(모든 키 순회), FLUSHALL, FLUSHDB, Delete Collections, Get All Collections Flush의 경우 명령어 호출했을 때는 멈추지만 필요할 땐 써야함
Keys 대신해서 scan명령을 여러번 나눠서 처리할 수 있다. scan <키값>
Collection의 모든 item을 가져와야할 때?
Collection의 일부만 가져오기 : Sorted Set
큰 Collection을 작은 여러개의 Collection으로 나눠서 저장. 하나 당 몇천개 안 쪽으로 저장.
Replication
A 라는 서버에 있는 데이터를 B 서버에서도 모두 동일하게 가지고있음. (Master - Slave, Original - Replicaof)
Async Replication
Replication Lag 이 발생할 수 있다.
A서버 데이터를 B서버에서 복제하는 그 사이에 이슈가 발생할 수 있다.
시간차가 길어질 경우 연결을 끊어버림. 그리고 다시 마스터-슬레이브 연결을 시도하는데 이때 부하가 늘어남.
DBMS로 보면 statement replication가 유사하다.
Replication 설정 과정
Secondary에 replicaof or slaveof 명령 전달
Secondary는 Primary에 sync 명령 전달
Primary 는 현재 메모리 상태를 저장하기 위해 - Fork
Fork 한 프로세서는 현재 메모리 정보를 disk에 dump
해당 정보를 secondary에 전달
Fork 이후의 데이터를 secondary에 계속 전달
Replication 주의할점
Replication 과정에서 fork 가 발생하므로 메모리 부족이 발생할 수 있다.
Redis-cli --rdb 명령은 현재 상태의 메모리 스냅샷을 가져오므로 같은 문제를 발생시킴
AWS나 클라우드의 Redis는 좀 다르게 구현되어서 좀 더 안정적
많은 대수의 Redis 서버가 Replica를 두고있다면
네트워크 이슈나 사람의 작업으로 동시에 replication이 재시도 되도록 하면 문제가 발생될 수 있다.
redis.conf 권장 설정 Tip
Maxclient 설정 50000
RDB/AOF 설정 off : 속도 빠름. 안정성 높음.
특정 commands disable
Keys
AWS의 ElasticCache는 이미 하고있음
전체 장애의 99%가 Keys와 slave 설정을 사용해서 발생
적절한 ziplist 설정
레디스 데이터 분산
데이터의 특성에 따라서 선택할 수 있는 방법이 달라짐
Cache 일때는 우아한 Redis
Persistent 해야하면 안 우아한 Redis
데이터 분산 방법
Application 레벨에서 분산
Consistent Hashing : twemproxy를 사용하는 방법으로 쉽게 사용 가능
서버가 추가되거나 사라지면 이슈 발생
Sharding
데이터를 어떻게 나눌것인가?
데이터를 어떻게 찾을것인가?
하나의 데이터를 모든 서버에서 찾아야하면?
Range : 특정 Range를 정의하고 해당 Range 에 속하면 거기에 저장.
서버 별로 일하는 양이 불균형해진다. 불균형하더라도 서버의 상황에 따라서 놀고있는 서버와 일하고 있는 서버를 바꿀수도 없다.
modular 로 처리할 때는 서버 대수를 2배로 늘려야 데이터 변환이 적고, 균등하게 분배할 가능성이 높다.
Indexed 방법으로 진행할수도 있는데, Key 가 어디에 저장되어야할지 관리하는 서버가 필요.
대신, 키 관리 서버가 죽으면 사용불가.
Rediscluster
Hash 기반으로 Slot 16384로 구분
Hash 알고리즘은 CRC16을 사용
Slot = crv16(key) % 16384
Key가 Key{hashkey} 패턴이면 실제 crc16에 hashkey가 사용된다.
특정 Redis 서버는 이 slot range 를 가지고 있고, 데이터 migration은 이 slot 단위의 데이터를 다른 서버로 전달하게된다. (migrateCommand 이용)
레디스 클러스터의 HA 는 기본적으로 일반적인 Redis 구조와 동일 Primary 가 죽으면 Secondary가 Primary 로 승격 (replication 구조로 이루어져있음)
각 슬롯으로 데이터를 넣어주는데 범위에 잘못된 데이터를 넣어주면 에러 리턴 및 몇번 슬롯으로 보내줘야하는지 알려줌
그리고 클라이언트가 맞는 슬롯에 맞게 다시 데이터 전달해줘야함
장점
자체적인 Primary, Secondary Failover.
Slot 단위의 데이터 관리
단점
메모리 사용량이 더 많음
Migration 자체는 관리자가 시점을 결정해야함
Library 구현이 필요함
Redis Failover
Coordinator 기반 Failover
Zookeeper, etcd, consul 등의 Coordinator 사용
위와 같은 구조에서, Redis 1번이 죽으면, Health Checker는 Redis #2를 Primary 로 승격시킨다. Health Checker는 Coordinator 에 current Redis가 Redis #2 라고 업데이트한다. Coordinator는 API 서버에 current Redis 가 변경되었다고 알려준다. 코드로 모두 작성해두어야함
장점은, Coordinator 기반으로 설정을 관리한다면 동일한 방식으로 관리 가능
VIP / DNS 기반 Failover
클라이언트에 추가적인 구현이 필요없다.
VIP 기반은 외부로 서비스를 제공해야하는 서비스 업자에 유리 (클라우드 업체 등)
DNS 기반은 DNS Cache TTL을 관리해야함. (아마존에서 DNS 기반으로 처리중)
사용하는 언어별 DNS 캐싱 정책을 잘 알아야함
툴에 따라서 한번 가져온 DNS 정보를 다시 호출하지 않는 경우도 존재
Redis Cluster의 사용
Monitoring Factor
Redis Info를 통한 정보
RSS - 가장 먼저 확인해야함. 실제 피지컬 메모리 얼마나 사용하고 있는지
Used Memory - 레디스가 생각하는 메모리 사용량
Connection 수 -레디스가 커넥션을 엄청 연결했다가 끊고 한다면 문제가 생겼음을 알수있다.
초당 처리 요청 수
System
CPU
Disk
Network rx/tx
CPU 가 100% 를 칠 경우
처리량이 매우 많다면?
좀 더 CPU 성능이 좋은 서버로 이전
실제 CPU 성능에 영향을 받음
O(N) 계열의 특정 명령이 많은 경우
Monitor 명령을 통해 특정 패턴을파악하는것이 필요
Monitor 잘못쓰면 부하로 해당 서버에 더 큰 문제를 일으킬수도있음. (짧게 쓰고 분석)
결론
기본적으로 Redis는 매우 좋은 툴
그러나 메모리를 빡빡하게 쓸 경우, 관리하기 어려움
32기가 장비라면 24기가 이상 사용하면 장비 증설 고려
Write 가 Heavy 할 때는 migration도 매우 주의
Client-output-buffer-limit 설정이 필요.
Cache일 경우는 문제 적게 발생
Redis가 문제가 있을 때는 DB 등의 부하가 어느정도 증가하는지 확인 필요
Consistent Hashing도 실제 부하를 아주 균등하게 나누지는 않음. Adaptive Consistent Hashing을 이용해볼 수도 있음.
Persistent Store의 경우
무조건 Primary/Secondary 구조로 구성이 필요
메모리를 절대 빡빡하게 사용하면 안됨.
정기적은 migration이 필요. (Secondary에서 백업)
가능하면 자동화 툴 만들어서 이용
RDB/AOF 가 필요하다면 Secondary에서만 구동. (AOF가 I/O가 균등하기 때문에 좀 더 안정적)