AWS

[AWS] botocore.exceptions.ClientError: An error occurred (RequestLimitExceeded) when calling the RunInstances operation (reached max retries: 4): Request limit exceeded. 발생 시

비번변경 2024. 6. 26. 12:30

현상

AWS 환경에서 매 시간마다 배치성 작업을 수행시키고 있다. 배치성 작업은 돌아갈 때마다 새 EC2를 생성하여 동작하고 EC2를 종료시키기를 반복하는데, 최근 EC2를 생성하는 과정에서 거의 매일 아래와 같은 에러가 발생하면서 실패하고 있었다.

botocore.exceptions.ClientError: An error occurred (RequestLimitExceeded) when calling the RunInstances operation (reached max retries: 4): Request limit exceeded.

매번 수동으로 Ec2를 다시 생성해왔는데, 현상을 좀 해소해보려고 한다.

 

 

API throttling

AWS EC2는 원활한 서비스 제공을 위해 리전 별로 AWS 계정에 대한 EC2 API 요청을 조절(throttle)한다. 타사 애플리케이션,  CLI 도구,  AWS 콘솔 등을 통한 API 호출은 최대 API 요청 제한을 초과하지 않도록 요청 속도(Request rate)와 리소스 속도(Resource rate)를 조절하는데, 만약 API 제한 한도를 초과하면 RequestLimitExceeded가 발생한다.

 

즉, 특정 시점에 RunInstances 요청을 너무 많이 발생했던 게 원인이었던 것 같다.

 

 

토큰 버킷 알고리즘

API 요청 조절 및 제한은 토큰 버킷 알고리즘으로 구현되어 있다.

토큰 버킷 알고리즘은 데이터 전송이 사전에 정의되어 있는 한계를 준수하는지 확인하거나 대역폭 및 버스트에 대해 설정되어 있는 제한에 적합한 전송 타이밍을 결정하는 데 사용하는 알고리즘이다.

동작 방식을 간단히 요약하면 일정한 크기의 버킷에 토큰을 미리 넣어두고, 패킷이 전송될 때마다 버킷 내 토큰을 소비한다. 만약 버킷 내 토큰이 부족하면 패킷은 버려지고, 소비된 토큰은 정해진 규칙에 의해 다시 채워지는 방식이다.

이때 버킷의 크기는 한 번에 전송할 수 있는 최대 데이터의 양을 뜻하고, 토큰 생성 속도는 네트워크의 전송 속도를 의미한다.

 

 

AWS EC2 API 조절 유형

EC2의 경우에는 API의 요청 속도와 자원 비율을 조절한다.

 

요청 속도

요청 속도 제한을 사용하면 요청할 때마다 버킷에서 토큰 하나가 제거된다. 버킷은 정해진 속도로 자동으로 채워지기 때문에 버킷 내 토큰이 최대 용량보다 낮으면, 최대 용량에 도달할 때까지 매초 설정된 개수의 토큰이 다시 채워진다. 그리고 토큰이 추가될 때 버킷 내 토큰의 수가 최대라면 추가될 토큰은 버려지게 된다. 

예로 들어, Describe* 요청의 버킷 최대 용량은 100이고 버킷 리필 비율은 20이다. 사용자는 1초에 최대 100개의 Describe* 요청을 할 수 있고, 1초에 100개보다 많은 요청을 하게 되면 일부 요청을 실패한다. 비워진 버킷은 1초마다 20개의 토큰이 채워지므로 5초 뒤에는 다시 최대 용량에 도달한다. 버킷에 추가된 토큰은 즉시 소비할 수 있기 때문에 한 번 비워진 버킷은 초당 10개의 API 요청을 처리할 수 있다.

 

리소스 속도

리소스 속도 제한을 사용하면 요청에 의해 영향을 받는 리소스 수에 따라 고갈되는 리소스 토큰 버킷을 사용한다.

예로 들어. RunInstances의 버킷 최대 용량은 1000이고, 리필 비율은 2이다. 따라서 1000개의 인스턴스를 대상으로 하는 1번의 요청, 또는 250개의 인스턴스를 대상으로 하는 4번의 요청을 호출하여 1000개의 인스턴스를 즉시 시작할 수 있다. 한 번 비워진 버킷은 1초에 최대 2개의 인스턴스를 시작할 수 있다.

 

 

원인

확인해 보니 RunInstances 요청은 최대 버킷 용량은 5이며, 버킷 리필 비율은 2이다. 그리고 리소스 속도 제한도 사용하는데 위에서 봤다시피, 버킷 최대 용량은 1000이고 리필 비율은 2이다.

아무래도 특정 시점에 리소스 버킷 용량보다 많은 인스턴스를 요청하고, 그로 인해 API 요청이 실패하면서 자동 재시도하는 과정에서 요청 속도 제한을 초과하는 것이 원인인 것 같다.

 

 

해결

확인해 보니 크게 재시도 횟수를 늘리거나, 재시도 시간 간격을 조정하는 방법을 사용할 수 있는 것 같다.

 

Boto3 config

이 글에서는 python boto3을 사용하여 RunInstances 요청을 수행하고 있다. 따라서 boto3 내 설정 방법을 찾아보았는데, 아래와 같이 재시도 횟수를 조정하는 방법을 찾을 수 있었다. (https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html)

import boto3
from botocore.config import Config

config = Config(
   retries = {
      'max_attempts': 10,
      'mode': 'standard'
   }
)

ec2 = boto3.client('ec2', config=config)

다만 재시도하는 시간 간격을 조정하는 방법은 없는 것 같다.

 

이 글에서는 EC2 생성하는 작업 자체의 재시도 시간 간격을 늘리는 방식을 적용했다. 기존에는 작업 실패 시 2번의 재시도를 5초 간격으로 수행하는지, 현재는 2번의 재시도를 15초 간격으로 수행하도록 변경한 상태에서 서비스 모니터링 중이다.

당장은 발생하지 않는데, 기간을 길게 보고 모니터링해야 할 것 같다.

 

 

참고 문서

https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-throttling.html

https://en.wikipedia.org/wiki/Token_bucket

[AWS] API throttling에 의한 AWS 정보 누락 현상