Lambda Execution, Concurrency, and Deployment
AWS Lambda runs application code without you managing servers, but it is not magic. A function is still executed inside a managed runtime, receives events from a source, consumes memory and CPU, opens network connections, writes logs, and competes for concurrency. This lesson teaches how Lambda execution, concurrency, and deployment fit together so you can predict behavior under normal traffic, bursts, and releases.
In the AWS Cloud Engineering course, Lambda sits between event-driven architecture and operational design. You should leave this chapter able to explain what happens when an event invokes a function, choose between unreserved and reserved concurrency, deploy a new version behind an alias, and diagnose the most common scaling and rollout failures.
Execution Model
A Lambda function is a named resource that combines code, runtime settings, an execution role, and configuration such as memory, timeout, environment variables, and networking. Invocation starts when a client, service, or event source asks Lambda to run the function. Lambda places the event into an execution environment, calls the configured handler, captures the response or error, and emits logs and metrics.
The execution environment is the managed sandbox that contains the runtime process and your initialized code. When Lambda creates a new environment, it downloads or mounts code, starts the runtime, runs initialization outside the handler, and then invokes the handler. That first request is a cold start. If another event arrives later and Lambda can reuse the same environment, initialization is skipped and the handler runs in a warm start. You cannot depend on reuse, but you can benefit from it by creating SDK clients, database pools, and static lookup data outside the handler.
Invocation type matters. A synchronous invocation waits for the handler response and returns success or failure to the caller. An asynchronous invocation accepts the event first, then Lambda retries certain failures and can send final failed events to a destination or dead-letter queue. Event source mappings, such as SQS or stream mappings, poll the source and invoke the function with batches; their retry and ordering behavior depends on the source.
Concurrency Internals
Concurrency is the number of function invocations running at the same time. If one request takes 200 milliseconds and traffic is 100 requests per second, steady-state concurrency is roughly 20. If the same request takes 2 seconds, concurrency rises to roughly 200. Lambda scaling is therefore driven by both request rate and duration.
By default, functions in an account and Region share an account concurrency quota. A function without its own reservation can use available shared concurrency. Reserved concurrency sets both a floor and a ceiling for a function: that capacity is held for the function, and the function cannot exceed it. Setting reserved concurrency to zero is a deliberate way to stop a function from running while preserving its configuration.
Provisioned concurrency is different. It keeps a configured number of execution environments initialized for a version or alias, reducing cold starts for latency-sensitive traffic. Reserved concurrency limits how many can run; provisioned concurrency prepares environments ahead of time. You often combine them by reserving enough capacity for the function and assigning provisioned concurrency to the production alias that serves interactive traffic.
Configuration Anatomy
The handler name tells Lambda which function to call, such as app.handler for a Python file named app.py with a function named handler. Memory controls both available RAM and proportional CPU. Timeout bounds execution time. The execution role grants AWS API permissions to the function code. Environment variables provide configuration, but secrets should come from a secrets service or encrypted parameter store rather than being copied into source code.
Deployment has its own vocabulary. $LATEST is the mutable working copy of a function. A version is an immutable snapshot of code and most configuration. An alias is a stable pointer to a version, optionally with weighted routing to a second version. Production callers should usually invoke an alias, not $LATEST, because aliases let you shift traffic and roll back without changing caller configuration.
Example 1: Handler Reuse
This handler creates the DynamoDB client outside the handler. On a warm start, the same client object can be reused. The deterministic output is the shape of the returned JSON; the specific request identifier changes per invocation.
import json
import os
import boto3
_dynamodb = boto3.resource('dynamodb')
_table_name = os.environ.get('TABLE_NAME', 'orders')
def handler(event, context):
order_id = event.get('orderId', 'unknown')
table = _dynamodb.Table(_table_name)
return {
'statusCode': 200,
'body': json.dumps({
'orderId': order_id,
'table': table.name,
'requestId': context.aws_request_id
})
}
If the event is {"orderId":"A100"} and TABLE_NAME is not set, the response body contains orderId as A100 and table as orders. The example does not write to DynamoDB; it shows where initialization work belongs.
Example 2: Estimating Concurrency
Use duration and request rate to reason about needed capacity before changing quotas. This small script calculates approximate steady-state concurrency for three workloads.
def estimated_concurrency(requests_per_second, average_duration_ms):
return requests_per_second * (average_duration_ms / 1000)
cases = [
('api-light', 50, 120),
('api-heavy', 50, 900),
('batch-worker', 200, 1500),
]
for name, rps, duration in cases:
value = estimated_concurrency(rps, duration)
print(f'{name}: {value:.1f}')
The expected output is api-light: 6.0, api-heavy: 45.0, and batch-worker: 300.0. The lesson is that the same request rate can require very different concurrency when handler duration changes.
Example 3: Reserved Concurrency Guardrail
The following commands inspect and then set a concurrency ceiling for one function. The command parses as a standalone Bash script when the variables are supplied. It prevents this function from consuming all shared concurrency in the Region.
set -euo pipefail
FUNCTION_NAME='orders-worker'
RESERVED_CONCURRENCY='25'
aws lambda get-function-concurrency \
--function-name "$FUNCTION_NAME" \
--output json || true
aws lambda put-function-concurrency \
--function-name "$FUNCTION_NAME" \
--reserved-concurrent-executions "$RESERVED_CONCURRENCY"
aws lambda get-function-concurrency \
--function-name "$FUNCTION_NAME" \
--output json
After the update, the final command returns JSON containing ReservedConcurrentExecutions with value 25. If traffic needs 40 concurrent executions, about 15 will be throttled until running invocations finish.
Example 4: Alias Deployment
This example publishes the current function state as an immutable version, points an alias to it, and shows how production callers can use the alias ARN. In real deployments, the alias update is usually automated by a pipeline.
set -euo pipefail
FUNCTION_NAME='orders-api'
ALIAS_NAME='prod'
VERSION=$(aws lambda publish-version \
--function-name "$FUNCTION_NAME" \
--query 'Version' \
--output text)
aws lambda update-alias \
--function-name "$FUNCTION_NAME" \
--name "$ALIAS_NAME" \
--function-version "$VERSION"
aws lambda get-alias \
--function-name "$FUNCTION_NAME" \
--name "$ALIAS_NAME" \
--query '{Alias:Name,Version:FunctionVersion}' \
--output json
The expected final output is a JSON object with Alias equal to prod and Version equal to the newly published version number. If the new version fails health checks, updating the alias back to the previous version is the rollback.
Design Choices and Trade-offs
Choose synchronous Lambda for request-response work where callers need an immediate result. Choose asynchronous invocation or event source mappings when work can be retried, buffered, or processed in batches. Batching improves throughput but changes failure behavior: one bad record can hold up a batch unless you use partial batch response features where supported.
Memory is a performance knob, not only a cost knob. More memory can reduce duration by giving the function more CPU, which may lower total cost for CPU-bound work. For network-bound work, more memory may not help. Timeout should be long enough for normal dependency latency but short enough to release concurrency when downstream systems are stuck.
Reserved concurrency protects neighboring workloads but can throttle the protected function. Provisioned concurrency improves latency but adds cost even when traffic is quiet. Versions and aliases make rollback clean, but only if callers invoke the alias and the pipeline records the previous version.
Failure Modes and Troubleshooting
Symptom: sudden throttles. CloudWatch shows Throttles increasing and callers receive throttling errors or delayed processing. The cause is usually account concurrency exhaustion, a reserved concurrency ceiling, or an event source sending more work than expected. Diagnose with function concurrency metrics, account quota usage, and get-function-concurrency. Correct by reducing duration, raising the reservation or quota, controlling source rate, or moving noisy functions behind their own reservations.
Symptom: high first-request latency. Percentile latency spikes after idle periods or deployments. The cause is cold starts, often made worse by large packages, VPC attachment, heavy initialization, or slow dependency setup. Diagnose by comparing init duration in logs with handler duration. Correct by trimming dependencies, moving only reusable setup outside the handler, using provisioned concurrency for critical aliases, or changing the workload to tolerate asynchronous processing.
Symptom: deployment succeeded but callers still run old code. The cause is commonly invoking an old version, forgetting to update an alias, or testing $LATEST while production uses prod. Diagnose by logging the function version from the context object and inspecting get-alias. Correct the alias pointer or caller ARN, then verify with a request that returns the deployed version.
Symptom: repeated processing of the same event. Lambda and event sources can retry after errors, timeouts, or ambiguous network failures. The cause is non-idempotent handler logic. Diagnose by correlating request IDs, event IDs, and downstream writes. Correct by using idempotency keys, conditional writes, deduplication windows, or making the operation safe to repeat.
Security, Performance, and Reliability
The execution role should grant only the AWS actions and resources the handler uses. A function that reads from one SQS queue and writes one DynamoDB table should not have broad account-wide permissions. Environment variables are visible to principals allowed to inspect function configuration, so store sensitive values in a managed secret and grant narrow read access.
Performance tuning starts with duration, memory, package size, and dependency latency. Reliability comes from bounded timeouts, retry-aware handlers, dead-letter or failure destinations for asynchronous work, and alarms on errors, throttles, iterator age for streams, and age of asynchronous events. Deployment reliability improves when aliases, canaries, and rollback commands are tested before an incident.
Hands-on Lab
Prerequisites: AWS CLI configured for a sandbox account, permission to manage Lambda and IAM roles, and a small existing test function or permission to create one. Use a non-production Region and a function that has no real customer traffic.
- Create or select a test function with a short handler that returns a version string.
- Invoke the function twice and inspect CloudWatch logs for request IDs and duration. Note whether an init duration appears.
- Run the concurrency estimation script from Example 2 using your expected request rate and average duration.
- Set reserved concurrency to a small value such as
2, then invoke several requests in parallel from a test client. Verify that throttles appear when simultaneous requests exceed the reservation. - Publish a version and point a
devalias to it. Invoke the alias ARN and confirm the response comes from the published version. - Change the function response string, publish a second version, and update the alias. Verify the alias now returns the new string.
- Rollback by updating the alias to the first version. Verify the original response returns again.
Cleanup: remove the reserved concurrency setting if it was only for the lab, delete unused test versions if your retention policy allows it, and delete the test function and role if they were created only for this exercise.
Assessment Exercises
- A function receives 80 requests per second and averages 750 milliseconds. Estimate required concurrency, then explain what happens if reserved concurrency is set to 30.
- Design an alias-based rollout for a new handler that changes database write behavior. What metric would make you stop or roll back?
- A stream-processing Lambda repeatedly fails on one record. Explain the likely impact on later records and propose a correction.
- Compare reserved concurrency and provisioned concurrency for a latency-sensitive API. When would you use both?
- Review a handler that writes an order record and then times out before responding. What idempotency design prevents duplicate orders on retry?
Summary
Lambda execution is built around managed execution environments, handler invocations, and reusable initialization. Concurrency is active work, shaped by request rate and duration, then bounded by account quotas and optional function reservations. Deployment is safest when immutable versions sit behind aliases that can move forward or backward. Treat these three areas as one design surface: execution affects latency, latency affects concurrency, concurrency affects reliability, and aliases determine whether releases and rollbacks are controlled.
