Subchapter 42.8
references/production.mdMarkdown8 KBView on GitHub
Pre-deployment checklist, architecture trade-offs, and operational patterns. Concise on purpose — the value here is the structured checklist and opinionated defaults, not API syntax.
AWS_IAM auth (not NONE) in productionaws:SecureTransport condition in resource policies (S3 buckets, SQS queues)BisectBatchOnFunctionError for stream sourcesMaximumRetryAttempts, MaximumEventAgeInSeconds)Prefer micro-Lambda (function per route) for greenfield: per-function least-privilege IAM, independent scaling + reserved concurrency, granular observability, smaller/faster cold starts. Use a Lambdalith when migrating an existing Express/FastAPI app or when a small team values deployment simplicity over granularity.
Reserved = guarantee capacity + protect downstream (cold starts still possible, throttles at the limit). Provisioned = eliminate cold starts (spills to on-demand beyond the count). Need both → provisioned ≤ reserved. Try SnapStart before Provisioned for Java/Python 3.12+/.NET 8+ (no cost for Java). See concurrency.md.
Use Powertools Logger (structured JSON, auto correlation IDs), Tracer (wraps X-Ray, auto-captures SDK/HTTP calls; annotate traces with business keys), Metrics (EMF — zero latency, writes to stdout vs ~5–20ms for synchronous PutMetricData; avoid PutMetricData in hot paths).
| Alarm | Metric | Threshold | Why |
|---|---|---|---|
| Error rate | Errors / Invocations | > 1% | Bugs / upstream failures |
| Throttles | Throttles | > 0 | Concurrency limit hit |
| Duration P99 | Duration P99 | > 80% of timeout | Catch slow functions before timeout |
| Iterator age | IteratorAge | > 60s | Stream processing falling behind |
| Concurrent executions | ConcurrentExecutions | > 80% of reserved | Approaching throttle threshold |
| DLQ depth | SQS ApproximateNumberOfMessagesVisible | > 0 | Failed messages accumulating |
Set log retention when creating log groups — the default is “never expire.”
Serverless apps are mostly about service integrations, not complex business logic — so the integration layer (tested in the cloud) is the most valuable, with few unit tests (pure logic) and few E2E. Structure handlers as thin adapters calling pure functions. Don’t rely on LocalStack/DynamoDB Local as primary testing (they diverge on IAM, quotas, error codes); don’t mock AWS SDK calls for integration tests. Iterate fast with sam sync / cdk watch; give each developer an isolated stack.
Lambda guarantees at-least-once — duplicates come from async retries, SQS visibility expiry, stream replays, client retries, Step Functions task retries. Use the Powertools Idempotency utility (Python/TS/Java/.NET), backed by a DynamoDB table with TTL.
Table: id (hash of idempotency key) + status (INPROGRESS/COMPLETED/EXPIRED), data (cached response), expiration (TTL).
Idempotency key by source:
| Source | Key |
|---|---|
| SQS | messageId |
| EventBridge | detail.id or composite |
| DynamoDB Streams | eventID |
| API Gateway / Function URL | Idempotency-Key header or body hash |
| Step Functions | Execution ID + task token |
TTL ≈ how long duplicates can arrive (API retries ~1h; SQS ≈ maxReceiveCount × visibility; stream replays ~24h).
Use for payloads > 6 MB (buffered limit), TTFB-sensitive responses, SSE, LLM token streaming, or large file generation.
awslambda.streamifyResponse + awslambda.HttpResponseStream.from); other runtimes need a custom runtime or Lambda Web Adapter.InvokeWithResponseStream API instead.Don’t stream small JSON (< 6 MB) — buffered is simpler.