diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..23baf58 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml diff --git a/ch04-rate-limiter/week1.md b/ch04-rate-limiter/week1.md new file mode 100644 index 0000000..085bfcd --- /dev/null +++ b/ch04-rate-limiter/week1.md @@ -0,0 +1,133 @@ +# Ch.5 — 처리율 제한장치 설계 +## 1. 요구사항 해석 + +### 기능 요구사항 + +- 다양한 형태의 제어규칙을 정의할 수 있도록 하는 유연한 시스템 +- 설정된 처리율을 초과하는 요청은 정확하게 제한 + +### 비기능 요구사항 + +- 낮은 응답시간 +- 가능한 한 적은 메모리 사용 +- 분산 환경에서 동작 +- 예외 처리 : 사용자에게 알려야함 +- 높은 결함 감내성 : 제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어서는 안 된다. + +--- + +## 2. 설계 구조 + +### **2.1 처리율 제한 위치 (서버측 API를 위한 장치 설계)** + +- 방법 1) 각 서버 애플리케이션 내부에 구현 + - 제어규칙에 대해 서버마다 각자 구현이 필요하고, 분산 환경에서 일관성 관리가 어려울 듯 + - `다양한 형태의 제어규칙을 정의할 수 있도록 하는 유연한 시스템` 에 부적합 + - `분산 환경에서 동작` 에 부적합 +- 방법 2) 클라이언트와 서버 사이에 구현 + - 클라이언트와 서버 사이에서 라우팅하며 처리율 체크 (리버스 프록시) + - 서버코드 변경 없이 독립적으로 운영 가능 + - `분산 환경에서 동작` 에 적합 + +방법 2를 채택한다. + +```java +클라이언트 → 리버스 프록시 (처리율 제한 장치) → 백엔드 서버 +``` + +--- + +### **2.2 카운트 저장소** + +- 방법 1) DB에 저장 + - 디스크 기반 I/O기 때문에 느리고, 동시성 제어 시 추가 병목 발생 가능 + - `낮은 응답시간` 부적합 +- 방법 2) 처리율 제한 장치 로컬 메모리 + - 장치가 1대일 때는 정확, 여러대라면 카운트가 분산되어 부정확함 + - 여러대일 경우 IP 마다 지정된 처리율 제한 노드로 가도록 한다면 보완될거 같은데, 노드 추가/삭제 시 매핑이 깨지고 특정 IP에 트래픽이 몰릴 경우 부하 분산 효과가 떨어짐 + - `설정된 처리율을 초과하는 요청은 정확하게 제한` 부적합 +- 방법 3) Redis + - 응답속도가 빠르고 공유 가능 + - `분산 환경에서 동작` 적합 + - `낮은 응답시간` 적합 + - TTL 기능으로 처리율 제한 시간기준 세팅가능 + - 원자적 연산으로 카운터 기능 해결 가능 + - `설정된 처리율을 초과하는 요청은 정확하게 제한` 적합 + +방법 3을 채택한다. + +--- + +### **2.3 설계 철학 — CP vs AP** + +- 방법 1) CP (일관성 우선) + - 기능 요구사항(설정된 처리율을 초과하는 요청은 정확하게 제한)과 부합 +- 방법 2) AP (가용성 우선) + - 비기능 요구사항(낮은 응답시간, 높은 결함 감내성)과 부합 + +방법 2 채택: 본 시스템은 **AP 시스템**으로 설계한다. + +> +> +> +> 모두 요구사항에 존재하는 부분이라 선택에 어려움이 있었지만, 처리율 제한 미들웨어는 정확한 차단보다 불필요한 차단 방지가 우선이라고 판단했다 +> + +--- + +## 3. 핵심 설계 결정 + +### 다양한 형태의 제어규칙을 정의할 수 있도록 하는 유연한 시스템 + +제한 기준은 아래 세가지로 조합할 수 있도록 설계 + +- 시간 (하루, n분 ..) +- 식별자 (사용자 ID, IP, 디바이스 ..) +- 횟수 (윈도우 내 허용 요청 수) + +### 설정된 처리율을 초과하는 요청은 정확하게 제한 + +만약 동일 ip로 1분에 100개의 요청을 제한 한다고 했을때? + +- 단순 카운터를 사용한 방식은 시간마다의 요청 건수 초기화가 어렵다. 그럼 어떻게하냐? +- 윈도우마다 요청을 정확히 카운트할 수 있는 방식이 필요함 + +방법 1) IP별 사이즈 고정 Set 활용 + +```java +1. 새 요청 들어옴 +2. Set이 가득 차지 않았으면 → 삽입 → 통과 +3. Set이 가득 찼으면 → 첫 번째(가장 오래된) 요소 확인 + → 윈도우(1분) 밖이면 제거 후 삽입 → 통과 + → 윈도우 안이면 → 차단 +``` + +- 매 요청마다 첫번째 요소 하나만 비교하면 됨 O(1) +- 단점 : 만료된 요소라도 Set이 가득 차지 않았으면 계속 저장하고 있음 + - `가능한 한 적은 메모리 사용` 부적합 + +방법 2) 만료 요소 전체제거 + +```java +1. 새 요청 들어옴 +2. 윈도우(1분) 밖의 요소 전부 제거 +3. 현재 카운트 확인 +4. 처리율 미만이면 삽입 → 통과 +5. 처리율 이상이면 → 차단 +``` + +- 요청이 들어올때마다 만료된걸 체크해서 삭제해서 위 방식보다 적은 메모리 사용 + - `가능한 한 적은 메모리 사용` 적합 +- 최악의 경우에 삭제 시 O(N) + - 다만, 책의 예시를 봤을때 N이 작을수록 O(1)에 수렴해 응답시간에 문제는 없을 듯 + +방법 2 채택: N이 수십만 이상일 경우 다른 방식을 생각해봐야함. 너무 느려서 .. + +--- + +## 4. 병목 & 장애 포인트 + +### 레디스를 사용하는데, 레디스 장애 시 어떻게 함? + +1. 처리율 제한을 타지 않고 바로 통과시키는 방법 +2. 레디스 클러스터 내 이중화/샤딩해서 복제/부하 분산을 해두는 방법 (1로 가는걸 최소화) \ No newline at end of file diff --git a/ch06-key-value-store/.gitkeep b/ch06-key-value-store/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ch06-key-value-store/week1.md b/ch06-key-value-store/week1.md new file mode 100644 index 0000000..a7aac88 --- /dev/null +++ b/ch06-key-value-store/week1.md @@ -0,0 +1,143 @@ +## 1. 요구사항 해석 + +### 기능 요구사항 + +- 키-값 쌍을 저장하고 조회할 수 있어야 한다 +- 값은 어떤 타입이든 가능 (문자열, 객체 등) +- 키 쌍 값의 크기는 10KB 이하 + +### 비기능 요구사항 + +- 가용성: 장애 발생 시에도 빠르게 응답 +- 확장성: 트래픽에 따라 서버 자동 증설/삭제, 대용량 데이터 저장 가능 +- 지연시간: 낮은 레이턴시 +- 일관성: 조정 가능한 수준 + +--- + +## 2. 설계 구조 + +단일 서버에 설계하면 기능 요구사항은 구현 가능하지만 비기능 요구사항을 충족하지 못한다. + +- 가용성 : 단일 서버 장애 시 전체 서비스 다운 (SPOF) +- 확장성 : 단일서버 증설은 스케일업만 가능해 확장에 한계가 있음. 자동 삭제도 불가. 대용량 데이터 저장에도 한계가 있음 +- 지연시간은 데이터가 늘어날수록 압축/디스크 이동 등 방법이 있겠지만, 디스크로 이동한 데이터에 대한 지연시간이 발생함 +- 일관성 : 단일이니 일관됨 + +따라서 분산 키-값 저장소가 필요하다. + +분산 키-값 저장소로 가정하고 요구사항을 다시 본다. + +- 가용성 : 서버가 여러 대이므로 일부 서버가 죽어도 다른 서버가 응답 가능 (SPOF 제거). 일부 서버가 죽었을 때 응답 가능하도록 데이터가 복제되어있어야함? +- 확장성 : 트래픽에 따라 서버를 자동 증설/삭제하면 됨. 대용량 데이터도 저장 가능함. 다만 자동 증설/삭제 시 해시 값의 변동이 있을텐데 이걸 해결할 수 있어야함. +- 지연시간 : 요청을 처리할 서버가 여러 개라 부하가 분산됨. 단, 서버간 통신이 이루어진다면 느릴 수 있음. +- 일관성 : 서버가 분산되어있어 데이터 동기화가 이루어져야함. + +### CAP란? + +> 어떤 두가지를 지키려면 나머지 하나는 희생되어야함을 의미 +> + - Consistency : 일관성 (조정가능) + - Avalability : 가용성 + - Partition Tolerance : 네트워크 장애로 파티션이 발생해도 시스템 계속 동작 + +P는 무조건 지켜져야 하기 때문에 대개는 CP 시스템과 AP 시스템으로 설계한다 + + +--- + +## 3. 핵심 설계 결정 + +### 가용성 설계 + +특정 서버가 죽었을 때 다른 서버가 응답하기 위해 데이터가 복제 되어있어야함 + +→ 근데 전체를 다 복제하면 의미가 없음 + +→ 몇개에만 복제하자 서버1은 서버2 서버4에 복제해두고.. 이런식? + +→ 복제 대상 서버는 원본 서버과 물리적인 위치가 분리되어있어야할듯 (장애 대비) + +데이터를 N개의 서버에 복제하는 방식 채택 + +- 복제본이 많을수록 가용성은 높아지지만 저장 비용이 증가하고 일관성 복잡도가 높아짐 +- 적당하게 복제본 개수를 세팅해서 가용성을 챙기고, 다만 일관성 부분을 따로 보완해야함 + +### 확장성 설계 + +단순하게 hash(key) % 서버수로 서버를 선택한다 + +→ 이 방법은 서버 증설/삭제 시 서버수의 영향을 받아 선택되는 서버 결과가 달라지게 됨 + +그럼 변동이 없는 서버의 해시값은 유지하면서, 삭제된 서버의 데이터만 서버를 재분배하거나 서버 추가시에는 그럼 따로 재분배를 해주는 방식? (안정 해싱이라 함) + +→ 재분배 중에 해당 데이터 조회 요청이 오면 지연 발생 가능 + +라우팅 테이블을 별도로 두고 어느 키가 어느 서버에 있는지 저장해두면? + +→ 테이블 관련 장애시 SPOF, 재분배 시 지연 없음 + +**안정 해싱 채택** + +- SPOF 없고 재분배 최소화, 다만 재분배 시 일시적 지연 발생 감수 + +### 일관성 설계 + +일관성은 조정 가능한 수준? 조정이 가능하게 설계하라는건가? 더 어렵다 + +1. 일관성 높은 시스템 : 쓰기 요청이 오면 모든 복제본에 반영되고 완료 응답. +2. 일관성 낮은 시스템 : 서버1에 저장되면 완료 응답. 복제본 동기화는 따로진행. +3. 결과적 정합성이 맞는 시스템 : 서버1에 저장되면 복제본 반영 보장. + +이 세가지 시스템을 운영 중 선택해서 쓸 수 있도록 해야함 .. + +요청 데이터가 저장된 서버가 N개라고 하면 + +1. 반영 응답 N개 전부 +2. 반영 응답 1개 +3. 반영 응답 1개 (반영보장) + +**결과적 일관성 채택** + +- 가용성을 챙기지만 일관성도 어느정도 챙김, 트레이드 오프 … 일시적으로 복제본값과 불일치 할 수 있음 + +### 지연시간 설계 + +지연시간은 서버간 통신이 잦을 수록 느려진다 + +→ 그럼 서버간 통신을 줄이자 + +→ 언제 서버간 통신이 발생하지? 서버 증설/삭제 시 데이터를 찾아 서버1로 갔는데 데이터가 없는 경우 + +**안정 해싱 채택** + +- 처음부터 맞는 서버로 라우팅해서 평상시 서버 간 통신 없음 +- 서버 증설/삭제 시 재분배 중 일시적 지연 발생 감수 + +--- + +## 4. 병목 & 장애 포인트 + +### 트래픽 급증 시 가장 먼저 무너질 곳 + +- 특정 키에 요청이 몰리게 되면 해당 서버만 과부하가 올 수 있음 + +→ 복제본 서버로도 요청이 분산될 수 있도록 랜덤하게 서버를 선택 + +### 데이터 일관성이 깨질 수 있는 시나리오 + +- 위에 복제본 서버로도 요청이 올 수 있다고 했으니 결과적 일관성 채택 시 아직 동기화 안된 복제본으로 읽기 요청이 오면 일관성이 깨질 수 있음 + +→ 모르겠음 + +### 장애 발생 감지 + +카프카의 컨슈머가 heartbeat를 보내는 것 처럼 서버가 heartbeat를 보내다가 일정 시간 안보내는걸 감지하면 장애로 간주 + +### 장애 발생 재처리 + +재처리는 복제본을 통해 자동으로 처리됨 + +근데 계속 장애 서버로 요청이 가면 지연시간 발생함 → 안정 해싱 링에서 제거 (서버 삭제처럼 동작되도록) + +장애 복구 후엔 서버 증설하듯 링에 다시 추가하여 데이터 재분배 → 데이터가 많으면 재분배 비용이 클듯 \ No newline at end of file diff --git a/ch06-key-value-store/week2.md b/ch06-key-value-store/week2.md new file mode 100644 index 0000000..23bd7a4 --- /dev/null +++ b/ch06-key-value-store/week2.md @@ -0,0 +1,87 @@ +## 1. 요구사항별 설계 + +- 가용성 + - 읽기 가용성 : 데이터를 N개 서버에 복제 + - 쓰기 가용성 : 낮은 W(quorum)을 설정해 일부 복제본만 응답해도 성공 처리 +- 확장성 + - 서버 증설 삭제 시 재배치 최소화 : 안정 해시 + - 서버 데이터 균등 분배 : 안정 해시 + 가상노드 + - 대용량 데이터 저장 가능 : 분산시스템 적용, 안정 해시로 부하 분산 +- 지연시간: 낮은 레이턴시 + - 빠른 라우팅 처리 : 안정 해시 + - 특정 핫키에 요청 집중 : 복제본으로 읽기 요청 랜덤 분산 +- 일관성 + - 조정 가능한 수준 : R/W quorum 값 조정하여 사용 + - 결과적 일관성 보장 형태를 채택 + - 복제로 인한 데이터 불일치 처리 + - 데이터 손실 감수 가능 → LWW (Last-Write-Wins) + - 데이터 손실 감수 불가 → 버저닝 + 벡터 시계 (채택) + +--- + +## 2. 요청 처리 흐름 + +### 7.1 GET 동작 + +``` +1. 클라이언트 GET(key) 호출 + -> 내부적으로 hash(key) 계산 -> N개 서버 식별 -> 중재자(첫 번째 서버)에 GET(key) 전송 + +2. 중재자가 N개 서버 중 R개에 읽기 요청 + -> 각 서버는 캐시를 확인하고 없으면 디스크에서 조회 + -> 조회된 데이터와 벡터시계값을 응답 + +3. R개 응답이 모이면, 충돌 비교 + -> 벡터 시계 비교 후 이전 버전은 무시 + +4. 결과를 클라이언트에게 반환 + +5. 벡터 시계 사용 시 클라이언트가 충돌 병합 후 저장 +``` + +Q. 근데 중재자가 선택되는 방식이 단순 첫번째로 고정되는게 맞을지? → 코디네이션 역할 . . + +### 7.2 PUT 동작 + +``` +1. 클라이언트 PUT(key, value, clock) 호출 + -> 내부적으로 hash(key) 계산 -> N개 서버 식별 -> 중재자(첫 번째 서버)에 PUT(key, value, clock) 전송 + -> clock에는 이전에 받은 벡터시계값을 같이 전달 + +2. 중재자가 clock 값을 1 증가시킨 새 벡터시계값 생성 + +3. N개 서버에 데이터 전파 + +4. W개 응답이 오면, 클라이언트에 성공 응답 + -> 오지않으면 timeout 처리 +``` + +--- + +## 3. 장애 감지 + +### 일시적 장애 + +각 서버는 주기적으로 heartbeat를 주고 받는다. + +응답이 없을 시 나머지 복제본으로 처리하지만, 일정 시간 동안 heartbeat가 없으면 영구 장애로 넘어간다. + +### 영구적 장애 + +영구 장애로 판단되면 반-엔트로피 프로토콜 + 머클트리를 사용해 사본 동기화한다고 함 + +왜 머클트리까지 쓰냐? + +트리로 구성해서 실제 차이분만 동기화할 수 있도록 함 → 동기화 비용이 축소 + +--- + +## 4. 1주차 대비 보완된 점 + +| 항목 | 1주차 | 2주차 | +| --- | --- | --- | +| 일관성 충돌 처리 | "결과적 일관성 채택"까지만, 구체적 메커니즘 없음 | LWW vs 벡터 시계 비교, 벡터 시계(+앱서버 병합) 채택 및 동작 원리 구체화 | +| 쓰기 가용성 | 별도 항목 없음 | 낮은 W(quorum)로 일부 응답만으로 성공 처리 | +| 요청 처리 흐름 | 명시 안 됨 | 코디네이터(중재자) 개념 도입, GET/PUT 단계별 동작 정의 | +| 데이터 일관성이 깨질 수 있는 시나리오 | "모르겠음" | 벡터 시계 비교 + R/W quorum(R+W>N) 조합으로 해결됨을 확인 | +| 장애 복구 방식 | "재분배" (모호) | 일시/영구 장애 구분, 영구 장애 시 머클 트리로 차이만 동기화 | \ No newline at end of file