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

바인하·2022년 9월 19일
0
post-thumbnail

처리율 제한 장치 : 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치
ex) HTTP는 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한 / API 요청 횟수가 정의된 임계치를 넘어서면 추가 도달한 호출은 모두 중단

사용자는 초당 2회 이상 새 글 작성 불가능
같은 ip 주소로는 하루에 10개 이상 계정 생성 불가
  • API에 처리율 제한 장치를 두면 좋은 점
  1. DoS 공격에 의한 자원 고갈 방지 가능
    • DoS 공격 : 서비스 거부 공격, 공격 대상을 상대로 서비스 불가 상태로 만드는 공격
  2. 비용 절감
    • 추가 요청 처리를 제한하므로 서버를 많이 두지않아도 되며, 우선순위가 높은 API에 더 많은 자원 할당 가능
  3. 서버 과부하 방지
    • 봇 트래픽 또는 잘못된 이용 패턴으로 유발된 트래픽을 걸러내는데 활용 가능

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

  • 클라이언트 VS 서버 측 제한 장치
  • 어떤 기준으로 API 호출을 제어해야 하는지 ex) IP주소, 사용자ID
  • 시스템 규모
  • 분산 환경여부
  • 독립된 서비스 or 어플리케이션 코드에 포함 가능성 여부

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

  • 처리율 제한 장치를 클라이언트에 두는 경우)

    • 클라이언트는 처리율 제한을 안정적으로 할 수 없음.
    • 클라이언트 요청은 쉽게 위변조가 가능하므로 모든 구현을 통제하는 것이 어려움
  • 서버에 두는 경우)

    • API 서버에 두기
    • 처리율 제한 미들웨어를 만들어, API 서버로 가는 요청을 통제하기
      클라우드 MSA 같은 경우, 처리율 제한 장치를 API 게이트웨이라 불리는 컴포넌트에 구현
      API

처리율 제한 장치를 어디에 두어야 하는지는 엔지니어링 인력, 기술스택, 우선순위, 목표에 따라 달라짐
1. 현재 사용하는 기술 스택 점검하기 : 서버 측 구현을 지원하기 충분할 정도로 효율이 높은가?
2. 사업 필요에 맞는 처리율 제한 알고리즘을 찾아라
3. 구현하기에 충분한 인력이 없다면 상용 API 게이트웨이를 사용하라

처리율 제한 알고리즘

1. 토큰 버킷: 지정된 용량을 갖는 컨테이너

  • 사전 설정된 양의 토큰이 주기적으로 채워짐 (토큰 공급기는 매초 N 개의 토큰을 추가)
  • 각 요청은 처리될 때마다 하나의 토큰 사용
  • 충분한 토큰이 있다면, 버킷에서 토큰 하나를 꺼내고 요청을 시스템에 전달
  • 충분한 토큰이 없다면 (버킷이 가득 차면), 해당 요청은 버려짐
  • 사전 설정된 양의 토큰이 주기적으로 채워짐 (토큰 공급기는 매초 N 개의 토큰을 추가)

토큰 버킷 알고리즘에서 중요한 것

  • 버킷 크기: 버킷에 담을 수 있는 토큰 최대 개수
  • 토큰 공급률: 초당 몇 개의 토큰이 버킷에 공급되는가
1. 통상적으로 API 엔드포인트마다 별도의 버킷을 둔다.
 ex) 좋아요는 5번까지, 친구는 150명까지 추가, 사용자마다 하루에 1번 포스팅가능 => 3개의 버킷 필요
2. IP주소별 처리율 제한 적용하려면 -> IP주소마다 버킷 하나씩 할당
3. 시스템 처리율을 초당 10,000 개 요청으로 제한하려면 => 모든 요청이 하나의 버킷 공유

[ 장점 ]

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

[ 단점 ]

  • 버킷 크기, 토큰 공급률 값을 적절하게 튜닝하는 것이 까다로움

2. 누출 버킷

  • 토큰 버킷과 비슷하지만 요청처리율 고정
  • FIFO 큐로 구현
요청이 도착하면 큐에 빈 자리가 있는 경우, 큐에 요청 추가
                   빈 자리가 없는 경우, 새 요청은 버린다
지정된 시간마다 큐에서 요청을 꺼내어 처리

[ 장점 ]

  • 큐의 크기가 제한되어 있어 메모리 사용량 측면에서 효율적
  • 고정 처리율을 갖고 있기에 안정 출력이 필요한 경우 적합

[ 단점 ]

  • 단시간에 많은 트래픽 몰리면, 큐에는 오래된 요청이 쌓이며, 그 요청을 제 때 처리하지 못하면 최신 요청이 버려짐
  • 2개 인자 올바른 튜닝이 까다로움

3. 고정 윈도 카운터

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

문제점) 윈도의 경계 부분에 많은 트래픽이 집중되면, 윈도에 할당된 양보다 더 많은 요청이 처리될 수 있다

[ 장점 ]

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

[ 단점 ]

  • 윈도 경계 부분에 많은 트래픽이 몰리면, 기대한 처리한도보다 많은 양의 요청을 처리하게 됨

4. 이동 윈도 로깅

  • 고정 윈도 카운터 알고리즘의 '경계 부분에 트래픽 집중되는 경우 설정한도보다 많은 요청을 처리하게 되는 문제' 를 해결하는 알고리즘
  • 요청의 타임스탬프를 추적하여 레디스의 정렬 집합 같은 캐시에 보관
  • 새 요청이 오면 만료된 타임스탬프 (현재 윈도 시작 시점보다 오래된 타임스탬프) 를 제거
  • 새 요청의 타임스태프를 로그에 추가
  • 로그의 크기 <= 허용치 인 경우 요청을 시스템에 전달, 아닌 경우 처리 거부

[ 장점 ]

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

[ 단점 ]

  • 거부된 요청의 타임스탬프도 보관하므로 다량의 메모리 사용

5. 이동 윈도 카운터
= 고정 윈도 카운터 + 이동 윈도 로깅

현재 1분간의 요청수 + 직전 1분 간의 요청수 X 이동윈도와 직전 1분이 겹치는 비율
= 3 + 5 * 70%
= 6.5

[ 장점 ]

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

[ 단점 ]

  • 직전 시간대에 도착한 요청이 균등하게 분포되어있다는 가정하에 추정치 계산하므로 다소 느슨

처리율 제한 알고리즘의 기본 아이디어

  • 얼마나 많은 요청이 접수되었는지 추적할 수 있는 카운터를 추적 대상별로 두고
  • 카운터 값이 어떤 한도를 넘어서면, 이후 도착한 요청은 거부

이 카운터를 DB에 보관한다면 디스크 접근으로 인해 속도가 매우 느리므로 사용X
→ 메모리상에서 동작하는 캐시는 빠르고, 시간에 기반한 만료정책 지원하므로 적절함


1. 클라이언트가 처리율 제한 미들웨어에 요청 전송
2. 미들웨어는 Redis의 지정버킷에서 카운터를 가져와서, 한도 도달 여부를 검사

  • 한도에 도달한 경우, 요청 거부
  • 한도에 도달하지 않은 경우, 요청을 API 서버로 전달 / 카운터 값 증가시킨 후 다시 레디스에 저장

3단계: 상세 설계

  • 처리율 제한 규칙은 어떻게 만들어지고, 어디에 저장되는가?
    - 규칙들은 설정 파일 형태로 디스크에 저장됨
  • 처리가 제한된 요청은 어떻게 처리되는가?
    - 요청이 한도에 걸리면 API는 HTTP 429 응답을 클라이언트에 전송
    (한도 제한에 걸린 메시지는 나중에 처리하기 위해 큐에 보관 가능)

  • 자신의 요청이 처리율 제한에 걸리고 있는지,
    처리율 제한에 걸리기까지 얼마나 많은 요청을 더 보낼 수 있는지 확인하는 방법
    HTTP 응답 헤더를 확인하자!

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

사용자가 너무 많은 요청을 보내는 경우 429 응답과 X-Ratelimit-Retry-After 헤더와 함께 반환

  • 처리율 제한 규칙은 디스크에 보관하며, 작업 프로세스는 수시로 규칙을 디스크에서 읽어 캐시에 저장

  1. 클라이언트 -> 서버에게 요청, 요청은 처리율 제한 미들웨어에 도달
  2. 처리율 제한 미들웨어는 제한 규칙을 캐시에서 가져옴
    • 처리율 제한에 걸리지 않은 경우) 요청을 API 서버로 전송
    • 처리율 제한에 걸린 경우) 429 에러를 전송

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

  1. 경쟁 조건 Race Condition
    - 레디스에서 카운터 값을 읽는데, 각 스레드가 병렬로 counter 값을 읽은 경우에 서로 다른 요청의 상태와는 상관없이 counter+1 값을 저장하게 된다.
    → 해결방법 : 락, 루아 스크립트, 정렬 집합이라 불리는 Redis 자료 구조 사용

  2. 동기화 Synchronization

  • 수백만 사용자를 지원하려면 처리율 제한 장치 서버 또한 여러 대가 필요, 따라서 동기화가 필요
  • 동기화를 하지 않는다면, 특정 제한 장치가 특정 클라이언트에 대해 아무것도 모르므로, 처리율 제한을 올바르게 할 수 없음
    해결방법 : 고정 세션을 활용하여 같은 클라이언트로부터의 요청은 항상 같은 처리율 제한 장치로 보내도록 함
    → 규모 확장성X, 유연X
    → 레디스와 같은 중앙 집중형 데이터 저장소 사용

성능 최적화

  1. 여러 데이터센터를 지원하는 것은 처리율 제한 장치에 매우 중요한 문제
  • 데이터센터에서 멀리 떨어진 사용자를 지원하려면 지연시간 증가할 수밖에 없음
  • 따라서, 에지 서버를 설치해두어 사용자의 트래픽을 가장 가까운 서버로 전달해서 지연시간 감소시킴
  1. 제한장치 간 데이터 동기화 시 최종 일관성 모델

모니터링

  • 채택된 처리율 제한 알고리즘이 효과적인가!
  • 정의한 처리율 제한 규칙이 효과적인가! 를 모니터링을 통해 확인한다

4단계: 마무리

<공부하면 좋은 부분>

  1. 경성 또는 연성 처리율 제한

    • 경성 : 요청 개수는 임계치 절대 넘을 수 X
    • 연성 : 요청 개수는 잠시동안 임계치 넘을 수 O
  2. 다양한 계층에서의 처리율 제한

    • 애플리케이션 (HTTP) 계층 이외에도 Iptables 를 이용한 IP주소 (3계층) 에도 적용 자능
  3. 처리율 제한을 회피하는 방법

    • 클라이언트 측 캐시를 사용하여 API 호출 횟수 감소
    • 처리율 제한의 임계치 이해하여 메시지 빈도 조절
    • 예외, 에러처리 코드 도입하여 예외상황에서 안전하게 복구될 수 있게 함
    • 재시도 로직 구현 시 백오프 (알고리즘 또는 프로세스가 다른 조치를 취하지 않은 상황) 시간은 충분하게!
profile
되면 한다

0개의 댓글