Back to the Basics

[Rate limit -1] Rate limit의 필요성 본문

Backend

[Rate limit -1] Rate limit의 필요성

9Jaeng 2024. 2. 18. 22:44
728x90
반응형

일요일 오후 저녁 피크 타임에 우리 팀이 관리하고 있던 서버가 죽어버리는 이슈가 있었다. 소 잃고 외양간 고치는 격으로 rate limit을 뒤늦게 구축하게 되었다. 당시 발생했던 상황들과 rate limit을 구축하면서 공부했던 부분에 대해서 기록을 하기 위해 이번 포스팅의 주제로 Rate limit으로 산정하였다.

먼저 이번 포스팅에서 1.Rate limit이 필요한 이유를 경험담을 담아 이야기 해보고 다음 포스팀에서 "Rate limit의 여러 종류들(알고리즘)"에 대해 알아본다.  그리고 그다음 포스팀에서 1. rest api에서의 rate limit과 graphel에서의 rate limit의 차이점에 대해 정리해 보고 2. 직접 구현을 해본 것에 대해 정리해 보려고 한다. 

몇 차례에 걸친 꾀나 시간이 걸리는 포스팅 일 것 같다. 

1. Rate limit이란?

일단 간단하게 개념부터 소개를 하자면 Rate limit이란 클라이언트에서 서버로 요청하는 횟수를 특정 기간동안 특정 횟수까지만 제한하는 장치를 말한다.

정해둔 제한 횟수를 초과하면 보통 아래와 같은 애러를 보낸다

HTTP Status Code description
429 TOO_MANY_REQUESTS API rate limit exceeded

https://developers.worksmobile.com/kr/docs/rate-limits

 

NAVER WORKS Developers

 

developers.worksmobile.com

rate limit은 트래픽 제어를 위해 사용되기도 하지만 위와 같이 요금제에 따라 호출수를 다르게 산정하기도 한다.

2. Rate limit이 필요한 이유

rate limit은 아래와 같은 기능을 한다 

DDos 공격으로부터 보호, Server 안정성 보장 및 서버 유지, 비용 제어 등등 

rate limit으로 제한된 기간(시간)안에 요청수가 제한되므로 무지막지한 요청 또는 패킷을 보내 서버를 마비시키는 디도스를 방어할 수 있고  평상시에 받는 부하를 고려하여 기준을 설정하므로 서버의 안정적인 관리가 용이하다. 

또한 대용량 파일을 업로드하는 무거운 요청은 조금 더 타이트하게 제한을 함으로써 서버로 요청을 하는 비용을 조절할 수 있다.

Rate limit이 꼭 필수인가? 를 생각해보면 예전에는 내가 제공하고 있는 서비스의 특성과 트래픽의 양 그리고 보안적인 요구사항에 따라 다르다고 생각했다.  내가 제공하고 있는 서비스가 사용자 인터렉션이 많지 않거나 컴퓨팅 자원을 많이 소모하는 경우가 아닌 경우, 트래픽이 그렇게 높지 않거나 순간적인 부하를 받을 일이 없다면 필수사항은 아니라고 생각을 했었다.

그런데 작년에 장애를 한 번 겪고나서 "기본적인 rate limit 은 필수적으로 있어야 한다"로 생각이 바뀌게 되었다.

위에 인트로에서 말을 했듯이 일요일 오후 저녁타임에 우리 팀이 관리하고 있던 서버가 죽어버리는 이슈가 있었다. 물론 하나의 pod(Kubernetes에서 배포할 수 있는 가장 작은 단위)가 죽었다고 해도 다른 pod로 트래픽이 분산되기 때문에  큰 이슈는 없을 수 있었지만 이번 상황은 좀 달랐다. 

pod 1대가 죽으면서 부하가 몰리는 현상이 발생하였고 죽었던 pod가 다시 살아나기도 전에 다른 pod들이 죽어버리는 도미노 식 재시작이 발생했다 OMG 😱😱😱

당시 CPU Throttling 그래프를 보면 뚝 뚝 끊어진 그래프를 볼 수 있는데 pod가 재시작되다가 다시 죽고를 반복한 모습이 보인다.  아직 주니어인 난 이런 그래프는 처음 봤었는데 당시 느꼈던 등골의 오싹함을 잊을 수 없을 거 같다. 

 

이슈의 원인은 이러했다. 다른 팀이 관리하는 A서버가 있고 우리가 관리하는 B서버가 있다. A서버에서 장애가 발생하였고 재시작이 되었다. B 서버는 A서버에 요청을 하는데 타임아웃 애러를 계속 받았고 B클라이언트에서는 타임아웃 시 특정 시간 후 재요청을 하도록 되어있었다. 이런 결과로 인해 B서버에 순간부하로 인한 재시작 쇼가 시작되었다. 하필 일요일.. 그것도 트래픽 피크시간대에 😇 ...

당시엔 꾀나 크리티컬 한 운영이슈였기 때문에 서버 scale up, scale out을 하여 서버가 부하를 견딜 수 있도록 대처가 되었다.

이렇듯 rate limit은 언제 어떤 장애로 인해 발생할 수 있는 문제를 막을 수 있는 장치이기도 하다. 그렇기 때문에 어떤 서비스이든 rate limit은 필수로 구축해 놔도 나쁘지 않을 것 같다..ㅎ

3. Rate limit의 기준

rate limit을 구축할 때 중요한 것이 바로 "기준"이다.

간단하게 구현할 수 있는 알고리즘인 fixed window rate limit을 예시로 들어보자.

fixed window 알고리즘은 특정 기간(fixed window) 동안 들어올 수 있는 요청 횟수를 제하한다.

기간 : 1초 , 횟수 :20번이라고 설정했다고 했을 때 과연 이 기준이 적절할까 고민을 해봐야 한다. 내 서버의 자원이 클라이언트당 1초에 50번을 견딜 수 없을 수도 있기 때문이다.

이런 기준을 확실하게 알기 위해서는 부하 테스트를 직접 해보거나 이전의 로그를 통해 분석을 해봐야한다. 하지만 이전 기록을 사용한 로그 분석으로도 100% 확신할 수는 없긴 하다.

rate limit을 구축하고 얼마 후  한번 더 부하가 몰리는 일이 있었는데 (소켓 재시작으로 인해 클라이언트에서 요청을 계속 보냈던 것이 문제였다)  rate limit이 앞단에서 막고 있었기 때문에 큰 이슈가 되지 않았지만 일부 api 요청의 경우 조금 더 타이트하게 기준을 수정해야 했었다. 이렇듯 rate limit 기준을 세우는 것 또한 쉽지 않은 일이다.

그래서 추가 작업으로 어떤 상황에서도 대응을 하기 위해 각 api에 적용된 rate limit 기준을 동적으로 수정할 있도록 하는 작업을 진행하였고 이후 발생한 장애에서도 대응을 할 수 있었다. 🙃

4. Rate limit 알고리즘 간략하게 소개

마지막으로 rate limit 알고리즘에는 무엇이 있는지 간략하게만 정리해 보았다. 각 알고리즘에 대한 상세 설명과 구현은 다음 포스팅에서 마저 정리할 예정이다.

알고리즘을 선택할 때에는 그냥 "좋아서", "유명하고 많이 써서"가 아닌 내 상황에 맞는 알고리즘을 선택해야 한다고 생각한다.

rate limit을 적용하는 작업을 진행할 때 알고리즘을 찾아봤던 이유가 있었다. 다른 C팀에서 사용하고 있던 rate limit를 사용하고자 하였으나 다른 알고리즘을 선택하게 되었다. 왜냐하면 graphel을 사용하는 우리 팀에는 맞지 않았던 것도 있었지만 이번에 재시작이 되었던 이유가 "순간 부하"를 막지 못했다는 점이었기 때문이다. C팀에서 사용하는 rate limit는 바로 아래에 소개된 Fixed window rate limit였다. 아래에서 설명을 더 하겠지만 이 알고리즘은 트래픽 편향이라는 문제가 발생할 수 있고 순간부하가 될 수 있었기 때문이다. 

1. Fixed window rate limiting

  • Fixed Window : 정해진 시간
  • window에 요청 건수가 기록된다
  • 해당 window에 요청 건수가 limit보다 크면 해당 요청은 처리 거부된다
  • 장점 : 구현이 쉬움. 메모리 사용 효율적임, 새로운 request의 starvation이 발생하지 않는다 (특정 프로세스의 우선순위가 낮아서 원하는 자원을 계속 할당받지 못하는 상태)
  • 단점 :
    • 기간 경계의 트래픽 편향문제가 발생할 수 있다. → 부하 증가
    • 분산 환경에서 race consition이 발생할 수 있다.
    • 고정된 크기 내에 일정 요청 개수만을 허용

2. Leaky bucket rate limiting

  • leaky bucket rate limiting은 constant 한 속도와 처리가능한 요청을 제어할 수 있다.
  • 양동이의 구멍 : 서버가 요청을 처리할 수 있는 지속적인 속도(constant)
  • 양동이 깊이 : 서버가 일정 기간 동안 처리할 수 있는 요청의 양
  • 고정 용량의 양동이에 물이 들어오면 버킷에 담기고 그 물은 constant rate으로 떨어진다.
  • 요청은 Queue에 담겨 들어온 순으로 처리된다.
  • Queue가 가득 차면 client에 errror를 return 한다.
  • 장점: 구현이 간단하고, 일정 시간 동안 폭주하는 요청에 대해서도 유연하게 대처할 수 있다. 메모리 사용 효율적임(queue의 사이즈가 정해져 있어서)
  • 단점:
    • 짧은 시간에 많은 양의 요청이 들어오면 버킷이 비어서 요청을 거부해야 한다.
    • 새로운 요청들은 처리되지 못하는 starv현상이 있다.

3. Token Bucket

  • Token Bucket은 위의 Leaky Bucket과 비슷하지만 Bucket을 Queue가 아닌 트래픽 제어를 위한 제어용 토큰을 관리하는 용도로 사용된다고 한다.
  • constant 한 속도로 처리하는 Leaky Bucket과 달리 Token Bucket은 버스트 요청도 가능하다고 한다.
  • Token을 일정 속도(rate)에 맞춰 추가해주어야 한다.
  • Bucket에 token이 없으면 error를 return 한다.
  • 죽, token bucket은 token이 배치되는 속도를 기반으로 액세스 속도를 유지하다.(유튜브 찾아보니 Shopify에서 이 방식을 사용하는 듯)
  • 장점 : burst of request도 일정 수준은 받을 수 있다고 한다. Max concurency에도 용이하다고 한다! (일시적으로 더 많은 토큰을 사용할 수 있으므로)
  • 단점 : 분산환경에서는 race condition이 발생할 수도 있다고 한다.

4. Sliding window rate limiting

  • Fixed window의 단점인 burst of request 문제를 해결하기 위해 window의 위치를 바꿔 보완한 알고리즘이라고 한다
  • 장점:
    • 공격자가 짧은 시간에 여러 요청을 보내도, 일부 요청만 거부됩니다.
    • burst of request 보완
    • 일정 수준의 request rate 유지 가능
  • 단점:
    • 구현이 복잡하며, 요청 수와 윈도우 크기 사이의 균형을 맞추는 것이 어려울 수 있습니다
    • 메모리 사용률이 높다

여기까지 rate limit에 대한 필요성과 대략적인 알고리즘에 대해 정리를 해보았다. 

이제는 장애가 발생하기 전에 기본적인 rate limit은 안정장치로 두도록 하자~ 기본적인 rate limit을 지원하는 라이브러리는 많으니까!

728x90
반응형
Comments