4. 처리율 제한 장치 설계
문제 이해 및 설계 범위 확정
처리율 제한 장치
클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하는 장치
API 요청 횟수가 제한 장치에 정의된 임계치를 넘어서면 추가로 도달한 모든 호출은 중단
처리율 제한 장치의 장점
DoS(Denial of Service) 공격에 의한 자원 고갈 방지
비용 절감
처리율을 제한하였기 때문에 서버에서 사용해야 하는 자원에 대해서 우선순위를 조정할 수 있다.
서버 과부화 방지
봇에서 오는 트래픽이나 사용자의 잘못된 사용 패턴으로 유발된 트래픽 필터링
요구사항
설정된 처리율을 초과하는 요청은 정확하게 제한한다.
처리율 제한 장치는 HTTP 응답시간에 악영향을 주어선 안되고, 낮은 응답 시간을 유지할 수 있도록 해야 한다.
가능한 적은 메모리를 사용해야 한다.
하나의 처리율 제한 장치를 여러 서버나 프로세스에서 공유할 수 있어야 한다. (분산 처리율 제한, Distributed Rate Limiting)
요청이 제한되었을 때는 사용자에게 분명하게 제한되었음을 전달하여야 한다.
제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어선 안된다. (높은 결함 감내성, Fault Tolerance)
개략적 설계안 제시 및 동의 구하기
클라이언트-서버 구조를 사용하여 책에서 설명하고 있다.
처리율 제한 장치는 어디에 둘 것인가?
클라이언트
쉽게 위변조가 가능하여서, 일반적으로는 처리율 제한을 안정적으로 처리할 수 있는 위치가 아니다.
모든 클라이언트의 구현을 통제하는 것도 어려울 수 있다.
미들웨어
API 서버에서 처리율을 제한하는 것 보다 API가 거쳐가는 미들웨어에서 요청을 제한하도록 하는 것이 효율적이다.
클라우드 내 마이크로서비스에서 처리율 제한 장치는 API Gateway 컴포넌트에서 구현된다.
API Gateway는 처리율 제한, SSL 중단, 사용자 인증, IP 허용 목록 관리 등을 지원하는 완전 관리형 서비스다.
고려사항
회사의 현재 기술 스택이나 엔지니어링 인력, 우선순위, 목표에 따라 처리율 제한 장치의 위치가 달라질 수 있다.
프로그래밍 언어, 캐시 서비스 등 현재 사용하고 있는 기술 스택을 점검하라.
현재 사용하고 있는 프로그래밍 언어가 서버 측 구현을 지원하기 충분할 정도로 효율이 높은지 확인
사업 필요에 맞는 처리율 제한 알고리즘을 찾아라
서버 측에서 모든 것을 구현하기로 했으면, 알고리즘은 자유롭게 사용 가능하다.
Third-Party API Gateway을 사용하기로 했다면, 알고리즘의 선택지는 종속된다.
마이크로서비스 기반이고, API Gateway를 이미 설계에 포함했다면 처리율 제한 기능은 API Gateway에 포함시켜야 할 수도 있다.
처리율 제한 서비스를 직접 만드는 데는 시간이 걸린다.
이를 구현할 인력이 없다면 사용 API Gateway를 쓰는 것이 바람직하다.
처리율 제한 알고리즘
토큰 버킷 알고리즘
동작 원리
토큰 버킷은 지정된 용량을 갖는 컨테이너, 사전 설정된 양의 토큰이 주기적으로 채워진다.
토큰이 꽉 찬 버킷에는 더 이상의 토큰은 추가되지 않는다.
버킷이 가득 차면 추가로 공급된 토큰은 버려진다.
각 요청은 처리될 때마다 하나의 토큰을 사용한다.
요청이 도착하면 버킷 내 충분한 토큰이 있는지 검사하게 된다.
충분한 토큰이 있는 경우, 버킷에서 토큰 하나를 꺼낸 후 요청을 시스템에 전달한다.
충분한 토큰이 없는 경우, 해당 요청은 버려진다.
파라미터
버킷 크기
버킷을 담을 수 있는 토큰의 최대 개수
토큰 공급율
초당 몇 개의 토큰이 버킷에 공급되는 지 설정
버킷 사용 개수
공급 제한 규칙에 따라 달라짐
통상적으로 API Endpoint 마다 별도의 버킷을 둔다.
IP 주소 별로 처리율 제한을 적용해야 한다면 IP 주소마다 버킷을 하나씩 할당해야 한다.
시스템의 처리율을 공통으로 제한해야 한다면, 모든 요청이 하나의 버킷을 공유하도록 해야 한다.
장점
구현이 쉽다.
메모리 사용 측면에서 효율적이다.
짧은 시간에 집중되는 트래픽(Burst of Traffic)도 처리 가능하다.
버킷에 남은 토큰이 있기만 하면 요청은 시스템에 전달될 것이다.
단점
버킷 크기와 토큰 공급율을 적절하게 튜닝해야 한다. 즉 휴리스틱하게 테스트하면서 값을 조정해야 한다.
누출 버킷 알고리즘
토큰 버킷 알고리즘과 비슷하지만, 요청 처리율이 고정되어 있는 점이 다르다.
FIFO(First-In-First-Out) 큐로 구성된다.
동작 원리
요청이 도착하면 큐가 가득 차 있는 지 본다.
빈 자리에 있는 경우에는 큐에 요청을 추가한다.
큐가 가득 차 있는 경우에는 새 요청은 버린다.
지정된 시간마다 큐에서 요청을 꺼내어 처리한다.
파라미터
버킷 크기
큐 사이즈와 같은 값
큐에는 처리될 항목이 보관된다.
처리율(Outflow Rate)
지정된 시간 당 몇 개의 항목을 처리할 지 지정하는 값
보통 초 단위로 표현
장점
큐의 크기가 제한되어 있어 메모리 사용량 측면에서 효율적
고정된 처리율을 갖고 있기 때문에, 안정된 출력이 필요한 경우에 적합
단점
단시간에 많은 트래픽이 몰리는 경우, 큐에는 오래된 요청들이 쌓이게 되고, 그 요청들을 제때 처리하지 못한다면 최신 요청들은 버려지게 된다.
누출 버킷 알고리즘도 휴리스틱하게 파라미터를 조정하여 최적의 값을 설정해야 한다.
고정 윈도 카운터 알고리즘
동작 원리
타임라인을 고정된 간격의 윈도로 나누고, 각 윈도마다 카운터를 붇인다.
요청이 접수될 때마다 이 카운터의 값은 1씩 증가한다.
이 카운터의 값이 사전에 설정된 임계치에 도달하면 새로운 요청은 새 윈도가 열릴 때까지 버려진다.
장점
메모리 효율이 좋다.
이해하기 쉽다.
윈도가 닫치는 시점에 카운터를 초기화하는 방식은 특정한 트래픽 패턴을 처리하기에 적합
단점
윈도 경계 부근에서 일시적으로 많은 트래픽이 몰려드는 경우, 기대했던 시스템의 처리 한도보다 많은 양의 요청을 처리하게 된다.
이동 윈도 로깅 알고리즘
동작 원리
요청의 타임스탬프 추적
타임스탬프 데이터는 보통 Redis의 정렬 집합(Sorted Set) 같은 캐시에 보관
새 요청이 오면 만료된 타임스탬프는 제거한다.
만료된 타임스탬프는 그 값이 현재 윈도의 시작 시점보다 오래된 타임스탬프
새 요청의 타임스탬프를 로그에 추가
로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달, 그렇지 않으면 처리 거부
장점
어느 순간의 윈도를 보더라도 허용되는 요청의 개수는 시스템의 처리율 한도를 넘지 않는다.
단점
다량의 메모리를 사용하여 거부된 요청의 타임스탬프를 보관
이동 윈도 카운터 알고리즘
고정 윈도 카운터 알고리즘과 이동 윈도 로깅 알고리즘을 결합한 알고리즘
장점
이전 시간대의 평균 처리율에 따라 현재 윈도의 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 잘 대응함
메모리 효율이 좋다.
단점
직전 시간대의 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하기 때문에 다소 느슨함.
Cloudflare의 실험에서 40억개의 요청 중에 시스템의 실제 상태와 맞지 않게 허용되거나 버려진 요청은 0.003% (1200만) 정도
해당 오차를 허용할 수 있다면, 대용량 분산처리 시 해당 방법을 사용하는 것이 유리
개략적인 아키텍처
얼마나 많은 요청이 접수되었는지를 추적할 수 있는 카운터를 추적 대상별로 두고, 이 카운터 값이 어떤 한도를 넘어서면 한도를 넘어 도착한 요청은 거부한다.
카운터는 데이터베이스에 보관하는 것은 디스크에 접근하는 로직때문에 느려서 사용이 불가능하다.
메모리 상에서 동작하는 캐시가 빠르고 TTL을 지원하기 때문에 적절하다.
Redis에서는 INCR과 EXPIRE 명령어를 지원한다.
INCR
메모리에 저장된 카운터의 값을 1만큼 증가시킨다.
EXPIRE
카운터에 타임아웃 값을 설정한다.
설정된 시간이 지나면 카운터는 자동으로 삭제된다.
동작원리
클라이언트가 처리율 제한 미들웨어에게 요청을 보낸다.
처리율 제한 미들웨어는 레디스의 지정 버킷에서 카운터를 가져와서 한도에 도달했는지를 검사한다.
한도에 도달했다면 요청은 거부한다.
한도에 도달하지 않았다면, 요청은 API 서버에 전달한다. 미들웨어는 카운터의 값을 증가시킨 후 다시 Redis에 저장한다.
상세 설계
처리율 제한 규칙
설정 파일 형태로 디스크에 저장하여 규칙을 관리
처리율 한도 초과 트래픽의 처리
어떤 요청이 한도 제한에 걸리면 API는 HTTP 429 응답(Too many requests)를 클라이언트에 보낸다.
경우에 따라서는 한도 제한에 걸린 메시지를 나중에 처리하기 위해 큐에 보관할 수 있다.
처리율 제한 장치가 사용하는 HTTP 헤더
서버는 클라이언트의 요청이 처리율 제한에 걸리고 있는 지를 감지하거나, 얼마나 많은 요청을 보낼 수 있는 지를 HTTP 응답 헤더에 포함하여 보낸다.
X-Ratelimit-Remaining
윈도 내에 남은 처리 가능 요청의 수
X-Ratelimit-Limit
매 윈도마다 클라이언트가 전송할 수 있는 요청의 수
X-Ratelimit-Retry-After
한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는 지 알림
사용자가 너무 많은 요청을 보내면 429 에러를 X-Ratelimit-Retry-After 헤더와 함께 반환하도록 한다.
상세 설계
처리율 제한 규칙은 디스크에 보관한다.
작업 프로세스는 수시로 규칙을 디스크에서 읽어 캐시에 저장한다.
클라이언트가 요청을 서버에 보내면 요청은 먼저 처리율 제한 미들웨어에 도달한다.
처리율 제한 미들웨어는 제한 규칙을 캐시에서 가져온다.
카운터 및 마지막 요청의 타임스탬프를 캐시에서 가져온다.
가져온 값들을 통해서 미들웨어는 다음과 같은 결정을 내린다.
해당 요청이 처리율 제한에 걸리지 않은 경우는 API 서버로 보낸다.
해당 요청이 처리율 제한에 걸렸다면 429 에러를 클라이언트에 보낸다. 요청을 버리거나 이후 실행되도록 큐에 저장한다.
분산 환경에서 처리율 제한 장치의 구현
경쟁 조건(Race Condition)과 동기화(Synchronization) 이슈를 해결해야 한다.
경쟁 조건(Race Condition)
캐시에서 데이터를 읽을 떄 경쟁 조건 이슈가 발생한다면, 읽은 값을 쓰기도 전에 이전 값을 읽어서 값이 누락될 수 있다.
경쟁 조건 문제를 해결하기 위해서 락(Lock)을 사용할 수 있다. 하지만 락은 시스템의 성능을 떨어뜨리는 문제가 있다.
락 대신 사용할 수 있는 방법
루아 스크립트(Lua Script)
정렬 집합(Sorted Set) 자료 구조
동기화 이슈
여러 대의 처리율 제한 장치 서버를 두게 된다면, 분산 처리로 인해서 더 많은 사용자를 감당할 수 있지만 동기화 문제가 발생한다.
고정 세션을 활용하여 클라이언트의 요청은 항상 같은 처리율 제한 장치로 보낼 수 있도록 하는 방법
추천하지 않고, 규모 면에서 확장성이 없고 유연하지 않다.
캐시를 사용하여 중앙 집중형 데이터 저장소를 사용하여 동기화 이슈를 해결한다.
성능 최적화
데이터 센터에서 멀리 떨어진 사용자를 처리하면 지연 시간이 증가하는 문제가 있다. 이 때문에 API Gateway 서비스들은 데이터 센터 별로 지정이 가능하다.
처리율 제한 장치 간에 데이터를 동기화 할 때 최종 일관성 모델(Eventual consistency model)을 사용한다.
모니터링
처리율 제한 장치를 설치한 이후에는 효과적으로 동작하는 지를 파악하기 위해서 모니터링을 통해서 지표를 확인한다.
확인해야 하는 지표
채택한 처리율 제한 알고리즘의 효율성
정의한 처리율 제한 규칙의 효율성
지표를 확인하면서 알고리즘, 규칙 등을 휴리스틱하게 변경하며 최적화를 진행한다.
마무리
경성 또는 연성 처리율 제한
경성 처리율 제한
요청의 개수는 임계치를 절대 넘어설 수 없다.
연성 처리율 제한
요청 개수는 잠시 동안은 임계치를 넘어설 수 없다.
다양한 계층에서의 처리율 제한
OSI 7 Layer에서 어플리케이션 계층에서의 처리율 제한만 이번 장에서 다루었다.
다른 계층에서도 각각의 방법으로 처리율 제한을 구현할 수 있다.
처리율 제한을 회피하는 방법, 클라이언트를 어떻게 설계하는 것이 최선인지
클라이언트 측 캐시를 사용하여 API 호출 횟수를 줄인다.
처리율 제한의 임계치를 이해하고, 짧은 시간 동안 너무 많은 메시지를 보내지 않도록 한다.
예외나 에러를 처리하는 코드를 도입하여 클라이언트가 예외적 상황으로부터 우아하게 복구될 수 있도록 한다.
Last updated