← DevOps← DevOps
DevOpsDevOps18 Th7, 2026Jul 18, 202618 phút đọc15 min read

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:

  1. 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.
  2. 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

  1. 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).
  2. 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.
  3. 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

PlatformMô hình runtimeThời gian chạy tối đaĐặc điểm nổi bật
AWS LambdaFunction (zip hoặc container image tới 10 GB)15 phútTích hợp event source sâu nhất (S3, SQS, DynamoDB, Kinesis, EventBridge); SnapStart giảm cold start cho JVM
Azure FunctionsFunction, nhiều hosting plan5–10 phút (Consumption), không giới hạn (Premium/Dedicated)Durable Functions cho orchestration có state; Flex Consumption plan
GCP Cloud FunctionsFunction (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 RunBất kỳ container nào, theo request hoặc job60 phút (request), dài hơn cho jobServerless container; scale về 0; concurrency > 1 mỗi instance
AWS FargateContainer sau ECS/EKSKhô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 AppsContainer, xây trên KEDA/DaprKhông giới hạn cứngScaling hướng event, scale về 0, thân thiện microservice
Cloudflare WorkersV8 isolate (JS/WASM) tại edgeGiớ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:

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 FunctionsGoogle 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ưởngCách giảm
RuntimeJVM/.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/imageArtifact càng lớn, tải và init càng chậmCắt dependency, tree-shake, dùng layer, tối giản container image
Code khởi tạoSetup global nặng chạy mỗi cold startLazy-load thứ không luôn cần; giữ init global gọn nhưng được tái dùng
Gắn VPCTừ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 memoryNhiều memory = nhiều CPU = init nhanh hơnTăng memory nếu init nghẽn CPU
Idle timeoutInstance bị thu hồi sau khi rảnhProvisioned Concurrency (Lambda), min-instances (Cloud Run), Premium plan (Azure)
Mô hình isolateWorkers bỏ qua hoàn toàn bước khởi động container/VMDù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ô 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 CDKPulumi 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ó:

Đá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

Không phù hợp

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

  1. 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ứ.
  2. 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).
  3. 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.
  4. Đặ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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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ò.
  11. 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.
  12. Á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.
  13. 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.
  14. 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.
  15. Ướ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.
  16. Ư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

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:

  1. 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.
  2. 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

  1. You write a function (a handler) and declare its trigger (HTTP request, queue message, file upload, schedule, database change).
  2. The platform packages, deploys, and runs it on demand — one instance per concurrent event by default.
  3. 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

PlatformRuntime modelMax durationNotable traits
AWS LambdaFunctions (zip or container image up to 10 GB)15 minDeepest event-source integration (S3, SQS, DynamoDB, Kinesis, EventBridge); SnapStart to cut JVM cold starts
Azure FunctionsFunctions, multiple hosting plans5–10 min (Consumption), unlimited (Premium/Dedicated)Durable Functions for stateful orchestration; Flex Consumption plan
GCP Cloud FunctionsFunctions (now “Cloud Run functions”)9 min (gen1) / 60 min (gen2, HTTP)Built on Cloud Run infrastructure since gen2
GCP Cloud RunAny container, request- or job-driven60 min (request), longer for jobsServerless containers; scale to zero; concurrency > 1 per instance
AWS FargateContainers behind ECS/EKSNo limit (long-running)Serverless containers for always-on services; no scale-to-zero by itself
Azure Container AppsContainers, built on KEDA/DaprNo hard limitEvent-driven scaling, scale-to-zero, microservices-friendly
Cloudflare WorkersV8 isolates (JS/WASM) at the edgeCPU-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:

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.

FactorEffectMitigation
Runtime choiceJVM/.NET slower to boot; Node/Python/Go faster; Rust/Go compiled binaries fastestPrefer lightweight runtimes for latency-sensitive paths; use Lambda SnapStart for Java
Package/image sizeBigger artifact = slower download & initTrim dependencies, tree-shake, use layers, minimize container images
Initialization codeHeavy global setup runs on every cold startLazy-load what isn’t always needed; keep global init lean but reused
VPC attachmentHistorically slow on Lambda (much improved with Hyperplane ENIs)Only attach a VPC when you must reach private resources
Memory allocationMore memory = more CPU = faster initRight-size upward if init is CPU-bound
Idle timeoutInstances reclaimed after idleProvisioned Concurrency (Lambda), min-instances (Cloud Run), Premium plan (Azure)
Isolate modelWorkers avoid container/VM start entirelyUse 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.

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:

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

Poor fit

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

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. Use dead-letter queues (DLQs) for async events — never silently drop failed events; route them somewhere inspectable and replayable, and alert on DLQ depth.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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