대규모 시스템 설계 기초 - 4. 처리율 제한 장치의 설계

박병준·2023년 3월 20일

처리율 제한 장치

네트워크 시스템에서 처리율 제한 장치(rate limiter)는 클라이언트 또는 서비스가 보내는 트래픽의 처리율(rate)을 제어하기 위한 장치이다.

API 요청 횟수가 제한 장치에 정의된 임계치(threshold)를 넘어서면 추가로 도달한 모든 호출은 처리가 중단(block)된다.

API에 처리율 제한 장치를 두면 좋은 점

  • DoS(Denial of Service) 공격 방지
    처리율 제한 장치로 DoS공격에 의한 자원 고갈을 방지한다.

  • 비용 절감
    추가 요청에 대한 처리를 제한하면, 서버를 많이 두지 않아도 되고 우선순위가 높은 API에 더 많은 자원을 할당할 수 있다. 또한 제3자 API 사용료도 절감할 수 있다.

  • 서버 과부하 방지
    봇(bot)에서 오는 트래픽이나 사용자의 잘못된 이용 패턴으로 유발되는 트래픽을 걸러낼 수 있다.

이제 처리율 제한 장치를 앞 포스팅에서 설명했던 4단계를 이용하여 설계해보자.


1단계 : 문제 이해 및 설계 범위 확정

요구사항

  • 설정된 처리율을 초과하는 요청은 정확하게 제한한다
  • 낮은 응답시간 : 이 처리율 제한 장치는 HTTP 응답시간에 나쁜 영향을 주어서는 곤란하다.
  • 적은 메모리
  • 분산형 처리율 제한 : 하나의 처리율 제한 장치를 여러 서버나 프로세스에 공유할 수 있어야 한다.
  • 예외 처리 : 요청이 제한되었을 때는 그 사실을 사용자에게 분명하게 보여주어야 한다.
  • 높은 결함 감내성 : 제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어서는 안 된다.

2단계 : 개략적인 설계안 제시 및 동의 구하기

처리율 제한 장치는 어디에 둘 것인가?

  • 클라이언트 측
    클라이언트 요청은 쉽게 위변조가 가능하여 안정적이지 않다.

  • 서버 측
    API서버 내부에 둔다.

  • 미들웨어

    • MSA 인 경우, 처리율 제한 장치는 보통 API Gateway 에 구현한다.
    • API Gateway: 처리율 제한, SSL 종단, 사용자 인증, IP 허용 목록 관리 등

일반적인 지침

  • 현재 사용하고 있는 기술 스택이 서버 측 구현을 지원하기에 충분한지 점검하라.
  • 여러분의 사업 필요에 맞는 처리율 제한 알고리즘을 찾아라.
  • MSA을 기반하고 있고, 인증이나 IP 허용 같은 기능을 이미 API Gateway 에 적용했다면 처리율 제한도 API Gateway 에 포함하는 것이 좋다.
  • 충분한 인력이 없다면 상용 솔루션도 고려해보는 것이 좋다.

처리율 제한 알고리즘

토큰 버킷 알고리즘

많은 기업들이 보편적으로 사용하는 알고리즘으로 통상적으로 API 엔드포인트마다 별도의 버킷을 둔다.

IP 주소별로 처리율 제한을 적용해야 한다면 IP 주소마다 버킷을 하나씩 할당해야 한다.
시스템의 처리율을 초당 10,000 개 요청으로 제한한다면, 모든 요청이 하나의 버킷을 공유하도록 해야 한다.

동작 방식

  • 토큰 버킷은 지정된 용량을 갖는 컨테이너다.
  • 이 버킷에는 사전에 설정된 양의 토큰이 주기적으로 채워진다. 토큰이 꽉 찬 버킷에는 더 이상의 토큰은 추가되지 않는다.
  • 각 요청은 처리될 때마다 하나의 토큰을 사용한다. 요청이 도착하면 버킷에 충분한 토큰이 있는지 검사하게 된다.
    • 충분한 토큰이 있는 경우, 버킷에서 토큰 하나를 꺼낸 후 요청을 시스템에 전달한다.
    • 충분한 토큰이 없는 경우, 해당 요청은 버려진다.

장점

  • 구현이 쉽다.
  • 메모리를 효율적으로 사용한다.
  • 짧은 시간에 집중되는 트래픽도 처리 가능하다.

단점

  • 버킷 크기와 토큰 공급률을 적절하게 튜닝하는 것이 까다롭다.

누출 버킷 알고리즘

토큰 버킷 알고리즘과 비슷하지만 요청 처리율이 고정되어 있다는 점이 다르다.
누출 버킷 알고리즘은 보통 FIFO 큐로 구현한다.

동작 방식

  • 요청이 들어오면 큐가 가득 차 있는지 체크한다.
  • 빈 자리가 있다면 큐에 요청을 추가한다.
  • 만약 큐가 가득 차 있다면 요청은 버린다.
  • 지정된 시간마다 큐에서 요청을 꺼내어 처리한다.

장점

  • 큐의 크기가 제한되어 있어 메모리 측면에서 효율적이다.
  • 고정된 처리율을 갖고 있기 떄문에 안정적인 출력(stable outflow rate)이 가능하다.

단점

  • 짧은 시간에 집중되는 트래픽의 경우 큐에는 오래된 요청들이 쌓이게 되고, 그 요청들을 제때 처리하지 못하면 최신 요청들을 버려지게 된다.
  • 토큰 버킷 알고리즘처럼 튜닝이 어렵다. (버킷 크기 & 처리율)

고정 윈도우 카운터 알고리즘

동작 방식

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

장점

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

단점

  • 윈도 경계 부근에서 일시적으로 많은 트래픽이 몰려드는 경우, 기대했던 시스템의 처리 한도보다 많은 양의 요청을 처리하게 된다.

이동 윈도우 로깅 알고리즘

이동 윈도 로깅 알고리즘은 고정 윈도 카운터의 문제점(윈도 경계 부근에 트래픽이 집중되는 경우 시스템에 설정된 한도보다 많은 요청을 처리하게 되는 것)을 해결한다.

동작 방식

  • 이 알고리즘은 요청의 타임스탬프(timestamp)를 추적한다. 타임스탬프 데이터는 보통 레디스(Redis)의 정렬 집합 같은 캐시에 보관한다.
  • 새 요청이 오면 만료된 타임스탬프는 제거한다. 만료된 타임스탬프는 그 값이 현재 윈도의 시작 시점보다 오래된 타임스탬프를 말한다.
  • 새 요청의 타임스탬프를 로그(log)에 추가한다.
  • 로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달한다. 그렇지 않은 경우에는 처리를 거부한다.

장점

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

단점

  • 거부된 요청의 타임스탬프도 보관하기 때문에 다량의 메모리를 사용한다.

이동 윈도우 카운터 알고리즘

고정 윈도 카운터 알고리즘과 이동 윈도 로깅 알고리즘을 결합한 것이다.

장점

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

단점

  • 직전 시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 추정치를 계산하기 때문에 100% 정확하지는 않다.
  • 하지만 클라우드플레어에서 수행한 실험에 의하면 오탐은 0.003% 에 불과했다고한다.

3단계 : 상세 설계

처리율 제한 규칙

domain: messaging
descriptors :
  - key: message_type
    Value: marketing
    rate_limits:
        unit: minute
        requests_per_unit: 5

위 규칙은 Lyft의 처리율 제한 오픈소스의 예제로 시스템이 처리할 수 있는 마케팅 메시지를 하루 최대 5개로 제한하고 있다. 이러한 규칙들은 보통 설정 파일 형태로 디스크에 저장된다.

처리율 한도 초과 트래픽의 처리

HTTP 429 응답 (Too many requests): 어떤 요청이 한도 제한에 걸릴때 응답

  • 경우에 따라서 한도 제한에 걸린 메시지를 나중에 처리하기 위해 큐에 보관할 수 있다.

처리율 제한 장치가 사용하는 HTTP 헤더

클라이언트가 자기 요청이 처리율 제한에 걸리고 있는지에 대한 정보를 아래 HTTP 헤더를 통해 전달한다.

X-Ratelimit-Remaining: 윈도 내에 남은 처리 가능 요청의 수
X-Ratelimit-Limit: 매 윈도마다 클라이언트가 전송할 수 있는 요청의 수
X-Ratelimit-Retry-After: 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는지 알림

분산 환경에서의 처리율 제한 장치의 구현

여러 대의 서버와 병령 스레드를 지원하도록 시스템을 확장하는 것에는 어려운 문제들이 있다.

경쟁 조건

Redis에 Counter 3을 가져와 작업을 완료해 다시 +1 하기 전에, 다른 요청이 Counter 3을 가져가서 두 요청이 Counter 4를 업데이트 하는 식의 현상 (원래 값은 5가 되어야한다.)

경쟁 조건은 일반적으로 Lock으로 해결하지만 시스템 성능을 떨어뜨릴 수 있다.
Lock 대신 Lua Script와 Redis의 Sorted Set을 사용하기도 한다.

동기화

처리율 제한 장치를 여러 대 두게 되면 클라이언트는 다음 요청을 각기 다른 제한 장치로 보낼 수 있어 동기화가 필요하다.

이에 대한 한가지 해결책은 고정 세션(sticky session)을 활용하여 클라이언트로부터의 요청은 항상 같은 처리율 제한 장치로 보내는 것이다. 하지만 이는 규모면에서 확장 가능하지 않고 유연하지도 않기 때문에 좋은 방법은 아니다.

이 문제를 해결하기 위한 가장 좋은 방법은 중앙 집중형 데이터 저장소를 사용하는 것이다.

성능 최적화

요청과 가까운 데이터 센터를 활용해 응답 지연 시간을 줄여 성능 최적화를 생각해볼 수 있다.
이때, 데이터 동기화를 수행한다면 최종 일관성 모델을 사용하면 된다.

모니터링

  • 채택된 처리율 제한 알고리즘이 효과적이다.
  • 정의한 처리율 제한 규칙이 효과적이다.

위 두가지를 모니터링을 통해 효과적으로 동작하고 있는지 확인할 필요가 있다.


4단계 : 마무리

경성(hard) 또는 연성(soft) 처리율 제한

  • 경성 처리율 제한 : 요청의 개수는 임계치를 절대 넘어설 수 없다.
  • 연성 처리율 제한 : 요청의 개수는 잠시 동안은 임계치를 넘어설 수 있다.

다양한 계층에서의 처리율 제한

애플리케이션 계층(7번 계층)에서의 처리율 제한 외에도 다른 계층에서의 제어도 가능하다.
ex) Iptables를 사용하여 IP 주소에 처리율 제한을 적용

처리율 제한을 회피하는 방법

  • 클라이언트 측 캐시를 사용하여 API 호출 횟수를 줄인다.
  • 예외나 에러를 처리하는 코드를 도입하여, 클라이언트가 예외적 상황으로부터 우아하게 복구될 수 있도록 한다.
  • 재시도 로직을 구현할 때는 충분한 백오프 시간을 두도록 한다.

출처
가상 면접 사례로 배우는 대규모 시스템 설계 기초(알렉스 쉬 지음 | 이병준 옮김)

profile
뿌셔뿌셔

0개의 댓글