🔥 언제 Lambda가 아닌 ECS/Fargate를 써야 하는가
강의 목차

어제 밤에 한 함수가 14분 50초에서 timeout으로 죽는 걸 봤다. 한 ETL 한 번이 평소엔 10분 안쪽으로 끝나는데, 그날은 source 데이터가 평소의 두 배쯤 들어왔고 한 부분에서 페이지네이션을 한 번 더 도는 곳에서 시간이 더 들어갔다. CloudWatch Logs에 마지막으로 찍힌 줄은 "page 2147 fetched"였고 그다음 줄이 Task timed out after 900.05 seconds였다. 함수는 멀쩡히 돌고 있었는데 단지 Lambda 추상 경계의 두 줄 중 하나에 부딪힌 것이다.
다음 날 아침에 이 문제를 어떻게 풀지 한 시간을 짚었다. 함수를 더 잘게 쪼개서 Step Functions로 묶을지, 아니면 Lambda를 떠날지. 나는 떠나는 쪽을 골랐다. 같은 워크로드의 데이터 양이 두 배가 됐다는 건 다음 분기에 또 두 배가 될 가능성이 있다는 뜻이고, 그러면 7.5분짜리 함수도 다시 timeout에 부딪힌다. 한 칸 옆으로 옮겨야 할 시점이 어떻게 드러나는지가 이 글의 본 주제다. 그날 아침에 정리한 결과가 여기에 있다.
Lambda의 한도 6개를 한 화면에 본다
Lambda의 한도는 Lambda란 무엇인가: 서버리스의 경계에서 두 줄로 짚었다. 한 호출 최대 15분(900초) timeout과 stateless 핸들러 두 줄. 그런데 운영에서 부딪히는 한도는 그 둘 외에도 네 개가 더 있고, 여섯 개를 같이 한 화면에 띄울 때 어떤 워크로드가 Lambda 바깥인지가 또렷하게 드러난다.
여섯 개를 한 줄씩 적는다.
- timeout: 900초(15분): 공식 docs에 한도 증가 신청 자체가 없다. hard limit.
- 메모리: 128 MB ~ 10,240 MB(10 GB): 1 MB 단위로 조절. 1,769 MB에서 1 vCPU 등가 (공식 메모리 docs).
- 페이로드: 동기 요청 6 MB / 동기 응답 6 MB / 비동기 1 MB: 동기 6 MB는 요청·응답 각각이고, 비동기 한도는 2025년 10월에 AWS가 256 KB에서 1 MB로 확장했다.
- /tmp ephemeral storage: 512 MB ~ 10,240 MB: 2022년 3월부터 최대 10 GB까지 가능하다.
- 배포 패키지: ZIP 250 MB(unzipped) / 컨테이너 이미지 10 GB: 큰 dependency가 있는 ML 함수는 ZIP 길로는 못 들어간다.
- 계정 동시 실행 한도: 리전당 1,000(soft): 동시성 docs. soft limit이라 신청으로 늘릴 수는 있지만 즉시는 아니다.

여섯 개를 같이 보면 한 패턴이 드러난다. 메모리는 한때 3,008 MB가 천장이었고, /tmp는 한때 512 MB 고정이었고, 비동기 페이로드는 어제까지도 256 KB였다. timeout도 출시 직후엔 5분이 한도였고, AWS가 2015년 10월에 5분으로, 이후 15분까지 키웠다. AWS도 한도가 좁다는 걸 알고 차근차근 키워 왔다. 그런데 15분에서 멈춰 있다. 출시 후 두 번 키운 다음 공식 한도 페이지에서 hard limit 칸으로 굳었다.
왜 안 키우느냐면, Lambda의 internal 모델 자체가 짧은 호출이 끝나면 컨테이너가 freeze된다는 가정 위에 서 있기 때문이다. 한 호출이 한 시간이고 두 시간이고 잡고 있으면 freeze 사이의 비율이 어긋나고 무료 한도와 burst capacity 같은 자원 공유 모델 전체가 동작을 멈춘다. 그래서 Lambda는 15분짜리 함수까지가 자기 영역이고 그 너머는 다른 도구의 영역이다. 어제 내 함수가 14분 50초에서 죽은 건 우연이 아니라 경계 바로 옆까지 와 있던 워크로드라는 뜻이다.
Stateless가 막는 5가지 패턴
한도 옆에 같이 사는 함정이 한 가지 더 있다. stateless 핸들러. 한 호출이 끝나면 그 다음 호출이 어디로 갈지를 보장 못 한다. Lambda란 무엇인가: 서버리스의 경계에서 Lambda 실행 환경 재사용은 우연이라고 정리한 대목이다. 핸들러 바깥 객체나 /tmp에 쌓인 데이터는 같은 컨테이너에 도착했을 때만 살아 있고, 트래픽 burst가 오면 새 컨테이너가 동시에 N개 뜬다. 이 형태가 실무에서 무엇을 막는지가 결정의 두 번째 축이다.
다섯 가지 패턴을 같이 본다.
WebSocket 장기 연결. 한 클라이언트가 한 함수에 30분 동안 connection을 잡고 있는 형태는 Lambda에서 안 된다. API Gateway WebSocket을 통해 메시지마다 새 함수 호출로 처리할 수는 있지만, 그건 connection 자체가 API Gateway 쪽에 매달려 있는 거지 Lambda 함수가 connection을 들고 있는 게 아니다. 게임 server 같은 함수가 connection을 직접 쥐는 형태는 Fargate task 한 개가 답이다.
Sticky session. 한 사용자가 같은 함수 컨테이너에 계속 도착해야 한다는 가정 자체를 Lambda가 받지 않는다. Lambda는 sticky session을 지원하지 않는다는 게 정확한 표현이다. 사용자 세션 데이터는 ElastiCache나 DynamoDB로 빼야 하고, 그러면 한 호출당 한 번 외부 lookup이 추가다. 한 사용자가 5분 동안 30번 요청하면 30번 lookup이고, 그 한 줄이 Fargate에서 in-memory 변수로 끝나는 걸 Lambda는 정직하게 줄당 비용으로 청구한다. 100만 사용자가 같은 패턴으로 들어오면 월 3,000만 lookup이 자라는데, DynamoDB on-demand 가격이 RRU 100만 개당 약 $0.125이므로 small item을 eventually consistent로 한 번 읽는다는 가정하에 약 $3.75쯤 추가다. 작은 숫자처럼 보이지만 이런 없어도 됐을 줄이 architecture에 두세 개 더 쌓이면 한 자릿수 달러가 두 자릿수 달러로 자란다.
큰 in-memory cache. 한 함수가 시작할 때 1 GB짜리 reference 데이터를 메모리로 올려놓고 쓰는 형태도 Lambda에서는 비싸다. cold start마다 1 GB를 다시 올려야 하고, Cold Start: 왜 첫 호출이 느린가에서 본 INIT 비용이 한 자릿수 초로 자란다. Provisioned Concurrency로 데워 둘 수는 있지만 그 비용이 24시간 한 task를 띄우는 Fargate보다 비싸지는 cross-over가 메모리 1 GB쯤에서 발생한다.
Real-time game server tick. 60 fps 한 프레임이 16.6ms인 게임 server는 한 호출당 16.6ms를 끝내야 하고, 그 사이에 함수 컨테이너가 freeze에서 thaw될 시간이 없다. 그리고 사용자 100명이 한 방에 있으면 100명의 state가 같은 process에 살아 있어야 한다. Lambda로는 처리 못 한다. 한 함수 호출의 단위가 한 프레임이 아니라 한 이벤트고, 한 이벤트마다 컨테이너가 다른 곳으로 갈 수 있기 때문이다.
큰 모델 로딩. 7B 이상 파라미터 LLM을 함수마다 메모리에 올리려는 형태는 Lambda 컨테이너 이미지 10 GB 한도에 부딪히기 시작한다. 모델 가중치만 GB 단위인 데다 런타임·라이브러리까지 같이 들어가면 이미지가 한도를 넘기 쉽다. quantization으로 압축하더라도 cold start마다 모델을 다시 로드해야 하므로 첫 호출이 분 단위로 자란다. ML inference는 SageMaker endpoint나 ECS task가 default 답이다.

이 다섯 가지 패턴은 한도 6개와 다른 종류의 함정이다. 한도는 숫자라 한 번 부딪혀 보면 알 수 있지만, stateless는 형태 자체라 부딪히기 전엔 안 드러난다. 그래서 architecture 단계에서 결정해야 한다.
한 칸 옆은 ECS Fargate
여기까지 한도 6개와 stateless 다섯 가지 형태. Lambda를 떠나기로 결정했다면 가장 가까운 칸은 언제 EC2가 아닌 다른 걸 써야 하는가에서 본 ECS Fargate다. 한 줄로 압축하자면, 컨테이너 한 task를 띄우는 데 EC2가 필요 없게 한 추상. 한 task 안에서 stateful이 살아 있고, timeout이 없고, 메모리는 한 task당 120 GiB까지 가져갈 수 있다. 한도 표는 정반대 방향으로 열려 있다.
다만 한 칸 옆으로 가면서 새 청구서가 들어온다. 셋이다.
첫째, 24시간 vCPU 단가. Lambda는 호출당과 GB-초당 두 축으로 비용을 청구하고 호출이 없으면 0이다. Fargate는 task가 살아 있는 시간 × vCPU 단가다. 같은 가벼운 HTTP API의 청구서가 EC2 ~$40, Fargate ~$27, Lambda ~$3.4로 줄어드는 그래프는 언제 EC2가 아닌 다른 걸 써야 하는가에서 한 번 봤다. 그 그래프는 burst 워크로드는 Lambda가 압도적으로 싸고, 24시간 워크로드는 Fargate가 그래도 EC2보다 싸지만 Lambda보다 8배 비싸다는 사실을 보여 준다.
둘째, Application Load Balancer. Lambda + API Gateway 조합에서 API Gateway가 들어오는 요청을 받았다면, Fargate에서는 ALB가 그 역할을 가져간다. US East 기준 시간당 약 $0.0225 + LCU 단가가 청구서 첫 줄로 올라간다(서울 리전은 보통 약간 높다). 한 task가 하루에 한 번만 호출되더라도 ALB는 24시간 살아 있다.
셋째, 컨테이너 이미지 운영. ZIP 함수는 코드를 zip으로 묶어 올리면 끝이지만, Fargate는 ECR에 push할 OCI 이미지가 한 개 있어야 한다. 그러면 빌드 파이프라인(GitHub Actions·CodeBuild)이 새로 따라오고, 이미지 보안 스캔과 vulnerable dependency 패치 책임이 들어온다. 언제 EC2가 아닌 다른 걸 써야 하는가의 책임 스펙트럼에서 EC2의 OS 책임은 가져가지만 컨테이너 이미지 책임은 사용자에게 남는 대목이다. Lambda 함수에는 없던 한 묶음이 새로 따라온다.
이 셋이 옮길 때 따라오는 새 비용이다. 그래도 워크로드의 형태가 한도 6개나 stateless 다섯 가지 형태 중 하나에 부딪히면, 이 셋을 받아들이는 게 정직하다. Lambda 청구서가 갑자기 자라거나, Provisioned Concurrency를 24시간 켜 두기 시작했거나, Step Functions로 함수를 잘게 쪼개는 일이 늘어났거나, 셋 중 하나가 드러나면 이미 Fargate 쪽 청구서보다 비싸졌을 가능성이 있다.
두 칸 옆은 EKS, 단 비용이 비싸다
Fargate 너머로 한 칸 더 옮기는 길이 있다. Amazon EKS. Kubernetes API를 그대로 받는 관리형 컨트롤 플레인. 언제 EKS인가에 답하기 전에 비용을 먼저 짚는다.
EKS 컨트롤 플레인은 시간당 $0.10이고, 한 달이면 약 $73이다. 여기까지가 standard support 14개월 동안의 단가. 그 이후엔 한 가지가 바뀐다. Kubernetes 버전이 standard support에서 빠지면 extended support로 들어가면서 시간당 $0.60으로 뛴다, 6배다. 한 cluster의 컨트롤 플레인 비용이 월 $73에서 월 $438이 된다. 그것도 12개월 동안. 그래서 EKS는 Kubernetes 버전 업그레이드를 매년 한 번씩 정직하게 따라가는 팀의 영역이다. 따라가지 못하면 청구서에서 6배 cliff에 부딪힌다.
EKS가 답인 경우는 둘 정도다. 첫째, 조직이 이미 Kubernetes를 쓰고 있어서 manifest와 운영 도구를 EKS에 그대로 가져갈 수 있는 경우. Helm chart, kustomize, ArgoCD가 이미 익숙하면 옮기는 비용이 매우 작다. 둘째, multi-cloud 호환성이 정말 필요한 경우. 정말 필요한지 한 번 더 자문해야 한다. 대부분의 multi-cloud는 들고 다니는 추상의 비용이 한 cloud의 vendor lock-in 비용보다 비싸다는 게 내 경험이다.
그 외에 컨테이너 워크로드를 띄우는 곳은 ECS Fargate가 default 답이고, EKS는 이미 Kubernetes다라는 전제가 있을 때만 비용이 합당하다.
운영에서 보이는 옮길 시점 4가지 신호
architecture 단계에서 결정한다는 건 듣기엔 좋지만, 실제로는 한 번 Lambda로 띄운 워크로드를 바라보다가 어느 날 옮겨야겠다는 결정이 나는 경우가 더 많다. 어제의 내 함수가 그랬던 것처럼. 그래서 운영에서 잡히는 신호 네 가지를 마지막으로 적어 둔다.
- 함수 timeout 한 번 이상. 14분 50초·14분·13분 30초처럼 900초 가까이서 죽는 호출이 한 달에 한 번 이상이면, 다음 분기엔 정기적으로 죽는다. 데이터 양이 자라는 워크로드의 자연스러운 곡선이다.
- Provisioned Concurrency 비용이 함수 invocation 비용을 넘어선 경우. 콜드 스타트를 가리려고 PC를 켰는데 그 비용이 한 달 청구서의 절반을 차지한다면, 같은 돈으로 Fargate task 한 개를 24시간 띄우는 게 더 정직하다. cross-over 지점.
- Step Functions state machine이 함수를 쪼개기 위해서만 늘어나는 경우. state machine은 서로 다른 일들을 묶는 도구지 한 일을 잘게 쪼개는 도구가 아니다. 같은 ETL을 5단계로 쪼개야 15분 한도를 안 깨는 상태라면, 한 task로 도는 ECS가 정답에 가깝다.
- 장기 연결·sticky session·in-memory cache가 한 번이라도 architecture 토론에 등장하는 경우. 한도 6개 중 하나가 아니라 워크로드 형태에 부딪힌 것이고, 형태는 quota 신청으로 해결할 수 없다. 첫 등장에서 옮기는 게 reverse cost가 적다.

관리형이 감추는 비용이라는 한 줄은 Lambda에서 Fargate로 옮길 때도 같은 모습으로 따라온다. Lambda는 짧은 burst엔 청구서에 정직하고, Fargate는 24시간과 stateful에 정직하다. 같은 워크로드를 잘못된 도구에 얹으면 청구서가 다른 줄에서 자란다. 어제의 내 함수가 14분 50초라는 신호로 가르쳐 준 게 그것이다.
마지막으로: 떠나도 다시 돌아올 수 있는 한 칸
여기까지 읽으면 Lambda는 좁은 도구처럼 들릴 수도 있다. 그건 절반만 맞다. 가벼운 HTTP API, S3 트리거, EventBridge 스케줄, 짧은 ETL 한 묶음은 모두 Lambda가 default 답이고, 언제 EC2가 아닌 다른 걸 써야 하는가에서 본 같은 워크로드의 청구서가 Fargate $27 vs Lambda $3.4인 곳에서 굳이 Fargate로 갈 이유가 없다. 떠나야 하는 곳과 머무는 곳이 한 워크로드의 형태로 갈리는 거지, Lambda 자체가 좋거나 나쁜 도구는 아니다. 한 도구의 영역이 어디까지인지를 안다는 건 그 옆 도구의 영역이 어디서 시작하는지를 같이 안다는 뜻이고, 이 글에서 그 경계 한 줄을 그어 둔다.
다음 섹션은 S3, 오브젝트 스토리지다. 컴퓨트 다섯 섹션을 정리한 다음, 그 컴퓨트가 들고 있던 데이터가 어디에 사는지를 짚는다.
참고 자료
- AWS Lambda 한도 공식 docs: 함수 timeout·메모리·페이로드·동시성 한도
- AWS Lambda 함수 timeout docs: 최대 900초(15분) hard limit
- AWS Lambda 메모리 docs: 128 MB ~ 10,240 MB, 1,769 MB에서 1 vCPU 등가
- AWS Lambda /tmp ephemeral storage docs: 512 MB ~ 10,240 MB
- Lambda 비동기 페이로드 1 MB로 풀린 발표 (2025-10): 256 KB → 1 MB 변경
- Amazon ECS task definition parameters: Fargate vCPU·메모리 조합표
- AWS Fargate task ephemeral storage docs: 20 GiB default ~ 200 GiB
- Amazon EKS pricing: 컨트롤 플레인 시간당 $0.10
- Amazon EKS extended support 가격 발표: standard $0.10/hr → extended $0.60/hr 6배 점프
- DynamoDB on-demand pricing: RRU 100만 개당 약 $0.125
- Elastic Load Balancing pricing: ALB 시간당 단가 + LCU









