Lambda
After this you can answer: How does a queued booking become a reliable Lambda invocation, and which limits actually control it?
Picture it
Section titled “Picture it”Where it sits
Section titled “Where it sits”Read left to right: a queue supplies work, Lambda runs code, and AWS APIs perform the effects.
An event source mapping is a Lambda resource that polls a source and invokes a function.
An execution role is the IAM role whose temporary credentials authorize the function and its SQS poller.
IAM is AWS’s permissions system; an action such as sqs:ReceiveMessage names an allowed API operation.
flowchart LR Q["SQS jobs"]:::aws -->|"poll + invoke: mapping; sqs:ReceiveMessage"| L["Lambda worker"]:::service L -->|"assume: trust lambda.amazonaws.com"| R["minitickets-dev-worker-role"]:::security L -->|"deduplicate: dynamodb:GetItem / PutItem"| D["DynamoDB processed-jobs"]:::aws L -->|"send: ses:SendEmail; FromAddress condition"| E["SES"]:::aws L -->|"log: logs:CreateLogStream / PutLogEvents"| C["CloudWatch Logs"]:::awsLegend: purple = this service; teal = app code; blue = AWS; amber = IAM; red = failure; dashed = designed, not built.
Open in the map →The arrow abbreviates sqs:ReceiveMessage; deletion also needs sqs:DeleteMessage.
The shared connection table preserves the full action names and code references.
The short path chapter gives the same flow in a few minutes.
How it works
Section titled “How it works”- The API’s outbox relay publishes a committed booking event to SNS. An outbox is a database table of events saved in the same transaction as the booking. SNS delivers separate copies to the email and ticket queues.
- Lambda’s SQS poller receives a batch. SQS hides those messages for the visibility timeout, the time before undeleted messages can be received again.
- Lambda finds an available execution environment or prepares another one. An execution environment is the isolated runtime, memory and temporary disk in which our code runs.
- The Node.js runtime loads module-level code during initialization, or Init. Then it calls
handler(event, context). The event contains SQS records; the context contains invocation information such asawsRequestId. handleBatchvalidates each message, checks its stable business identity, sends email and records completion. Invalid or unsuccessful messages are reported individually.- The handler returns
batchItemFailures, containing the SQS message IDs that failed. The mapping hasReportBatchItemFailuresenabled, so Lambda deletes the other messages. - Failed messages become visible again after their current visibility delay. After the queue’s receive threshold, SQS moves them to its dead-letter queue, or DLQ, where failed messages can be investigated.
How it works: one batch
Section titled “How it works: one batch”sequenceDiagram participant Q as SQS jobs participant P as Lambda poller box rgb(237,233,254) Lambda participant H as worker handler end participant D as DynamoDB participant E as SES P->>Q: ReceiveMessage, worker role Q-->>P: Batch, messages temporarily hidden P->>H: Invoke with SQS records H->>D: GetItem stable job ID H->>E: SendEmail if not completed H->>D: Conditional PutItem completion H-->>P: batchItemFailures: failed IDs only P->>Q: DeleteMessage for successful IDs Q-->>P: Failed IDs visible again, retry or DLQLegend: blue = AWS participants; purple group = this service’s handler; arrows show the batch and its effects.
Idempotency means repeating an operation has the same intended effect as doing it once. Our completion record reduces duplicate email, but it does not make SES and DynamoDB one transaction. If SES accepts an email and the completion write fails, a retry can send another email. Two simultaneous workers can also both pass the initial read. Explain this limitation in an interview.
Under the hood
Section titled “Under the hood”Init, cold starts and reuse
Section titled “Init, cold starts and reuse”Lambda must prepare an environment before it can run the handler. It initializes extensions, the runtime and static function code.
A cold start is the additional preparation needed for a new environment. A warm invocation reuses an initialized environment.
Module-level SDK clients in apps/worker/src/lambda.ts survive ordinary reuse, so we avoid constructing clients for every message.
Reuse is an optimization, not a durability promise. Lambda can replace an environment at any time.
Never put the only copy of a processed-job record in memory. AWS explains the environment lifecycle.
Finish awaited work before returning. A background promise is not a reliable way to send the email later. The environment may freeze after the invocation, and unrelated work can resume during a later call. Keep request-specific data local to the handler to avoid accidentally reusing another customer’s data. Measure initialization separately from handler duration before deciding that every slow invocation is a cold start.
Three invocation models, three retry owners
Section titled “Three invocation models, three retry owners”| Model | What happens first | Who owns retries? |
|---|---|---|
| Synchronous | Caller waits for the function result | Caller or calling integration; Lambda does not automatically retry the function error |
| Asynchronous | Lambda accepts the event into its internal queue | Lambda’s asynchronous delivery configuration |
| Event source mapping | Lambda polls SQS or a stream and invokes a batch | Mapping and source-specific behavior |
For asynchronous invocation, the default for a function error is two further attempts, normally after one and two minutes. Throttling and service errors use a different retry path, with an event age of up to six hours by default. A destination receives an invocation record, including outcome information. An asynchronous DLQ instead captures the discarded event with failure attributes. Configure and monitor destination permissions too; failure capture can itself fail. These are the asynchronous retry rules and destination rules.
Our SQS workers use the third model. They do not use Lambda’s asynchronous retry count or function-level asynchronous DLQ. Kinesis and DynamoDB Streams retain ordered records in shards, which are subdivisions of a stream. Their mappings track progress through stream positions. Batch retries can delay later records in a shard; configure retry limits, record age and failure capture for that source. SQS messages instead become visible again and eventually follow the queue’s redrive policy. Start failure analysis by identifying the source and invocation model. AWS distinguishes the retry owners.
Concurrency and scaling
Section titled “Concurrency and scaling”Concurrency is how many invocations run at once. In the default execution model used here, an environment handles one invocation at a time. Rough required concurrency is arrival rate multiplied by average execution duration: 20 batches per second lasting 0.25 seconds require about five concurrent invocations. That is an estimate for capacity planning, not a guarantee about burst or tail latency.
Reserved concurrency sets aside capacity for one function and also caps that function. It does not initialize environments. Provisioned concurrency maintains initialized environments for a published version or alias and costs money while allocated. A maximum concurrency on an SQS mapping caps that mapping’s concurrent batches without reserving account capacity. These settings answer different questions. A queue cap cannot guarantee capacity when other functions consume the account pool. AWS documents the concurrency controls.
Our recorded account limit is 10, not the commonly quoted default of 1,000. It is a historical account fact, not a live reading today. The worker’s optional reservation remains unset. Its code records a refused reservation under that low quota. AWS’s general documentation describes keeping 100 units unreserved for ordinary larger quotas; do not generalize our account’s historical minimum of 10 to other accounts. Read the current account setting before proposing reservations. The recorded case preserves the evidence boundary.
Memory, temporary storage and packages
Section titled “Memory, temporary storage and packages”More configured memory gives proportionally more CPU. A faster render can offset a higher per-millisecond memory charge, so benchmark the complete operation.
/tmp is local temporary disk. It can survive environment reuse, but is neither shared between environments nor durable storage.
Use it for replaceable intermediate data, never the authoritative ticket or job state.
A zip deployment package contains code and dependencies for a Lambda runtime.
A container image packages the runtime and code in an image stored in ECR, AWS’s container registry.
The image must implement Lambda’s runtime interface; an arbitrary web-server image is not sufficient.
Our one worker image has two entry points, selected per function, and targets arm64.
An image architecture mismatch can cause exec format error.
AWS describes the image requirements.
A Lambda layer is a separately versioned zip archive of dependencies mounted under /opt.
Layers apply to zip functions. For our image functions, dependencies are included in the image instead.
An image’s filesystem layers are a different mechanism. Lambda’s layer documentation makes this distinction explicit.
Versions, aliases, VPC and secrets
Section titled “Versions, aliases, VPC and secrets”A published version captures immutable function code and most configuration. $LATEST is the mutable unpublished function.
An alias is a named pointer to a published version; weighted routing can divide traffic between two versions.
This allows a small rollout and a quick routing rollback, but it cannot undo database side effects.
MiniTickets currently updates unqualified functions; it does not build version/alias rollout resources.
See versions and aliases.
Attaching Lambda to our VPC, an isolated customer network, gives it private-network access through managed network interfaces.
It does not give the function a public IP, even in a public subnet. Internet access then needs appropriate routing, usually a NAT gateway, or private service endpoints where supported.
Our workers need SQS, DynamoDB, SES, S3 and Secrets Manager APIs, not Postgres or Valkey.
They therefore have no vpcConfig; this means outside our customer VPC, not outside AWS’s managed network.
AWS documents VPC access and internet routing.
The ticket function reads its signing key from Secrets Manager using secretsmanager:GetSecretValue.
Its environment contains the secret’s identifier, not the key. The code caches a successful read within the process.
A rotation design must account for that cache; a long-lived warm environment does not automatically reread a changed secret.
Never log a signing key, session token or full message containing private data.
Limits and numbers
Section titled “Limits and numbers”At the time of writing; check the AWS docs. These numbers concern the default Lambda function model used here. AWS also documents Managed Instances and durable functions with different behavior; do not apply these bounds to every newer Lambda execution model.
| Setting | Value | Source |
|---|---|---|
| Invocation timeout | Up to 900 seconds | Lambda quotas |
| Memory; one vCPU equivalent | 128–10,240 MB; at 1,769 MB | Lambda quotas |
| Temporary disk | 512–10,240 MB | Lambda quotas |
| Package sizes | Zip: 50 MB direct upload, 250 MB expanded including layers; image: 10 GB expanded | Lambda quotas |
| Payload | Synchronous request/response: 6 MB each; asynchronous: 1 MB | Lambda quotas |
| Per-function scaling rate | Up to 1,000 new environments per 10 seconds, subject to available capacity | Scaling |
| Account concurrency | General default 1,000; ours recorded as 10, current value unverified | AWS quotas, our case |
| Email worker | 256 MB; 10-second timeout; mapping batch 10, concurrency cap 5 | worker-lambda.ts, queue-trigger.ts |
| Ticket worker | 512 MB; 30-second timeout; mapping batch 1, concurrency cap 2 | infra/src/tickets/lambda.ts |
| Visibility | Email 60 seconds; tickets 180 seconds | infra/src/jobs/queues.ts (createJobQueues for both kinds) |
In MiniTickets
Section titled “In MiniTickets”Built in the repository; environment currently switched off. These are implementation facts, not a claim that either function is running now.
Read infra/src/jobs/worker-lambda.ts → createWorkerLambda for the email function and
infra/src/jobs/queue-trigger.ts → createQueueTrigger for the polling configuration.
Then read apps/worker/src/lambda.ts → handler and
apps/worker/src/batch.ts → handleBatch for execution.
SDK calls use short connection/request timeouts; whole-batch worst cases can still exceed ten seconds.
The code handles records sequentially, so increasing batch size can increase timeout risk.
infra/src/tickets/lambda.ts → createTicketLambda selects ticket-lambda.handler from the same image.
Ticket IDs and object keys are deterministic, so retrying unfinished generation can converge on the same ticket state.
Email’s send-before-record sequence has a wider duplicate window. Reliability depends on the effect, not merely on using the same queue mechanism.
Our local runner calls the same handler, but local success cannot establish AWS IAM permissions, account quotas or SES delivery.
The committed infra/Pulumi.dev.yaml has data, compute and entry disabled. No AWS settings were changed for this page.
Connections
Section titled “Connections”The trust policy lets lambda.amazonaws.com assume the worker role. The permission policy authorizes the API calls.
The role is attached to minitickets-dev-worker; iam:PassRole controls who may attach it during deployment.
Lambda pulls the image through the ECR repository policy, not through an ECS-style second execution role.
| From → to | What happens | What allows it |
|---|---|---|
| Worker Lambda → CloudWatch alarmsonly when computeEnabled = true | Errors > 0 in 5 minutes → worker-errors |
|
| Worker Lambda → ECR repositoriesonly when computeEnabled = true | the Lambda service pulls the worker image when the function is created or updated |
|
| Worker Lambda → Worker Lambda execution roleonly when computeEnabled = true | the worker function runs with this role |
|
| Worker Lambda → CloudWatch log groupsonly when computeEnabled = true | worker lines go to /minitickets/dev/worker |
|
| Jobs SQS queue → Worker Lambdaonly when computeEnabled = true | the event source mapping polls the queue and calls the worker with batches |
|
| Worker Lambda → Processed-jobs tableonly when computeEnabled = true | GetItem before sending; a conditional PutItem after sending |
|
| Worker Lambda → SES email identitiesonly when computeEnabled = true | SendEmail from no-reply@minitickets.theversehub.in |
|
The ticket function has a separate role for its queue, ticket table, tickets/* S3 writes and the signing secret.
No inbound security group rule is needed to invoke our worker. Queue polling and role permissions are the relevant connection controls.
When it fails
Section titled “When it fails”flowchart TB T["Handler timeout or failed message"]:::failure -->|"not deleted: mapping failure response"| V["SQS visibility expires"]:::aws V -->|"receive again: redrive policy"| N{"Receive count exceeds 3?"}:::aws N -->|"no: mapping polls"| R["Invoke worker again"]:::service N -->|"yes: SQS redrive"| D["jobs-dlq"]:::failure D -->|"visible count above 0: CloudWatch metric"| A["jobs-dlq-not-empty alarm"]:::aws A -->|"publish: SNS topic policy"| S["SNS alerts"]:::aws S -->|"email: confirmed subscription"| O["Operator email"]:::appLegend: red = failure; blue = AWS queue and alarm behavior; purple = Lambda; teal = notification recipient.
| What you see | Why | Fix |
|---|---|---|
Task timed out after 10.00 seconds, or REPORT status timeout | Batch, SDK retries or dependency latency exceeds the invocation budget | Bound dependency calls; reduce batch work; measure before increasing timeout and visibility together |
Throttles rises; queue age grows | Account pool, reserved concurrency or scaling rate limits execution | Read current quota and competing use; align mapping caps and downstream capacity |
| Failed messages disappear after a normal return | Mapping lacks ReportBatchItemFailures, or handler incorrectly returns an empty success response | Verify mapping and response contract; malformed or nonexistent failed IDs instead fail the whole batch and cause retry |
AccessDeniedException or no polling | Role permission, resource policy or mapping state is wrong | Check exact denied action/resource; verify trust and mapping state; do not add broad permissions |
| Duplicate email despite successful processing | Crash after send or concurrent processing before completion | Use business IDs; accept/document the remaining window or adopt a provider-supported deduplication design |
The current worker-errors alarm uses Lambda’s Errors metric. A caught per-message error returned in a successful partial response need not increase that metric.
Queue age and application logs are essential. A DLQ can contain failures even when invocation-level error counts look healthy.
The built jobs-dlq-not-empty alarm checks visible DLQ messages above zero over one minute and publishes through the alerts topic. It keeps its state when data is missing. A confirmed email subscription is required; no current live subscription status is claimed.
At 10x / 100x
Section titled “At 10x / 100x”At 10x, start with arrival rate, batch duration and oldest-message age. Five concurrent email batches can still exceed the SES sandbox’s sending rate. Concurrency is simultaneous work, not emails per second. Throttle the actual dependency calls or obtain appropriate production quotas. More memory may shorten CPU work, but it does not remove a mail-service rate limit.
At 100x, account concurrency and downstream capacity require a coordinated plan. Separate workers by dependency and keep queue caps explicit. Use business-level deduplication, bounded retries and a replay runbook before raising throughput. Watch durable outbox rows as well as SQS; a failed publisher can leave queues empty while bookings wait in Postgres. For long-running or consistently busy processing, compare ECS tasks with Lambda using measured cost and operational needs. Avoid unbounded parallel requests inside one invocation: they bypass the practical protection suggested by a function concurrency cap.
Common interview traps
Section titled “Common interview traps”- “SQS invokes Lambda asynchronously, so Lambda retries twice.” The mapping polls SQS. Source visibility and redrive govern retry, not the asynchronous invocation retry setting.
- “Reserved concurrency removes cold starts.” It reserves capacity. Provisioned concurrency initializes environments.
- “A maximum concurrency of five reserves five.” It caps that mapping but reserves nothing in the account pool.
- “A successful function return means every message succeeded.” Partial responses can report record failures without an invocation error.
- “Idempotency means exactly one email.” Our completion record cannot atomically commit an external email send.
- “A VPC makes Lambda secure by default.” Private routing and IAM solve different problems; a public subnet does not give Lambda a public IP.
Design question
Section titled “Design question”A ticket sale creates 10,000 bookings in a minute. How do you process confirmations without delaying checkout?
Commit the booking and outbox event together. Publish a stable event identity, then fan out to independently buffered email and ticket work. Use Lambda mappings with separate batch sizes and concurrency caps. Budget capacity from measured duration and dependency quotas. Deduplicate by booking/job identity; deterministic ticket keys allow retries to repair incomplete generation. Accept the documented duplicate-email window or choose a delivery integration with a stronger idempotency contract. Return the booking result before waiting for either worker. Expose pending ticket state honestly. Use the existing email DLQ, age and worker-error alarms; add equivalent ticket-pipeline coverage before scaling that path.
flowchart LR A["Booking transaction"]:::app -->|"commit booking + event: SQL transaction"| O["Outbox"]:::app O -->|"publish: api task role sns:Publish"| S["SNS booking-events"]:::aws S -->|"deliver: subscription + queue policy"| Q["Independent SQS queues"]:::aws Q -->|"poll: mappings + queue IAM"| L["Lambda workers"]:::service L -->|"write/send: scoped execution roles"| D["DynamoDB / S3 / SES"]:::awsLegend: teal = app transaction; blue = managed services; purple = this service; arrows name the mechanism and permission.
Say it in an interview
Section titled “Say it in an interview”Follow-ups
Section titled “Follow-ups”Why create SDK clients outside the handler?
What if the handler times out after sending?
Would provisioned concurrency help this queue?
How would you access private RDS?
Why not use ECS for the worker?
Remember it
Section titled “Remember it”- Identify the invocation model before explaining retries.
- Reserved concurrency reserves and caps; provisioned concurrency initializes.
- SQS partial failure reporting needs both mapping configuration and valid failed message IDs.
- Keep visibility comfortably above the function timeout; AWS recommends at least six times it plus the batch window.
- A repeatable business effect matters more than an “exactly once” label.
Advanced: Console checks, safe CLI and deeper runtime details
Lambda console → the function → Configuration → Triggers, Concurrency, Permissions and Environment variables.
Read metadata only; do not copy secret values. Monitor → Metrics shows Duration, Errors, Throttles and ConcurrentExecutions.
The REPORT line includes memory use and can include initialization duration. Our JSON lines carry correlation and invocation IDs.
aws lambda get-account-settings --region us-east-1 --query 'AccountLimit.ConcurrentExecutions'aws lambda list-event-source-mappings --function-name minitickets-dev-worker --region us-east-1 --query 'EventSourceMappings[].{State:State,Batch:BatchSize,Failures:FunctionResponseTypes,Scaling:ScalingConfig}'aws lambda get-function-configuration --function-name minitickets-dev-worker --region us-east-1 --query '{Memory:MemorySize,Timeout:Timeout,Architecture:Architectures,Package:PackageType,Vpc:VpcConfig,Logs:LoggingConfig}'These reads can return not-found while the committed compute switch is off. They were not executed for this page. Source-specific batching and the visibility recommendation are in AWS’s SQS configuration guide. SnapStart, durable functions and Managed Instances are separate choices with their own runtime, region and pricing constraints. This implementation uses none of them. Do not claim all Lambda functions necessarily share the single-invocation environment model described here.
Checkpoint
Section titled “Checkpoint”Answer these without scrolling up. If one feels hard, reread that section.
- Who polls SQS, and which role authorizes it?
- Which invocation model has the built-in two-retry default?
- How do reserved, provisioned and mapping concurrency differ?
- Where can duplicate email still occur?
- Why do our functions have no customer VPC configuration?
Redraw it from memory: SQS, Lambda poller, handler, effects and delete versus retry
Try this: Read the mapping configuration in code and identify the batch size, cap and partial-response setting.
Read this book online: https://book.theversehub.in/