Post 03 scaled an EC2 fleet behind a load balancer; Post 04 added managed data behind it. Most modern AWS architectures are not all-server, all-the-time — large parts of them run as events flowing through serverless glue. This post covers the building blocks: Lambda (run code on demand), API Gateway / Function URLs (turn Lambda into HTTP), SQS (queues for decoupling), SNS (pub/sub fanout), and EventBridge (the bus for routed events). Combined, they let you build entire services with no servers to manage and bills proportional to traffic.

Series map: 00 Foundations → 01 Network First → 02 Compute & Storage → 03 Scale & Survive → 04 Managed Data → 05 Serverless & Events (this post) → 06 Workflow Orchestration → 07 Observability & Security → 08 IaC.


Learning Outcome

  • Write and deploy a Lambda function (Python or Node).
  • Configure Lambda memory, timeout, environment, IAM role, layers.
  • Expose Lambda over HTTP via API Gateway or Function URLs.
  • Use SQS for async work decoupling and back-pressure.
  • Use SNS for fanout to multiple subscribers.
  • Use EventBridge for cross-service event routing and SaaS integrations.
  • Identify cold-start sources and mitigation patterns.

Chapter 1 — Lambda essentials

A Lambda function is a piece of code (handler) plus configuration (memory, timeout, role, env vars) that AWS runs in response to an event. Billing is per-request plus per-GB-second of execution time.

Minimal Python Lambda

1
2
3
4
def lambda_handler(event, context):
name = event.get("name", "world")
return {"statusCode": 200, "body": f"Hello, {name}!"}

Package and deploy:

1
2
3
4
5
6
7
8
9
10
11
12
13
zip -j function.zip lambda_handler.py
aws lambda create-function \
--function-name hello \
--runtime python3.12 \
--role arn:aws:iam::123456789012:role/lambda-basic-exec \
--handler lambda_handler.lambda_handler \
--zip-file fileb://function.zip

aws lambda invoke \
--function-name hello \
--payload '{"name":"Ada"}' \
--cli-binary-format raw-in-base64-out out.json

Knobs that matter

Knob Range Note
Memory 128 MB - 10,240 MB CPU scales with memory — doubling memory ≈ doubles speed
Timeout 1 sec - 15 min killed if exceeded
Ephemeral storage 512 MB - 10 GB at /tmp persists across warm invocations
Concurrency up to 1000+ by default reserved / provisioned controls
VPC none (default) or attached only attach if you need VPC resources

Trick: memory is the most important tuning knob. Tools like Lambda Power Tuning test multiple memory sizes and recommend the sweet spot — usually faster AND cheaper than the 128 MB default.


Chapter 2 — Cold starts

When AWS provisions a fresh container for a function, the cold start cost is:

  1. Download the zip / image.
  2. Initialise the runtime.
  3. Run module-level / static code.
  4. Invoke the handler.

After the first invoke, the container stays warm a few minutes — subsequent calls skip steps 1-3.

Runtime Typical cold start
Python / Node 100-500 ms
Go / Rust (compiled) 50-200 ms
Java 300 ms - 2 s
.NET 200 ms - 1 s
Container image 0.5 - 3 s

Mitigation

  • Provisioned Concurrency — keep N warm containers ready; always-on cost.
  • SnapStart (Java) — snapshot init state; restore in ~1/10 the time.
  • Smaller package — fewer dependencies, faster init.
  • Avoid VPC unless needed — ENI attach adds 100-500 ms.
  • Move heavy setup to module level — reused across warm invocations.
1
2
3
4
5
6
7
import boto3
# initialised once per cold start, reused on warm calls
s3 = boto3.client("s3")

def lambda_handler(event, context):
s3.get_object(...)

Chapter 3 — Event sources

Lambdas don't run on a schedule by default — they're invoked by event sources.

Source Pattern
API Gateway / Function URL / ALB sync HTTP → Lambda → HTTP response
S3 new object created / deleted → Lambda
EventBridge schedule cron / rate → Lambda
EventBridge events AWS API event → Lambda
SQS poll a queue → Lambda
SNS publish → Lambda
DynamoDB Streams row change → Lambda
Kinesis stream record → Lambda
CloudWatch Logs subscription log line → Lambda
Direct invoke (SDK / CLI) sync or async call

Sync vs async vs polling

  • Sync — caller waits for response (API Gateway, Function URL, direct RequestResponse invoke).
  • Async — Lambda returns immediately, processes in background; failures retried twice + optional DLQ (S3, SNS, EventBridge).
  • Polling — Lambda runtime polls SQS / Kinesis / DynamoDB Streams in batches.

Three ways an event reaches a Lambda: sync (the caller waits), async (retried twice, then an optional DLQ) and polling SQS in batches with a visibility timeout and a DLQ


Chapter 4 — API Gateway and Function URLs

Two ways to put HTTP in front of Lambda:

API Gateway HTTP API

  • Custom domain + TLS via ACM.
  • Authn (JWT, Cognito, IAM, Lambda authoriser).
  • Throttling, usage plans, API keys.
  • Per-route Lambda integration or HTTP/private integration.
  • ~USD 1 per million requests + data transfer.

Minimal wire-up: create HTTP API → add route ANY /{proxy+} → Lambda integration → deploy → get an execute-api.amazonaws.com URL.

Lambda Function URL

  • One HTTPS endpoint per Lambda.
  • No API Gateway in between (cheaper, simpler).
  • No built-in WAF, custom domain (manual via CloudFront), or throttling.
  • Best for internal tools, webhooks, prototypes.
1
2
3
4
aws lambda create-function-url-config \
--function-name hello \
--auth-type NONE

REST API (older)

The older "REST API" flavour exists for compatibility (request validation, models, X-Ray detail). For new projects in 2026, HTTP API is the right default.


Chapter 5 — SQS (Simple Queue Service)

SQS is a managed message queue. Producers send-message, consumers (often Lambda) receive-message + delete-message after success.

Two queue types:

Type Order Duplicates Use
Standard best-effort possible most async work
FIFO strict per group exactly-once order-sensitive workflows

Lambda + SQS pattern

1
2
[API or Service A] → SQS Standard → [Lambda processes in batches]

Benefits:

  • Decoupling — A returns immediately; processing runs in background.
  • Back-pressure — if Lambda concurrency caps, messages sit in queue (no data loss).
  • Retries — Lambda errors keep message visible; goes to DLQ after N attempts.
  • Batch processing — Lambda receives up to 10 messages at once.

Visibility timeout

When a consumer receives a message, it becomes invisible to others for the visibility timeout (default 30 sec). If not deleted by then, the message reappears for another consumer. Set this to at least 2× your processing time.

Dead Letter Queue (DLQ)

Configure a DLQ on a source queue. After N receive attempts (typical: 3-5), the message moves to the DLQ for inspection. Alarm on DLQ message count > 0 — that means production has a stuck pattern.


Chapter 6 — SNS (Simple Notification Service)

SNS is pub/sub fanout — a publisher sends one message to a topic; AWS delivers a copy to every subscriber.

Subscriber types:

  • SQS queue
  • Lambda
  • HTTPS endpoint
  • Email / SMS
  • Mobile push
  • Kinesis Firehose

Common fanout pattern

1
2
3
4
5
6
7
8
9
[Order Service]
│ publish "order_placed"
▼
[SNS topic]
├── → SQS for inventory service
├── → SQS for email service
├── → Lambda for analytics
└── → SQS for fulfilment service

Each subscriber gets its own copy. Adding a new subscriber doesn't require changing the publisher.

Filter policies

Subscribers can filter by message attributes — only receive a subset:

1
2
{ "event_type": ["order_placed"], "region": ["ap-east-1"] }

Chapter 7 — EventBridge (the bus)

EventBridge is a richer event router than SNS:

Trait SNS EventBridge
Fanout yes yes
Filter by attributes by content (JSON match)
Schedule no yes (cron / rate)
SaaS integrations no many (Datadog, Zendesk, GitHub via partner sources)
AWS event sources no every AWS service auto-publishes
Schema registry no yes
Latency < 100 ms < 500 ms
Cost per published message per matched rule

Bus types

  • Default bus — every AWS API event lands here automatically. React to "EC2 state change", "S3 object created", "RDS failover", etc.
  • Custom bus — your own events.
  • Partner bus — events from SaaS providers (Datadog, Auth0, Zendesk).

Targets

A rule routes matching events to targets:

  • Lambda
  • Step Functions
  • SQS
  • SNS
  • Kinesis
  • API destinations (any HTTPS endpoint)
  • Cross-account / cross-region buses

Scheduled rules

The cleanest way to run "every 5 minutes" or "every weekday 09:00 HKT":

1
2
3
4
5
6
7
8
aws events put-rule \
--name nightly-report \
--schedule-expression "cron(0 1 * * ? *)"

aws events put-targets \
--rule nightly-report \
--targets "Id=1,Arn=arn:aws:lambda:..."

In 2026, the newer EventBridge Scheduler service is preferred for pure schedules — better scale, more options (time zones, one-time, payload customisation).

Trick: SNS for simple fanout (one source → many same-message subscribers). EventBridge for routing different events to different places, especially when AWS services are the source or you want content-based filtering. Both are cheap; pick by access pattern, not cost.


Chapter 8 — A small end-to-end serverless service

Pattern: "user uploads image → resize → notify uploader".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Browser]
│ PUT pre-signed S3 URL
▼
[S3 bucket]
│ s3:ObjectCreated event
▼
[Lambda: resize]
│ writes thumbnail back to S3
│ publishes to SNS topic "image_processed"
▼
[SNS]
├── [Lambda: send email via SES]
└── [SQS: analytics queue]
▼
[Lambda: write event to DynamoDB]

Components:

  • S3 bucket with event notification → resize Lambda.
  • Resize Lambda uses Pillow (Python image lib) — package as a Lambda Layer.
  • SNS topic with two subscribers.
  • DynamoDB table for analytics.
  • All wired with IAM roles scoped to least-privilege.

This entire architecture has zero servers to manage and bills only when an image is uploaded.


Chapter 9 — Limits worth remembering

Limit Value
Lambda timeout 15 min
Lambda memory 10 GB
Lambda zip package 50 MB direct, 250 MB unzipped
Lambda container image 10 GB
Lambda concurrency (default) 1000 / region / account
Lambda sync payload 6 MB
Lambda async payload 256 KB
Lambda /tmp 10 GB
SQS message size 256 KB (link to S3 for bigger)
SQS retention 14 days max
SNS message size 256 KB
EventBridge event size 256 KB
API Gateway timeout 29 sec (sync)

Chapter 10 — When NOT to use serverless

  • Long-running CPU jobs (> 15 min) — use Step Functions (Post 06), ECS Fargate, or AWS Batch.
  • Steady high-throughput services where always-on EC2 / Fargate is cheaper than per-request Lambda billing.
  • Cold-start-sensitive workloads where even 100 ms hurts and Provisioned Concurrency cost wipes the savings.
  • Heavy WebSocket / persistent connections — use API Gateway WebSocket + Lambda or ECS.

Serverless wins when traffic is spiky, glue-shaped, or genuinely event-driven. Steady-state high-CPU workloads belong on Fargate / EC2.


Practice Exercises

Exercise 1 — Hello Lambda

Create + invoke a Python Lambda that echoes the event. Test with the CLI.

Exercise 2 — HTTP API + Lambda

Wrap Exercise 1 in API Gateway HTTP API. Hit the URL with curl. Add a POST /echo route that returns the JSON body.

Exercise 3 — S3 thumbnail trigger

Create an S3 bucket; a Lambda triggered on s3:ObjectCreated:* that, given an image, writes a half-size thumbnail to a sibling bucket. Use a Lambda Layer for Pillow.

Exercise 4 — SQS + Lambda batch

Create an SQS queue + a Lambda triggered by it. A simple producer script enqueues 100 jobs. Verify Lambda processes them in batches, set the DLQ, force a few failures and confirm they land in the DLQ after retries.

Exercise 5 — SNS fanout

Create an SNS topic; subscribe a Lambda + an SQS queue. Publish a message. Confirm both receive the message. Add a filter policy so the queue receives only messages with priority == "high".

Exercise 6 — EventBridge schedule

Create an EventBridge schedule that triggers a Lambda every 5 minutes. The Lambda fetches https://api.github.com/repos/<owner>/<repo> and writes the stargazer count to DynamoDB.


Cheat-sheet

What How
Create Lambda aws lambda create-function ...
Invoke aws lambda invoke --function-name X out.json
Tail logs aws logs tail /aws/lambda/X --follow
Memory tunes CPU bigger = faster + (often) cheaper
Skip VPC unless needed avoid cold-start ENI attach
HTTP wrapper API Gateway HTTP API
Tiny HTTP endpoint Lambda Function URL
Async decoupling SQS Standard + Lambda
Order-sensitive SQS FIFO
Visibility timeout ≥ 2× processing time
DLQ alarm on DLQ count > 0
Pub/sub fanout SNS topic + subscribers
Filter subscriptions SNS filter policy
Cross-service routing EventBridge bus + rules
AWS-event source EventBridge default bus
Scheduled job EventBridge Scheduler

What's next

Post 06 covers Step Functions — the state-machine service that orchestrates multiple Lambdas / API calls / human steps into a workflow with retries, branching, parallelism, and the saga pattern for distributed transactions.


References

Note: this post is in progress. Planned additions: WebSocket APIs, EventBridge Pipes, Kinesis vs SQS decision tree, container-image Lambdas.