What it means
Every Lambda REPORT line carries two memory numbers, and the relationship between them is the most actionable diagnostic Lambda produces. Memory Size is the allocation you configured. Max Memory Used is the highest the runtime saw the process reach during that invocation. When the second equals the first, the process was at its ceiling.
What makes this signal unusual is that it is often the only warning you get, and it frequently appears on invocations that succeeded. Memory is not consumed smoothly; it moves in allocations. A run that peaks at exactly its limit and returns normally did so because the next allocation request happened not to come. The identical workload with a slightly larger input, or a garbage collection that ran a few milliseconds later, gets SIGKILL instead — and that failure carries no stack trace at all. The successful run at 100% and the mysterious silent death are the same condition observed at two different moments.
There is a second reason this goes unnoticed for months at a time: Lambda does not publish max memory used as a CloudWatch metric. Invocations, errors, duration, throttles and concurrency all have metrics. Memory does not. The number exists only as text inside the REPORT line, so no dashboard shows it, no default alarm fires on it, and finding it means reading log lines. That is how functions come to sit at 95% utilisation for months and then start failing during a traffic peak with nothing in the monitoring having changed.
The number is worth reading in the other direction too. A function allocated 1024 MB that never exceeds 180 MB is paying for roughly five times the memory it uses on every single invocation, and the fix is a one-line configuration change. The same two numbers that warn you about an imminent OOM tell you where you are overpaying — which is why it is worth having them summarised across a window of invocations rather than squinting at one REPORT line at a time.
CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. A memory ceiling is a measurement, not an outcome. If the allocation that crossed it was fatal the invocation was killed and CloudWatch counts an error; if the function happened to finish first, the invocation is recorded as a success and nothing is counted — which is exactly why this signal is worth watching in its own right.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
7 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
SuccessMemory ceiling7c2ab910-1f44-4a3e-9c07-2b8f1d5e6a72
- 05:31:22.010PLAT
START RequestId: 7c2ab910-1f44-4a3e-9c07-2b8f1d5e6a72 Version: $LATEST
- 05:31:22.014INFO
INFO Thumbnail job started key=uploads/DSC_4417.tiff
- 05:31:22.640INFO
INFO Source decoded 6048x4024
- 05:31:23.918INFO
INFO Resized to 1280x851
- 05:31:24.077INFO
INFO Uploaded thumbnails/DSC_4417.jpg bytes=214088
- 05:31:24.081PLAT
END RequestId: 7c2ab910-1f44-4a3e-9c07-2b8f1d5e6a72
- 05:31:24.081PLAT
REPORT RequestId: 7c2ab910-1f44-4a3e-9c07-2b8f1d5e6a72 Duration: 2071.33 ms Billed Duration: 2072 ms Memory Size: 512 MB Max Memory Used: 512 MB
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
Read the two numbers on the REPORT line together. Memory Size is what you configured; Max Memory Used is the high-water mark the runtime observed. When they match exactly, the invocation was at its limit. Above roughly 85% of Memory Size is close enough that ordinary input variation will reach it.
Do this across many invocations rather than one. A single run at the ceiling might be an outlier; a p95 at the ceiling is a function that will start failing on a busy day. The pattern to look for is a cluster of runs pinned at exactly the allocation mixed in with runs that died leaving no stack trace — those are the same failure caught at two different moments.
Causes, most likely first
The allocation was sized for the average case, not the largest one
Compare Max Memory Used across a wide sample. If most invocations sit comfortably low and a minority pin at the ceiling, memory tracks something about the input — payload size, result count, image dimensions — and the allocation was chosen from the typical case rather than the worst one.
A warm container has accumulated state across invocations
Sort invocations by time within a single log stream and watch Max Memory Used over the sequence. Low on the cold start and climbing with each subsequent request on the same container means something at module scope is retaining memory between invocations.
The language runtime's own heap limit is derived from the allocation
Check whether the runtime reported a heap error just before the ceiling — JavaScript heap out of memory on Node, java.lang.OutOfMemoryError on the JVM. Those runtimes size their heap from the container limit, so the two ceilings arrive at nearly the same moment and the runtime usually speaks first.
The function has simply outgrown its setting
Look at whether the ceiling runs are succeeding. A function that consistently uses 100% and consistently returns correct results is not leaking and not misbehaving — it has grown into its allocation and is now running with no margin at all.
Fixes
Right-size from the observed peak, with real headroom
Take the highest Max Memory Used across a representative window and add roughly 30%. Lambda allocates from 128 MB to 10,240 MB, and because CPU scales with memory a raise often shortens duration enough to offset much of the extra per-millisecond cost.
yaml# Observed peak was 512 MB of a 512 MB allocation.
# 512 / 0.7 = 731, so round up to a value with real headroom.
Resources:
ExportFunction:
Type: AWS::Serverless::Function
Properties:
MemorySize: 768
Alarm on the ceiling instead of waiting for the kill
Lambda does not publish max-memory-used as a CloudWatch metric, which is why this signal is so widely missed — it exists only as text inside the REPORT line. A metric filter extracts it, so you can alert on approaching the ceiling rather than discovering it through failed invocations.
bashaws logs put-metric-filter \
--log-group-name /aws/lambda/export-function \
--filter-name max-memory-used \
--filter-pattern '[r=REPORT, ...]' \
--metric-transformations \
metricName=MaxMemoryUsedMB,metricNamespace=Lambda/Custom,metricValue=1
Reduce the working set before raising the number
If memory use scales with input, raising the allocation buys time rather than fixing anything. Stream large payloads instead of buffering them, page through result sets rather than collecting them, and drop references to anything large as soon as you are done with it so the collector can actually reclaim it.
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- AWS Lambda Developer Guide — Configuring function memory
- AWS Lambda Developer Guide — Working with Lambda function metrics
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.