캐시
인메모리를 쓰는 이유
- 애플리케이션 레벨의 로컬캐시가 빠르지만 두 가지 문제
- 데이터유실
- 데이터동기화
- 서버가 두 대만 되어도 하나가 바뀌면 다른 서버에 어떻게 알려주냐부터 이슈
- 그래서 인메모리 캐시를 외부에 놓고 참조한다
- 네트워크를 통하므로 로컬보다 느리지만 위 두 문제가 없다
캐시가 유실되면?
- 인메모리는 모든 데이터가 디스크가 아닌 메모리에 올라가 있어
- 유실될 수도 있으므로 캐시
- 유실안 되는 개념은 스토어
- 캐시가 날라가면 디비로 가는데 준비를 안 해 놓아서 디비가 못 버티는 경우가 실제 꽤 많이 일어난다
- 썬더링 커즈 - 캐시 키가 하나만 사라져도 십만 리퀴스트 일어나면 디비로 간다
예) 트위터 - 하이라키 캐시구조 1, 2, 3차까지, 비싸므로 파레토 법칙 적용해서
- 캐시 key가 하나가 없어진 사이에 이거에 요청이 몰리는 경우, DB가 죽을 수 있다
DB에 두고 공유하는 거랑, Redis 등의 in memory DB 서버를 따로 두고 쓰는 거랑 어떤 차이가 있는지?
- 디스크에 접근하면 초당 1천번 정도라면(DB는 디스크를 쓰게 되어 있다), 메모리에 접근하면 10만번이 가능 (in memory DB는 메모리만 쓴다)
캐시에 데이터를 불러올 때
- 해시구조 키 밸류 (멤캐시드는 이것만 지원)
- 그 안에 셋
- 아니면 소티드 셋
Cache를 스토리지처럼 쓸 수 있는 Data grid
- 레디스도 클러스터 있지만 클러스터보다 하나의 서버 개념
- 캐시 스토리지처럼 쓸 수 있다는 데이터그리드로 넘어가는 것이 새로운 경향?
- 해이즐캐스트, 인피니스팬
- 데이터 리플리케이션을 잘 전보다 복잡하고 잘 구현하는 개념
네트워크에서 상용으로 쓰는 레디스
- 기본설정으로 쓰면 장애로 이어질 것
- 엘라스틱 캐시 관리 이슈
캐시에 올릴 데이터 기준
어떤 데이터가 많이 잡히는지 볼 수 있는 방법?
- 태그 히트률을 가져와서 따로 테이블에 저장
- key마다 태그를 달아서 어떤 데이터가 많이 생성되는지 볼 수 있다. 혹은 read마다 update
Redis를 서버 동기화 목적?
- Redis를 서버 동기화 목적으로 쓰는 경우? 메시지큐 pub/sub
- 가볍다. sub이 없으면 pub해도 그냥 없어지므로, 보통 list 자료구조를 많이 쓴다.
- 그외 용도로 Redis를 활용하는 방법?
- 다양한 자료구조를 지원하므로
- 소티드셋 - 랭킹에 쓰기 좋다.
- API limitter - 초당 몇 개만 써라
레디스 랭킹API에 쓰면
- 보통 랭킹은 디비에 유저 스코어 저장하고 오더 바이로 정렬 후 읽어오기
- 개수가 많아지면 속도문제 발생
- 레디스는 소티드셋을 쓰면 랭킹 구현 가능
- 덤으로 리플리케이션도 가능
- 다만 한계에 종속될 수도
- 저장할 id가 1개당 100byte라고 하면
- 10명 1k
- 10000명 1M
- 10조면 1TB
레디스 장점
- 레디스 자료구조가 Atomic하기 때문에 해당 Race Condition을 피할 수 있다.
레디스 사용처
- 리모트 데이터 스토어
- A서버, B서버, C서버에서 데이터를 공유하고 싶을 때
- 한 대에서만 필요하다면 전역 변수를 쓰면 되지 않을까?
- 주로 많이 쓰는 곳들
- 인증 토큰 통을 저장
- 랭킹 보드로 사용
- 유저 API Limit
- 잡큐
Redis Collections
- Strings(키, 밸류)
- 기본사용법
- Set
- Get
- mset ...
- mget <key1 ...
- List(삽입 시 헤드와 테일은 빠르지만 중간은 느리다)
- Set
- 기본사용법
- SADD
- Value가 이미 Key에 있으면 추가되지 않는다.
- SMEMBERS
- SISMEMBER
- 보통 특정 유저를 Follow하는 목록을 저장할 때 많이 쓴다. 유니크하므로
- Sorted Set
- Set은 순서가 없지만 Sorted Set은 Score를 줘서 순서를 보장할 수 있다
- 기본사용법
- ZADD
- Value가 이미 Key에 있으면 해당 Score로 변경된다.
- 스코어 값 순서대로 정렬되어서 저장된다.
- ZRANGE
- 해당 Index 범위 값을 모두 돌려줌
- Zrange testkey 0 -1
- 모든 범위를 가져옴
- 번호가 인덱스, -1이 끝을 의미
- 유저 랭킹보드로 사용할 수 있음
- 소티드 셋의 스코어는 정수형이 아니라 더블 타입이므로 주의해야
- 실수가 표현할 수 없는 정수값들이 존재
- 간혹 발생하는 내가 왜 그랬을까의 대표적 사례
- Sorted Sets - 정렬이 필요한 값이 필요하다면?
select * from rank order by score limit 50, 20;
select * from rank order by score desc limit 50, 20;
- Hash
- Key 밑에 sub key가 존재
- 키밸류 안에 다시 키밸류가 존재
- 기본 사용법
- Hmset
- Hgetall
- 해당 key의 모든 subkey와 value를 가져옴
- Hget
- Hmget ...
Collection 주의사항
- 하나의 컬렉션에 너무 많은 아이템을 담으면 좋지 않음
- 10000개 이하 몇천 개 수준으로 유지하는 게 좋음
- Expire는 Collection의 item 개별로 걸리지 않고 전체 Collection에 대해서만 걸림
- Expire - 삭제
- 즉 해당 10000개의 아이템을 가진 Collection에 expire가 걸려 있다면 그 시간 후에 10000개의 아이템이 모두 삭제
레디스 운영
- 메모리 관리를 잘하자
- In-memory이므로 피지컬 메모리 이상 사용하면 당연히 문제 발생
- 바로 죽지 않고 Swap 일어난다
- Swap - 메모리 꽉차면 디스크로 내리고 비면 다시 메모리로 올리고
- Swap이 한 번이라도 있다면 해당 메모리 Page 접근 시마다 느려짐
- Maxmemory를 설정하더라도 이보다 더 사용할 가능성이 큼
- jemalloc? 때문에 레디스는 자기가 사용하는 메모리 크기를 정확하게 알 수 없기 때문에
- RSS 값을 모니터링해야 함
- 메모리 관리
- 큰 모메로 사용하는 인스턴스 하나보다 적은 메모리 인스턴스 여러 개가 더 안전하다.
- 라이트가 헤비한 레디스는 포크를 해서 최대 메모리를 2배까지 쓸 수 있다.
- 24기가면 48기가를 소모, 8기가면 16기가를 소모
- 메모리 부족할 때는?
- 좀더 메모리 많은 장비로 Migration
- 있는 데이터 줄이기
- 다만 Swap 사용중이라면 프로세스 재시작해야 함
- 메모리 줄이기 위한 설정
- 기본적인 Collection 틀
- Hash -> HashTable
- Sorted Set -> Skeplist와 HashTable
- Set -> HashTable
- 해당 자료 구조들은 메모리를 많이 사용함
- Ziplist를 이용하자
- In-memory 특성상 적은 개수라면 선형 탐색을 하더라도 빠르다.
- List, hash, sorted set 등을 ziplist로 대체해서 처리함는 설정이 존재
- O(N) 관련 명령어는 주의하자
- 싱글스레드라 한 번에 한 명령만 처리
- 긴 시간이 필요한 명령을 수행하면? 망한다.
- 초당 10만TPS 처리하는데 1개만 1초 걸려도 끝장
- 대표적인 O(N) 명령어들
- KEYS
- 한 번에 모든 데이터 가져오는 명령
- scan 명령으로 하나의 긴 명령을 짧은 여러 번 명령으로 바꿀 수 있다
- FLUSHALL, FLUSHDB
- Delete Collections
- Get All Collections
- Collection의 모든 item을 가져와야 할 때?
- Collection의 일부만 거져오거나
- 큰 Collection을 작은 여러 개의 Collection으로 나눠서 저장
- Userranks -> Userrank1, Userrank2, Userrank3
- 하나당 몇천 개 안쪽으로 저장하는 게 좋음
Redis Replication
- Async Replication
- Replication Lag이 발생할 수 있다.
- A 데이터 바뀌면 B 데이터에게 쏴주는데 그 틈에 생긴 변화
- 많이 벌어지면 슬레이브가 마스터와 연결을 끊어버린 후 일정 기간 이후 다시 연결되면서 부하
'Replicaof'(>=5.0.0) or 'slaveof'
명령으로 설정 가능(마스터, 슬레이브가 바뀐 용어)
- DBMS로 보면 statement replication이 유사
- Replication 설정 과정
- Secondary에 replicaof or slaveof 명령을 전달
- 마스터의 호스트 IP, 도메인 어드레스, 포트
- Secondary는 Primary에 sync 명령 전달
- Primary는 현재 메모리 상태를 저장하기 위해
- Fork한 프로세서는 현재 메모리 정보를 디스크에 덤프
- 해당 정보를 secondary에 전달
- Fork 이후의 데이터를 Secondary에 계속 전달
- Redis Replication 시 주의할 점
- Replication 과정에서 포크가 발생하므로 메모리 부족이 발생할 수 있다
- Redis-cli –rdb 명령은 현재 상태의 메모리 스냅샷을 가져오므로 같은 문제를 발생시킴
- AWS나 클라우드의 Redis는 좀 다르게 구현되어서 해당 부분이 좀더 안정적
- 많은 대수의 레디스 서버가 Replica를 두고 있다면
- 네트워크 이슈나 사람의 작업으로 동시에 replication이 재시도되도록 하면 문제가 발생할 수 있다
- ex) 같은 네트웍 안에 30GB를 쓰는 Redis Master 100대 정도가 리클리케이션을 동시에 재시작하면 어떤 일이 벌어질 수 있을까?
redis.conf 권장 설정 Tip
- Maxclient 설정 50000
- RDB/AOF 설정 off
- 특정 커맨드 disable
- Keys
- AWS의 ElasticCache는 이미 하고 있음
- 전체 장애의 90% 이상이 KEYS와 SAVE 설정을 사용해서 발생
Redis 데이터 분산
- 데이터의 특성에 따라 선택할 수 있는 방법이 달라진다.
- Cache일 때는 우아한 Redis?
- Persistent 해야만 안 우아한 Redis!
- 데이터 분산 방법
- Application
- Consistent Hashing
- twemproxy를 사용하는 방법으로 쉽게 사용 가능
- Sharding
- Redis Cluster
참고할만한 sample 설정?
Cache에 대한 새로운 접근법/논문?
- Cache를 썼을 때 생기는 문제점. Look-Aside caching
- read/write 발생빈도에 따라 range 조절가능> 좀더 적은 수의 cache 서버로 같은 트래픽을 처리할 수 있다. adaptive c???
- hot data는 memory에, cold data는 ssd에.
15년차 개발자로서 다른 분들에게 말하고 싶은 것
- 오픈소스를 쓴다면 코드를 꼭 봐라.
- 알고 넘어가는 것/ 에러를 근본적으로 해결하는 태도가 중요하다
Link