AWS Builder Center
Lambda's 10 GB Memory Wall Is Gone

Lambda's 10 GB Memory Wall Is Gone

Lambda Managed Instances runs your function on EC2 with up to 32 GB of memory while AWS handles the instances. I loaded 11 GB into one and queried it at 22ms.

Cloud Engineer
I hit Lambda's 10 GB memory ceiling and it ruined a week. The workload loaded a few gigabytes of reference data into memory, ran some analytics, and returned results. On my laptop it was 40 lines of Python. On Lambda it wouldn't fit. The cap wasn't negotiable, so the "solution" was to rip the in-memory data out, put it in a database, and rewrite the whole thing around network calls. Or move to EC2 and take on patching, scaling policies, and load balancers.
Neither option was about the code. Both were about the platform's limits.
Lambda Managed Instances removes that fork. It runs your Lambda function on EC2 instance types you pick, with memory limited only by the instance family (16 GB on an m7g.xlarge, 64 GB on an r7g.2xlarge, more if you go bigger), while AWS still handles provisioning, scaling, patching, and routing. You keep the Lambda programming model. You just stop hitting the wall.
I ran a demo to prove it: an 11 GB numpy array loaded at init, queried at 22-33ms per request, five concurrent invocations sharing the same copy. Here's how it works, what the setup looks like, and what went wrong along the way.

What It Actually Is

Standard Lambda runs your function on infrastructure you never see, with fixed limits: 10 GB memory, a 250 MB unzipped package, one invocation per execution environment. Those limits are the price of never thinking about servers.
Lambda Managed Instances loosens the limits without handing you the servers. Your function runs on EC2 instances in your account from the families you choose:
  • Compute-optimized (C), general-purpose (M), memory-optimized (R), and network-optimized instances
  • Graviton4 included
  • Memory bounded by the instance type, not Lambda's 10 GB ceiling
AWS manages the instance lifecycle: provisioning, scaling, patching, and request routing. You get EC2 pricing levers (Savings Plans, Reserved Instances) that standard Lambda never gave you, because now there are actual instances to commit to. There's also a 15% management fee on top of EC2 on-demand pricing for the managed lifecycle.
It's not Fargate and it's not raw EC2. You still write a Lambda handler, still deploy it like a function, still get managed scaling. The instances underneath are the only thing that changed. One difference worth knowing: standard Lambda isolates with Firecracker MicroVMs. Managed Instances isolates with containers on EC2 Nitro. The capacity provider is the security boundary.

Multi-Concurrency Changes the Math

Standard Lambda handles one invocation per execution environment. Two concurrent requests means two environments, each with its own copy of whatever you loaded into memory. If your init loads a 900 MB model, ten concurrent requests means ten copies of that model.
Lambda Managed Instances lets one execution environment handle multiple invocations at once. You load the model or the dataset once, and many concurrent invocations share it. For memory-heavy, I/O-bound work, the expensive thing (loading gigabytes into RAM) happens once per instance instead of once per concurrent request.
You control this with PerExecutionEnvironmentMaxConcurrency. Higher values suit I/O-bound analytics where invocations spend time waiting on computation, not blocking resources from each other.

The Demo: 11 GB In-Memory, Five Concurrent Queries

I allocated an 11 GB numpy array at init time (1.47 billion float64 elements), then ran statistical queries against it. Standard Lambda can't do this. Its maximum is 10,240 MB.

The Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
import os, time

INIT_START = time.time()
ARRAY_SIZE = int(os.environ.get("ARRAY_SIZE_GB", "11"))
ELEMENTS = ARRAY_SIZE * (1024**3) // 8

DATA = np.random.rand(ELEMENTS)
INIT_DURATION = time.time() - INIT_START

def handler(event, context):
slice_size = int(event.get("slice_size", 1_000_000))
offset = int(event.get("offset", 0))
data_slice = DATA[offset:offset + slice_size]

return {
"statusCode": 200,
"body": {
"mean": float(np.mean(data_slice)),
"std": float(np.std(data_slice)),
"p99": float(np.percentile(data_slice, 99)),
"query_time_ms": ...,
"init_time_s": round(INIT_DURATION, 2),
"array_size_gb": ARRAY_SIZE,
"memory_rss_mb": ...
}
}
The array loads once. Every invocation queries a slice of it. On Managed Instances with concurrency set to 5, five queries run against the same 11 GB without copying it.

The Infrastructure

One capacity provider, one function, a container image pushed to ECR:
  • Capacity provider: lmi-blog-demo-v2, two subnets (us-east-1a, us-east-1b), m6i.2xlarge instances (8 vCPU, 32 GB), max 24 vCPUs
  • Function: 14,336 MB configured memory, 4 GiB per vCPU, max concurrency 5 per environment
  • Image: Python 3.12 base + numpy, pushed to ECR
funcrtion
function
capacity
capacity

What Happened During Setup

The first attempt failed. I used m5.xlarge instances (16 GB), but 14 GB of function memory plus OS/container overhead doesn't fit on 16 GB of total instance RAM. The error was clear: IncompatibleInstanceTypes. Deleted the capacity provider, recreated with m6i.2xlarge (32 GB), and it worked.
The minimum MaxVCpuCount is higher than I expected. I tried 8 vCPUs and got rejected ("below the recommended minimum"). Bumped to 16, then 24 for the larger instances. The docs don't state the floor explicitly, so you discover it at create time.
Publishing the function version is what actually provisions instances. That took about 3 minutes. Before publishing, the function sits in ActiveNonInvocable state.

The Result

1
2
3
4
Array: 11.00 GB (1,476,395,008 float64 elements)
Memory: 14,336 MB configured, 11,307 MB RSS observed
Init: 14.73 seconds
Query: 22-33 ms (1M element slice)
version 2
version 2
Five concurrent invocations, all 200 OK, all 22-33ms:
1
2
3
4
5
Invocation 0: 26.45 ms
Invocation 1: 27.03 ms
Invocation 2: 24.35 ms
Invocation 3: 22.99 ms
Invocation 4: 32.84 ms
All five returned memory_rss_mb: 11307. Same address space. One copy of the 11 GB array served all five queries.
e
results
results
Results
Results

CloudWatch Tells the Story

The logs show one init (14.73s), then invocations arriving at 28ms intervals with no re-init:
1
2
3
4
5
6
7
[INIT] Allocating 11 GB numpy array (1,476,395,008 float64 elements)...
[INIT] Array allocated. Shape: (1476395008,), Size: 11.00 GB
[INIT] Total init time: 14.73s
platform.initReport: initializationType "lambda-managed-instances", durationMs 15081
platform.report: requestId "b16c30d5...", durationMs 36
platform.report: requestId "616b4211...", durationMs 28
platform.report: requestId "b1293560...", durationMs 28
apaci
capacity
capacity
The capacity provider logs show exactly what launched:
1
2
"Launched m6i.2xlarge instance i-0d82b0e609be55ce8 in us-east-1a"
"Launched m6i.2xlarge instance i-0d41f1113d8a3a3e9 in us-east-1b"
Two instances for AZ resiliency. Lambda picked m6i.2xlarge from the allowed list.
est
test
test
test
test

How It Works: Capacity Providers

The one new concept is the capacity provider. It defines the EC2 infrastructure your functions run on: the VPC, the permissions, the allowed instance types, and the scaling behavior. You create it once, then point functions at it.
A capacity provider needs:
  • A VPC with at least two subnets across different Availability Zones, plus a security group
  • An operator IAM role (use the AWSLambdaManagedEC2ResourceOperator managed policy)
  • Instance requirements: processor architecture and the instance types you'll allow
  • Auto scaling settings: a maximum number of EC2 vCPUs
Once the capacity provider exists, you create a function with a CapacityProviderConfig block referencing the ARN. Then you pick the memory size and the memory-to-vCPU ratio. Publishing a version is what triggers actual instance provisioning, and it takes a few minutes.
Scaling is based on CPU utilization, and instances scale up before they're saturated. The tradeoff: instances don't scale to zero. More on that below.

Memory and vCPU Rules

There are floors you can't go below:
  • Minimum 2 GB memory and 1 vCPU. Functions here are meant to handle multiple concurrent invocations, so they need at least one full vCPU.
  • The memory-to-vCPU ratio is configurable (2:1, 4:1, 8:1). Pick it to match your workload.
  • Python often wants a higher ratio (4:1 or 8:1) because of how it handles multi-concurrency.
Size memory to your dataset plus about 50% overhead, then watch CloudWatch and tune. In my demo, 11 GB of data fit comfortably in 14 GB configured memory (78.8% utilization).

When to Use It vs. Standard Lambda

Use Lambda Managed Instances when:
  • Your function needs more than 10 GB of memory
  • Traffic is high-volume and predictable
  • You want EC2 Savings Plans or Reserved Instance discounts
  • You benefit from loading something expensive once and serving many concurrent invocations against it
Stay on standard Lambda when:
  • Traffic is spiky or low-volume and you need true scale-to-zero
  • Your function fits comfortably under 10 GB
  • Each invocation is short, stateless, and doesn't share expensive in-memory state
The dividing line is the traffic shape as much as the memory. Standard Lambda wins for bursty, unpredictable, small-footprint work because it scales to zero. Managed Instances wins for steady, memory-hungry work because sustained EC2 capacity with a commitment discount beats per-millisecond billing at volume.

What You Pay

Pricing has three parts: $0.20 per million requests, EC2 instance costs, and a 15% management fee on the EC2 on-demand price.
AWS's own pricing example from the docs (100M requests/month on m7g.xlarge in us-east-1 with 3-year Compute Savings Plan):
ComponentMonthly cost
Requests (100M × $0.20/M)$20
EC2 instances (2,000 hrs × $0.0457)$91.40
Management fee (15% of on-demand price)$48.96
Total$160.36
My demo ran for about 2 hours on two m6i.2xlarge instances. Total cost: roughly $2-3, most of which was the idle time between tests while I grabbed screenshots.

Things to Know Before You Build

You need a VPC. The capacity provider requires at least two subnets across AZs and a security group.
It doesn't scale to zero. Without traffic, it scales down to the minimum execution environments configured (not zero). You pay for that baseline during idle periods.
Provisioning takes minutes. Publishing a function version triggers real instance launches. In my case, about 3 minutes from publish to Active.
Instance types matter more than you'd think. My first attempt failed because 14 GB of function memory doesn't fit on a 16 GB instance. The OS, container runtime, and Lambda agent all need headroom. Go one size up from what your math says you need.
vCPU minimums aren't documented clearly. I tried MaxVCpuCount of 8 and got rejected. The error message just says "below the recommended minimum." 16 worked for xlarge instances, 24 for 2xlarge.
Managed instances are hidden in EC2. The instances run in your account but don't show up in the EC2 console or DescribeInstances by default. You can see them in the capacity provider's CloudWatch logs, and there's a visibility toggle if you need to find them.
Watch the concurrency setting for CPU-bound work. High per-environment concurrency is great for I/O-bound analytics. For CPU-bound functions, packing many invocations onto one environment can starve them.

Cleanup

1
2
3
4
5
6
7
8
9
10
11
12
# Delete function version, then base function
aws lambda delete-function --function-name lmi-blog-demo --qualifier 2
aws lambda delete-function --function-name lmi-blog-demo

# Delete capacity provider (must remove function association first)
aws lambda delete-capacity-provider --capacity-provider-name lmi-blog-demo-v2

# Clean up supporting resources
aws ecr delete-repository --repository-name lmi-blog-demo --force
aws ec2 delete-security-group --group-id sg-xxx # after instances terminate
aws iam detach-role-policy --role-name lmi-blog-demo-operator --policy-arn arn:aws:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator
aws iam delete-role --role-name lmi-blog-demo-operator
The capacity provider must be deleted to stop billing. Instances terminate as part of the deletion process. The security group can't be deleted until the instance ENIs detach, which takes a few minutes after instance termination.

What I'd Build Next

The 11 GB numpy array proves the concept. The real application is an embedding index, a large model, or a dataset that needs to stay resident across requests. The pattern is the same: load once, query many times, pay for sustained capacity instead of per-invocation memory.
Combined with S3 Files (mount S3 as a filesystem on Lambda), you could load a dataset from S3 at filesystem speed, hold it in memory, and serve queries against it without a database in the path at all. That's the post for next time.
Built and tested August 27, 2026 in us-east-1. Instance type: m6i.2xlarge. Memory: 14,336 MB configured, 11 GB allocated. Total invocations: 7, all successful. Total cost: ~$2-3. Screenshots and code are from a real run, not mocked.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article