처리율 제한 Rate Limit

@gmkseta · January 27, 2022 · 4 min read

처리율 제한 장치(rate limiter)란?

  • 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치이다.
  • 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한한다.
  • 요청 횟수가 임계치(threshold)를 넘어서면 추가로 도달한 모든 호출은 처리가 중단(block)된다.
  • 오픈 API나 외부에 특정 API를 제공하는 서비스의 경우 시간당 몇 회 호출이나 하루에 몇 회 호출 등의 제한을 걸어두곤 한다.

왜 처리율 제한이 필요한가??

  • DoS(Denial of Service) 공격에 의한 자원 고갈을 방지할 수 있다. 과한 트래픽으로 부터 서비스를 보호한다.
  • 비용을 절감한다. 추가 요청에 대한 처리를 제한하면 서버를 많이 두지 않아도 되고, 우선순위가 높은 API에 더 많은 자원을 할당할 수 있다.
  • 초과하는 횟수에 따라 과금하는 비즈니스 모델로 활용이 가능하다.
  • 서버 과부하를 막는다. 봇애서 오는 트래픽이나 사용자의 잘못된 이용 패턴으로 유발된 트래픽을 걸러내는데 사용 가능

처리율 제한 장치의 위치

서버에서의 처리율 제한

  • 처리하고자 하는 조건에 따라 서버측에서 제한을 건다.

미들웨어에서의 처리율 제한

  • 미들웨어에서 API 서버로 가는 요청을 통제한다.
  • 요청이 너무 많이오면 429 code (Too many requests)를 리턴해준다. 2022 01 28 01 14 01

클라이언트에서의 처리율 제한 장치

  • 클라이언트의 요청은 쉽게 위변조가 가능해서 안정적으로 처리율 제한을 걸 수 없다.
  • 하지만 꼭 클라이언트가 어플이나 브라우저가 아닌 서버일 수 있음 (소비자)
  • 제공자는 그냥 거절하면 그만이지만 클라이언트에서는 다양한 방법을 사용 가능

    • 다시 요청할 수 있을 때까지 대기
    • 일정 시간 기다린 후 그때도 통과를 못한다면 타임아웃 처리
    • 요청 취소

처리율 제한 알고리즘

1. Token Bucket

  • 토큰 버킷은 지정된 용량을 갖는 컨테이너이고, 이 버킷에는 설정된 양의 토큰이 주기적으로 채워진다.
  • 토큰이 꽉 차있으면 더 이상 토큰은 추가되지 않는다.
  • 요청이 들어오면 버킷에 토큰이 있는지 확인한다.

    • 충분한 토큰이 있으면 하나의 토큰을 버리고 요청을 처리한다.
    • 토큰이 없으면 요청은 버려진다.

2022 01 28 01 53 33

  • 가장 간단하고 보편적으로 쓰인다.
  • 통상적으로 엔드포인트마다 별도의 버킷을 둔다.

    • IP별로 처리율을 제한하고 싶다면 IP주소마다 버킷을 하나씩 할당한다.
    • 시스템의 처리율을 제한하고 싶다면 모든 요청이 하나의 버킷을 공유하도록 한다.
  • 버킷의 크기, 토큰 공급률을 인자로 받는다

장점

  • 구현이 쉽다.
  • 메모리 사용 측면에서도 효율적이다.
  • 짧은 시간에 집중되는 트래픽도 처리 가능하다. 버킷에 남은 토큰이 있기만 하면 요청은 시스템에 전달된다.

단점

  • 버킷의 크기와 토큰 공급률이라는 인자를 적절하게 튜닝하는게 까다롭다.

2. Leaky Bucket

  • 요청이 도착하면 큐가 가득 차 있는지 본다. 빈 자리가 있는 경우에는 큐에 요청을 추가한다.
  • 큐가 가득 차 있는 경우에는 새 요청은 버린다.
  • 지정된 시간마다 큐에서 요청을 꺼내어 처리한다.

2022 01 28 02 35 24

  • 토큰 버킷 알고리즘과 비슷하지만 요청 처리율이 고정되어있다.
  • 보통 FIFO로 구현한다.
  • 버킷 크기, 처리율을 인자로 받는다.

장점

  • 큐의 크기가 제한되어 있어 사용량 측면에서 효율적이다.
  • 고정된 처리율을 갖고 있으므로 안정적인 출력이 가능하다.

단점

  • 단시간에 많은 트래픽이 몰리는 경우 큐에는 오래된 요청들이 쌓이게 되고, 그 요청들을 제때 처리 못하면 최신 요청들이 버려진다.
  • 버킷 크기, 처리율을 튜닝하기 까다롭다.

3. Fixed Window Counter

  • 타임라인을 고정된 간격의 window로 나누고 각 window마다 counter 를 붙인다.
  • 요청이 접수될 때마다 이 counter값은 1씩 증가한다.
  • 이 counter 값이 threshold 에 도달하면 새로운 요청은 새 윈도가 열릴 때까지 버려진다.

2022 01 28 03 00 13 2022 01 29 22 12 32

  • 매 초마다 할당량 이상이 오면 초과분은 버려진다.
  • 이 알고리즘의 가장 큰 문제는 경계 시간대에 몰리면 window에 할당된 양보다 더 많은 요청이 처리될 수 있다.

장점

  • 메모리 효율이 좋다.
  • 이해하기 쉽다.
  • window가 닫히는 시점에 카운터를 초기화하는 방식은 특정한 트래픽 패턴을 처리하기에 적합하다.

단점

  • 경계에서 일시적으로 많은 트래픽이 몰리는 경우 기대했던 시스템의 처리 한도보다 많은 양의 요청을 처리해야한다.

4. Sliding Window Log

  • 요청의 타임스탬프를 레디스의 sorted set같은 캐시에 보관한다.
  • 새 요청이 오면 만료된 타임스탬프는 제거한다.

    • 만료된 타임스탬프는 그 값이 현재 윈도의 시작 시점보다 오래된 타임스탬프를 말한다.
  • 새 요청의 타임스탬프를 로그에 추가한다.
  • 로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달한다. 그렇지 않은 경우에는 처리를 거부한다.

2022 01 29 22 14 39

  • 3의 경계에 편향한 트래픽에서의 문제를 대응하기 위한 알고리즘이다.

장점

  • 어느 순간의 윈도를 보더라도 허용되는 요청의 개수는 시스템의 처리율 한도를 넘지 않는다.

단점

  • 각 요청에 대한 타임스탬프를 보관하기 때문에 메모리를 많이 사용한다.

5. Sliding Window Counters

  • 현재 윈도에서의 요청 수 + 직전 윈도에서의 요청 수 * 이동 윈도와 겹치는 비율

2022 01 30 00 05 46

  • 고정 윈도 카운터 알고리즘과 슬라이딩 윈도를 결합한 알고리즘

장점

  • 이전 시간대의 평균 처리율에 따라 현재 윈도의 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 잘 대응한다.
  • 메모리 효율이 좋다.

단점

  • 직전시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하므로 다소 느슨하다.
  • 하지만 그렇게 심각한 문제는 아님

Redis를 이용한 구현 생각해보기

  • 특정 기간에 n회 라는 제한
  • api가 여러개라면 api의 종류를 key로 넣는다.

    • api:posts
  • 유저별로 개수를 제한한다면 user id 를 넣는다.

    • api:posts:123
  • 특정 기간의정보.

    • 윈도를 어떻게 설정할 것이냐에 따라 키가 달라짐
  • 5분마다 n회라면?

    • api:posts:123:202201271005
    • api:posts:123:202201271010
    • api:posts:123:202201271015
  • value 는 호출 수

References

@gmkseta
안녕하세요 개발자 김성준입니다.