처리율 제한 장치는 네트워크 시스템에서 클라이언트 또는 서비스가 보내는 트래픽의 처리율(rate)을 제어하기 위한 장치다.
HTTP를 예로 들면 이 장치는 특정기간 내에 전송되는 클라이언트의 요청 횟수를 제한한다. API 요청 횟수가 제한 장치에 정의된 임계치를 넘어서면 추가로 도달한 모든 호출은 처리가 중단된다.
몇 가지 예시
API에 rate limiter를 두면 좋은 점
3장에서 소개한 4단계 접근법을 적용해 차근차근 설계해보자 >_<
처리율 제한 장치는 여러가지 알고리즘을 사용해서 구현할 수 있고 각각 고유한 장단점을 갖기 때문에, 어떤 알고리즘을 사용해야할지 면접관과 소통하며 결정을 내려야한다.
책의 예시에서는 다음과 같은 질문을 해봤다.
일단 기본적인 클라이언트-서버 통신 모델을 사용.
클라이언트 vs 서버
클라이언트에 둔다면: 클라이언트 요청은 쉽게 위변조가 가능해서 제한을 안정적으로 걸 수 있는 장소가 되지 못함. 클라이언트는 여러 형태가 될 수 있기에 모든 클라이언트를 통제하는 것도 어려울 것이다.
처리율 제한 미들웨어를 만들 수 있다. 해당 미들웨어에서 API 서버로 가는 요청들을 제어할 수 있다.
만약 처리율 제한 미들웨어에 의해 요청이 가로막히면 클라이언트는 HTTP 상태코드 429(Too many requests)
를 받는다.
API 게이트웨이
폭넓게 채택된 기술인 클라우드 마이크로서비스의 경우 보통 API 게이트웨이라 불리는 컴포넌트에 처리율 제한장치가 구현된다. (API 게이트웨이는 처리율 제한 외에도 SSL termination, authentication, IP whitelist 관리 등을 지원하는 완전 위탁관리형 서비스, 즉 클라우드 업체가 유지 보수를 담당하는 서비스다.)
처리율 제한장치 위치에 대해서도 정답은 없으므로 회사의 여러 상황을 고려해야하한다.
서버 측에서 모든 것을 구현할 수 있는지 체크
마이크로서비스에 기반해 답변할 경우, 본인 설계에 API 게이트웨이가 이미 포함돼있다면 처리율 제한 기능을 API 게이트웨이에 포함시켜야 할 수도 있다.
이제 알고리즘을 고를 때가 왔어요
동작원리는 다음과 같다.
알고리즘의 인자
이 알고리즘의 인자는 다음 두 가지이다.
버킷의 개수
제한 규칙에 따라 달라진다.
버킷 크기, 토큰 공급률을 적절하게 튜닝하는 것이 어렵다.
토큰 버킷 알고리즘과 비슷하지만 요청 처리율이 고정돼있다.
요청 처리율이 고정되어있다는 컨셉을 그림처럼 연상할 수 있다.
누출 버킷 알고리즘은 보통 FIFO 큐로 구현한다.
위의 내용처럼, 고정윈도 카운터 알고리즘은 큰 문제가 있었다. 이동 윈도 로깅 알고리즘은 이 문제를 해결한다.
아주 정교한 메커니즘. 어느 순간의 윈도를 보더라도 허용되는 요청의 개수는 시스템의 처리율 한도를 넘지 않는다.
다량의 메모리가 사용된다. 거부된 요청의 타임스탬프도 보관하기 때문이다.
Fixed window counter의 경계 문제와 Sliding window log의 로그 보관 비용 등의 문제점을 보완할 수 있는 알고리즘이다.
두 가지 접근법이 있는데, 책에서 소개한 방법은 다음과 같다
시간에 따라 움직이는 윈도의 카운터를 어떻게 계산할까?
위 그림에서 1분 15초에 요청이 들어오는데,
타임슬롯의 크기가 1분이고 limit이 10으로 설정돼있다고 하면
현재 counter는 9 * 0.75 + 5 = 11.75
이 되어 요청이 거부될 것이다.
그렇다면 카운터 추적 대상, 알고리즘과 별개로 카운터를 어디에 보관할지를 정해야한다
레디스에 카운터를 보관한다고 쳐보자~
lyft의 오픈소스인 ratelimit을 사용하면 처리율 제한 규칙을 이런 식으로 정의할 수 있다.
domain: messaging
descriptors:
# Only allow 5 marketing messages a day
- key: message_type
value: marketing
descriptors:
- key: to_number
rate_limit:
unit: day
requests_per_unit: 5
위의 configuration은 시스템이 처리할 수 있는 마케팅 메시지의 최대치를 하루 5개로 제한하고 있다.
오픈소스를 사용하지 않더라도 우리가 풀어야하는 문제에서는 이런식으로 규칙을 유동적으로 정할 수 있어야한다.
여러가지 방법으로 처리할 수 있다.
우리의 요구사항 중 하나가, 클라이언트 예외처리였다. 즉 처리율 제한 장치에 의해 요청이 거부당했을 경우 그것을 클라이언트가 예외처리해서 사용자에게 알려주도록 구현해야한다.
그렇다면 클라이언트는 자기 요청이 처리율 제한에 걸리고 있는지를 어떻게 감지할까? 우리가 설계하고 있는 처리율 제한 장치는 다음의 HTTP 헤더를 클라이언트에게 보낸다.
X-Ratelimit-Remaining
: 윈도 내에 남은 처리 가능의 요청 수X-Ratelimit-Limit
: 매 윈도마다 클라이언트가 전송할 수 있는 요청의 수X-Ratelimit-Retry-After
: 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야하는지 알림.429 응답을 반환하는 경우에 X-Ratelimit-Retry-After
헤더를 함께 반환하도록 한다.
플로우
여러대의 서버와 병렬 스레드를 지원하도록 시스템을 확장하는 경우 다음 두 가지 문제를 해결해야한다.
경쟁 조건 문제를 해결하는 가장 널리 알려진 해결책은 락(lock)이다. 하지만 락은 시스템의 성능을 상당히 떨어뜨린다는 문제가 있다.
위 설계에서 락 대신 쓸 수 있는 해결책이 두 가지 있다.
(자세한 설명은 알아서 찾아봐야한다)..
수백만 사용자를 지우너하려면 처리율 제한 장치 서버가 여러대 필요할 것이다. 웹계층은 무상태이므로 클라이언트는 요청을 각기 다른 제한장치로 보낼 수 있기 때문에 모든 처리율 제한 장치 서버의 동기화가 필요해진다.
sticky session을 사용하는 방법이 있지만, 이 방법은 규모면에서 확장 가능하지 않으며 유연하지도 않다.
최종 일관성 모델
을 사용하는 것을 고려해보세여알고리즘
이 효과적인지제한 규칙
이 효과적인지를 기본적으로 확인해야한다.
제한 규칙이 너무 빡빡하면 많은 유효한 요청이 처리되지 못할 수도 있다는 점을 알아야 한다.
때로는 유효한 트래픽이 어떤 이벤트로 급증할 수도 있다. 이런 이벤트를 미리 알고 있다면 알고리즘을 바꾸는 것을 생각해봐야한다. ex) 깜짝 세일 때 토큰 버킷 알고리즘으로 바꾼다.
이번 장에서 했던 것
더 이야기 해보면 좋을 주제
출처
- token bucket 이미지 - https://emqx.medium.com/the-configuration-guide-of-emq-x-rate-limit-5f2e84bb628a
- leaky bucket 이미지 - https://itnext.io/rate-limiting-with-leaky-bucket-algorithm-using-go-generics-7a5086c02695
- fixed window counter 알고리즘 이미지: https://github.com/Salah856/System-Design/blob/main/Design%20Rate%20Limiter.md
- rate limeters synchronization 이미지 : https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm