ServerlessServerless
Thuộc bộ tài liệu DevOps Roadmap.
Tổng quan
Serverless là mô hình thực thi trên cloud trong đó provider quản lý toàn bộ việc cấp phát server, scaling và bảo trì, còn bạn chỉ trả tiền cho lượng compute thực sự dùng. “Serverless” không có nghĩa là không có server — mà là bạn không phải quản lý chúng. Hình thức nổi tiếng nhất là Functions as a Service (FaaS): các đơn vị code nhỏ được kích hoạt bởi event, tiêu biểu là AWS Lambda, Azure Functions, Google Cloud Functions và Cloudflare Workers. Nhánh mới hơn là serverless container (Google Cloud Run, AWS Fargate, Azure Container Apps) — áp dụng cùng mô hình trả theo mức dùng, scale về 0, nhưng cho cả container image.
Với DevOps, serverless giảm mạnh gánh nặng vận hành: không vá OS, không capacity planning, không server chạy không tải. Việc scale từ 0 lên hàng nghìn execution đồng thời là việc của platform. Đổi lại, bạn chấp nhận các ràng buộc — giới hạn thời gian chạy, cold start, API đặc thù của từng vendor — cùng một mô hình chi phí và debug khác hẳn.
Serverless tỏa sáng với glue code hướng event, API có traffic đột biến hoặc khó dự đoán, và các pipeline xử lý bất đồng bộ. Nó không phù hợp với workload chạy dài, yêu cầu latency cực thấp, hoặc tải ổn định cực lớn — khi đó compute chuyên dụng rẻ hơn và dễ dự đoán hơn.
Hai điều “serverless” thực sự cam kết
Bỏ qua lớp marketing, serverless đưa ra hai cam kết cụ thể định nghĩa cả thể loại này:
- Scale về 0. Khi không có request nào tới, bạn không chạy gì và không trả gì. Một VM hay pod Kubernetes truyền thống luôn bật, luôn tính tiền. Chính đặc tính này khiến serverless “màu nhiệm” về kinh tế với workload nhiều thời gian rảnh, và tệ hại về kinh tế với workload bận liên tục.
- Scale tự động, tỉ lệ theo request. Platform thêm capacity khi tải tăng và bỏ đi khi tải giảm, không có policy autoscaling nào cho bạn tinh chỉnh. Bạn không bao giờ provision cho đỉnh; platform lo việc đó theo từng request.
Mọi thứ còn lại — các ràng buộc, mô hình chi phí, cold start — đều bắt nguồn từ hai cam kết này. Khi ai đó hỏi “workload này có hợp serverless không?”, thực chất họ đang hỏi “workload này hưởng lợi từ scale-về-0 và co giãn theo request nhiều hơn hay chịu thiệt vì các ràng buộc đi kèm nhiều hơn?”
Kiến thức nền tảng
FaaS trong một phút
- Bạn viết một function (handler) và khai báo trigger (HTTP request, message từ queue, file upload, lịch chạy, thay đổi database).
- Platform đóng gói, deploy và chạy nó khi có event — mặc định mỗi event đồng thời chạy trên một instance riêng.
- Bạn bị tính phí theo số invocation và thời gian compute (GB-giây), thường kèm free tier khá rộng rãi.
# Handler AWS Lambda (Python)
import json
# Chạy MỘT LẦN mỗi cold start — tái dùng phần setup đắt đỏ qua các lần gọi "ấm"
db_client = create_db_client()
def handler(event, context):
name = event.get("queryStringParameters", {}).get("name", "world")
return {
"statusCode": 200,
"body": json.dumps({"message": f"Hello, {name}!"})
}
Điểm mấu chốt về cấu trúc nằm ở dòng comment: handler chạy trên mỗi invocation, nhưng code bên ngoài handler chỉ chạy khi cold start. Đặt phần khởi tạo đắt đỏ (DB client, đối tượng SDK, load config) ở global scope nghĩa là các lần gọi ấm tái dùng nó miễn phí — một trong những tối ưu đòn bẩy cao nhất trong serverless.
Các platform chính
| Platform | Mô hình runtime | Thời gian chạy tối đa | Đặc điểm nổi bật |
|---|---|---|---|
| AWS Lambda | Function (zip hoặc container image tới 10 GB) | 15 phút | Tích hợp event source sâu nhất (S3, SQS, DynamoDB, Kinesis, EventBridge); SnapStart giảm cold start cho JVM |
| Azure Functions | Function, nhiều hosting plan | 5–10 phút (Consumption), không giới hạn (Premium/Dedicated) | Durable Functions cho orchestration có state; Flex Consumption plan |
| GCP Cloud Functions | Function (nay là “Cloud Run functions”) | 9 phút (gen1) / 60 phút (gen2, HTTP) | Chạy trên hạ tầng Cloud Run từ gen2 |
| GCP Cloud Run | Bất kỳ container nào, theo request hoặc job | 60 phút (request), dài hơn cho job | Serverless container; scale về 0; concurrency > 1 mỗi instance |
| AWS Fargate | Container sau ECS/EKS | Không giới hạn (chạy dài) | Serverless container cho service luôn bật; bản thân không scale về 0 |
| Azure Container Apps | Container, xây trên KEDA/Dapr | Không giới hạn cứng | Scaling hướng event, scale về 0, thân thiện microservice |
| Cloudflare Workers | V8 isolate (JS/WASM) tại edge | Giới hạn CPU-time (ms–phút) | Cold start gần như bằng 0, chạy tại 300+ điểm edge, không deploy theo region |
Hai họ, đánh đổi khác nhau. Họ FaaS (Lambda, Cloud/Azure Functions, Workers) tối ưu cho đơn vị event nhỏ và tính tiền chi tiết nhất. Họ serverless-container (Cloud Run, Fargate, Container Apps) tối ưu cho tính di động (ngôn ngữ/binary tùy ý, không khóa framework) và cho việc một instance phục vụ nhiều request đồng thời. Workers là một loài thứ ba: isolate tại edge, đánh đổi môi trường Node/OS đầy đủ lấy cold start gần bằng 0 và phân phối toàn cầu.
Kiến trúc hướng event (event-driven)
Serverless function là consumer tự nhiên trong hệ thống event-driven:
- Đồng bộ (request/response): API Gateway → Lambda → response (client chờ kết quả). Lỗi hiện trực tiếp cho caller; không có retry tự động.
- Bất đồng bộ (fire-and-forget): event upload lên S3 → Lambda resize ảnh. Platform xếp event vào hàng và tự retry khi lỗi (Lambda mặc định retry hai lần), với dead-letter queue hứng message “độc”.
- Theo stream/queue (poll): SQS/Kinesis/Pub/Sub → function xử lý theo batch; platform lo polling, batching, checkpoint và đảm bảo thứ tự (theo shard với Kinesis, theo message-group với FIFO SQS).
Ba kiểu invocation này hành xử khác nhau khi lỗi, retry và thứ tự — một khác biệt hay làm người mới vấp. Chọn nhầm kiểu (ví dụ gọi đồng bộ một downstream chậm trong khi lẽ ra nên đưa vào queue) là lỗi thiết kế phổ biến.
Thiết kế event-driven tách rời producer khỏi consumer: các service giao tiếp qua event, scale độc lập, lỗi được cô lập và có thể retry. Các công cụ orchestration ghép function thành workflow: AWS Step Functions, Azure Durable Functions và Google Workflows cho phép biểu diễn retry, rẽ nhánh, song song và bước phê duyệt của con người một cách khai báo, thay vì nối function bằng tay.
Cold start
Cold start xảy ra khi platform phải tạo môi trường thực thi mới: cấp phát sandbox, tải code, khởi động runtime và chạy phần khởi tạo của bạn. Nó thêm latency (từ vài chục mili-giây tới vài giây) cho request đầu tiên mà instance đó phục vụ. Các request tiếp theo tới cùng instance ấm bỏ qua toàn bộ bước này.
Vòng đời: cold start → các lần gọi ấm → rảnh → bị thu hồi. Không có cách nào ghim một instance ấm mãi mãi trên consumption plan; platform thu hồi instance rảnh để giải phóng capacity. Đây là lý do tải thấp ổn định nghịch lý thay lại có thể chậm hơn tải cao ổn định — tải thấp khiến instance rảnh và bị thu hồi giữa các request.
| Yếu tố | Ảnh hưởng | Cách giảm |
|---|---|---|
| Runtime | JVM/.NET khởi động chậm hơn; Node/Python/Go nhanh hơn; binary compile Rust/Go nhanh nhất | Ưu tiên runtime nhẹ cho đường nhạy latency; dùng Lambda SnapStart cho Java |
| Kích thước package/image | Artifact càng lớn, tải và init càng chậm | Cắt dependency, tree-shake, dùng layer, tối giản container image |
| Code khởi tạo | Setup global nặng chạy mỗi cold start | Lazy-load thứ không luôn cần; giữ init global gọn nhưng được tái dùng |
| Gắn VPC | Từng rất chậm trên Lambda (đã cải thiện nhiều nhờ Hyperplane ENI) | Chỉ gắn VPC khi cần chạm resource private |
| Cấp phát memory | Nhiều memory = nhiều CPU = init nhanh hơn | Tăng memory nếu init nghẽn CPU |
| Idle timeout | Instance bị thu hồi sau khi rảnh | Provisioned Concurrency (Lambda), min-instances (Cloud Run), Premium plan (Azure) |
| Mô hình isolate | Workers bỏ qua hoàn toàn bước khởi động container/VM | Dùng edge runtime (Cloudflare Workers) cho latency cực thấp |
Khi nào cold start quan trọng và khi nào không. Với công việc bất đồng bộ và batch, cold start chẳng đáng bận tâm — không ai chờ. Với API nội bộ traffic ổn định, instance luôn ấm và cold start hiếm khi xảy ra. Cold start chỉ cắn ở các đường hướng người dùng, nhạy latency, đột biến — và đó đúng là nơi provisioned concurrency hay edge runtime xứng đáng đồng tiền. Đừng trả tiền provisioned concurrency cho một batch job chạy đêm.
Khái niệm chính
Mô hình chi phí
Bạn trả cho số request + thời gian chạy × memory, tính theo GB-giây:
AWS Lambda (us-east-1, x86, tham khảo):
$0.20 mỗi 1 triệu request
$0.0000166667 mỗi GB-giây
Ví dụ: 5 triệu request/tháng, 512 MB, trung bình 200 ms
Request: 5M × $0.20/1M = $1.00
Compute: 5M × 0.2s × 0.5 GB × $0.0000167 ≈ $8.33
Tổng ≈ $9.33/tháng (chưa trừ free tier)
Giờ hãy so cùng đoạn code đó ở lưu lượng cao, ổn định:
Cùng function ở 500 req/s liên tục (≈1.3 tỷ request/tháng), 200 ms, 512 MB:
Request: ~1.3B × $0.20/1M ≈ $260
Compute: ~1.3B × 0.2s × 0.5 GB × $0.0000167 ≈ $2,167
Tổng ≈ $2,400/tháng
Một cặp container/VM luôn bật xử lý cùng tải ổn định đó
có thể chỉ tốn một phần nhỏ số tiền này.
Đây chính là điểm giao cắt định nghĩa kinh tế serverless: rẻ khi nhiều thời gian rảnh và đột biến, đắt khi bận và ổn định. Đâu đó quanh mức “bận liên tục phần lớn thời gian trong ngày”, một container right-size hoặc VM reserved trở nên rẻ hơn. Hãy mô phỏng trước khi cam kết.
Để ý chi phí ẩn không xuất hiện trong công thức per-invocation: phí per-request của API Gateway (thường lớn hơn cả hóa đơn Lambda), ingest/lưu trữ log CloudWatch, phí NAT Gateway cho function gắn VPC ra internet, data transfer giữa các service, và số lần chuyển trạng thái của Step Functions. Dòng compute thường không phải con số lớn nhất trên hóa đơn serverless.
Concurrency: con số thực sự chi phối hành vi
Scaling serverless được hiểu rõ nhất qua concurrency — bao nhiêu instance chạy cùng lúc.
- Mặc định FaaS: một instance xử lý đúng một request tại một thời điểm. 1.000 request đồng thời → 1.000 instance. Dễ suy luận nhưng nhân đôi áp lực xuống downstream (xem vấn đề database bên dưới).
- Cloud Run / Container Apps: một instance xử lý nhiều request đồng thời (cấu hình được, ví dụ
--concurrency=80). Với API thiên I/O điều này rẻ hơn hẳn — một instance phục vụ 80 request thay vì bật lên 80 instance. - Reserved / maximum concurrency: giới hạn function scale tới đâu để bảo vệ downstream (database, API bên thứ ba bị rate-limit) khỏi bị một cơn bão scaling nhấn chìm.
Mô hình tư duy: serverless bỏ đi policy scaling của bạn nhưng không bỏ đi hậu quả scaling. Platform sẽ vui vẻ scale bạn lên 10.000 instance đồng thời, và cả 10.000 sẽ cùng lúc cố mở kết nối database.
Ví dụ: luồng Lambda + API Gateway
Client ──HTTPS──▶ API Gateway ──invoke──▶ Lambda ──▶ DynamoDB
│ │
└── auth (Cognito/JWT) └── log/trace → CloudWatch/X-Ray
Deploy cả stack bằng AWS SAM:
# template.yaml (AWS SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.handler
Runtime: python3.12
MemorySize: 256
Timeout: 10
Events:
Api:
Type: HttpApi
Properties:
Path: /hello
Method: GET
sam build && sam deploy --guided # đóng gói + deploy
sam local invoke HelloFunction # test local
sam logs -n HelloFunction --tail # stream log
Bức tranh framework deploy: AWS SAM và Serverless Framework tập trung vào FaaS và ngắn gọn; AWS CDK và Pulumi cho bạn dùng ngôn ngữ lập trình đa dụng; Terraform/OpenTofu là mặc định cloud-agnostic khi serverless chỉ là một phần của một estate lớn hơn. Với Cloud Run và serverless dạng container, một Dockerfile đơn giản cộng gcloud run deploy hoặc Terraform thường là đủ.
Serverless container
Cloud Run là ví dụ gọn gàng nhất: đưa cho nó bất kỳ container nào lắng nghe trên $PORT, nó sẽ lo TLS, autoscaling (kể cả về 0), revision và chia traffic cho canary release.
gcloud run deploy my-api \
--image=gcr.io/my-project/my-api:1.4.0 \
--region=asia-southeast1 \
--min-instances=0 --max-instances=20 \
--concurrency=80 \
--cpu=1 --memory=512Mi \
--allow-unauthenticated
So với FaaS, serverless container có:
- Không khóa framework — ngôn ngữ nào, binary nào, dependency nào cũng được; chạy được trong container là chạy được ở đây.
- Concurrency cao mỗi instance — một instance phục vụ nhiều request, nên API thiên I/O rẻ hơn nhiều so với FaaS một-request-mỗi-instance.
- Tính di động — cùng image chạy trên laptop của bạn, trên Cloud Run và trên Kubernetes, nên rời đi chỉ là redeploy, không phải viết lại. Điều này giảm mạnh lock-in so với FaaS, vốn có signature handler và định dạng event đặc thù vendor.
- Trải nghiệm dev local dễ hơn —
docker runtái hiện production trung thực hơn nhiều so với việc giả lập một event source của FaaS.
Đánh đổi là bạn sở hữu container (base image, vá lỗi, kích thước) và không có các tích hợp event-source native sâu vốn khiến Lambda mạnh cho glue code.
Khi nào nên và không nên dùng serverless
Phù hợp
- Traffic đột biến, khó dự đoán, hoặc trung bình thấp (không trả tiền khi rảnh)
- Glue code hướng event: xử lý file/ảnh, webhook, notification, job theo lịch/cron, pipeline kích hoạt bởi S3
- Prototype nhanh và team nhỏ không có nguồn lực vận hành
- Song song hóa theo đợt (fan-out 1.000 worker cho một batch job rồi scale về 0)
- Backend cho site JAMstack và app mobile có usage biến động
- Automation và “đường ống cloud” — phản ứng với event hạ tầng, ChatOps, dọn dẹp theo lịch
Không phù hợp
- Process chạy dài (> 15 phút trên Lambda), websocket quy mô cực lớn, server có state
- Đường đi cần latency cực thấp không chịu được cold start (trừ khi dùng edge runtime hoặc provisioned concurrency)
- Tải cao và ổn định 24/7 — VM/container reserved sẽ rẻ hơn đáng kể
- Workload cần GPU, memory rất lớn, hoặc phần cứng chuyên dụng (dù điều này đang dần thay đổi)
- Workload database nặng, nói chuyện nhiều làm cạn connection pool dưới mô hình một-request-mỗi-instance của FaaS
- Team cần tính di động tuyệt đối — API FaaS, trigger và định dạng event là đặc thù vendor (serverless container là lối thoát ở đây)
Tóm tắt thẳng thắn: serverless không phải mặc định và cũng không phải trào lưu — nó là một công cụ có điểm mạnh ở công việc hướng event và traffic biến động, nơi sự đơn giản vận hành xứng đáng để đánh đổi lấy ràng buộc. Với tay tới nó ở đó; dùng container/Kubernetes cho service thông lượng cao ổn định; và đừng để giáo điều “serverless-first” hay “serverless-never” quyết định thay bạn.
Best Practices
- Giữ function nhỏ, một nhiệm vụ — một trigger, một trách nhiệm; dễ test, deploy, bảo mật và suy luận về lỗi hơn một “Lambdalith” ôm đồm mọi thứ.
- Thiết kế handler idempotent — trigger bất đồng bộ và stream sẽ retry khi lỗi, và giao hàng at-least-once nghĩa là trùng lặp sẽ xảy ra; xử lý cùng một event hai lần phải an toàn (dùng idempotency key, conditional write, hoặc bảng dedup).
- Khởi tạo bên ngoài handler — tạo DB client, đối tượng SDK và load config ở global scope để các lần gọi ấm tái dùng; điều này giảm cả latency lẫn chi phí. Không bao giờ mở kết nối mới mỗi lần gọi.
- Đặt timeout và memory tường minh, và đo đạc — right-size cả hai; trên Lambda, nhiều memory cũng đồng nghĩa tỉ lệ thuận nhiều CPU, nên tăng lên có thể khiến function chạy xong nhanh hơn và rẻ hơn. Dùng AWS Lambda Power Tuning để tìm điểm tối ưu.
- Dùng dead-letter queue (DLQ) cho event bất đồng bộ — không bao giờ âm thầm bỏ rơi event lỗi; đưa chúng về nơi có thể kiểm tra và replay, và cảnh báo theo độ sâu DLQ.
- Tránh chuỗi function gọi function đồng bộ — bạn trả tiền cho thời gian chờ của cả hai, nhân đôi các kiểu lỗi và cộng dồn latency cold-start; hãy dùng queue, Step Functions, Durable Functions hoặc event bus.
- Lưu toàn bộ state ở bên ngoài — function là ephemeral và không chia sẻ gì; dùng DynamoDB/Redis/S3 cho state và Step Functions/Durable Functions cho state của workflow. Không bao giờ dựa vào disk local hay state trong bộ nhớ tồn tại giữa các lần gọi.
- Giữ package deploy gọn nhẹ — artifact nhỏ giúp cold start nhanh; cắt dependency, tree-shake, tách dependency nặng vào layer, hoặc chuyển sang container image mảnh.
- Bảo vệ downstream khỏi cơn bão scaling — function scale lên hàng nghìn instance sẽ cạn kiệt connection của database quan hệ; dùng reserved/maximum concurrency, RDS Proxy (hoặc connection pooler tương đương), và queue làm bộ đệm giữa tầng co giãn và tầng cố định capacity.
- Instrument mọi thứ bằng structured log và tracing — correlation ID và distributed tracing (X-Ray, OpenTelemetry, Application Insights) là bắt buộc khi một request đi qua API Gateway, một function, một queue và ba managed service; thiếu chúng, debug chỉ là đoán mò.
- Quản lý cold start có chủ đích, không phản xạ — chỉ dùng provisioned concurrency / min-instances trên đường hướng người dùng nhạy latency; để công việc batch và bất đồng bộ trên on-demand thường nơi cold start chẳng quan trọng.
- Áp least-privilege IAM cho từng function — mỗi function có role riêng giới hạn đúng resource nó chạm; một function bị xâm nhập hay có bug không được phép đọc mọi table trong account.
- Deploy qua IaC và CI/CD — SAM, Serverless Framework, CDK, Pulumi hoặc Terraform; không bao giờ sửa code function trực tiếp trên console, nếu không state đã deploy sẽ trôi khỏi source control và không còn tái tạo được.
- Theo dõi toàn bộ hóa đơn, không chỉ compute — tính cả API Gateway, ingest/giữ log, NAT Gateway và transfer giữa service; đặt retention cho log (log thô là chi phí âm thầm, không giới hạn) và budget alert.
- Ước lượng chi phí trước khi cam kết và đánh giá lại khi traffic tăng — tính request × thời gian chạy × memory so với một container nhỏ luôn bật; câu trả lời đúng thay đổi khi workload trưởng thành từ prototype đột biến sang service production ổn định.
- Ưu tiên serverless container khi cần tính di động hoặc concurrency cao — Cloud Run / Container Apps / Fargate giúp bạn tránh API handler đặc thù vendor và cho một instance phục vụ nhiều request, vừa rẻ hơn cho API thiên I/O vừa dễ migrate hơn.
Tài liệu tham khảo
- roadmap.sh — DevOps Roadmap
- AWS Lambda Documentation
- AWS Lambda operator guide — tối ưu hiệu năng
- AWS Lambda — Provisioned Concurrency
- AWS Lambda Power Tuning
- Azure Functions Documentation
- Azure Durable Functions
- Azure Container Apps Documentation
- Google Cloud Run Documentation
- Google Cloud Functions Documentation
- Google Cloud Run — Cold start và concurrency
- Cloudflare Workers Documentation
- AWS Serverless Application Model (SAM)
- AWS Step Functions Documentation
- Serverless Framework Documentation
- Serverless Land (pattern và ví dụ của AWS)
- Martin Fowler — Serverless Architectures (Mike Roberts)
- CNCF — Serverless Whitepaper
Part of the DevOps Roadmap knowledge base.
Overview
Serverless is a cloud execution model where the provider fully manages server provisioning, scaling, and maintenance, and you pay only for the compute actually consumed. “Serverless” doesn’t mean there are no servers — it means you don’t manage them. The best-known form is Functions as a Service (FaaS): small units of code triggered by events, exemplified by AWS Lambda, Azure Functions, Google Cloud Functions, and Cloudflare Workers. A newer branch, serverless containers (Google Cloud Run, AWS Fargate, Azure Container Apps), applies the same pay-per-use, scale-to-zero model to full container images.
For DevOps, serverless shifts the operational burden dramatically: no OS patching, no capacity planning, no idle servers. Scaling from zero to thousands of concurrent executions is the platform’s job. In exchange, you accept constraints — execution time limits, cold starts, vendor-specific APIs — and a different cost and debugging model.
Serverless shines for event-driven glue code, APIs with spiky or unpredictable traffic, and asynchronous processing pipelines. It’s a poor fit for long-running, latency-critical, or extremely high-volume steady workloads, where dedicated compute is cheaper and more predictable.
The two things “serverless” actually promises
Beneath the marketing, serverless makes two concrete promises that define the category:
- Scale to zero. When no requests arrive, you run nothing and pay nothing. A traditional VM or Kubernetes pod is always on, always billing. This is the property that makes serverless economically magical for idle-heavy workloads and economically terrible for constantly-busy ones.
- Automatic, request-proportional scaling. The platform adds capacity as load rises and removes it as load falls, with no autoscaling policy for you to tune. You never provision for peak; the platform does it per-request.
Everything else — the constraints, the cost model, the cold starts — flows from these two promises. When someone asks “is this workload a good fit for serverless?”, they are really asking “does this workload benefit from scaling to zero and per-request elasticity more than it suffers from the constraints those properties impose?”
Fundamentals
FaaS in a nutshell
- You write a function (a handler) and declare its trigger (HTTP request, queue message, file upload, schedule, database change).
- The platform packages, deploys, and runs it on demand — one instance per concurrent event by default.
- You’re billed per invocation and per compute time (GB-seconds), typically with a generous free tier.
# AWS Lambda handler (Python)
import json
# Runs ONCE per cold start — reuse expensive setup across warm invocations
db_client = create_db_client()
def handler(event, context):
name = event.get("queryStringParameters", {}).get("name", "world")
return {
"statusCode": 200,
"body": json.dumps({"message": f"Hello, {name}!"})
}
The key structural insight is in the comment: the handler runs on every invocation, but code outside the handler runs only on a cold start. Putting expensive initialization (DB clients, SDK objects, config loading) in global scope means warm invocations reuse it for free — one of the highest-leverage optimizations in serverless.
Major platforms
| Platform | Runtime model | Max duration | Notable traits |
|---|---|---|---|
| AWS Lambda | Functions (zip or container image up to 10 GB) | 15 min | Deepest event-source integration (S3, SQS, DynamoDB, Kinesis, EventBridge); SnapStart to cut JVM cold starts |
| Azure Functions | Functions, multiple hosting plans | 5–10 min (Consumption), unlimited (Premium/Dedicated) | Durable Functions for stateful orchestration; Flex Consumption plan |
| GCP Cloud Functions | Functions (now “Cloud Run functions”) | 9 min (gen1) / 60 min (gen2, HTTP) | Built on Cloud Run infrastructure since gen2 |
| GCP Cloud Run | Any container, request- or job-driven | 60 min (request), longer for jobs | Serverless containers; scale to zero; concurrency > 1 per instance |
| AWS Fargate | Containers behind ECS/EKS | No limit (long-running) | Serverless containers for always-on services; no scale-to-zero by itself |
| Azure Container Apps | Containers, built on KEDA/Dapr | No hard limit | Event-driven scaling, scale-to-zero, microservices-friendly |
| Cloudflare Workers | V8 isolates (JS/WASM) at the edge | CPU-time limited (ms–minutes) | Near-zero cold start, runs in 300+ edge locations, no per-region deploy |
Two families, different trade-offs. The FaaS family (Lambda, Cloud/Azure Functions, Workers) optimizes for tiny event-driven units and the finest-grained billing. The serverless-container family (Cloud Run, Fargate, Container Apps) optimizes for portability (any language/binary, no framework lock-in) and for one instance serving many concurrent requests. Workers are a third thing entirely: edge isolates that trade the full Node/OS environment for near-zero cold start and global distribution.
Event-driven architecture
Serverless functions are natural consumers in event-driven systems:
- Synchronous (request/response): API Gateway → Lambda → response (client waits). Errors surface directly to the caller; there is no automatic retry.
- Asynchronous (fire-and-forget): S3 upload event → Lambda resizes image. The platform queues the event and retries automatically on failure (Lambda retries twice by default), with a dead-letter queue for poison messages.
- Stream/queue-based (poll): SQS/Kinesis/Pub/Sub → function processes batches; the platform manages polling, batching, checkpointing, and ordering guarantees (per-shard for Kinesis, per-message-group for FIFO SQS).
These three invocation types behave differently on failure, retries, and ordering — a distinction that trips up newcomers. Reaching for the wrong one (e.g., calling a slow downstream synchronously when it should be queued) is a common design mistake.
Event-driven design decouples producers from consumers: services communicate through events, scale independently, and failures are isolated and retryable. Orchestration tools stitch functions into workflows: AWS Step Functions, Azure Durable Functions, and Google Workflows let you express retries, branching, parallelism, and human-approval steps declaratively instead of chaining functions by hand.
Cold starts
A cold start happens when the platform must create a new execution environment: allocate a sandbox, download code, start the runtime, and run your initialization code. It adds latency (tens of milliseconds to several seconds) to the first request served by that instance. Subsequent requests to the same warm instance skip all of this.
The lifecycle: cold start → warm invocations → idle → reclaimed. There is no way to pin an instance warm forever on consumption plans; the platform reclaims idle instances to free capacity. This is why steady low traffic can paradoxically feel slower than steady high traffic — low traffic means instances go idle and get reclaimed between requests.
| Factor | Effect | Mitigation |
|---|---|---|
| Runtime choice | JVM/.NET slower to boot; Node/Python/Go faster; Rust/Go compiled binaries fastest | Prefer lightweight runtimes for latency-sensitive paths; use Lambda SnapStart for Java |
| Package/image size | Bigger artifact = slower download & init | Trim dependencies, tree-shake, use layers, minimize container images |
| Initialization code | Heavy global setup runs on every cold start | Lazy-load what isn’t always needed; keep global init lean but reused |
| VPC attachment | Historically slow on Lambda (much improved with Hyperplane ENIs) | Only attach a VPC when you must reach private resources |
| Memory allocation | More memory = more CPU = faster init | Right-size upward if init is CPU-bound |
| Idle timeout | Instances reclaimed after idle | Provisioned Concurrency (Lambda), min-instances (Cloud Run), Premium plan (Azure) |
| Isolate model | Workers avoid container/VM start entirely | Use edge runtimes (Cloudflare Workers) for ultra-low latency |
When cold starts do and don’t matter. For asynchronous and batch work, cold starts are irrelevant — nobody is waiting. For internal APIs with steady traffic, instances stay warm and cold starts are rare. Cold starts only bite on user-facing, latency-sensitive, spiky paths — and that’s exactly where provisioned concurrency or an edge runtime earns its keep. Don’t pay for provisioned concurrency on a nightly batch job.
Key Concepts
Cost model
You pay for requests + duration × memory, billed in GB-seconds:
AWS Lambda (us-east-1, x86, indicative):
$0.20 per 1M requests
$0.0000166667 per GB-second
Example: 5M requests/month, 512 MB, 200 ms average
Requests: 5M × $0.20/1M = $1.00
Compute: 5M × 0.2s × 0.5 GB × $0.0000167 ≈ $8.33
Total ≈ $9.33/month (before free tier)
Now contrast the same code at high, constant volume:
Same function at 500 req/s sustained (≈1.3B requests/month), 200 ms, 512 MB:
Requests: ~1.3B × $0.20/1M ≈ $260
Compute: ~1.3B × 0.2s × 0.5 GB × $0.0000167 ≈ $2,167
Total ≈ $2,400/month
A pair of always-on containers/VMs handling the same steady load
could cost a fraction of that.
This is the crossover that defines serverless economics: cheap when idle-heavy and spiky, expensive when busy and steady. Somewhere around “consistently busy a large fraction of the day,” a right-sized container or reserved VM becomes cheaper. Model it before committing.
Watch the hidden costs that don’t show up in the per-invocation math: API Gateway per-request fees (often larger than the Lambda bill itself), CloudWatch/log ingestion and storage, NAT Gateway charges for VPC-attached functions reaching the internet, cross-service data transfer, and Step Functions state transitions. The compute line is frequently not the biggest number on a serverless bill.
Concurrency: the number that actually governs behavior
Serverless scaling is best understood through concurrency — how many instances run at once.
- FaaS default: one instance handles exactly one request at a time. 1,000 simultaneous requests → 1,000 instances. This is simple to reason about but multiplies downstream pressure (see the database problem below).
- Cloud Run / Container Apps: one instance can handle many concurrent requests (configurable, e.g.,
--concurrency=80). For I/O-bound APIs this is dramatically cheaper — one instance serves 80 requests instead of spinning up 80. - Reserved / maximum concurrency: cap how far a function can scale to protect downstream systems (a database, a rate-limited third-party API) from being overwhelmed by a scaling storm.
The mental model: serverless removes your scaling policy but not your scaling consequences. The platform will happily scale you to 10,000 concurrent instances, and all 10,000 will try to open a database connection at once.
Example: Lambda + API Gateway flow
Client ──HTTPS──▶ API Gateway ──invoke──▶ Lambda ──▶ DynamoDB
│ │
└── auth (Cognito/JWT) └── logs/traces → CloudWatch/X-Ray
Deploying the whole stack with AWS SAM:
# template.yaml (AWS SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.handler
Runtime: python3.12
MemorySize: 256
Timeout: 10
Events:
Api:
Type: HttpApi
Properties:
Path: /hello
Method: GET
sam build && sam deploy --guided # package + deploy
sam local invoke HelloFunction # test locally
sam logs -n HelloFunction --tail # stream logs
Deployment framework landscape: AWS SAM and the Serverless Framework are FaaS-focused and terse; AWS CDK and Pulumi give you general-purpose languages; Terraform/OpenTofu is the cloud-agnostic default when serverless is one part of a larger estate. For Cloud Run and container-based serverless, a plain Dockerfile plus gcloud run deploy or Terraform is usually enough.
Serverless containers
Cloud Run is the cleanest example: give it any container listening on $PORT, and it handles TLS, autoscaling (including to zero), revisions, and traffic-splitting for canary releases.
gcloud run deploy my-api \
--image=gcr.io/my-project/my-api:1.4.0 \
--region=asia-southeast1 \
--min-instances=0 --max-instances=20 \
--concurrency=80 \
--cpu=1 --memory=512Mi \
--allow-unauthenticated
Compared to FaaS, serverless containers offer:
- No framework lock-in — any language, any binary, any dependency; if it runs in a container, it runs here.
- High concurrency per instance — one instance serves many requests, so I/O-bound APIs cost far less than one-request-per-instance FaaS.
- Portability — the same image runs on your laptop, in Cloud Run, and in Kubernetes, so migrating off is a redeploy, not a rewrite. This dramatically reduces lock-in versus FaaS, whose handler signatures and event formats are vendor-specific.
- A gentler local-dev story —
docker runreproduces production far more faithfully than emulating a FaaS event source.
The trade-off is that you own the container (base image, patching, size) and you don’t get the deep native event-source integrations that make Lambda so powerful for glue code.
When serverless fits — and when it doesn’t
Good fit
- Spiky, unpredictable, or low-average traffic (pay nothing when idle)
- Event-driven glue: file/image processing, webhooks, notifications, scheduled/cron jobs, S3-triggered pipelines
- Rapid prototyping and small teams without ops capacity
- Burst parallelism (fan out 1,000 workers for a batch job, then scale to zero)
- Backends for JAMstack sites and mobile apps with variable usage
- Automation and “cloud plumbing” — reacting to infrastructure events, ChatOps, scheduled cleanup
Poor fit
- Long-running processes (> 15 min on Lambda), massive-scale websockets, stateful servers
- Ultra-low-latency paths that can’t tolerate cold starts (unless using edge runtimes or provisioned concurrency)
- High, steady 24/7 load — reserved VMs/containers become materially cheaper
- Workloads needing GPUs, very large memory, or specialized hardware (though this is slowly changing)
- Heavy, chatty database workloads that would exhaust connection pools under FaaS’s one-request-per-instance model
- Teams that require full portability — FaaS APIs, triggers, and event formats are vendor-specific (serverless containers are the escape hatch here)
The honest summary: serverless is not a default and not a fad — it’s a tool whose sweet spot is event-driven work and variable traffic where operational simplicity is worth accepting constraints. Reach for it there; reach for containers/Kubernetes for steady high-throughput services; and don’t let “serverless-first” or “serverless-never” dogma make the decision for you.
Best Practices
- Keep functions small and single-purpose — one trigger, one responsibility; easier to test, deploy, secure, and reason about failure modes than a “Lambdalith” that does everything.
- Make handlers idempotent — async and stream triggers retry on failure, and at-least-once delivery means duplicates happen; processing the same event twice must be safe (use idempotency keys, conditional writes, or dedup tables).
- Initialize outside the handler — create DB clients, SDK objects, and load config in global scope so warm invocations reuse them; this cuts both latency and cost. Never open a fresh connection per invocation.
- Set explicit timeouts and memory, and measure — right-size both; on Lambda more memory also means proportionally more CPU, so a bump can make a function finish faster and cost less. Use AWS Lambda Power Tuning to find the sweet spot.
- Use dead-letter queues (DLQs) for async events — never silently drop failed events; route them somewhere inspectable and replayable, and alert on DLQ depth.
- Avoid synchronous function-to-function chains — you pay for both functions’ wall time, multiply failure modes, and compound cold-start latency; use queues, Step Functions, Durable Functions, or an event bus instead.
- Store all state externally — functions are ephemeral and share nothing; use DynamoDB/Redis/S3 for state and Step Functions/Durable Functions for workflow state. Never rely on local disk or in-memory state surviving between invocations.
- Keep deployment packages lean — smaller artifacts mean faster cold starts; trim dependencies, tree-shake, split heavy deps into layers, or move to a slim container image.
- Guard downstream systems against scaling storms — a function scaling to thousands of instances will exhaust a relational database’s connections; use reserved/maximum concurrency, RDS Proxy (or equivalent connection pooler), and queues as buffers between the elastic tier and the fixed-capacity tier.
- Instrument everything with structured logs and tracing — correlation IDs and distributed tracing (X-Ray, OpenTelemetry, Application Insights) are essential when a single request crosses API Gateway, a function, a queue, and three managed services; without them, debugging is guesswork.
- Manage cold starts deliberately, not reflexively — use provisioned concurrency / min-instances only on user-facing latency-sensitive paths; leave batch and async work on plain on-demand where cold starts don’t matter.
- Apply least-privilege IAM per function — give each function its own role scoped to exactly the resources it touches; a compromised or buggy function should not be able to read every table in the account.
- Deploy via IaC and CI/CD — SAM, Serverless Framework, CDK, Pulumi, or Terraform; never hand-edit function code in the console, or your deployed state drifts from source control and becomes unreproducible.
- Watch the whole bill, not just compute — account for API Gateway, log ingestion/retention, NAT Gateway, and inter-service transfer; set log retention (raw logs are a silent, unbounded cost) and budget alerts.
- Model costs before committing and re-evaluate as traffic grows — estimate requests × duration × memory versus a small always-on container; the right answer changes as a workload matures from spiky prototype to steady production service.
- Prefer serverless containers when portability or high concurrency matters — Cloud Run / Container Apps / Fargate keep you off vendor-specific handler APIs and let one instance serve many requests, which is both cheaper for I/O-bound APIs and easier to migrate.
References
- roadmap.sh — DevOps Roadmap
- AWS Lambda Documentation
- AWS Lambda operator guide — performance optimization
- AWS Lambda — Provisioned Concurrency
- AWS Lambda Power Tuning
- Azure Functions Documentation
- Azure Durable Functions
- Azure Container Apps Documentation
- Google Cloud Run Documentation
- Google Cloud Functions Documentation
- Google Cloud Run — Cold start and concurrency
- Cloudflare Workers Documentation
- AWS Serverless Application Model (SAM)
- AWS Step Functions Documentation
- Serverless Framework Documentation
- Serverless Land (AWS patterns and examples)
- Martin Fowler — Serverless Architectures (Mike Roberts)
- CNCF — Serverless Whitepaper