diff --git a/redis/week04/taejun/.gitkeep b/redis/week04/taejun/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/redis/week04/taejun/ClientSideCaching.md b/redis/week04/taejun/ClientSideCaching.md new file mode 100644 index 0000000..1ef5428 --- /dev/null +++ b/redis/week04/taejun/ClientSideCaching.md @@ -0,0 +1,114 @@ +# Client-Side Caching + +- ClientSideCaching은 Server에서 높은 퍼포먼스를 내기위한 중요한 방법 중 하나나이다. +- DB와 같은 Storage가 아닌 별개의 공간에 (Memory)에 데이터를 저장하는 것이다. + - 저장할 수 있는 데이터의 크기는 DB에 비해서 적지만, 속도에서 차이가 많이난다. + - DB에 가해지는 부담 또한 줄일 수 있다. +- 자주 사용되는 것을 주로 저장한다. +- 여기서 말하는 ClientSide는 WebApplication, Server는 Redis + +## Client-Side Cache 지원 전략 + +### Redis - Tracking +- Redis 6.0부터 지원한다. + - CLIENT TRACKING이라는 명령어를 통해서 활성화 한다. +- Server는 특정 Client가 엑세스한 Key를 기억한다. (이래서 Tracking) +- 동일한 Key가 수정되면, Server는 Client에게무효화 Mesage를 보낸다. + - Client가 Memory에 가지고있는 Key Set에 대해서만 무효화 메세지를 보낸다. + +장점 +- Redis(Server) 의 부하 감소 + - Cache를 Redis에서 질의하지 않기 떄문 +- 응답시간 개선 +- 네트워크 트래픽 감소 +- 데이터 동기화 성능 향상 + +단점 +- Application(Client)의 복잡성 증가 및 메모리 사용량 증가 + +### TwoConnections +- Redis6부터 지원하는 RESP3프로토콜을 사용하면, 같은 Conection내에서, Query 송신과 무효화 메세지 수신을 모두 수행 할 수 있다. +- 하지만 몇몇 Client는 2개의 Connection을 선호한다 (RESP2 프로토콜에서도 사용가능) + - Conenction1 → 무효화 메세지 + - Connection2 → Query +- PubSub 채널을 구독한다. + +```bash +(Connection 1 -- used for invalidations) +CLIENT ID # (1) 현재 Client의 Id를 가져온다. +:4 +SUBSCRIBE __redis__:invalidate # (2) 무효화 메세지 채널 구독 +# RESP 프로토콜을 사용하는 pubsub 형식 +# *n은 n개의 요소로 이루어진다는 의미이다. +# $n은 n의 길이를 가진다는 의미이다. +# 마지막은 Client가 현재 구독하고 있는 Channel의 수를 나타낸다. +*3 +$9 +subscribe +$20 +__redis__:invalidate +:1 + +# Get + +(Connection 2 -- data connection) +CLIENT TRACKING on REDIRECT 4 # (2) Client Id를 입력하여 Tracking을 시작하고 키 변경사항을 1의 Client에 Reirect 시킨다. ++OK + +GET foo +$3 #RESP +bar + +# Set +(Some other unrelated connection) +SET foo bar ++OK + +# After Set + +*3 +$7 +message +$20 +__redis__:invalidate +*1 +$3 +foo # foo가 변경됐다. + +``` + +### DefaultMode +- 전체 DataSet이 아닌, 변경된 부분의 정보에 대해서만 클라이언트에게 전송하여, 네트워크 트래픽을 최소화한다. +- 무효화 테이블에 Caching가능성이 있는 Client의 목록을 추가한다. + - Client의 고유한 Id만을 저장한다. +- Client가 많아지면 성능상의 문제가 생길 수 있따. + - Server는 ClientKey에 대한 더 많은 데이터를 유지해야한다. + - Client는 실제로 Caching을 하지 않은 데이터에 대한 무효화 메세지를 자주 받을 수 있게 된다. + +### Opt-in +- OPTIN명령을 통해서 트래킹을 활성화한다. +- 명시적으로 캐싱할 것을 전달하는 것이다. + - Server의 부하가 줄어든다. (관리해야 할 것이 줄어든다) + - 무효화 메세지를 받을 가능성도 줄어든다. +- 기본적으로 Read명령어를 수행해도 Cache가 되지않았다고 가정한다. + - Cache를 한다는 것을 명시적으로 전달 한 후부터 추적을 한다. + +```bash +CLIENT TRACKING on REDIRECT 1234 OPTIN + +CLIENT CACHING YES ++OK +GET foo +"bar" +``` + +### BroadCastMode +- Redis 측에서 변경 정보에 대해서 기억하지 않는다. +- Client측에서 Prefix를 구독한다 + - 자기것이 아니면 버리는 Filtering 로직이 필요하다. +- BCAST 키워드를 통해서 활성화한다. +```kotlin +CLIENT TRACKING on REDIRECT 1234 BCAST PREFIX {prefix} +``` +- opt-in같이 prefix를 등록하고, 해당 prefix에 해당하는 Key의 변경에 대해서 BroadCast한다. +- Client와 prefix를 연결 짓는 prefix table을 사용한다. \ No newline at end of file diff --git a/redis/week04/taejun/KeySpace.md b/redis/week04/taejun/KeySpace.md new file mode 100644 index 0000000..d97cd51 --- /dev/null +++ b/redis/week04/taejun/KeySpace.md @@ -0,0 +1,105 @@ +# Keyspace + +## Key에 대한 규칙 +Key의 최대 길이는 512MB이다. + +1. 긴 Key는 좋은 방법이 아니다. + 1. 메모리 측면 + 2. Key를 조회하는데 많은 비용이 든다. + 3. Key가 길다면 sha1과 같은 것으로 해싱하는게 더 좋은 방식이다. +2. 짧은 Key 또한 좋은 방법이 아니다. + 1. 가독성이 더 중요하다. + 2. 짧은 Key를 사용하는 것이 메모리 측면에서 더 좋을 수 있으나, 올바른 균형을 찾는 것이 중요하다. +3. 일정한 Schema를 사용하는 것이 좋다. + 1. ex) object-type:id (comment:4321:reply:to) + +## Key에 대한 질의 + +### [1] EXISTS +- Key 존재 유무에 대해서 쿼리한다. +- 있으면 1 없으면 0 + +### [2] DEL +- Key와 Value를 모두 삭제 +- Key에 해당하는 값이 있었다면 1, 없었다면 0을 리턴 +- 동기적인 명령어이다. + +### [3] TYPE +- Key에 해당하는 Value의 타입에 대해서 리턴한다. + +### [4] EXPIRE +- Key를 만료시킨다. +- 시간 해상도는 초(Sec) 또는 밀리초(milisSec)으로설정할 수 있다. + - expire에 설정되는 해상도는 항상 밀리초이다. +- Expire정보는 Disk에 저장되고 Replication된다. + - 언제 만료가 될지 저장이 되있다. + - 즉, Redis가 종료되어있어도 시간은 흐르는 것이다. + +### [5] TTL +- 남은 만료시간 체크 +- 시간해상도는 항상 밀리초이다. + +### [6] UNLINK +- 비동기적인 Key삭제 +- 별도의 쓰레드에서 백그라운드로 삭제한다. +- 논리적 처리시간은 O(1), 실제 처리시간은 O(N) +- del은 동기적이기 떄문에, 많은 양의 Key를 삭제하게 된다면 시스템에 장애를 유발 할 수 있다. + +### [7] PERSIST +- 만료 옵션이 지정되있는 Key를 영구보관으로 바꾼다. +- 만료옵션을 제거한다. + - 0: 키가 존재하지 않거나, TimeOut옵션이 지정되어있지 않음 + - 1: TimeOut옵션 삭제 +## Key 탐색 + +### [1] Scan +- 호출 당 소수의 Key만을 가져온다. + - O(N)명령어들 보다 훨씬 좋다. + - KEYS + - SMEMBERS +- 중복을 허용한다. + - 중복 Key에 대한 중복제거는 Application의 몫이다. + +### [2] KEYS +- O(N)명령어다. + - 결과를 리턴하기 전까지 RedisServer에 해당하는 모든 명령어가 Blocking된다. + - 사용하면 안된다. +- KeySpace를 관리하고 싶다면, SCAN명령어나 Set자료구조를 사용하는것이 더 적합하다. +### KEYS Expression +- ?: 하나의 문자 혹은 숫자와 일치하는 모든 Key를 보여준다. + - ```shell + keys h?llo; + - hello + - hallo + - hqllo + - ... + ``` +- *: 나머지 조건에 일치하는 모든 Key를 보여준다. + - ```shell + keys h*llo + - heeeeeeeeeeeeeeeello + - hello + - hasdcadfqwaerqasdfasdfllo + - ... + ``` +- []: 사이에 있는 문자와 일치하는 것들의 Key를 보여준다. + - ```shell + keys h[ae]llo + - hallo + - hello + ``` + - 괄호 내부에 있는 것들의 조합을 의미하는 것이 아니다. +- [^]: 괄호에 포함된 것들을 제외하고 검색한다. + - ```shell + keys h[^e]llo + - hallo + - hillo + - ... + ``` +- [-]: 범위에 포함된 것들을 검색한다. + - ```shell + keys h[a-c]llo + - hallo + - hbllo + - hcllo + ``` \ No newline at end of file diff --git a/redis/week04/taejun/KeySpaceNotification.md b/redis/week04/taejun/KeySpaceNotification.md new file mode 100644 index 0000000..24dced1 --- /dev/null +++ b/redis/week04/taejun/KeySpaceNotification.md @@ -0,0 +1,44 @@ +## Redis KeySpace Notification + +- PubSub을 통해서, 데이터에 영향을 미치는 이벤트를 구독 할 수 있다. + - 실제로 Key가 변경 됐을 떄만 이벤트를 발행한다. +- 수신 할 수 있는 이벤트 + - Key에 영향을 미치는 모든 작업 + - LPUSH작업이 가능한 모든 Key + - 만료가 되는 Key +- **PubSub은 Fire & Forget구조이고, Client가 받지 못했다면 추가로 할 수 있는 작업은 없다.** +- Server에 부하를 주는 구조이다, + - Key변경 사항을 모니터링 + - 이벤트 생성 및 전송 + - Network 트래픽의 증가 + +### 이벤트 유형 + +- 2가지의 Event를 발생 시킨다. + - KeySpace 채널 + - 이벤트 이름을 메세지로 받는다. + - **PUBLISH __keyspace@0__:mykey del** + - KeyEvent 채널 + - 특정 키에 대한 메세지를 받는다. + - **PUBLISH __keyevent@0__:del mykey** + +### 설정 + +- 기본적으로 Disable 되어있다. + - CPU를 사용하기 떄문에, 조심해서 다루어야 하기 때문이다. +- redis.conf의 notify-keyspace-events 설정 또는, CONFIG SET을 통해서 활성화 된다. + - CONFIG SET은 Runtime에 설정을 바꾸는 역할 + - redis.conf는 Server가 뜰 때 읽는 설정이기 때문에, 변경 할 경우 서버를 재시작 해야한다. + +### TTL Event + +- 아래와 같을 때 Event가 생성된다. + - Key에 Access했을 떄, 만료된 것으로 확인 된 경우 + - 한번도 Access되지 않은 Key를 수집할 수 있도록 백그라운드에서 점진적으로 Key를 찾을 경우 +- 즉 만료가 확정된 시점에 Event가 발행되는 것이 아니다. + - 상당한 지연이 있을 수 있다. + +### Cluster에서의 Event + +- Cluster에서의 Event는 모든 Node에게 BroadCast되지 않는다. +- Client가 모든 Key에 대한 Event를 수신하려면, 각 노드에 대한 Subscription을 구현해야 한다. \ No newline at end of file diff --git a/redis/week04/taejun/Pattern.md b/redis/week04/taejun/Pattern.md new file mode 100644 index 0000000..613285e --- /dev/null +++ b/redis/week04/taejun/Pattern.md @@ -0,0 +1,128 @@ +## [1] BulkLoading + +- 한번에 대량의 데이터를 Insert할 때 사용된다. + - Redis프로토콜 형식의 파일을 생성하여 Server에 전달한다. + - PipeLining보다 더 빠른 데이터 Insert속도를 보여준다. +- 단순히 Redis제공 명령어를 통해서 다량의 데이터를 Insert하는 것은 권장되지 않는다. + - RTT가 증가한다. + - PipeLining을 사용할 수 있지만, 대량의 데이터를 Write하려면, Write하는 동시에 Read도 가능해야한다. +- 초기 Data를 Setting하는데 특화되어 있다. + +### Redis Protocol + +- Redis Client가 구현해야 하는 프로토콜이다. + - 구현이 간단하다. + - 분석속도가 빠르다. + - 사람이 읽을 수 있다. +- 정수, 문자열, 배열 등 다양한 데이터 유형을 직렬화할 수 있다. +- 문자열 배열을 RedisServer에게 보낸다. + - 명령 + - 인수 +- Client와 Server간의 통신에만 이용된다. + - ClusterNode간의 통신은 별개이다. + +### PipeLining과의 차이점은? + +- 상호보완적인 관계라고 볼 수 있다. +- Usecase의 차이점 + - Pipelining: 일반적인 경우에 적당한 양의 데이터를 하나의 단위로 묶어서 보낼 때 유용 + - Bulkloading: 파일 형태로 대량의 데이터를 묶어서 전송 +- 보내는 형태 + - Pipelining: 명령어를 연속적으로 보냄 + - Bulkloading: RedisProtocol을 구현한 형태의 파일을 전송 + +### netcat을 통한 전송 + +```bash +cat data.txt | nc localhost 6379 +``` + +- netcat을 통해서 해당 서버에 데이터를 전송한다. +- 전송하는 데이터(파일)은 Redis프로토콜 형식에 맞추어야한다. +- 명령들이 개별적으로 실행된다. +- netcat은 RedisServer의 처리결과에 대해서 알지 못하기 떄문에 개발자가 직접 찾아야한다. + +### redis-cli pipe모드 + +```bash +cat commands.txt | redis-cli --pipe + +All data transferred. Waiting for the last reply... +Last reply received from server. +errors: 0, replies: 1000000 +``` + +- 2.6 이상버전부터 제공한다, +- 명령어를 pipelining(일괄처리) 한다. + - 어느 명령에서 오류가 발생했는지 알기 힘들다. + - 데이터(명령, 인수)를 읽음과 동시에, 파싱한다. +- netcat만큼 빠르다. + +## [2] Distributed Lock + +- Redis를 분산락매니저 (DLM)으로 많이사용한다. +- 아래의 3가지 특성을 만족해야한다. + - MutualExclusion(상호배제): Lock은 동시에 하나의 Client만 소유 할 수 있다. + - DeadLock-Free(교착상태X): 어떤 상태에서도 Deadlock이 발생해서는 안되며, 모든 Client는 언제든지 Lock을 Acquire하고 Release할 수 있어야한다. + - Fault Tolerance(장애 내구성): 일부시스템에 장애가 발생해도, 노드가 정상적으로 동작해야 한다. 즉, 일부가 실패하는 경우 대다수가 정상적으로 동작하기 때문에 Lock의 Acquire과 Release에 문제가 생기면 안된다. + +### FailOver에 대해서 좀더 생각해보자! + +- 가장 쉬운 방법은 Instance에 Key를 생성 하는 것 + - Key에는 Expire가 있고, 장애가 생기더라도 언젠가는 삭제될 것이기 떄문이다. + - 하지만 Replica환경에서 MutualExclusion이 무너질 수 있다. + - Redis는 SPOF가 될 수 있다. + - MasterNode에 장애가 생긴다면? + - Redis의 복제는 Async하다. + 1. Replica로 Lock을 획득하기 위해서 Write한 Key가 복제되기 전 MasterNode가 다운된다. + 2. Replica는 Master로 승격된다. + 3. 승격된 Master에는 Key가 Write되지 않은 상태이기 때문에, 동시에 Lock이 획득 될 수 있다. + + +### 단일 Instance에서의 적합한 구현 + +1. Key를 통한 Lock Acquire + + ```bash + # Lock Acquire + SET resource_name my_random_value NX PX 30000 + + # Lock Release with LuaScript + if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) + else + return 0 + end + ``` + + - 모든 요청에서 Unique한 Key를 30000ms동안 획득 할 수 있다. + - 안전한 방식으로 LuaScript를 사용하여 Lock을 Release한다. + - 다른Client가 생성한 Lock을 지우지 않기 위해서 중요하다. + - value에는 random한 값이 들어있는데 그 값은 Lock을 얻은 Client밖에 모르기 때문에 일치할 때만 Lock을 Release할 수 있는 권한을 부여한다. + +### Redlock + +- SingleInstance가 아닌, 여러개의 Master가 있는 환경 (Cluster)에서는 불완전하다. +- Redis에서 구현할 것을 권장하는 알고리즘이다. (물론 완벽하게 안전하지는 않다) +- 진행 순서 + 1. 현재시간을 ms 단위로 가져온다. + 2. 모든 Instance에 동일한 Key이름과 Random한 Value를 통해서 순차적으로 잠금을 얻으려고 시도한다. + 3. 당연하게도 2에서 여러Node에 대해서 순차적으로 잠금을 시도하기 위한 시간은 Release시간보다 적어야 한다. + 4. Lock Release시간보다, Lock을 Acquire를 하는 시간이 적고, 정족수(Quorum: N/2 + 1)이상의 Node에서 Lock을 얻었다면 전체적으로 Lock을 획득 한 것으로 판단한다. + 5. Lock획득이 실패한 경우, 모든 Instance에서의 Lock Release를 시도한다. + 6. Lock Acquire에 실패한 경우, 경쟁을 방지하기 위해서 무작위 시간 지연 후에 재시도한다. + 7. 부분적으로만 Lock을 획득(일부 Instance에서만 획득) 했을 경우, 빠르게 Lock을 해제 해야 다른 Client가 Key의 만료 이전에 빠르게 Lock을 획득 할 가능성을 높일 수 있다. +- 한계점 + 1. 모든 Node가 일관된 시간을 가지고 있지 않다. (조금의 정합성이 틀어질 수 있다) + - 거의 근접한 시간에 갱신된다는 것을 가정한다. + 2. Application의 중단문제 + - GC 등 별도의 사유로 인해서 Application이 로직 실행도중 중단 될 수 있다. + - Lock을 획득 한 시점에서의 중지 후 재 실행은 MutalExclusion을 보장 하지 못하게 될 수 있다. + +- 보완점 + - FencingToken의 구현 + - FencingToken은 각 Lock의 순서를 식별 할 수 있는 고유한 식별자로 동작한다. + - 잠깐의 중단이 있고 재개할 때 이미 Lock은 Release되어 있을 수 있다. 이때 FencingToken의 최신성 여부 판단으로 인해서 Lock유효성 판단이 가능하다. + - 이미 Lock을 획득한 자원이 다시 Lock을 획득하려는 중복작업을 방지한다. + - 시간을 변경해서는 안된다. + - 수동적인 시간 변경은 MutualExclusion을 깰 수 있기 때문이다. \ No newline at end of file diff --git a/redis/week04/taejun/Pipelining.md b/redis/week04/taejun/Pipelining.md new file mode 100644 index 0000000..0c2409e --- /dev/null +++ b/redis/week04/taejun/Pipelining.md @@ -0,0 +1,23 @@ +## Pipelining + +- 초창기 Redis버전부터 지원했기 때문에, 버전에 상관없이 사용 가능하다. +- Redis명령을 일괄 처리하여 RTT를 최소화한다. +- Redis는 TCP Server이며, Request와 Response를 사용하는 Client-Server모델이다. + - Client가 Server에 쿼리를 날린다. + - Server는 해당 명령을 수행하고, 응답을 Client에게 다시 보낸다. +- Client와 Server는 네트워크를 통해서 연결되어있다. + - 빠를수도 있고, (LoopBack) + - 느릴 수도 있다. (Host사이에 많은 Network Hop이 껴 있을 경우) +- Client가 Server로 요청을 보내고, 응답을 받아오는데까지 걸리는 시간을 RTT(왕복시간) 이라고 한다. + - 만약 Redis가 하나의 요청을 250ms에 걸려서 처리한다면 Server가 100K의 요청을 받을 수 있어도, 4개의 요청밖에 처리하지 못할 것이다. +- RTT뿐만 아니라 I/O관점에서도 유리하다. + - read(), write()와 같은 SystemCall을 하나의 요청으로 다 처리할 수 있기 때문이다. + +### 진행과정 + +- Client가 다수의 명령을 Server로 일괄적으로 보낸다. + - 보냈던 요청을 한번에 받는다. + - 각각의 요청을 대기하는 Latency를 줄일 수 있기 떄문에 성능향상에 유리하다. +- 너무 많은 명령어를 한번에 PipeLine에 보내면 서버의 부하가 증가 할 수 있다. + - Server가 보낼 응답을 Memory에 저장해두고있기 떄문이다. + - 적절한 크기의 배치 사이즈를 찾는 것이 중요하다. \ No newline at end of file