# Lambda and Serverless — Part 2: Building Serverless Applications

## 1. Common Architectures

```
API:          client → API Gateway (HTTP API) → Lambda → DynamoDB
async work:   S3 upload → Lambda (thumbnail) → S3      |   API → SQS → Lambda workers
events:       service → EventBridge bus → rules → Lambda / Step Functions / SQS
streams:      DynamoDB Streams / Kinesis → Lambda (batches, ordered per shard)
scheduled:    EventBridge Scheduler (cron/rate) → Lambda
orchestration: Step Functions state machine calling Lambdas and AWS APIs directly  (20-eventbridge_and_step_functions/)
```

**Serverless** = no servers to manage, scale to zero, pay per use: Lambda, API Gateway, DynamoDB, SQS/SNS, EventBridge, Step Functions, S3, Aurora Serverless, Fargate (container-level).

## 2. Writing Good Functions

- **Small, single-purpose** handlers; shared code in libraries/layers.
- **Idempotent**: async and poll-based invocations are at-least-once; use idempotency keys (e.g. Powertools Idempotency with a DynamoDB table — `distributed-systems/message-brokers/09-delivery_guarantees`).
- **Validate input** at the edge (API Gateway request validation or in the handler).
- **Structured logs** with correlation IDs, metrics via EMF, traces via X-Ray/OpenTelemetry — **Powertools for AWS Lambda** (TS/Python/Java/.NET) provides logger, metrics, tracer, idempotency, batch processing, parameters.
- **Don't wait in Lambda**: polling loops and long sleeps burn money; use Step Functions waits or SQS delays.
- **Return early from API handlers**; offload slow work to queues.

## 3. Error Handling by Invocation Type

| Type | Failure behaviour | Configure |
|---|---|---|
| sync | error returned to caller | caller retries, proper status codes |
| async | 2 retries with backoff, then dropped unless configured | `maximum_retry_attempts`, `maximum_event_age`, **on-failure destination** (SQS/SNS/EventBridge/Lambda) |
| SQS | message becomes visible again; redrive to DLQ after maxReceiveCount | partial batch failures, visibility timeout ≥ 6 × function timeout |
| Kinesis/Streams | batch retried until success or expiry — **blocks the shard** | `bisect_batch_on_function_error`, `maximum_retry_attempts`, on-failure destination |

## 4. Packaging and Deployment

- Bundle with **esbuild** (tree-shaken single file, minified) — smaller packages = faster cold starts.
- **Versions** (immutable snapshots) and **aliases** (`live` → version 42) enable safe rollouts: CodeDeploy can shift alias traffic gradually (canary 10% for 5 min → 100%) with automatic rollback on CloudWatch alarms.
- IaC: SAM, CDK (`22-cloudformation_and_cdk/`), Terraform (`devops-cloud/terraform/`), Serverless Framework.
- **Container images** (up to 10 GB) for large dependencies (ML libraries).

## 5. Cost Model

```
cost = requests × $0.20 per million + GB-seconds × ~$0.0000166667 (x86; arm64 ~20% less)
example: 50M requests/month, 120 ms avg, 512 MB → 50M × 0.12 s × 0.5 GB = 3,000,000 GB-s ≈ $50 + $10 requests
```

Lambda is very cheap for spiky or low-to-moderate traffic; at sustained high throughput, containers (Fargate/ECS/EKS) or EC2 can be cheaper. Also count API Gateway, data transfer, CloudWatch Logs ingestion (often the surprise line item) and NAT gateway data processing for VPC functions.

## 6. When Not to Use Lambda

- Long-running work (> 15 min) or steady CPU-heavy workloads → containers.
- Ultra-low, consistent latency requirements where cold starts hurt (or pay for provisioned concurrency).
- Workloads needing persistent connections per process (WebSockets via API Gateway WebSocket APIs is possible but different), large local state, or GPUs.
- Chatty access to relational DBs without RDS Proxy.

## Under the Hood: How Event Source Mappings Poll SQS

For SQS, Lambda runs **pollers** on your behalf (you don't pay for them): they long-poll the queue, gather up to `batch_size` messages (or wait up to `maximum_batching_window_in_seconds`), and invoke your function synchronously with the batch. On success the poller deletes the messages; on error they stay invisible until the visibility timeout expires and are redelivered — or, with `ReportBatchItemFailures`, only the reported message IDs are left for redelivery. The number of pollers scales up with queue depth (adding concurrency over time) and down when invocations throttle or error, and **maximum concurrency** on the mapping caps how many concurrent invocations the queue can drive — the right knob to protect a downstream database, rather than reserved concurrency (which would throttle pollers and push messages back to the queue).

![Serverless building blocks: API Gateway, S3, SQS, EventBridge and streams invoke Lambda synchronously, asynchronously or through polling; failures go to destinations or DLQs; versions and aliases with gradual traffic shifting deploy safely; cost is requests plus GB-seconds](diagrams/02-serverless-patterns.svg)

## Common Pitfalls

1. **Heavy initialization inside the handler** — paid on every invocation.
2. **Non-idempotent async handlers** — duplicate side effects.
3. **No DLQ/destination for async invocations** — failed events silently dropped.
4. **Unbounded concurrency against a SQL database**.
5. **Timeout longer than the API Gateway timeout** — clients get 504 while the function keeps running.
6. **Huge bundles** with the full AWS SDK and dev dependencies.

## Quick Reference (Part 2)

```
Powertools: logger · metrics (EMF) · tracer · idempotency · batch · parameters
async: retries + on-failure destination · SQS: partial batch failures, DLQ · streams: bisect batch
deploy: esbuild bundle · versions + aliases + CodeDeploy canary with alarm rollback
cost: requests + GB-s (+ API GW, logs, NAT) · avoid for >15 min or steady heavy load
