diff --git "a/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\341\204\214\341\205\241\341\206\274 2\341\204\216\341\205\241 \341\204\207\341\205\251\341\204\213\341\205\252\341\206\253 \341\204\211\341\205\245\341\206\257\341\204\200\341\205\250.md" "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\341\204\214\341\205\241\341\206\274 2\341\204\216\341\205\241 \341\204\207\341\205\251\341\204\213\341\205\252\341\206\253 \341\204\211\341\205\245\341\206\257\341\204\200\341\205\250.md" new file mode 100644 index 0000000..8b354d6 --- /dev/null +++ "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\341\204\214\341\205\241\341\206\274 2\341\204\216\341\205\241 \341\204\207\341\205\251\341\204\213\341\205\252\341\206\253 \341\204\211\341\205\245\341\206\257\341\204\200\341\205\250.md" @@ -0,0 +1,66 @@ +## 2차 설계 보완 +> 설정된 처리율을 초과하는 요청은 정확하게 제한한다. +> 낮은 응답시간: 이 처리율 제한 장치는 HTTP 응답시간에 나쁜 영향을 주어서는 곤란하다. +> 가능한 한 적은 메모리를 써야 한다. +> 분산형 처리율 제한: 하나의 처리율 제한 장치를 여러 서버나 프로세스에서 공유할 수 있어야 한다. +> 예외 처리: 요청이 제한되었을 대는 그 사실을 사용자에게 분명하게 보여주어야 한다. +> 높은 결함 내성: 제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어서는 안 된다. + +### 요청 제한에 대해 사용자에게 가시적으로 안내 +- X-Ratelimit-Remaining: 윈도우 내 잔여 요청 수 +- X-Ratelimit-limit: 매 윈도우 마다 클라이언트가 전송할 수 있는 요청의 수 +- X-Ratelimit-Retry-After: 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는지 알림 +### 슬라이딩 윈도우 +가정: 제한이 Limit 1분 + +기존: 요청을 수집하여 현재 요청이 들어오는 시점 기준으로 1분 내 토큰의 수를 카운팅하여 요청의 허용 여부를 결정 +책에서 제한 하는 방법: 1분이라는 슬롯 기준으로 현재 슬롯의 요청횟수와 직전 슬롯의 요청 횟수를 비율로 계산하여 횟수를 결정 +1분의 1/2이지난(1:30 시점)에서는 직전 1분의 뒤에 30초 내 요청수 * 0.5 + 현재 요청수 * 0.5로 하여 비율 계산 + +### 높은 결함 내성 +MSA 환경에서 rate limiter의 장애로 다른 모든 API에 대한 Rate Limit이 동작하지 않으면 안되기 때문에 middle ware로 api서버 별로 앞단에 위치시키는 것이 적합해보인다 + +### 분산형 처리율 제한 +in-memory DB를 활용하여 하나의 DB를 rate limiter 들이 공유하여 사용하게하면 MSA 환경에서 Scale Out 시에도 문제 없음 + +## Sequence Diagram +```mermaid +sequenceDiagram + participant C as Client + participant GW as Gateway + participant RL as rate-limiter + participant MEM as In-Memory DB (공유) + participant Q as Queue + participant W as worker + participant DB as Database + + C->>GW: API 요청 (api-key) + GW->>RL: rate-limit 체크 요청 (api-key) + RL->>MEM: 현재 슬롯 카운트 + 직전 슬롯 카운트 조회 + + alt limit(timeout) 내 응답 + MEM-->>RL: count_cur(현재 슬롯), count_prev(직전 슬롯) + Note over RL: 슬라이딩 윈도우 계산
weight = 슬롯 경과시간 / 윈도우 길이 (예: 30s/60s = 0.5)
count = count_prev × (1-weight) + count_cur × weight + alt count ≥ limit + RL-->>GW: 거부 (deny) + GW-->>C: 429 Too Many Requests
X-Ratelimit-Limit, X-Ratelimit-Remaining: 0
X-Ratelimit-Retry-After + else count < limit + RL->>MEM: 현재 슬롯 +1 (key + TTL) + RL-)Q: 카운트 증가 이벤트 발행 (비동기) + RL-->>GW: 허용 (allow) + GW-->>C: 200 OK
X-Ratelimit-Limit, X-Ratelimit-Remaining, X-Ratelimit-Retry-After + end + else timeout · In-Memory DB 무응답 + RL-->>GW: 장애 내성 - 허용 (allow, fail-open) + GW-->>C: 200 OK (헤더는 마지막 알려진 값 또는 생략) + end + + Note over Q,DB: 비동기 영속화 (요청 흐름과 분리되어 지연이 없음) + Q->>W: 카운트 이벤트 소비 + W->>DB: 카운트 영속화 + + Note over RL,MEM: 분산 처리율 제한: 모든 rate-limiter 인스턴스가
동일한 In-Memory DB(예: Redis)를 공유 → API 서버 Scale-out 시에도 카운트 일관성 유지 + + Note over GW,RL: 높은 결함 내성: rate-limiter를 각 API 서버 앞단의
미들웨어로 배치 → 한 limiter 장애가 다른 API의 제한 로직에 영향 주지 않음 +``` + diff --git "a/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\354\236\245 \354\262\230\353\246\254\354\234\250 \354\240\234\355\225\234 \354\236\245\354\271\230\354\235\230 \354\204\244\352\263\204.md" "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\354\236\245 \354\262\230\353\246\254\354\234\250 \354\240\234\355\225\234 \354\236\245\354\271\230\354\235\230 \354\204\244\352\263\204.md" new file mode 100644 index 0000000..10c27a8 --- /dev/null +++ "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/4\354\236\245 \354\262\230\353\246\254\354\234\250 \354\240\234\355\225\234 \354\236\245\354\271\230\354\235\230 \354\204\244\352\263\204.md" @@ -0,0 +1,47 @@ + +## 요구사항 +> 설정된 처리율을 초과하는 요청은 정확하게 제한한다. +> 낮은 응답시간: 이 처리율 제한 장치는 HTTP 응답시간에 나쁜 영향을 주어서는 곤란하다. +> 가능한 한 적은 메모리를 써야 한다. +> 분산형 처리율 제한: 하나의 처리율 제한 장치를 여러 서버나 프로세스에서 공유할 수 있어야 한다. +> 예외 처리: 요청이 제한되었을 대는 그 사실을 사용자에게 분명하게 보여주어야 한다. +> 높은 결함 내성: 제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어서는 안 된다. + +## 설계 +![아키텍처.png](https://github.com/Kimchuljoong/system-design-study/blob/main/04-%E1%84%8E%E1%85%A5%E1%84%85%E1%85%B5%E1%84%8B%E1%85%B2%E1%86%AF_%E1%84%8C%E1%85%A6%E1%84%92%E1%85%A1%E1%86%AB_%E1%84%8C%E1%85%A1%E1%86%BC%E1%84%8E%E1%85%B5%E1%84%8B%E1%85%B4_%E1%84%89%E1%85%A5%E1%86%AF%E1%84%80%E1%85%A8/%E1%84%8B%E1%85%A1%E1%84%8F%E1%85%B5%E1%84%90%E1%85%A6%E1%86%A8%E1%84%8E%E1%85%A5.png?raw=true) + +## Sequence Diagram +```mermaid +sequenceDiagram + participant GW as Gateway + participant RL as rate-limiter + participant MEM as In-Memory DB + participant Q as Queue + participant W as worker + participant DB as Database + + GW->>RL: rate-limit 체크 요청 (api-key) + RL->>MEM: api-key 버킷 카운트 조회 + + alt limit(timeout) 내 응답 + MEM-->>RL: 현재 카운트 반환 + alt 카운트 ≥ 임계치 + RL-->>GW: 불가능 (deny) + else 카운트 < 임계치 + RL->>MEM: 버킷 +1 (고유값 + TTL) + RL-)Q: 카운트 증가 이벤트 발행 (비동기) + RL-->>GW: 가능 (allow) + end + else timeout · In-Memory DB 무응답 + RL-->>GW: 장애 내성 - 가능 (allow) + end + + Note over Q,DB: 이후 비동기 영속화 (요청 흐름과 분리) + Q->>W: 카운트 이벤트 소비 + W->>DB: 카운트 영속화 +``` + +## 설계 이유 +- in-memory 사용으로 접근 속도 보장 +- message queue를 활용하여 사용자의 접근 결과를 비동기적으로 영속화 +- gateway에서 rate-limiter에 요청과 응답 사이에 요청 대기 시간 제한으로 서비스에 영향 최소화(rate-limiter 실패 시에는 원래 api 그대로 호출 서비스 가용성 보장) diff --git "a/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/\341\204\213\341\205\241\341\204\217\341\205\265\341\204\220\341\205\246\341\206\250\341\204\216\341\205\245.png" "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/\341\204\213\341\205\241\341\204\217\341\205\265\341\204\220\341\205\246\341\206\250\341\204\216\341\205\245.png" new file mode 100644 index 0000000..e231b2c Binary files /dev/null and "b/04-\341\204\216\341\205\245\341\204\205\341\205\265\341\204\213\341\205\262\341\206\257_\341\204\214\341\205\246\341\204\222\341\205\241\341\206\253_\341\204\214\341\205\241\341\206\274\341\204\216\341\205\265\341\204\213\341\205\264_\341\204\211\341\205\245\341\206\257\341\204\200\341\205\250/\341\204\213\341\205\241\341\204\217\341\205\265\341\204\220\341\205\246\341\206\250\341\204\216\341\205\245.png" differ