Skip to content

Commit cf0b11f

Browse files
authored
feat: support Lambda Managed Instance (#625)
* feat: support Lambda Managed Instance * fix example and tests * chore: update dependencies * chore: fix formatting * docs: fix LMI example README to match SAM template * test: add concurrent request forwarding and body isolation tests * test: add concurrent request forwarding tests for LMI support
1 parent 5187096 commit cf0b11f

13 files changed

Lines changed: 537 additions & 158 deletions

File tree

Cargo.lock

Lines changed: 156 additions & 138 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ http = "1.2.0"
2222
http-body = "1.0.1"
2323
hyper = { version = "1.5.2", features = ["client"] }
2424
hyper-util = "0.1.10"
25-
lambda_http = { version = "1.0.1", default-features = false, features = [
25+
lambda_http = { version = "1.1.0-rc1", default-features = false, features = [
2626
"apigw_http",
2727
"apigw_rest",
2828
"alb",
2929
"pass_through",
3030
"tracing",
31+
"experimental-concurrency"
3132
] }
3233
serde_json = "1.0.135"
3334
tokio = { version = "1.48.0", features = [

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The same docker image can run on AWS Lambda, Amazon EC2, AWS Fargate, and local
1212
- Run web applications on AWS Lambda
1313
- Supports Amazon API Gateway Rest API and Http API endpoints, Lambda Function URLs, and Application Load Balancer
1414
- Supports Lambda managed runtimes, custom runtimes and docker OCI images
15+
- Supports Lambda Managed Instances for multi-concurrent request handling
1516
- Supports any web frameworks and languages, no new code dependency to include
1617
- Automatic encode binary response
1718
- Enables graceful shutdown
@@ -95,14 +96,13 @@ The readiness check port/path and traffic port can be configured using environme
9596
| Environment Variable | Description | Default |
9697
|--------------------------------------------------------------|--------------------------------------------------------------------------------------|------------|
9798
| AWS_LWA_PORT | traffic port (falls back to `PORT`) | "8080" |
98-
| AWS_LWA_HOST | traffic host | "127.0.0.1"|
9999
| AWS_LWA_READINESS_CHECK_PORT | readiness check port, default to the traffic port | AWS_LWA_PORT |
100100
| AWS_LWA_READINESS_CHECK_PATH | readiness check path | "/" |
101101
| AWS_LWA_READINESS_CHECK_PROTOCOL | readiness check protocol: "http" or "tcp", default is "http" | "http" |
102102
| AWS_LWA_READINESS_CHECK_HEALTHY_STATUS | HTTP status codes considered healthy (e.g., "200-399" or "200,201,204,301-399") | "100-499" |
103103
| AWS_LWA_ASYNC_INIT | enable asynchronous initialization for long initialization functions | "false" |
104104
| AWS_LWA_REMOVE_BASE_PATH | the base path to be removed from request path | None |
105-
| AWS_LWA_ENABLE_COMPRESSION | enable gzip compression for response body | "false" |
105+
| AWS_LWA_ENABLE_COMPRESSION | enable gzip/br compression for response body (buffered mode only) | "false" |
106106
| AWS_LWA_INVOKE_MODE | Lambda function invoke mode: "buffered" or "response_stream", default is "buffered" | "buffered" |
107107
| AWS_LWA_PASS_THROUGH_PATH | the path for receiving event payloads that are passed through from non-http triggers | "/events" |
108108
| AWS_LWA_AUTHORIZATION_SOURCE | a header name to be replaced to `Authorization` | None |
@@ -131,8 +131,8 @@ For example, you could have configured your API Gateway to have a /orders/{proxy
131131
Each resource is handled by a separate Lambda functions. For this reason, the application inside Lambda may not be aware of the fact that the /orders path exists.
132132
Use AWS_LWA_REMOVE_BASE_PATH to remove the /orders prefix when routing requests to the application. Defaults to empty string. Checkout [SpringBoot](examples/springboot) example.
133133

134-
**AWS_LWA_ENABLE_COMPRESSION** - Lambda Web Adapter supports gzip compression for response body. This feature is disabled by default. Enable it by setting environment variable `AWS_LWA_ENABLE_COMPRESSION` to `true`.
135-
When enabled, this will compress responses unless it's an image as determined by the content-type starting with `image` or the response is less than 32 bytes. This will also compress HTTP/1.1 chunked streaming response.
134+
**AWS_LWA_ENABLE_COMPRESSION** - Lambda Web Adapter supports gzip/br compression for response body. This feature is disabled by default. Enable it by setting environment variable `AWS_LWA_ENABLE_COMPRESSION` to `true`.
135+
When enabled, this will compress responses unless it's an image as determined by the content-type starting with `image` or the response is less than 32 bytes. Compression is not supported with response streaming (`AWS_LWA_INVOKE_MODE=response_stream`). If both are enabled, compression will be automatically disabled with a warning.
136136

137137
**AWS_LWA_INVOKE_MODE** - Lambda function invoke mode, this should match Function Url invoke mode. The default is "buffered". When configured as "response_stream", Lambda Web Adapter will stream response to Lambda service [blog](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/).
138138
Please check out [FastAPI with Response Streaming](examples/fastapi-response-streaming) example.
@@ -172,6 +172,23 @@ Lambda Web Adapter forwards this information to the web application in a Http He
172172

173173
Lambda Web Adapter forwards this information to the web application in a Http Header named "x-amzn-lambda-context". In the web application, you can retrieve the value of this http header and deserialize it into a JSON object. Check out [Express.js in Zip](examples/expressjs-zip) on how to use it.
174174

175+
## Lambda Managed Instances
176+
177+
Lambda Web Adapter supports [Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html), which allows a single Lambda execution environment to handle multiple concurrent requests. This can improve throughput and reduce costs for I/O-bound workloads.
178+
179+
When running on Lambda Managed Instances, Lambda Web Adapter automatically handles concurrent invocations by forwarding multiple requests to your web application simultaneously. Since most web frameworks (Express.js, FastAPI, Spring Boot, etc.) are already designed to handle concurrent requests, your application should work without modification.
180+
181+
### Considerations for Multi-Concurrency
182+
183+
When using Lambda Managed Instances, keep these points in mind:
184+
185+
- **Shared state**: Global variables and in-memory caches are shared across concurrent requests. Ensure your application handles shared state safely.
186+
- **Connection pooling**: Use connection pools for databases and external services rather than single connections.
187+
- **File system**: The `/tmp` directory is shared across concurrent requests. Use unique file names or implement file locking to avoid conflicts.
188+
- **Resource limits**: Memory and CPU are shared across concurrent requests. Monitor resource usage under concurrent load.
189+
190+
Lambda Managed Instances works with both buffered and response streaming modes.
191+
175192
## Graceful Shutdown
176193

177194
For a function with Lambda Extensions registered, Lambda enables shutdown phase for the function. When Lambda service is about to shut down a Lambda execution environment,
@@ -203,6 +220,7 @@ The `AWS_LWA_LAMBDA_RUNTIME_API_PROXY` environment varible makes the Lambda Web
203220
- [FastAPI with Background Tasks](examples/fastapi-background-tasks)
204221
- [FastAPI with Response Streaming](examples/fastapi-response-streaming)
205222
- [FastAPI with Response Streaming in Zip](examples/fastapi-response-streaming-zip)
223+
- [FastAPI with Response Streaming on Lambda Managed Instances](examples/fastapi-response-streaming-lmi)
206224
- [FastAPI Response Streaming Backend with IAM Auth](examples/fastapi-backend-only-response-streaming/)
207225
- [Flask](examples/flask)
208226
- [Flask in Zip](examples/flask-zip)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.aws-sam/
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# FastAPI Response Streaming with Lambda Managed Instances
2+
3+
This example shows how to use Lambda Web Adapter to run a FastAPI application with response streaming on [Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) (LMI).
4+
5+
Lambda Managed Instances allows a single Lambda execution environment to handle multiple concurrent requests, improving throughput and reducing costs for I/O-bound workloads like streaming responses.
6+
7+
## Prerequisites
8+
9+
Lambda Managed Instances requires a VPC with:
10+
- At least one subnet (two subnets recommended)
11+
- A security group that allows outbound traffic
12+
13+
If you don't have a VPC configured, you can use the default VPC or create one.
14+
15+
## How does it work?
16+
17+
This example combines three Lambda features:
18+
19+
1. **Lambda Web Adapter** - Runs your FastAPI app on Lambda without code changes
20+
2. **Response Streaming** - Streams responses back to clients as they're generated
21+
3. **Lambda Managed Instances** - Handles multiple concurrent requests per execution environment
22+
23+
### Key Configuration
24+
25+
```yaml
26+
LMICapacityProvider:
27+
Type: AWS::Serverless::CapacityProvider
28+
Properties:
29+
CapacityProviderName: !Sub "${AWS::StackName}-capacity-provider"
30+
VpcConfig:
31+
SubnetIds: !Ref SubnetIds
32+
SecurityGroupIds: !Ref SecurityGroupIds
33+
ScalingConfig:
34+
MaxVCpuCount: 20
35+
AverageCPUUtilization: 70.0
36+
37+
FastAPIFunction:
38+
Type: AWS::Serverless::Function
39+
Properties:
40+
CodeUri: app/
41+
Handler: run.sh
42+
Runtime: python3.13
43+
MemorySize: 2048
44+
Environment:
45+
Variables:
46+
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
47+
AWS_LWA_INVOKE_MODE: response_stream
48+
PORT: 8000
49+
Layers:
50+
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:26
51+
CapacityProviderConfig:
52+
Arn: !GetAtt LMICapacityProvider.Arn
53+
PerExecutionEnvironmentMaxConcurrency: 64
54+
FunctionUrlConfig:
55+
AuthType: NONE
56+
InvokeMode: RESPONSE_STREAM
57+
```
58+
59+
- `AWS::Serverless::CapacityProvider` - Creates the LMI capacity provider with VPC configuration
60+
- `CapacityProviderConfig.Arn` - References the capacity provider
61+
- `CapacityProviderConfig.PerExecutionEnvironmentMaxConcurrency: 64` - Up to 64 concurrent requests per instance
62+
- `AWS_LWA_INVOKE_MODE: response_stream` - Configures Lambda Web Adapter for streaming
63+
- `FunctionUrlConfig.InvokeMode: RESPONSE_STREAM` - Enables streaming on the Function URL
64+
65+
## Build and Deploy
66+
67+
First, get your VPC subnet and security group IDs:
68+
69+
```bash
70+
# List subnets in your default VPC
71+
aws ec2 describe-subnets --filters "Name=default-for-az,Values=true" \
72+
--query 'Subnets[*].[SubnetId,AvailabilityZone]' --output table
73+
74+
# List security groups
75+
aws ec2 describe-security-groups --filters "Name=group-name,Values=default" \
76+
--query 'SecurityGroups[*].[GroupId,GroupName]' --output table
77+
```
78+
79+
Build and deploy:
80+
81+
```bash
82+
sam build --use-container
83+
sam deploy --guided
84+
```
85+
86+
During guided deployment, you'll be prompted for:
87+
- `SubnetIds` - Comma-separated list of subnet IDs (e.g., `subnet-abc123,subnet-def456`)
88+
- `SecurityGroupIds` - Comma-separated list of security group IDs (e.g., `sg-abc123`)
89+
90+
## Verify it works
91+
92+
Open the Function URL in a browser. You should see a message stream back character by character, with a unique request ID prefix like `[a1b2c3d4] This is streaming from Lambda Managed Instances!`.
93+
94+
### Test concurrent requests
95+
96+
To verify LMI is working, send multiple concurrent requests:
97+
98+
```bash
99+
# Get your function URL
100+
URL=$(aws cloudformation describe-stacks --stack-name fastapi-response-streaming-lmi \
101+
--query 'Stacks[0].Outputs[?OutputKey==`FastAPIFunctionUrl`].OutputValue' --output text)
102+
103+
# Send 10 concurrent requests
104+
for i in {1..10}; do curl -s "$URL" & done; wait
105+
```
106+
107+
Each response will have a different request ID, but they may share the same execution environment (visible in CloudWatch logs).
108+
109+
## Considerations
110+
111+
When using LMI with streaming:
112+
113+
- **VPC**: LMI requires VPC configuration. Ensure your subnets have internet access (via NAT Gateway) if your function needs to call external services
114+
- **Shared state**: FastAPI/Uvicorn handles concurrency natively, but avoid mutable global state
115+
- **Memory**: With 64 concurrent requests, ensure sufficient memory (2048MB in this example)
116+
- **Timeouts**: Streaming responses can run up to 15 minutes with Function URLs
117+
- **Scaling**: `MaxVCpuCount` controls the maximum vCPUs the capacity provider can provision across all instances

examples/fastapi-response-streaming-lmi/__init__.py

Whitespace-only changes.

examples/fastapi-response-streaming-lmi/app/__init__.py

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from fastapi import FastAPI
2+
from fastapi.responses import StreamingResponse
3+
import asyncio
4+
import uuid
5+
6+
app = FastAPI()
7+
8+
@app.get("/health")
9+
async def health():
10+
return {"status": "healthy"}
11+
12+
13+
async def streamer(request_id: str):
14+
"""Stream a message character by character with request ID for tracing."""
15+
message = f"[{request_id}] This is streaming from Lambda Managed Instances!\n"
16+
for char in message:
17+
yield char
18+
await asyncio.sleep(0.05)
19+
20+
21+
@app.get("/")
22+
async def index():
23+
"""Stream response - each concurrent request gets a unique ID."""
24+
request_id = str(uuid.uuid4())[:8]
25+
return StreamingResponse(streamer(request_id), media_type="text/plain")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fastapi==0.115.5
2+
uvicorn==0.32.0
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
PATH=$PATH:$LAMBDA_TASK_ROOT/bin \
4+
PYTHONPATH=$PYTHONPATH:/opt/python:$LAMBDA_RUNTIME_DIR \
5+
exec python -m uvicorn --port=$PORT main:app

0 commit comments

Comments
 (0)