rateLimit 고민 기록

JULY 14, 2024

이번에 톡사원증이 오픈을 하게 되었다.
톡사원증은 특정 증명서 발급을 통해 회사 이력을 카드로 인증해주는 서비스이다.
요 기쁜 소식을 사내에 오픈 공지를 올렸을때, 순간적으로 트래픽이 살짝 높아지게 되었는데, 이때 증명서 발급 신청 건이 우수수 실패가 되었다.

맙소사… 아직 대외로 오픈소식을 알리기도 전인데 이게 왠 날벼락인가…
이대로라면 프로모션시에 큰 장애가 날 듯 싶어 급하게 rate limiter 를 개발하게 되었다.

요구사항은,

  1. 증명서 별로, 발급 신청시 rate limit 을 적용한다.
  2. 분산환경에서 동작해야 한다.
  3. 발급신청시에 요청수 체크 및 카운트를 증가 시키고, 그외 특정 api 들에서도 요청수 체크를 할 수 있어야 한다.

구현방법

rate limit 를 구현하는 방법은 다양하다.

nginx에서 사용자 IP 별 요청을 제한하거나 어플리케이션 단에서 queue를 이용하는 방법, 또는 redis 를 이용하는 방법 등 여러가지 방법이 있는데, 이중 최대한 쉽게 구현할 수 있는 redis 를 사용하기로 했다. (예시도 많고..)

그리고 방법은 2가지를 정할 수 있는데,

구현방법1) 고정 윈도 카운터

  • 타임라인을 고정된 윈도우로 나누고, 각 윈도우마다 카운트 체크를 한다.
  • 카운트가 임계치를 넘어가면 새로운 윈도우가 열릴때까지 요청은 버려진다.

01

고정 윈도 카운터의 단점

  • 이 방법은 redis 공식 문서에도 소개된 만큼 구현하기 쉬운 장점이 있지만, (https://redis.io/docs/latest/commands/incr/)
  • 아래와 같이 윈도우 경계부근에서 일시적으로 많은 트래픽이 몰려드는 경우에는, 기대했던 시스템의 처리 한도보다 최대 2배의 많은양을 처리하게 된다.

02

ts = CURRENT_UNIX_TIME()
keyname = key+":"+ts
MULTI
    INCR(keyname)
    EXPIRE(keyname,10)
EXEC
current = RESPONSE_OF_INCR_WITHIN_MULTI
IF current > 100 THEN
    ERROR "too many requests per second"
ELSE
    PERFORM_API_CALL()
END

구현방법2) 이동 윈도우

  • 매번 현재시간을 기준으로 윈도우를 나누고, 각 윈도우마다 카운트 체크를 한다.

03

이동 윈도우의 단점

  • 위 그림만 봐서는 고정 윈도우의 단점을 해결 할 수 있을 듯.. 보였지만
  • 실제 구현 방법을 보면 이 방법도 함정이 보인다..ㅠㅜ
current = 0
MULTI
ZREMRANGEBYSCORE $key 0 ($currentTime - $slidingwindow)
current = ZRANGE $key 0 -1
EXEC

IF current > 100 THEN
ERROR "too many requests per second"
ELSE
PERFORM_API_CALL()
MULTI
ZADD $key $currentTime
EXPIRE $key $slidingwindow
EXEC
  • 위 코드는 sorted set를 이용한 이동 윈도우 구현 방법이고, 데이터는 현재 시간이 들어간다.
  • sorted set 이기 때문에 시간순으로 정렬이 되며, ZREMRANGEBYSCORE 명령어를 이용하여 현재 윈도우에서 제외되는 데이터를 지운다.

04

이때, ZREMRANGEBYSCORE 명령어와 ZADD 명령어는 한 트랜잭션에 존재하지 않기 때문에 정말 짧은 순간에 트래픽이 몰렸을 경우, 방어가 되지 않는 문제가 생긴다.


그래서 결론은?

각 단점을 다시 정리하면,

  • 고정윈도우는, 정해진 시간(윈도우)에 burst traffic 발생시에 최대 2배 처리가 되고,
  • 이동윈도우는, 동일하거나 정말 짧은 시간에 burst traffic 발생시 아예 방어가 안된다.

하지만 전자증명서 서비스 특성상, 정말 짧은시간에 burst traffic이 발생할 확률보단 정해진 시간에 burst traffic 이 발생할 확률이 높아 두번째 방법을 선택하게 되었다.

아직 개발 전이지만.. 시간이 너무 촉박하다ㅠㅜ 어휴


작업 기록 블로그