본문 바로가기
개발/대규모 시스템 설계

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

by Mingvel 2023. 3. 5.
  • 처리율 제한 장치는 클라이언트로부터 유입 되는 요청 수를 조절하는 장치이다 (임의의 요청 임계치에 도달하면 요청을 drop하는 것과 같은 후속 조치를 취할 수 있도록 한다)
  • 예시
    • 사용자는 초당 2번 이상의 로그인 요청을 할 수 없다
    • 사용자는 1분에 30개 이상의 댓글을 작성할 수 없다
  • 장점
    • DOS 공격에 의한 자원 고갈을 방지할 수 있다
    • 써드파티 api 요청으로 읺나 과금과 관련 된 요청의 수를 제한하면, 비용 절감의 효과를 얻을 수 있다
    • 서버의 과부하를 막을 수 있다

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

  • 클라이언트 측에서 제한 해야 할까? 혹은 서버 측에서 제어 해야 할까?

  • 제한의 기준은 무엇인가? IP? ID?

  • 시스템의 규모는 어느정도여야 하나?? (요청 수는 얼마나 수용이 가능해야 하는가?)

  • 처리율 제한 장치는 분산 환경에서도 동작해야 하는가?

  • 독립된 서비스로 존재해야 하는가? 혹은 어플리케이션의 코드에 포함되어도 되는가?

  • 요구사항 요약

    • 처리 응답 시간을 최소화해야 한다
    • 처리 임계치에 도달했을 때 들어온 요청은 드랍하면 안된다. 후속 조치가 있어야 한다 --> 서버측에서 드랍한다면, 클라이언트에게 적절한 후속 조치를 유도해야한다 (응답 헤더, 응답 코드 등등)
    • 처리율 제어를 클라이언트 사이드에서 하는 것은 서버측에서 하는 것보다 변수가 많으므로 서버측에서 제어하도록 한다
      • 클라이언트는 위변조가 쉽다. 따라서 모든 클라이언트의 구현을 통제하는 것은 어려울 수 있다
    • 처리율 제어를 하는 궁극적인 목적은 서버측에 걸리는 부하를 예상 가능한 범위로 제한하여 최대의 가용성을 유지하는 것이라고 생각한다
      • 따라서 서버에서 감당 가능한 부하 범위와, 실제 DAU와 같은 지표를 분석하는 것이 중요하다
      • 처리율 제어 장치가 Peak 에도 감당이 가능하도록 설계되어야 할까?
    • 분산 환경에서도 처리율 제어가 가능해야 한다
    • 결함이 발생하더라도 전체 시스템에 영향을 주어서는 안된다
    • 요청이 제한되었을 때는 사용자에게 메세지를 분명하게 전달해야 한다 (클라이언트의 요청이므로, 정상 응답을 받을 수 없는 이유를 요청자가 명확하게 알 수 있도록 해야한다)
    • 시스템 자원 사용을 최소화 해야 한다 (메모리, CPU 등)

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

  • 처리율 제어 장치를 서버 사이드에 둘 경우

    • API gateway를 사용할 경우 gateway에서 설정으로 제어가 가능하다
      • 설정으로 제어한 임계치를 넘었을 경우 429 응답 (too many request)을 클라이언트에게 돌려준다
  • 처리율 제어 장치 고려 사항

    1. 프로그래밍 언어, 캐시 서비스 등 현재 사용중인 기술 스택을 점검하라.
    2. 나에게 맞는 처리율 제어 알고리즘을 선택하라
    3. 내 설계가 gateway를 이용하고 있는 마이크로서비스 환경이라면 해당 기능은 gateway에 포함시켜야 할 수도 있다
    4. 처리율 제어 장치를 구현할 인력이 부족하다면 상용 api gateway를 사용하는 것도 나쁘지 않은 대안이 될 수 있다
  • 처리율 제한 알고리즘

    1. 토큰 버킷
      • 토큰을 보관하는 버킷이 있다
      • 주기적으로 토큰을 버킷에 리필한다
      • 토큰 버킷에 토큰이 꽉 차잇으면, 리필한 토큰은 그대로 버려진다 (overflow)
      • 요청이 들어오면 토큰 버킷을 확인하고, 유휴 토큰이 있으면 해당 토큰을 들고 요청을 수행한다
      • 요청이 들어오면 토큰 버킷을 확인하고, 유휴 토큰이 없으면 해당 패킷은 drop 된다
      • 장점
        • 구현이 쉽다
        • 메모리 측면에서 효율적이다
        • 토큰 버킷에 유휴 토큰이 존재하면 서버에 요청이 전달되기에, 짧은 시간에 몰리는 트래픽 대응이 가능하다
      • 단점
        • 버킷 크기와 토큰 공급률 값을 튜닝하는 것이 까다로운 일이 될 수 있다
    2. 누출 버킷
      • 버킷을 Queue로 표현한다
      • 요청이 들어오면 버킷(Queue)가 꽉 찼는지 확인한다
        • 버킷이 꽉 찼으면 요청을 drop한다
        • 버킷(Queue)이 가득 차지 않았으면 요청을 Queue에 넣고 FIFO 처리한다
      • 처리율 이라는 인자를 사용한다
        • 지정된 시간에 얼마나 요청을 처리할 것인지 정의한다
      • 장점
        • Queue의 크기가 지정되어 있어 메모리 사용량 측면에서 효율적이다
        • 고정된 처리율을 갖고 있기에 안정적인 처리가 필요할 경우 적합하다
      • 단점
        • 요청이 몰릴 때, Queue가 가득차면 최신 요청들이 drop될 수 있다
        • 버킷 크기처리율이라는 인자를 사용하는데, 해당 인자들을 튜닝하기 까다로울 수 있다
    3. 고정 윈도 카운터
      • 타임라인을 고정된 윈도우(간격) 으로 나누고 요청이 올때마다 count를 증가시킨다
      • count가 임계치에 돋라하면 해당 윈도우에 들어온 이후 요청은 모두 drop된다
      • 윈도우 간의 경계에서 트래픽이 몰릴 수 있다
        • 1분 윈도우, count 제한 5인 경우
        • 0분 59초 ~ 1분 1초 사이에 10개의 트래픽이 올 경우
        • 2초 동안 10개의 트래픽을 수용해야 한다
          • 원래 예상대로라면 2분 동안 10개의 요청을 수용해야 한다 (평균적으로 12초에 1개의 요청!)
        • 장점
          • 메모리 효율이 좋다
          • 이해하기 쉽다
          • 윈도우가 닫히는 시점에 count를 초기화 하는 방식은 특정한 트래픽 패턴을 처리하기에 용이하다
        • 단점
          • 윈도우 경계 부분에서 트래픽이 몰릴 경우, 예상한 트래픽 처리량보다 많은 트래픽을 처리해야할 수도 있다
    4. 이동 윈도 로그
      • 고정 윈도 카운터 알고리즘과 베이스는 같다
      • 단 윈도우 범위가 동적이라는 것이 차이점이다
      • 타임스탬프 로그를 기록한다
      • 예시
        • 1분 윈도우, count 제한 2인 경우
        • 0:01, 0:30 에 요청이 들어온다 = 허용 (로그 기록)
        • 0:31에 요청이 들어온다 = 타임스탬프 로그 기록 후 drop (이미 2개의 count가 존재)
        • 1:31에 요청이 들어온다 => 0:01, 0:30의 로그는 이미 만료된 값이므로 로그를 삭제하고 요청을 허용한다. + 로그 기록
      • 장점
        • 윈도우를 바라보는 시점에 관게없이 동일한 처리율을 보여준다
      • 단점
        • 거부 된 요청의 타임스탬프 정보도 기록하기 때문에, 메모리 자원을 많이 소모한다
    5. 이동 윈도 카운터
      • 고정 윈도 카운터 + 이동 윈도 로그 알고리즘의 혼합 형태
      • 1분 윈도우일 때, 현재 count 처리 식
        • 이전 윈도 요청 수 + 현재 윈도 요청수 * 직전 1분이 겹치는 비율
          • 직전 1분이 겹치는 비율은 최초 윈도 로그가 찍힌 시점으로부터 1분으로 계산한다
      • 장점
        • 평균 처리율에 따라 현재 윈도의 상태를 계산하므로, 비교적 짧은 시간에 몰리는 트래픽 대응이 원활하다
        • 메모리 효율이 좋다
      • 단점
        • 다소 느슨할 수 있다

개략적인 아키텍쳐 구상

  • 카운터는 어떻게 구현하는 것이 바람직할까??

    • 메모리 캐시 서비스를 이용해볼까?
      • 메모리에서 동작하기에 성능도 빠르고, 캐시 만료 정책을 포함하고 있기에 선택에 적합해 보인다
        • 상황
          1. api 요청이 들어온다
          2. 어플리케이션은 redis에서 count 값을 조회한 뒤 요청을 수락할 수 있는 상태인지 판단한다
          3. 요청 수락이 가능한 상태면 count 값을 증가시켜 다시 redis에 넣고 요청을 진행한다
          4. 요청 수락이 불가능한 상태이면 요청을 drop 한다
  • 처리율 한도 초과 트래픽의 처리

    • HTTP 429 응답 (too many request)을 이용하자
    • 한도에 걸린 트래픽은 드랍하지 않고 Queue에 보관하여 처리가 가능할 때 처리한다
  • 처리율 한도 초과 응답 헤더

    • X-Ratelimit-Remaining : 처리 가능 counter 수
    • X-Ratelimit-Limit : 전체 counter 수
    • X-Ratelimit-Retry-after : 한도 제한에 걸리지 않기 위해 몇 초 뒤에 요청을 다시 보내야 하는지 알림
  • 이 모든 과정은 처리율 제한 미들웨어 (api gateway) 에서 이뤄짐을 전제한다

분산 환경에서의 처리율 미들웨어 구현

  • 경쟁 조건
    • 락(Lock) 으로 해결 가능하지만 성능 저하 이슈로 추천하지 않는다
      • 대안
        • 루아 스크립트
        • 정렬 집합
  • 동기화
    • 웹 계층은 무상태 계층이므로, 한 클라이언트의 연속적인 요청이 각기 다른 api 서버로 유입될 수 있다
    • 따라서 Redis와 같은 중앙 집중형 저장소를 사용하는 것을 추천한다
  • 성능 최적화
    • 사용자의 트래픽을 가장 가까운 엣지 서버로 전달할 수 있게 만들어, 지연시간을 최소화한다
    • 제한 장치 간에 데이터를 동기화 할 때, 최종 일관성 모델을 적용한다
  • 모니터링
    • 처리율 제한 장치 도입 이후, 모니터링을 통해 현재 설정한 정책과 같은 항목들이 올바르게 측정되었는지 재검토 해본다
      • 처리 규칙이 느슨한가?
      • 혹은 처리 규칙이 너무 빡빡한가?
      • 피크 시간에도 대응이 가능한가?

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

  1. 클라이언트 캐시를 이용하여 api 호출을 최소화한다
  2. 처리율 제한 임계치를 클라이언트와 서버 모두 이해하고, 규칙에 맞게 메세지 발생과 처리를 설계한다
  3. 에러에는 에러 메세지를 반드시 포함하여, 클라이언트가 처리 제한 상황 이후에 대응할 수 있도록 유도한다
  4. 요청 재시도할 경우, 충분한 시간을 갖고 요청을 시도할 수 있도록 한다 (충분한 back-off 시간)
반응형

'개발 > 대규모 시스템 설계' 카테고리의 다른 글

6. Key - Value Storage 설계  (0) 2023.03.19
5. 안정 해시 설계  (2) 2023.03.12
3. 시스템 설계 interview  (0) 2023.02.26
2. 개략적인 규모 추정  (0) 2023.02.19
1. 사용자 수에 따른 규모 확장성  (0) 2023.02.12

댓글