Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed redis/week04/taejun/.gitkeep
Empty file.
114 changes: 114 additions & 0 deletions redis/week04/taejun/ClientSideCaching.md
Original file line number Diff line number Diff line change
@@ -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을 사용한다.
105 changes: 105 additions & 0 deletions redis/week04/taejun/KeySpace.md
Original file line number Diff line number Diff line change
@@ -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
```
44 changes: 44 additions & 0 deletions redis/week04/taejun/KeySpaceNotification.md
Original file line number Diff line number Diff line change
@@ -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을 구현해야 한다.
128 changes: 128 additions & 0 deletions redis/week04/taejun/Pattern.md
Original file line number Diff line number Diff line change
@@ -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을 깰 수 있기 때문이다.
Loading