4장: 처리율 제한 장치의 설계

Y·2022년 11월 3일
0

*<가상 면접 사례로 배우는 대규모 시스템 설계 기초> 을 참고하여 작성한 게시물입니다.

처리율 제한 장치의 설계

처리율 제한 장치(rate limiter)는 클라이언트 또는 서비스가 보내는 트래픽의 처리율(rate)를 제어하기 위한 장치다. HTTP를 예로들면 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한하고, API 요청 횟수가 제한 장치에 정의된 임계치를 넘어서면 추가로 도달한 호출은 처리가 중단된다. 예를들면 1)사용자는 초당 2회 이상 새 글을 올릴 수 없다. 2)같은 IP 주소로는 하루에 10개 이상 계정을 요청할 수 없다. 3)같은 디바이스는 주당 5회 이상 리워드를 요청할 수 없다 등이다. 이를 통해 DoS 공격 방지, 비용 절감, 서버 과부하 방지가 가능하다.

위치

클라이언트 요청은 쉽게 위변조가 가능하기 대문에, 서버 측에 두거나 처리율 제한 미들웨어를 두는 쪽이 적절할 것이다. 미들웨어엔 API 게이트웨이가 있는데, 이를 통해 처리율 제한, SSL 종단, 사용자 인증, IP 허용 리스트 관리등이 가능하다. (마이크로 서비스인 경우에는 보통 미들웨어를 이용하게 될 것이다.)

처리율 제한 알고리즘

  • 토큰 버킷 : 지정된 용량을 갖는 컨테이너로, 사전 설정된 양의 토큰이 주기적으로 채워지며 꽉 차면 더 이상 추가되지 않는다. (꽉 찼을때 추가로 공급된 토큰은 버려진다.) 요청이 처리될 때마다 토큰을 하나씩 사용하고, 요청이 도착하면 버킷에 충분한 토큰이 있는지 검사하게 된다. 충분하면 토큰 하나를 꺼내 전달, 충분하지 않다면 해당 요청은 버려진다. 엔드포인트마다 별도의 버킷을 두거나, IP 주소별로 제한하려면 IP 주소마다 버킷을 할당하고, 시스템 전체 처리율을 제한하려면 모든 요청이 하나의 버킷을 공유하도록 해야할 것이다.
  • 누출 버킷 : 요청 처리율이 고정되어 있는 것으로, 요청이 도착하면 큐가 가득찼는지 보고 빈자리가 있으면 큐에 요청을 추가, 큐가 가득 차있다면 새 요청은 버리고, 지정 시간마다 큐에서 요청을 꺼내 처리한다.
  • 고정 윈도 카운터 : 타임라인을 고정 간격의 윈도로 나누고, 각 윈도마다 카운터를 붙인다. 요청이 접수될때마다 카운터의 값은 1씩 증가하고, 카운터 값이 사전에 설정된 임계치에 도달하면 새 요청은 새 윈도가 열릴 때까지 버려진다. 그러나 경계에 순간적으로 많은 트래픽이 집중되면 할당된 양보다 더 많은 요청이 처리될 수 있다.(ex.분마다 초기화되는데 2:00:30~2:01:30에 요청 집중)
  • 이동 윈도 로그 : 요청의 타임스탬프를 추적하여, 새 요청이 오면 만료된 타임스탬프(현재 윈도의 시작지점보다 오래된 타임스탬프)를 제거, 새 요청의 타임스탬프를 로그에 추가한다. 로그의 크기가 허용치보다 같거나 작으면 요청을 전달하고, 그렇지 않으면 처리 거부한다.
  • 이동 윈도 카운터 : 고정 윈도 카운터와 이동 윈도 로깅을 결합한 것이다. 한도가 분당 7개라고 할 때, 현재 1분간의 요청 수+직전 1분간의 요청수x이동 윈도와 직전 1분이 겹치는 비율을 현재 윈도에 온 요청의 개수로 계산한다. (사실 이 알고리즘은 정확히 이해를 못했다.)

설계

처리율 제한을 구현하기 위해서 요청 횟수 카운터는 레디스 등의 캐시 메모리에 저장해두고 관리할 수 있을 것이다. 만약 한도 제한에 걸린 요청은 보관했다가 나중에 처리한다면 다음과 같이 설계할 수 있다.

  • 처리율 제한 규칙은 디스크에 보관하고, 작업 프로세스는 수시로 규칙을 디스크에서 읽어 캐시에 저장한다.
  • 클라이언트가 요청을 서버에 보내면 요청은 먼저 처리율 제한 미들웨어에 도달한다.
  • 처리율 제한 미들웨어는 제한 규칙을 캐시에서 가져온다. 아울러 카운터 및 마지막 요청의 타임스탬프를 레디스 캐시에서 가져온다. 가져온 값들에 근거하여 해당 미들웨어는 다음과 같은 결정을 내린다.
    • 해당 요청이 처리율 제한에 걸리지 않은 경우는 API 서버로 보낸다.
    • 해당 요청이 처리율 제한에 걸렸다면 429 too many requests 에러를 클라이언트에 보낸다. 한편 해당 요청은 그대로 버릴 수도 있고 메시지 큐에 보관할 수도 있다.

단일 서버의 경우 설계가 어렵지 않으나, 여러 대의 서버와 병렬 스레드를 지원하도록 하려면 경쟁 조건과 동기화 문제를 해결해야 한다. 경쟁 조건을 해결하는 가장 널리 알려진 해결책은 lock인데, 이는 성능을 떨어트린다. 락 대신 루아 스크립트, 정렬 집합이라 불리는 레디스 자료 구조를 쓸 수 있다. 동기화 문제를 해결하기 위해서는 고정 세션을 이용(추천하지 않음)하거나, 중앙 집중형 데이터 저장소를 쓰는 방식이 있다. 성능 최적화를 위해 에지 서버를 심어둘 수 있고, 또한 제한 장치간에 최종 일관성 모델을 사용하여야한다. 그리고 모니터링을 통해 효과적으로 동작하고 있는지 확인해야한다.

추가 키워드

  • 경성(hard) 또는 연성(soft) 처리율 제한 : 경성 - 요청의 개수는 임계치를 절대 넘을 수 없다. 연성 - 잠시 동안은 임계치를 넘어설 수 있다.
  • 애플리케이션 계층(OSI 기준 7계층)뿐만 아니라 다른 계층에서도 처리율 제한이 가능함. ex.Iptables를 사용하면 3계층에 IP주소 처리율 제한을 둘 수있음.
  • 클라이언트 설계. 캐시를 사용하여 API 호출 횟수를 줄이고, 처리율 제한 임계치를 이해하여 짧은 시간 동안 너무 많은 메시지를 보내지 않도록 하고, 예외나 에러를 처리하는 코드를 도입하여 예외적 상황으로부터 우아하게 복구될 수 있도록 한다. 재시도 로직을 구현할때는 충분한 백오프 시간을 둔다.
profile
개발자, 학생

0개의 댓글