AWS Lambda를 동시에 500개 이상 호출하기

들어가면서

프로젝트에 사용된 스택에 관한 간단한 생각을 적어봤습니다. 바쁘신 분들은 바로 본문으로 가셔도 됩니다.

Python

코드 작성이 정말 쉽다. Syntax가 매우 간결하다. 인덴트로 scope가 구분돼 {, } syntax보다 훨씬 읽기 편하다. Generic한 코딩이 일반적이고, 자연스럽다. 편해진 만큼 방만해지기 쉽고, 빨리 작성되는 만큼 놓치는 부분도 많았다.

Go

코드 작성이 쉽다. gofmt 덕분에 코드 스타일 걱정 없이 빠르게 개발이 가능하다. 엄청 빠르다. C/C++ 에서 의존성 때문에 골치아팠던 개발자라면 Go는 정말 새로운 세상이나 마찬가지다.

AWS

이제는 클라우드라는 말을 들으면 AWS가 떠오를 정도. 전세계적으로도 대세이기도 하고, 서비스 안정성도 대단하다. 무슨 서비스가 있는지 모를 정도로 다양한 서비스가 있지만, 실제 MSA를 설계할 때는 문서와의 싸움이다. 어떤 게 되고, 어디까지 되는지 하나하나 스펙을 미리 검사해놓지 않으면 개발 도중에 아키텍처를 바꿔야 하는 순간이 수도 없이 찾아온다. 이제는 Pulumi 덕분에 AWS resource 단에서의 Trial & Error 걱정은 많이 줄어들었다.

Pulumi 튜토리얼 (Python)

Pulumi 시작하기(1) - Introduction
> Pulumi 시작하기(1) - Introduction[https://devbull.xyz/pulumi-tutorial-introduction/]Pulumi 시작하기(2) - 코드 작성 및 배포[https://devbull.xyz/pulumi-tutorial-code-and-deploy/]Pulumi 시작하기(3) - API Gateway v2 + Lambda 예제[https://devbull.xyz/pulumi-tutorial-apigateway-lambda/…

🚧 내가 겪은 문제점

목표 : 최대 500개의 lambda instance를 동시에 실행시키는 것

얼핏 들으면 간단한 문제인 것 같다. 처음 DynamoDB를 Trigger로 Lambda를 실행했을 때, Concurrent execution이 20~30에 머무르는 걸 봤을 때는 금방 해결할 수 있는 문제인 줄 알았다. 처음에는 계정 또는 특정 Lambda 함수의 Concurrency limit 때문인 줄 알았다. 그래서 계정 자체의 Concurrency limit을 2000으로, 해당 함수에도 Reserved concurrency를 200을 설정해봤다. 그래도 안되더라..

Lambda scaling에 관한 AWS 공식 문서를 보면, 서울 리전은 Burst quota가 500개다. 다시 말하면, 처음 Lambda를 트리거했을 때 동시에 실행할 수 있는 컨테이너의 갯수가 제한돼 있다는 것이다. 물론 500개 quota를 다 사용하고 나면 그보다 많은 수의 컨테이너를 실행할 수 있다.

그럼 왜 실행이 제대로 안 되는 것일까? Lambda는 실행 시마다 Lambda 서비스에 할당된 EC2에 Container를 띄워서 실행하는 방식이다. 만일 방금 실행한 것과 같은 함수의 실행 요청이 5분 안에 들어오면, 컨테이너가 재사용된다. Concurrency란 이 컨테이너가 동시에 몇 개 실행되는지를 나타내는 것이다.

그제야 문제를 파악할 수 있었는데, Lambda를 어떻게 Trigger 하느냐에 따라서 Lambda가 동시에 실행할 수 있는 인스턴스가 달라진다는 것이었다. 이는 Lambda가 메시지를 수신하는 구조에서 비롯되는 문제였다. 각 Lambda 실행 명령은 먼저 Event queue에 쌓이고, 이를 바탕으로 Lambda 비동기 호출이 발생한다. 따라서 Event queue에 어떻게 작업을 쌓느냐가 중요한 점이다.

시도해본 방법들

1. Step functions의 Dynamic parallelism

AWS 자체적으로 병렬 처리가 가능한 Step functions의 Dynamic parallelism 기능을 사용하면 Lambda를 병렬적으로 호출할 수 있다.

  • Map step 당 최대 49~50개까지 밖에 호출 안됨 (By specification)
  • Nested map 구조로 설계 시 200개까지 가능 (By test)

2. SQS lambda trigger

Lambda를 동시에 실행시키는 방법 중 AWS에서 공식적으로 추천하는 방법은 SQS를 이용하는 것이다. 하지만 SQS를 Event sourcef로 했을 경우, 최초 실행 시 5개 컨테이너가 실행되고 이후 1분마다 최대치가 60개씩 늘어난다.

`5 -> 65 -> 125 -> ...`

이 프로젝트의 람다 실행시간이 보통 3분 미만인 걸 생각하면 동시성을 제대로 활용하기는 어려운 방법이다.

3. Set execution time longer

정 SQS를 사용하고 싶다면, 강제로 실행 시간을 늘리는 방법도 있다. 하지만 실행 시간이 곧 요금인 Lambda에서 이런 행위는 정말 비효율적이다. 만일 500개의 Lambda를 최대 180초간 강제로 실행시킨다면, 256MB 메모리 기준 요금은 다음과 같다.

단위 변환
할당된 메모리 크기: 256 MB x 0.0009765625 GB in a MB = 0.25 GB per 해당 없음
요금 계산
500 requests x 180,000 ms x 0.001 ms to sec conversion factor = 90,000.00 total compute (seconds)
0.25 GB x 90,000.00 seconds = 22,500.00 total compute (GB-s)
22,500.00 GB-s x 0.0000166667 USD = 0.38 USD (monthly compute charges)
500 requests x 0.0000002 USD = 0.00 USD (monthly request charges)
Lambda 비용 – 프리 티어 제외 (monthly): 0.38 USD

4. Simple HTTP/API request

JMeter(Answered from AWS support)

AWS Business support에 해당 문제를 해결할 수 있는 서비스가 있는지 문의했더니, JMeter와 같은 HTTP 테스트 툴을 이용하라고 했다. API Endpoint를 만들어주는 서비스인 API Gateway를 Lambda 함수의 트리거로 설정한 다음, HTTP 요청을 동시에 보내면 된다.

이 방법은 잘 동작하긴 했지만, CPU와 램 사양을 굉장히 많이 타기 때문에 람다에서 바로 실행하기에는 무리가 있었다. 다만 여기서 아이디어를 얻어 직접 더 가벼운 파이썬 구현체를 만들어 보기로 했다.

Python future

파이썬의 future 는 동시 병렬처리를 위해 고안된 concurrent 라이브러리의 모듈 중 하나다. 이를 이용해 HTTP 요청을 엔드포인트에 동시에 보내도록 했다. 하지만 파이썬은 너무 느렸다.

5. ECS

k8s를 이용해 실시간으로 컨테이너를 띄우고, 각 컨테이너에서 API 요청을 보낼 생각을 해봤지만 이건 완전 주객전도다. 그럼 애초에 람다 대신 컨테이너를 띄워서 작업을 실행하지...

  • Cannot initiate desired number of containers at once (AWS Limit)
  • Too expensive

6. Dynamodb + Kinesis

Kinesis에서 보내주는 스트림을 제대로 사용하려면 sharding을 엄청 많이 넣어야 하기 때문에 비효율적이다.

🔧 Go로 해결

결국 정말 간편하고 빠른 동시성 실행을 지원하는 Go로 오게 되었다. 구현 방법은 굉장히 간단한데, 그냥 Goroutine 안에 AWS Go SDK로 Lambda를 직접 호출했다.

벤치마크

Lambda function을 호출해본 결과이다.

Method# of reqTime (s)
Python concurrent.future10023.02
Go5002.03

결론

확실히 Go는 네트워크, 서버 관련해서는 최고의 언어인 것 같다. 고수준 언어같은 Syntax에 Low level까지 다룰 수 있는 언어라 앞으로 활용성이 무궁무진해 보인다. 미리미리 공부해 놓아야겠다.

최근 Go를 이용해 구현한 Typora + Ghost 이미지 업로더인데 시간 나시면 꼭 확인 부탁드립니다👍

Indosaram/tgug
Seamlessly upload your images to your Ghost blog. Contribute to Indosaram/tgug development by creating an account on GitHub.

관련 글

TGUG - Typora-Ghost image Uploader in Go
IntroductionTypora [https://typora.io/]는 내가 몇년 째 쓰고 있는 매우 편리하고 심플한, 그리고 예쁜 마크다운 편집기다. 마크다운편집이 아니더라도 메모장으로도 자주 사용한다. 특히 코드블럭과 수식도 자유롭게 넣을 수 있기 때문에 더욱 자주 사용하게 된다. 테마도커스터마이징이 가능하고, 기본 테마도 충분히 예쁘다. 나는 Night를 주로 사용한다. 일렉트론 기반의 Windows, Mac, Linux를모두 지원하는데다 가볍기까지해서 새로운 PC를 세팅할 때마다 반드시 설치해주는 최애 프로그램 중 …