All Articles

AWS Lambda 쓰로틀링 오류 대응기

AWS Lambda 스택으로 이루어져 있는 일부 페이지와 API에서 간헐적으로 502 Bad Gateway 오류가 발생하였습니다. 5번 중 1번 꼴로, 아래처럼 오류 HTML이 담겨 있는 응답이 떨어졌습니다.

해당 스택이 Lambda로 이루어져 있어서, 마땅히 살펴볼 만한 곳이 많지 않았습니다. EC2 기반의 서비스면 일단 인스턴스로 들어가서 로그를 살펴보기 시작할텐데, 서버리스에서 문제가 발생하니 난감합니다. 일단 간헐적으로 오류가 발생하니, 오류가 발생할 때까지 요청을 날리고 오류가 발생하면 응답을 덤프를 뜨도록 스크립트를 작성했습니다.

import requests
import datetime
import pickle

try:
    success_count = 0
    fail_count = 0

    while True:
        r = requests.get('https://www.peoplefund.co.kr/team/')

        print(datetime.datetime.now(), r.elapsed, r.status_code)
        if r.status_code == 200:
            success_count += 1
        else:
            fail_count += 1
            with open(datetime.datetime.now().strftime('team/team_%Y%m%d_%H%M%S_%f.dump'), 'wb') as f:
                pickle.dump(r, f)

except KeyboardInterrupt:
    print('success_count = %s, fail_count = %s, success_rate = %.2f %%' % (success_count, fail_count, success_count * 100 / (success_count + fail_count)))

잘 될 때에는 지속적으로 잘 되는데, 안 될 때에는 지속적으로 안 되는 모습을 보여주었습니다. 또한 테스트를 진행 중인 Lambda 함수 뿐만 아니라, 다른 Lambda 함수도 덩달아 실패하는 것을 확인하고, Lambda 함수 전체에 문제가 발생하고 있다는 것을 직감했습니다.

Lambda 함수가 붙어 있는 Target Group의 메트릭을 살펴 보다, Load Test를 진행했을 시점에 Lambda User Error 지표가 크게 증가했다는 점을 발견할 수 있었습니다.

하지만 Lambda User Error라는 키워드로 검색해 보았을 때 별다른 수확이 없어, 조사는 다시 원점으로 돌아갔습니다.

Lambda 함수의 로그를 살펴보았는데, 아무리 뒤져 보아도 해당 시간에 오류가 발생한 기록이 없는 것이었습니다! Traceback이라도 있으면 문제 해결에 큰 도움이 될 것이었겠지만, 관련된 오류 로그는 보이지 않았습니다.

네트워크의 어디서 문제가 발생하는지를 파악해야 합니다. 문제가 발생하는 네트워킹 구간을 좁혀야 했습니다. 외부 망에서 요청하면 CloudFront에서 가공한 HTML이 나오기 때문에 정확한 원인을 파악하는데 방해가 되었습니다. 내부 망으로 스트레스 테스트 스크립트를 가지고 와서 돌려 보았고, 가공되지 않은 원본의 오류 응답을 볼 수 있었습니다.

HTTP/1.1 502 Bad Gateway
Server: awselb/2.0

<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white"><center><h1>502 Bad Gateway</h1></center></body>
</html>

502 Bad Gateway 오류를 뱉어내는 주체는 ELB였습니다. ELB는 접근 로그를 S3에 남기는 기능을 지원합니다. 다행히 설정이 켜져 있었습니다. S3에서 ELB가 남기는 로그를 다운로드 받아 보았습니다. 많고 많은 로그 더미를 뒤지다가, 단서가 될 만한 오류 문자열을 발견할 수 있었습니다.

"LambdaThrottling"

Lambda Throttling 이라는 키워드로 구글 검색을 시작했고, 곧 도움이 될 만한 문서를 발견했습니다.

Lambda 함수가 동시에 실행될 수 있는 개수가 정해져 있다는 것을 알게 되었습니다. Lambda는 계정 별로 Concurrency limit이 정해져 있고 이를 초과하게 되면 쓰로틀링이 걸리게 됩니다.

이제 왜 Lambda 로그가 존재하지 않았는지의 의문이 해소되었습니다. Lambda 함수가 쓰로틀링에 걸려 실행되지 않았으니 당연히 Lambda 로그도 남지 않았던 것입니다.

문서를 보면, ConcurrentExecutions 지표를 보면 어떤 함수가 동시에 실행되는지를 알 수 있다고 되어 있습니다. CloudWatch에서 ConcurrentExecutions 지표를 보고 어떤 Lambda 함수가 쓰로틀링을 걸 정도로 많이 호출되고 있는지를 확인했습니다.

그 결과, 하나의 Lambda 함수가 지나치게 많이 동시에 실행되고 있어서 다른 Lambda 함수들이 실행되지 못하고 있음을 확인하였습니다.

문제가 되는 함수에 동시성 제한을 걸어 동시에 너무 많이 실행되지 않도록 조치를 취했습니다. 수 시간동안 모니터링을 진행한 결과, 502 Bad Gateway 오류가 발생하는 비율이 상당히 줄어들었음을 확인했습니다.


인프라의 문제로 인해 API의 실행이 간헐적으로 실패하는 문제의 트러블슈팅을 경험한 것은 처음이었습니다. 경험적으로 인프라가 문제가 있으면 전체 서비스가 죽어 버리는 경우가 많아 문제의 범위를 줄이기가 수월했는데, 이번 경우는 문제의 범위를 줄이는 작업이 상당히 까다로웠습니다.

Lambda 함수는 정상일 것이고 사이의 인프라에 문제가 있을 것이라고 가정하고 문제 파악을 시작했는데, 예상과는 완전히 다른 쪽에 문제의 원인이 있어서 문제의 근본 원인을 찾아 해결하고 난 후에도 약간 얼떨떨했습니다. 폭주하는 다른 Lambda 함수가 원인이었다는 것은 예상을 한 벗어나는 원인이었였습니다.