# Lambda and Serverless — Part 1: The Execution Model

## 1. What Lambda Is

**AWS Lambda** runs your function in response to events without you managing servers. You upload code (zip or container image), choose memory (128 MB – 10 GB; CPU scales proportionally), set a timeout (≤ 15 min), and AWS runs as many copies as needed. You pay per request and per GB-second of execution (`03-ec2/` compares the model with always-on instances).

```ts
// handler.ts — Node.js runtime
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";

export const handler = async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> => {
  const id = event.pathParameters?.id;
  return { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ id }) };
};
```

## 2. Event Sources and Invocation Types

| Invocation | Who calls | Retries | Examples |
|---|---|---|---|
| **synchronous** | caller waits for the result | caller's responsibility | API Gateway, ALB, Function URL, SDK `Invoke` |
| **asynchronous** | Lambda queues the event internally | 2 retries, then **on-failure destination / DLQ** | S3 events, SNS, EventBridge |
| **poll-based (event source mapping)** | Lambda polls the source and invokes with batches | depends on source (redelivery / bisect batch) | SQS, Kinesis, DynamoDB Streams, Kafka |

For SQS, configure **partial batch responses** (`ReportBatchItemFailures`) so one bad record doesn't retry the whole batch (`07-sqs_and_sns/` exercise 3).

## 3. The Lifecycle: Cold and Warm Starts

```
first request (or scale-out)  → COLD START: download code, start a new execution environment
                                (Firecracker microVM), init runtime, run your top-level code (INIT)
                              → INVOKE handler
later requests                → WARM: reuse the same environment, only INVOKE runs
idle for a while              → environment frozen, then reclaimed
```

Each execution environment handles **one request at a time**; concurrency = number of environments. Consequences:

- Put expensive setup (SDK clients, DB connection, config loading) **outside the handler** — it runs once per environment and is reused across warm invocations.
- Global state persists between invocations in the same environment — caches are fine, but never store per-request data globally.
- Cold starts add ~100 ms–1 s (more with large packages, VPC attachment used to add more, Java without SnapStart). Mitigations: smaller bundles (esbuild tree-shaking), lighter runtimes, **provisioned concurrency** (pre-initialized environments, billed hourly), **SnapStart** (Java/Python/.NET snapshot of the initialized environment).

## 4. Concurrency and Scaling

- **Account concurrency limit** per region (default 1,000) shared by all functions.
- **Reserved concurrency** guarantees capacity for a function *and* caps it — a protection for downstream systems (e.g. a database that can take only 100 connections).
- Scaling rate: each function can add ~1,000 concurrent executions every 10 seconds.
- **Throttling** (`429 TooManyRequestsException`) happens when limits are reached: synchronous callers get errors; async events are retried by Lambda for up to 6 hours.

Databases and Lambda: hundreds of concurrent environments each opening connections can exhaust a relational database → use **RDS Proxy** (connection pooling), DynamoDB, or cap reserved concurrency.

## 5. Configuration Essentials

- **Memory** also buys CPU (1,769 MB ≈ 1 vCPU). More memory often makes functions faster *and* cheaper — measure (AWS Lambda Power Tuning).
- **Timeout**: slightly above p99, and below the caller's timeout (API Gateway max 29 s).
- **Environment variables** for config; secrets from Secrets Manager/SSM with caching (`18-secrets_manager_ssm_and_kms/`).
- **Execution role**: least-privilege IAM role the function assumes (`21-iam_deep_dive/`).
- **VPC**: attach only if the function must reach private resources (RDS, ElastiCache); it then needs NAT or VPC endpoints for AWS APIs.
- **Architecture**: arm64 (Graviton) is ~20% cheaper per GB-second.

## Under the Hood: Firecracker MicroVMs

Each execution environment runs inside a **Firecracker** microVM — a minimal KVM-based virtual machine monitor written in Rust that boots a stripped-down Linux kernel in ~125 ms with a few MB of overhead. Many microVMs from different customers share a physical worker host, with hardware-virtualization isolation between them (stronger than containers sharing a kernel). Your code's lifecycle maps onto it: **Init** (start the runtime, run top-level code, up to 10 s free on cold start for on-demand functions), **Invoke** (handler runs; the environment is frozen — CPU paused — between invocations, which is why background work after returning a response doesn't reliably run), **Shutdown**. A placement service decides which worker gets new environments, and the frontend routes each invocation to an idle warm environment for that function version when one exists — otherwise a cold start happens.

![An event reaches Lambda; if no warm environment exists a Firecracker microVM is started and the init code runs once, then the handler runs per invocation; warm environments are reused one request at a time and concurrency equals the number of environments, capped by reserved and account limits](diagrams/01-lambda-lifecycle.svg)

## Quick Reference (Part 1)

```
invocation: sync (API GW/ALB) · async (S3/SNS/EventBridge, retries + destinations) · poll (SQS/Kinesis/Streams batches)
cold start = new environment + init; warm = reuse → do setup outside the handler
1 request per environment at a time · reserved concurrency = guarantee + cap · RDS Proxy for SQL databases
memory = CPU · timeout < caller timeout · arm64 cheaper · VPC only when needed
