-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from 11th-SSAFY-19/jaeho/chapter4
[2주차]_4장_처리율 제한 장치의 설계_변재호
- Loading branch information
Showing
1 changed file
with
289 additions
and
0 deletions.
There are no files selected for viewing
289 changes: 289 additions & 0 deletions
289
04장/[2주차]_4장_처리율 제한 장치의 설계_변재호.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
<aside> | ||
💡 처리율 제한 장치 : 클라이언트가 보내는 트래픽의 처리율을 제어하기 위한 장치. | ||
</aside> | ||
|
||
- ex) HTTP에서 특정 기간 내에 전송되는 클라이언트의 요청 횟수(threshold)를 제한 | ||
- ex1) 사용자는 초당 2회 이상 새글 올릴 수 없음 | ||
- ex2) 같은 IP 주소로는 하루에 10개 이상의 계정을 생성할 수 없음 | ||
- ex3) 같은 디바이스로는 주당 5회 이상 리워드를 요청할 수 없음 | ||
- 처리율 제한 장치의 장점 | ||
- Dos(Denial of Service) 공격에 의한 자원 고갈 방지 | ||
- 비용 절감 | ||
- 서버 비용 하락 | ||
- 우선순위가 높은 api에 더 많은 자원 할당 | ||
- 서버 과부하 방지 | ||
|
||
# 1단계. 문제 이해 및 설계 범위 확정 | ||
|
||
> 주체 : 클라이언트 측 제한 vs 서버 측 제한?<br> | ||
> 기준 : api 호출 vs ip 주소 vs 사용자 id vs …?<br> | ||
> 시스템 규모 : 작음 vs 큼?<br> | ||
> 환경 : 분산?<br> | ||
> 독립성 : 독립적 vs 애플리케이션 코드 ?<br> | ||
> 처리 결과 : 요청이 걸러진 결과를 알리기 vs 안 알리기?<br> | ||
> … | ||
- 요구사항 | ||
- 요청을 정확히 제한 | ||
- 낮은 응답시간 | ||
- 가능한 적은 메모리 | ||
- 분산형 처리율 제한 | ||
- 예외 처리 | ||
- 높은 결함 감내성 (fault tolerance) | ||
|
||
<br> | ||
|
||
# 2단계. 개략적 설계안 제시 및 동의 구하기 | ||
|
||
## 1. 처리율 제한 장치를 어디에 둘 것인가? | ||
|
||
1. ~~클라이언트 측~~ | ||
- 클라이언트 요청은 쉽게 위변조가 가능 ⇒ 부적절 | ||
2. 서버 측 : api 서버 측 | ||
3. 미들웨어 (middleware) : 클라이언트 - 미들웨어 - api 서버 | ||
|
||
- 보통 `API 게이트웨이` 라는 컴포넌트에 구현 ⇒ 클라우드 마이크로 서비스 | ||
- 처리율 제한 | ||
- SSL 종단 (termination) | ||
- 사용자 인증 (authentication) | ||
- IP 허용 목록 (whitelist) | ||
|
||
<aside> | ||
💡 처리율 제한 ‘서비스’를 직접 만드는 데 시간이 든다 ⇒ API 게이트웨이가 효과적 | ||
|
||
</aside> | ||
|
||
## 2. 처리율 제한 알고리즘 | ||
|
||
### 토큰 버킷 알고리즘 (token bucket) | ||
|
||
- `토큰 버킷` : 지정된 용량을 갖는 컨테이너 | ||
|
||
![4-1. 토큰버킷.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/a5b3ee4e-14b3-49e8-a138-aea8e48edd63) | ||
|
||
- 방식 | ||
1. 토큰 공급기는 정해진 매 초마다 몇 개의 토큰을 공급한다. | ||
1. 토큰이 꽉 차면, 더이 상 토큰이 추가되지 않음 | ||
2. 각 요청은 하나의 토큰을 사용한다. | ||
1. 토큰이 없는 경우, 그 요청은 버려짐 | ||
|
||
![4-2. 토큰버킷2.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/86e080a4-f7dd-4727-a9b1-71101684b408) | ||
|
||
- 2개의 인자를 받음 | ||
1. 버킷 크기 : 버킷에 담을 수 있는 토큰의 최대 개수 | ||
2. 토큰 공급률 (refill rate) : 초당 몇 개의 토큰이 버킷에 공급되는가 | ||
- 보통 API 엔드포인트마다 별도의 버킷을 둠 | ||
- ex1) 사용자마다 ‘하루에 한번만’ 포스팅 가능, 친구는 ‘150명까지 추가 가능’, 좋아요는 ‘5번만 누를 수 있음’ ⇒ 사용자마다 3개의 버킷이 필요함 | ||
- ex2) IP 주소별로 처리율 제한 적용 ⇒ IP 주소마다 버킷이 필요함 | ||
- ex3) 시스템의 처리율 : 초당 10000개 요청 제한 ⇒ 모든 요청이 하나의 버킷을 공유해야함 | ||
- 장점 | ||
- 구현이 쉬움 | ||
- 메모리 사용 효율적 | ||
- **burst of traffic** (짧은 시간에 집중되는 트래픽) 처리 가능 | ||
- 단점 | ||
- 두 파라미터 (버킷 크기, 토큰 공급률) 를 적절하게 튜닝하기 어려움 | ||
|
||
### 누출 버킷 알고리즘 (leaky bucket) | ||
|
||
- 토큰 버킷과 유사 + 요청 처리율 고정 | ||
- FIFO (First-In-First-Out) 큐로 구현 | ||
|
||
![4-3. 누출 버킷.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/cc4e5424-e389-4f55-aff6-c95a5fcda239) | ||
|
||
- 방식 | ||
1. 요청이 도착하면 큐가 가득 차 있는지 확인 | ||
1. 빈자리가 있으면, 큐에 요청을 추가 | ||
2. 가득 차 있으면, 요청 버림 | ||
2. 지정된 시간마다 큐에서 요청을 꺼내어 처리 | ||
- 2개의 인자를 받음 | ||
1. 버킷 크기 : 큐 (처리될 항목들) 사이즈 | ||
2. 처리율 (outflow rate) : 지정된 시간당 몇 개의 항목을 처리할 수 있는가 | ||
- 장점 | ||
- 메모리 사용량 효율적 - 큐의 크기가 제한 | ||
- 안정적 출력 - 고정된 처리율 | ||
- 단점 | ||
- burst of traffic 에 취약 - 오래된 요청이 쌓이고, 새로운 요청이 버려짐 | ||
- 두 파라미터 튜닝 어려움 | ||
|
||
### 고정 윈도 카운터 (fixed window counter) | ||
|
||
![ex) 초당 3개의 요청만 허용하는 경우](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/e505f2d3-36ed-454e-93c1-7c5fd970407c) | ||
|
||
ex) 초당 3개의 요청만 허용하는 경우 | ||
|
||
- 방식 | ||
1. 타임라인을 고정된 간격의 윈도로 나누고, 각 윈도마다 카운터를 붙인다. | ||
2. 요청이 접수될 때마다, 카운터 값이 1씩 증가 | ||
3. 카운터 값이 사전에 설정된 임계치 (threshold)에 도달하면, 새로운 요청은 새 윈도가 열릴 때까지 버려짐 | ||
- 장점 | ||
- 메모리 효율성 | ||
- 이해하기 쉬움 | ||
- 윈도가 닫히는 시점에 카운터 초기화 ⇒ 특정 트래픽 패턴을 처리하기 적합 | ||
- 단점 | ||
- 윈도 경계 부근에서 burst of traffic에 취약 | ||
- ex) | ||
2:00 ~ 2:10 : 5개 & 2:10 ~ 2:20 : 5개 이지만, | ||
(윈도 위치를 옮기면) 2:05 ~ 2:15 : 10개 가 되어버릴 수 있음 | ||
|
||
### 이동 윈도 로그 (sliding window log) | ||
|
||
- 방식 | ||
1. 요청의 타임스탬프 (timestamp)를 추적 | ||
1. 이 데이터는 보통 redis의 정렬 집합 (sorted set) 같은 캐시에 보관 | ||
2. 새 요청이 오면 만료된 타임스탬프(=현재 윈도의 시작시점보다 오래된 타임스탬프) 제거 | ||
3. 새 요청의 타임스탬프를 로그(log)에 추가 | ||
4. 로그의 크기가 허용치보다 같거나 작으면 시스템에 전달. 아니면 처리 거부 | ||
|
||
![4-5. 이동 윈도 로그.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/82aa011f-af0d-4286-ba71-e34ec9bdb8d9) | ||
|
||
- 장점 | ||
- 메커니즘이 정교 | ||
- 어느 순간의 윈도를 보더라도, 허용되는 요청의 개수는 시스템 처리율 한도를 넘지 않음 | ||
- 단점 | ||
- 다량의 메모리 사용 - 거부된 요청의 타임스탬프도 보관하므로 | ||
|
||
### 이동 윈도 카운터 (sliding window counter) | ||
|
||
- 고정 윈도 카운터 + 이동 윈도 로그 | ||
|
||
![4-6. 이동윈도카운터.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/563657ec-d46b-4d88-ade8-5354d7594f4c) | ||
|
||
- 방식 | ||
- 이전 시간대의 평균 처리율에 따라 현재 윈도 상태를 계산 | ||
- ex) 현재 1분간의 요청 수 + 직전 1분간의 요청 수 x 이동 윈도와 직전 1분이 겹치는 비율 | ||
- 장점 | ||
- burst of traffic 취약점 강화 | ||
- 메모리 효율성 | ||
- 단점 | ||
- ~~다소 느슨~~ - 직전 시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하므로 | ||
|
||
## 개략적인 아키텍처 | ||
|
||
<aside> | ||
💡 1. 요청 접수 개수를 추적할 카운터 | ||
2. 추적 대상 설정 (사용자별? IP주소별? API엔드포인트별? …) | ||
3. 시스템 허용 threshold 넘어가면 요청 거부 | ||
|
||
</aside> | ||
|
||
- 카운터 보관 ⇒ 캐시 (ex. Redis) | ||
- 데이터베이스는 너무 느림 - 디스크 접근 | ||
- 시간에 기반한 만료 정책 지원 | ||
- Redis | ||
- INCR : 메모리에 저장된 카운터 값을 1만큼 증가 | ||
- EXPIRE : 카운터에 타임아웃 값을 설정. 설정 시간 지나면 카운터 자동 삭제 | ||
|
||
![4-7. 개략.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/dd40cd15-e4b0-4ddf-8a4a-fa2c34f4a02c) | ||
|
||
- 과정 | ||
1. 클라이언트 → 처리율 제한 미들웨어 : 요청 보냄 | ||
2. 처리율 제한 미들웨어 ↔ 레디스 : 카운터 가져와서 threshold 확인 | ||
1. 안되면 거부 | ||
3. 처리율 제한 미들웨어 → API 서버 : 요청 전달 | ||
1. 처리율 제한 미들웨어 → 레디스 : 카운터 값 증가 | ||
|
||
<br> | ||
|
||
# 3단계. 상세 설계 | ||
|
||
<aside> | ||
💡 처리율 제한 규칙은 어떻게 만들어지고 어디에 저장? | ||
처리가 제한된 요청들은 어떻게 처리? | ||
|
||
</aside> | ||
|
||
## 1. 처리율 제한 규칙 | ||
|
||
- 보통 `설정 파일 (configuration file)` 형태로 디스크에 저장 | ||
- ex) | ||
```yaml | ||
# 마케팅 메시지는 하루 5개까지만 허용한다. | ||
domain: messaging | ||
descriptors: | ||
- key: message_type | ||
value: marketing | ||
rate_limit: | ||
unit: day | ||
requests_per_unit: 5 | ||
``` | ||
## 2. 처리율 한도 초과 트래픽의 처리 | ||
- 응답 : HTTP 429 (too many requests) | ||
- HTTP 헤더 : 클라이언트가 자기 요청이 제한에 걸렸는지 어떻게 확인하나? | ||
- X-Ratelimit-Remaining : 윈도 내에 남은 처리 가능 요청 수 | ||
- X-Ratelimit-Limit : 매 윈도마다 클라이언트가 전송할 수 있는 요청 수 | ||
- X-Ratelimit-Retry-After : 한도 제한에 걸리지 않으려면 몇 초 뒤에 다시 보내야하는가 | ||
- 처리 : 제한 걸린 메시지를 큐에 보관했다 나중에 처리 | ||
## 상세 설계 | ||
![4-8. 상세.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/c1ddcfc2-564c-4e61-b871-448ad66869b2) | ||
1. 처리율 제한 규칙 : 디스크에 보관 | ||
2. 작업 프로세스는 이 규칙을 수시로 디스크에서 읽어 → 캐시에 저장 | ||
3. 클라이언트 요청 → 처리율 제한 미들웨어 : 요청 전송 | ||
4. 미들웨어 ↔ 캐시 : 제한 규칙 가져옴 | ||
미들웨어 ↔ 레디스 : 카운터, 마지막 요청의 타임스탬프 가져옴 | ||
5. 가져온 값들에 근거하여 요청 통과 or 거부 | ||
1. 거부할 땐 429 (too many requests) 에러 or 메시지 큐에 보관 | ||
## 분산 환경에서의 설계 | ||
> 경쟁 조건 | ||
> 동기화 | ||
### 경쟁 조건 (race condition) | ||
- 레디스에서 카운터를 읽고 쓰는 상황에서, 병렬적인 요청 처리 상황 | ||
- 해결 | ||
- ~~락 (lock)~~ | ||
- 성능이 매우 안 좋아짐 | ||
- 루아 스크립트 (Lua Script) | ||
- 정렬 집합 (Sorted Set) | ||
### 동기화 이슈 (synchronization) | ||
- 처리율 제한 장치를 여러개 두는 상황 | ||
- 해결 | ||
- ~~고정 세션 (sticky session)~~ : 같은 클라이언트의 요청은 항상 같은 장치로 보내기 | ||
- 확장성 x, 유연성 x | ||
- 중앙 집중형 데이터 저장소 사용 (like Redis) | ||
![4-9. redis.png](https://github.com/11th-SSAFY-19/large-scale-system-design/assets/17850099/55aca6f5-fbf9-4bc5-b1be-4576829d65b6) | ||
### 성능 최적화 | ||
1. 지연시간 (latency) 줄이기 | ||
→ 세계 곳곳에 에지 서버 (edge server) 심기 | ||
2. 제한 장치 간 데이터 동기화 | ||
→ 최종 일관성 모델 (eventual consistency model) 사용하기 | ||
### 모니터링 (monitoring) | ||
1. 채택된 처리율 제한 알고리즘이 효과적인가 | ||
2. 정의한 처리율 제한 규칙이 효과적인가 | ||
<br> | ||
# furthermore | ||
## 경성(hard) vs 연성(soft) 처리율 제한 | ||
- hard : 요청의 개수는 임계치를 절대 넘으면 안됨 | ||
- soft : 요청 개수는 잠시 동안은 임계치를 넘을 수 있음 | ||
## 다양한 계층에서의 처리율 제한 | ||
- 현재까지는 7계층에서의 처리율 제한 | ||
- 3계층 처리율 제한도 가능 | ||
- ex) IP table → IP 주소에 처리율 제한 적용 | ||
## 처리율 제한을 회피하는 방법 (클라이언트 설계) | ||
- 클라이언트 측 캐시 사용 ⇒ API 호출 횟수 줄이기 | ||
- threshold 를 생각하며, 짧은 시간 동안 너무 많은 메시지 보내지 않기 | ||
- 예외, 에러 처리 코드 도입 ⇒ 복구 가능하도록 | ||
- 재시도 로직 구현할 때, 충분한 백오프(back-off) 시간 두기 |