[Typescript] Tried out Lambda SnapStart now that it supports container images [Rust]
This page has been translated by machine translation. View original
Introduction
In September 2026, AWS Lambda SnapStart added support for functions packaged as container images.
Until now, SnapStart was exclusive to managed runtimes for Java / Python / .NET, but
with container images, it can now be used for Node.js and Rust (provided.al2023) functions as well.
provided.al2023 is an OS-only runtime that only provides Amazon Linux 2023 as the OS
and does not include a language runtime.
It is intended for languages that compile to native binaries, such as Rust and Go,
and is used by placing an executable called bootstrap that embeds the Runtime Interface Client.
For containers, public.ecr.aws/lambda/provided:al2023 is the base image.
You might think SnapStart is unnecessary for Rust since its native binary already has fast cold starts,
but let's try it anyway.
I also frequently write Lambdas in TypeScript, so I'll test that too.
- Compare SnapStart on/off with minimal Hello World functions (Rust / TypeScript)
- Same comparison with functions that have intentionally heavy initialization (several seconds, tens of seconds)
Note that "Init" in this article refers to the Init phase where Lambda starts the runtime after preparing the execution environment
and executes initialization code outside the handler (module loading and pre-processing before main).
※ AWS describes this initialization as the biggest factor in cold starts
※ In the container functions this time, environment preparation other than Init also took several hundred milliseconds
In regular functions, it is recorded as Init Duration in the REPORT line of the first invocation in a new execution environment.
※ Except when Init was re-executed after exceeding 10 seconds
Conclusion:
Under the conditions of this test, whether SnapStart speeds up cold starts
depended on the length of the Init processing.
For Hello World-level code, the SnapStart version is slower, but
for cases where Init is approximately 2.1 seconds or longer, both Rust and TypeScript
had cold starts (client-side E2E p50) reduced to under 1 second.
About Snap Start
How It Works
Normal Lambda creates an execution environment after a request arrives and initializes the runtime and code.
With SnapStart enabled, this initialization is performed at version publication time,
and the memory and disk state of the initialized execution environment (Firecracker microVM)
is encrypted and cached as a snapshot.
Thereafter, instead of re-initializing on the first invocation or during scale-out, it restores from the snapshot.
Supported Scope and Constraints
As of September 2026, the official documentation (SnapStart overview and container image hooks page) is organized as follows:
| Deployment format | Runtime | SnapStart |
|---|---|---|
| ZIP / Container | Java 11+, Python 3.12+, .NET 8+ (managed runtimes and supported base images) | Supported. Lambda manages the lifecycle |
| ZIP | Node.js, Ruby and other managed runtimes, OS-only runtime | Not supported |
| Container | provided.al2023 / Node.js / Ruby base images, custom base images, custom RIC |
Supported. Must comply with the lifecycle contract for containers |
The lifecycle contract for containers requires that
when the AWS_LAMBDA_INITIALIZATION_TYPE environment variable is
snap-start at initialization completion, execute the before-snapshot hook and call
GET /runtime/restore/next of the Runtime API, and after restoration (after this call returns HTTP 200),
execute the after-restore hook before entering the normal invoke loop.
If hooks are not needed, you can opt-in by simply adding the following label to your Dockerfile:
LABEL com.amazonaws.lambda.feature.snapstart="Allow"
If neither the /restore/next implementation nor the label is present, version publication will fail.
Also, the combined total of Init and before-snapshot hooks has a limit of max(function timeout, 130 seconds).
Other main constraints are as follows:
- Only effective on published versions (or aliases pointing to them). Cannot be used with
$LATEST - Cannot be used together with Provisioned Concurrency, Amazon EFS, Amazon S3 Files, or ephemeral storage exceeding 512 MB
- Unique values generated during initialization (IDs, secrets, PRNG state, etc.) may be identical across multiple restored environments. Also, network connections established during initialization are not guaranteed to be in a valid state after restoration, so reconnection should be performed as needed
Logs and Billing
In SnapStart functions, Init is performed at publication time, so Init Duration does not appear in REPORT at invocation time.
Instead, the Init time is recorded in INIT_REPORT at publication time,
and REPORT for invocations that created a new execution environment will include
Restore Duration and Billed Restore Duration.
Cold start time is defined as Restore Duration + Duration.
Pricing for runtimes other than the Java managed runtime includes
a snapshot cache per published version (minimum 3 hours while the version is active)
and a charge for each restoration. Both are based on the function's memory allocation.
For example, in the US East (us-east-1) region, the cache is
0.0000015046 USD/GB-second and restoration is 0.0001397998 USD/GB.
※ As of 2026/09/04
※ Tokyo region is the same price
※ usagetype APN1-Lambda-SnapStart-Cached-GB-S / APN1-Lambda-SnapStart-Restored-GB
The pricing page does not specifically mention container images,
but from the wording that "charges apply for runtimes other than the Java managed runtime,"
it appears that Rust / Node.js in containers are also subject to charges.
What Functions Benefit Most
AWS's blog states:
- For functions where initialization completes in a few hundred milliseconds, significant performance improvement from SnapStart cannot be expected
- In such cases, Provisioned Concurrency is recommended
It also states that snapshots are saved in 512 KB chunks, and the larger the function, the more chunks there are, affecting restoration performance.
SnapStart Support for Rust and Node.js
The official AWS Rust runtime lambda_runtime introduced SnapStart support via the SnapStartResource trait in 1.3.0.
In 1.4.0 used this time, you can confirm code that checks AWS_LAMBDA_INITIALIZATION_TYPE == "snap-start"
and executes before_snapshot → /restore/next → after_restore.
This means Rust has the runtime handling the "when hooks are needed" implementation, so it should work without a label.
On the other hand, grepping the Runtime Interface Client of the Node.js 22 base image used this time (public.ecr.aws/lambda/nodejs:22, runtime-release 22.23.2-6a8edcbc)
found no implementation of restore/next.
Therefore, Node.js was opted-in using the label method (this may change in the future).
Environment
| Item | Value |
|---|---|
| Region | ap-northeast-1 (Tokyo) |
| Lambda | Container image / x86_64 / 512 MB |
| Rust | rustc 1.95.0, lambda_runtime 1.4.0, tokio, serde_json. Cross-built with cargo lambda build --release --x86-64 (cargo-lambda 1.9.1) |
| Rust base image | public.ecr.aws/lambda/provided:al2023 (image 43.6 MB) |
| TypeScript | Node.js 22 base image public.ecr.aws/lambda/nodejs:22 (image 138.1 MB), bundled with esbuild |
| Tools | AWS CLI 2.34.50, boto3 1.43.83 |
| Measurement client | boto3 Invoke from within Japan. Round-trip for warm invocations is approximately 70 ms |
SnapStart off and on are compared using separate functions created from the same image.
(-off uses ApplyOn=None, -on uses ApplyOn=PublishedVersions)
Setup
Rust
The only dependencies in Cargo.toml are lambda_runtime = "1.4.0", serde_json, and tokio.
To verify that the SnapStart lifecycle actually works, hooks that only output logs are registered.
※ The code in this article is a simplified excerpt for explanation purposes
use lambda_runtime::{BoxFuture, Error, LambdaEvent, Runtime, SnapStartResource, service_fn};
struct LifecycleLogger;
impl SnapStartResource for LifecycleLogger {
fn before_snapshot(&self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async { println!("[snapstart] before_snapshot"); Ok(()) })
}
fn after_restore(&self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async { println!("[snapstart] after_restore"); Ok(()) })
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let init_type = std::env::var("AWS_LAMBDA_INITIALIZATION_TYPE").unwrap_or_default();
println!("[init] AWS_LAMBDA_INITIALIZATION_TYPE={init_type}");
Runtime::new(service_fn(|event: LambdaEvent<serde_json::Value>| async move {
Ok::<_, Error>(serde_json::json!({ "request_id": event.context.request_id }))
}))
.register_snapstart_resource(std::sync::Arc::new(LifecycleLogger))
.run()
.await
}
The Dockerfile simply places the pre-built binary, with no label attached.
FROM public.ecr.aws/lambda/provided:al2023
COPY target/lambda/bootstrap/bootstrap ${LAMBDA_RUNTIME_DIR}/bootstrap
ENTRYPOINT ["/var/runtime/bootstrap"]
TypeScript
The initialization type is recorded in module scope (Init phase),
and the handler simply returns it.
import type { Handler } from "aws-lambda";
const initType = process.env.AWS_LAMBDA_INITIALIZATION_TYPE ?? "unknown";
console.log(`[init] AWS_LAMBDA_INITIALIZATION_TYPE=${initType}`);
export const handler: Handler = async (event, context) => ({
statusCode: 200,
body: JSON.stringify({ initType, requestId: context.awsRequestId }),
});
Bundled into dist/index.js with esbuild and placed in the Node.js base image with a label.
FROM public.ecr.aws/lambda/nodejs:22
LABEL com.amazonaws.lambda.feature.snapstart="Allow"
COPY dist/index.js ${LAMBDA_TASK_ROOT}/
CMD ["index.handler"]
Deployment
After pushing to ECR, the SnapStart version is created with --snap-start ApplyOn=PublishedVersions,
a version is published, and we wait for State=Active.
% aws lambda create-function \
--function-name snapstart-bench-rust-on \
--package-type Image --code "ImageUri=${ECR_URI}@${DIGEST}" \
--role "$ROLE_ARN" --architectures x86_64 \
--memory-size 512 --timeout 10 \
--snap-start ApplyOn=PublishedVersions
% VERSION=$(aws lambda publish-version --function-name snapstart-bench-rust-on \
--query Version --output text)
% aws lambda wait function-active-v2 --function-name snapstart-bench-rust-on --qualifier "$VERSION"
% aws lambda get-function-configuration --function-name snapstart-bench-rust-on \
--qualifier "$VERSION" --query '{State:State,SnapStart:SnapStart}'
# => State: Active, SnapStart.OptimizationStatus: On
% aws lambda create-alias --function-name snapstart-bench-rust-on --name live --function-version "$VERSION"
Version publication succeeded even for the Rust image without a label,
and OptimizationStatus was On.
The time from publication to Active was 1–4 seconds for SnapStart off,
and 50–65 seconds for SnapStart on (up to 125 seconds for functions with heavy initialization).
Since the snapshot is rebuilt every time a version is published,
this time is incurred with every deployment. (CI/CD pipelines should take note of this)
Try
Measurement Method
To measure cold starts, simply calling sequentially won't work since the 2nd and subsequent invocations will be warm,
so a new execution environment must be created each time.
This time, 3 rounds of 24 → 48 → 72 concurrent invocations per function (144 requests total) were run with boto3,
and the following were extracted from REPORT lines returned with LogType=Tail.
- Normal cold:
Init Duration - SnapStart cold:
Restore DurationandBilled Restore Duration - Additionally, client-side round-trip time (E2E)
Note the following points. All figures in the article account for these.
-
Init Durationdoes not represent the entire cold start for container functions. It does not include microVM preparation or image retrieval,
and the difference from client E2E was approximately 480 ms for Rust and approximately 240 ms for TypeScript.
On the other hand,Restore Durationis officially stated to "include processing outside the microVM,"
and the actual difference was approximately 150–175 ms. -
Init Duration/Restore Durationappear even in proactively initialized environments.
Lambda may initialize environments ahead of invocations even on-demand,
and in such cases the invocation appears cold in REPORT but is effectively warm from the client (approximately 90 ms).
Lines where client time is shorter than server time are excluded, and only the first burst (24 concurrent) immediately after publication is
used for cold start figures. -
If Init exceeds 10 seconds,
Init Durationdoes not appear in REPORT.
The on-demand Init phase is cut off at 10 seconds and retried during the first invocation.
In this case, the cold start shows up inDuration, so rows withDuration > 5 secondswere treated as cold.
All CSV rows were checked against CloudWatch Logs REPORT lines and RequestId,
confirming there were no invocations before the measurement and that cold start determinations matched.
Functions with Heavy Initialization
To verify the official explanation that "functions with initialization completing in a few hundred milliseconds won't see improvement,"
functions with heavier initialization were also prepared using the same configuration.
Dictionary data bundled in the image (JSONL, 1 million rows, 181 MB, generated with a fixed random seed) is loaded during Init,
and test data structures are built.
The state is read-only and deterministic, so it is safe to include in the snapshot.
The handler randomly references 200 entries determined by the request ID.
// Init (beginning of main): load dictionary and build 3 indexes
let limit: usize = std::env::var("DICT_LIMIT").ok().and_then(|v| v.parse().ok()).unwrap_or(500_000);
let dict = load_dictionary("/var/task/dictionary.jsonl", limit)?;
fn load_dictionary(path: &str, limit: usize) -> Result<Dictionary, Error> {
let reader = BufReader::with_capacity(1 << 20, File::open(path)?);
let mut entries: Vec<Entry> = Vec::with_capacity(limit);
for line in reader.lines().take(limit) {
entries.push(serde_json::from_str(&line?)?);
}
let mut by_word = HashMap::with_capacity(entries.len());
let mut trigrams: HashMap<[u8; 3], Vec<u32>> = HashMap::new();
for e in &entries {
by_word.insert(e.word.clone(), e.id);
for w in e.word.as_bytes().windows(3) {
trigrams.entry([w[0], w[1], w[2]]).or_default().push(e.id);
}
}
let by_id: HashMap<u32, Entry> = entries.into_iter().map(|e| (e.id, e)).collect();
Ok(Dictionary { by_id, by_word, trigrams })
}
TypeScript implements the same workload using ESM + top-level await.
const dict = await loadDictionary("/var/task/dictionary.jsonl", limit);
// Module scope = Init
async function loadDictionary(path: string, limit: number): Promise<Dictionary> {
const entries: Entry[] = [];
const rl = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
for await (const line of rl) {
entries.push(JSON.parse(line));
if (entries.length >= limit) break;
}
const byId = new Map<number, Entry>(), byWord = new Map<string, number>(), trigrams = new Map<string, number[]>();
for (const e of entries) {
byId.set(e.id, e); byWord.set(e.word, e.id);
for (let i = 0; i + 3 <= e.word.length; i++) {
const k = e.word.slice(i, i + 3);
(trigrams.get(k) ?? trigrams.set(k, []).get(k)!).push(e.id);
}
}
return { byId, byWord, trigrams };
}
The heaviness of Init can be changed via environment variables, and 3 variants were created for each language.
| Variant | Content | Rust | TypeScript |
|---|---|---|---|
| A: Several seconds | Load dictionary and build index | 500K rows (memory usage 327 MB) | 150K rows (memory usage 178 MB) ※ |
| B: Tens of seconds | Repeat A INIT_PASSES times (memory doesn't increase since previous result is cleared before reloading) |
7 times (Init at publication 39.5 sec) | 15 times (Init at publication 66.8 sec) |
| C: sleep 30 seconds | No data reading, just sleep(30 s). Long time but small memory |
Memory usage 2.4 MB | Memory usage 51 MB |
- Memory usage is the physical memory (RSS: Resident Set Size) actually used by the function's process. This is the value read by the function itself at the end of Init (Rust reads from
/proc/self/statusVmRSS, Node.js usesprocess.memoryUsage().rss), used as a reference for the amount of memory included in the snapshot. This is separate fromMax Memory Used(peak value for the entire execution environment) in Lambda'sREPORTline. - Loading 500K rows in Node.js results in approximately 470 MB of memory usage (measured locally), which doesn't fit within 512 MB, so the row count was reduced.
- Since Rust and TypeScript use different row counts, please compare the off/on difference within each language.
On-demand versions of B and C have Init exceeding 10 seconds, so the function timeout was changed to 90 seconds (60 seconds for A).
Verification Results
SnapStart Operation Verification
These are CloudWatch Logs for the Rust SnapStart version.
※ From a function published in us-east-1 for verification of log format; figures are separate from the Tokyo measurements
Init and hooks are recorded at publication time, restoration and hooks at first invocation time,
and Restore Duration is appended to REPORT.
[init] AWS_LAMBDA_INITIALIZATION_TYPE=snap-start at=1788402754940
[snapstart] before_snapshot at=1788402754941
INIT_REPORT Init Duration: 177.09 ms
[snapstart] after_restore at=1788402924927 ← 170 seconds later, at first invoke
RESTORE_REPORT Restore Duration: 372.48 ms
START RequestId: de245e74-... Version: 1
REPORT RequestId: de245e74-... Duration: 1.54 ms Billed Duration: 2 ms Memory Size: 512 MB Max Memory Used: 16 MB Restore Duration: 372.48 ms Billed Restore Duration: 0 ms
The init_type returned by the handler is snap-start for all requests in the SnapStart version,
the Init timestamp retains the value from publication time and is identical across all environments,
confirming that the snapshot is being replicated.
Minimal Function
First, let's compare SnapStart on/off with a "function that does almost nothing."
How to read the measurements:
-
24 concurrent invocations immediately after publication, collecting only those that created new execution environments (cold starts).
Invocations that hit environments Lambda proactively prepared in advance are excluded (see Measurement Method). Count is 22 for Rust off, 24 for others.
※ Rust off had 2 invocations reusing the same environment -
"Server-side cold" is the value from Lambda logs (
REPORTline); for SnapStart off it'sInit Duration, for on it'sRestore Duration, each added to handler execution time (Duration). -
"Client E2E" is the round-trip time seen from the caller. Since it includes network time and the time Lambda takes to prepare the execution environment, it is close to the actual wait time experienced by users.
-
p50 is the middle value (median) when measurements are sorted in ascending order, p90 is the upper limit excluding the slowest 10%. For 24 samples, p50 is the 12th value and p90 is the 22nd.
| Language | Metric | SnapStart off | SnapStart on | Difference (on − off) |
|---|---|---|---|---|
| Rust | Server-side cold (Init or Restore + Duration) | 19 ms | 377 ms | +358 ms |
| Rust | Client E2E p50 / p90 | 497 / 507 ms | 563 / 630 ms | +66 / +123 ms |
| TypeScript | Server-side cold | 274 ms | 418 ms | +144 ms |
| TypeScript | Client E2E p50 / p90 | 530 / 656 ms | 568 / 671 ms | +38 / +15 ms |

The figure illustrates the same invocations as the table above. Bars represent p50, vertical lines represent p90.
Result: SnapStart does not improve speed for Hello World
This is because SnapStart Restore has a fixed overhead of approximately 380–400 ms.
Since Hello World initialization is only 18 ms for Rust and 270 ms for TypeScript,
the time saved by skipping initialization is shorter than the time Restore takes.
For round-trip time from the caller (client E2E), the difference narrows to 15–125 ms,
but the result that the SnapStart version is still slower remains unchanged.
This is consistent with the official explanation that "functions with initialization completing in a few hundred milliseconds cannot expect improvement."
Note that the Billed Restore Duration, the billable portion of the restore time, was 0–2 ms.
(Snapshot cache charges per version are billed separately)
Rust Functions with Heavy Initialization
| Variant | Metric | SnapStart off | SnapStart on |
|---|---|---|---|
| A: Dictionary load (Init approx. 4.6 sec) | Server-side Init / Restore p50 (p90) | 4,579 (4,759) ms | 673 (792) ms |
| A | Client E2E p50 (p90) | 4,894 (5,045) ms | 884 (1,000) ms |
| B: Tens of seconds | First invocation Duration p50 | 15,690 ms (re-initialization) | 5 ms (Restore 693 ms) |
| B | Client E2E p50 (p90) | 25,985 (27,816) ms | 871 (924) ms |
| C: sleep 30 seconds | First invocation Duration p50 | 30,027 ms (re-initialization) | 1.3 ms (Restore 356 ms) |
| C | Client E2E p50 (p90) | 40,333 (40,361) ms | 514 (568) ms |

The figure shows client E2E p50 (bars) and p90 (vertical lines), with the unit in seconds.
Gray is SnapStart off, blue is on, aligned on the same scale.
A reduced cold start (client E2E p50) from approximately 4.9 seconds to 0.88 seconds, B from approximately 26 seconds to 0.87 seconds,
and C from approximately 40 seconds to 0.51 seconds.
TypeScript Functions with Heavy Initialization
| Variant | Metric | SnapStart off | SnapStart on |
|---|---|---|---|
| A: Dictionary load (Init approx. 2.1 sec) | Server-side Init / Restore p50 (p90) | 2,115 (2,417) ms | 546 (650) ms |
| A | Client E2E p50 (p90) | 2,412 (2,700) ms | 746 (861) ms |
| B: approx. 60 seconds ※ | First invocation Duration p50 | 59,213 ms (re-initialization) | 27 ms (Restore 565 ms) |
| B | Client E2E p50 (p90) | 69,483 (71,907) ms | 762 (854) ms |
| C: sleep 30 seconds | First invocation Duration p50 | 30,137 ms (re-initialization) | 18 ms (Restore 395 ms) |
| C | Client E2E p50 (p90) | 40,409 (40,417) ms | 577 (715) ms |
※ B was set to 15 repetitions aiming for 30 seconds, but in on-demand re-initialization the time per pass increased, resulting in approximately 60 seconds in practice

The figure shows client E2E p50 (bars) and p90 (vertical lines), with the unit in seconds.
Gray is SnapStart off, blue is on, aligned on the same scale.
TypeScript shows the same trend; client E2E p50 went from approximately 2.4 seconds to 0.75 seconds for A, approximately 69 seconds to 0.76 seconds for B,
and approximately 40 seconds to 0.58 seconds for C.
Behavior When Init Exceeds 10 Seconds
The logs for the on-demand versions of B and C recorded the Init timeout and re-execution as follows:
[init] AWS_LAMBDA_INITIALIZATION_TYPE=on-demand ...
INIT_REPORT Init Duration: 9999.36 ms Phase: init Status: timeout
[init] AWS_LAMBDA_INITIALIZATION_TYPE=on-demand ... ← Process restarts and retries Init
[init] slept 30000 ms
REPORT RequestId: afba3be3-... Duration: 30027.70 ms Billed Duration: 30028 ms Memory Size: 512 MB ...
This behavior matches the official documentation stating
"the Init phase must complete within 10 seconds; if exceeded, Init is retried within the function timeout during the first invocation."
The retried Init is included in Billed Duration in the REPORT line and is billed as normal execution time.
The cold start from the client's perspective becomes 10 seconds + Init time, resulting in approximately 40 seconds for the sleep 30-second function.
Since the SnapStart version relaxes the Init limit to max(function timeout, 130 seconds), the 10-second cutoff no longer occurs for B/C in this test.
Restore Time and Memory Usage
| Function | Memory usage at snapshot creation | Restore Duration p50 |
|---|---|---|
| Rust sleep 30 seconds | 2.4 MB | 356 ms |
| Rust minimal | Memory usage not collected (Max Memory Used in REPORT is 16 MB) | 376 ms |
| TypeScript sleep 30 seconds | 51 MB | 395 ms |
| TypeScript dictionary 150K rows | 178–188 MB | 546–565 ms |
| Rust dictionary 500K rows | 327 MB | 673–693 ms |
Under the conditions of this test, Restore time did not depend on the length of Init,
but showed a tendency to increase with larger memory usage at snapshot creation time.
While B (tens of seconds) and A (a few seconds) had nearly the same Restore time, C with smaller memory was faster.
This is consistent with the official blog's explanation that "the more chunks there are for larger functions, the more restoration performance changes,"
though the effects of runtime, image, and cache hierarchy have not been isolated.
Also, the first handler invocation immediately after restoration was slower than warm invocations of the same SnapStart version (across all rounds).
(Duration p50: Rust 3.4 → 4.9 ms, TypeScript 3.8 → 33.7 ms)
This is thought to be due to the restored memory being paged in on demand,
but the cause including why the difference is larger for Node.js has not been investigated.
Cost
The differences in how billing works are as follows.
- SnapStart off: Every cold start, the Init time is billed directly as execution time (Lambda has unified billing for the Init phase as of August 2025). In the
REPORTlines from this experiment,Billed Durationwas 4,581 ms for the Rust dictionary load and 59,213 ms for TypeScript's approximately 60 seconds. - SnapStart on: The execution time billed per cold start is only a few tens of milliseconds (
Billed Duration3–34 ms +Billed Restore Duration0–1 ms). Instead, a restore fee is charged for each restoration, and a cache fee is charged per published version with SnapStart enabled.
The unit prices for the Tokyo region (confirmed via the AWS Price List API) are as follows.
| Item | Unit Price |
|---|---|
| Execution time (x86_64) | 0.0000166667 USD/GB-second |
| SnapStart restore (per restore) | 0.0001397998 USD/GB |
| SnapStart cache | 0.0000015046 USD/GB-second (per version, minimum 3 hours) |
Comparing the cost per cold start for a 512 MB function, the restore fee for on is 0.5 GB × 0.0001397998 = approximately 0.00007 USD, which is nearly the same for any function.
| Function (off Init billed duration) | off | on |
|---|---|---|
| Rust minimal (20 ms) | 0.00000017 USD | 0.00007 USD |
| TypeScript minimal (275 ms) | 0.0000023 USD | 0.00007 USD |
| Rust dictionary load (4.6 seconds) | 0.000038 USD | 0.00007 USD |
| TypeScript dictionary load (2.1 seconds) | 0.000018 USD | 0.00007 USD |
| Rust tens-of-seconds class (15.7 seconds) | 0.00013 USD | 0.00007 USD |
| TypeScript ~60 seconds (59.2 seconds) | 0.00049 USD | 0.00007 USD |
There are two key points.
- One restore charge is equivalent to approximately 8.4 seconds of Init execution time (0.0001397998 ÷ 0.0000166667). SnapStart only becomes cheaper per cold start when the Init billed duration exceeds approximately 8.4 seconds. Since both scale proportionally with memory, this threshold remains the same regardless of memory configuration.
- The cache fee is approximately 0.065 USD per day per 512 MB version, or approximately 1.95 USD over 30 days.
As an example, consider a 512 MB function with 10,000 cold starts per month.
- Init 4.6 seconds: off approximately 0.38 USD, on approximately 0.70 + 1.95 = approximately 2.65 USD. SnapStart is more expensive.
- Init 15.7 seconds: off approximately 1.31 USD, on approximately 2.65 USD. Still more expensive.
- Init 15.7 seconds with 100,000 cold starts per month: off approximately 13.1 USD, on approximately 8.95 USD. SnapStart becomes cheaper.
SnapStart is not a cost-reduction feature; it is a feature that buys you lower latency.
For functions where Init exceeds 8 seconds and cold starts are frequent, it can also pay off in terms of cost.
※ Whether the first Init attempt that was cut off at 10 seconds is billed is unconfirmed, as there is no mention in the official blog either.
※ Even with SnapStart, the execution time of initialization code outside the handler, hooks, and re-initialization runs triggered by Lambda for runtime updates are subject to billing.
Init at publish time is slower than on-demand
The INIT_REPORT at snapshot creation time was 8.5 seconds for Rust A and 5.6 seconds for TypeScript A,
which were significantly longer than the on-demand Init p50 (4.6 seconds and 2.1 seconds respectively).
The on-demand side was also not uniform — in round 1 of Rust A, 17 out of 24 observations were approximately 4.6 seconds,
and 7 were between 1.6 and 1.9 seconds, showing a bimodal distribution.
Differences in the Lambda infrastructure's image layer and file cache state are suspected as candidates,
but this could not be isolated from the logs in this experiment.
SnapStart pays this cost up front at publish time,
so it does not appear on the caller's side.
Summary
- With container images, SnapStart can be used with Node.js and Rust as well. Since
lambda_runtime1.3.0 and later implements the lifecycle, it worked for Rust without any labels. For Node.js, the RIC in the base image used this time had no implementation, so opt-in was done via label. - Under the conditions of this experiment, whether SnapStart was effective depended more on Init duration than on the language.
- For Hello World-level functions (Init a few hundred milliseconds or less), the fixed cost of restoration (approximately 350–400 ms) was larger, and it actually became slower.
- For cases where Init was approximately 2.1 seconds or more, cold start (client-side E2E p50) fell below 1 second for both Rust and TypeScript.
- The range from Init 270 ms to 2.1 seconds was not measured.
- When Init exceeds 10 seconds, regular Lambda cuts it off at 10 seconds and retries Init within the first invocation. Cold start becomes "approximately 10 seconds + Init duration," and the retried portion is billed (whether the first 10 seconds that were cut off are billed is unconfirmed). For this scale of Init where low latency is required, SnapStart is a strong candidate.
- Restore time was not related to Init duration, but tended to increase as the memory usage at snapshot creation time was larger. The first invocation immediately after restoration is also slightly slower than warm.
- Costs are generally higher with SnapStart. One restore charge is equivalent to approximately 8.4 seconds of Init execution time, and a cache fee (approximately 1.95 USD per month for 512 MB) is also charged per version. For functions other than those with Init exceeding 8 seconds and frequent cold starts, this is an additional cost for buying lower latency.
- There are three things to keep in mind when measuring. Look at the client-side time, not just
Init Duration. Exclude invocations of environments that were already pre-initialized. Init that exceeds 10 seconds appears inDuration.
A rule of thumb for decision-making: "If Init is under 1 second, don't use it; if it's a few seconds, measure first; if it exceeds 10 seconds and low latency is required, it's a strong candidate."
Restore time after the cache has warmed up, the impact of cache tiers, arm64, and different memory sizes were not tested this time.
References
- AWS What's New "AWS Lambda now supports SnapStart for container image functions" (September 2, 2026): https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/
- AWS Lambda Developer Guide "Improving startup performance with Lambda SnapStart": https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html
- AWS Lambda Developer Guide "Implementing SnapStart hooks for container images": https://docs.aws.amazon.com/lambda/latest/dg/snapstart-runtime-hooks-custom.html
- AWS Lambda Developer Guide "Monitoring for Lambda SnapStart": https://docs.aws.amazon.com/lambda/latest/dg/snapstart-monitoring.html
- AWS Lambda Pricing (SnapStart section): https://aws.amazon.com/lambda/pricing/
- AWS Lambda Developer Guide "Understanding the Lambda execution environment lifecycle": https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html
- AWS Lambda Developer Guide "When to use Lambda's OS-only runtimes" (description of
provided.al2023): https://docs.aws.amazon.com/lambda/latest/dg/runtimes-provided.html - AWS Compute Blog "Under the hood: how AWS Lambda SnapStart optimizes function startup latency" (August 19, 2025): https://aws.amazon.com/blogs/compute/under-the-hood-how-aws-lambda-snapstart-optimizes-function-startup-latency/
- docs.rs
lambda_runtime1.3.0snapstartmodule: https://docs.rs/lambda_runtime/1.3.0/lambda_runtime/snapstart/index.html - aws/aws-lambda-rust-runtime: https://github.com/aws/aws-lambda-rust-runtime/releases
- AWS Compute Blog "AWS Lambda standardizes billing for INIT Phase": https://aws.amazon.com/blogs/compute/aws-lambda-standardizes-billing-for-init-phase/