DEV Community

Cover image for How My AWS Lambda Runs Faster Than Yours: Here’s how to optimize Lambda Cold Starts with SnapStart
Matia Rašetina
Matia Rašetina

Posted on

How My AWS Lambda Runs Faster Than Yours: Here’s how to optimize Lambda Cold Starts with SnapStart

If you ever worked with AWS Lambda, you know about one of their main weak points — cold starts. It’s been a pain point for any team building serverless applications with libraries that take lots of storage space. Whether it’s a data science processing library, machine learning models or complex custom libraries, cold starts can quickly become the dominant source of latency.

In early 2022, AWS launched Lambda SnapStart, a feature used to enable the developers to lower Lambda initialization times, and in late 2024, it was also enabled for Python runtimes as well. We’re going into more detail about SnapStart and how it works in the next heading.

In this post, I’ll walk you through how SnapStart works with Python 3.12, give you AWS CDK code examples which you can copy and paste straight into your project to enable SnapStart on your Lambdas, and show you how the impact of enabling SnapStart with real benchmark data from 10 cold start runs.

Full code can be found by clicking on the link here.

What Is AWS Lambda SnapStart?

Lambda SnapStart is a feature which enables the developers to mitigate the expensive and long Lambda environment initialization phases. What SnapStart does is it captures a snapshot of your execution environment when a new Lambda version is published and used for the first time, right after the initialization phase completes. For each subsequent cold start, the snapshot is restored as your Lambda execution environment instead of initializing the environment again, significantly reducing startup time.

For Python workloads, this is very impactful because if you are using Lambda for your ETL pipeline, smaller Machine Learning models, initializations of time-costly database connections etc., libraries like Pandas, NumPy and others are costing you precious time to initialize inside the Lambda environment and your user experience decreases dramatically. As mentioned, by using SnapStart and creating a snapshot of the Lambda environment, we are lowering the cold start time, which enables us to provide better user experience. When the Lambda is warm, the Lambda invocation time stays more or less the same, even if you have SnapStart enabled. The main point here is to only lower the cold start time.

Below you can see the request lifecycle with a standard Lambda execution environment and when SnapStart is enabled. This image is taken from the official AWS documentation, and if you want to read into more detail about SnapStart, you can do that by clicking on the link here.

Architecture Overview and Test Setup

The overview of this experiment is as following — we have 2 Lambdas, where one has SnapStart enabled and the other uses the standard Lambda environment without SnapStart.

We are only going to focus on importing the libraries in the Lambda environment, the Lambda handler method returns a “hello-world” back to the user, as we are not testing and measuring Lambda invocation times in this post. However, I’ve made the code log invocation time as well, so if you want to test something out in addition to importing the libraries, you can do that with ease.

Both have the same Lambda code — they are going to import some of the most used libraries like json, os, logging, however, then we are going to try and import libraries which cost Lambda initialization time the most — libraries like pandas, joblib and numpy.

Configuration of the Lambdas are:

  • ARM64 architecture
  • 1024MB of memory
  • Python 3.12 used as Lambda runtime
  • 30s timeout

Both Lambdas are run 10 times, with a 10 minute waiting time after any of the invocations, to have a very good chance of the Lambdas transition from a “warm” to a “cold” state. Information about the initialization times were taken from CloudWatch logs, and the Lambdas were invoked straight inside the AWS Console. Examples of the initialization times from CloudWatch can be seen in the following images:

Enabling SnapStart with AWS CDK (Python)

The following CDK code contains the project configuration — 2 AWS Lambdas, where one has SnapStart enabled, and the other one has SnapStart disabled, forcing it to have a standard Lambda execution environment.

from aws_cdk import (
  Stack,
  Duration,
  CfnOutput,
  aws_lambda as _lambda,
  aws_lambda_python_alpha as lambda_python,
  aws_iam as iam,
)
from constructs import Construct

class BenchmarkStack(Stack):
  def __init__(self, scope: Construct, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)

        # 0. Creating a common configuration for each of the 2 Lambdas
    common = dict(
      runtime=_lambda.Runtime.PYTHON_3_12,
      entry="benchmark",
      index="handler.py",
      handler="handler",
      architecture=_lambda.Architecture.ARM_64,
      memory_size=1024,
      timeout=Duration.seconds(30),
      bundling=lambda_python.BundlingOptions(
        image=_lambda.Runtime.PYTHON_3_12.bundling_image,
        platform="linux/arm64",
      ),
    )

    # 1. SnapStart Lambda
    snapstart_fn = lambda_python.PythonFunction(
      self, "SnapStartFn",
      snap_start=_lambda.SnapStartConf.ON_PUBLISHED_VERSIONS, # Enable SnapStart for all published versions of the Lambda
      **common,
    )

    # 2. Standard Lambda (no SnapStart)
    standard_fn = lambda_python.PythonFunction(
      self, "StandardFn",
      # Do NOT include the "snap_start" argument, so it uses the regular
      # Lambda execution environment 
      **common,
    )

    # 3. Create versions
    snapstart_version = snapstart_fn.current_version
    standard_version = standard_fn.current_version

    # 4. Create aliases
    snapstart_alias = _lambda.Alias(
      self,
      "SnapStartAlias",
      alias_name="live",
      version=snapstart_version,
    )

    standard_alias = _lambda.Alias(
      self,
      "StandardAlias",
      alias_name="live",
      version=standard_version,
    )
Enter fullscreen mode Exit fullscreen mode

It’s very important that the Lambda, which has SnapStart enabled, has an alias.

Lambda SnapStart requires aliases because it only works on published versions, and aliases provide a stable pointer to a specific version of your Lambda code with all of your requirements, code and libraries so AWS can safely create, cache, and restore the created snapshot.

Lambda Handler Code used for Testing Lambda SnapStart

# Light and quick imports of these libraries
import json
import logging
import os
import time
from typing import Any, Dict

# Heavy libraries which take time to initialize
# in the Lambda environment
import pandas
import numpy
import joblib

LOG = logging.getLogger()
LOG.setLevel(os.environ.get("LOG_LEVEL", "INFO"))

INIT_EPOCH_MS = int(time.time() * 1000)
LOG.info(json.dumps({"phase": "init", "event": "complete", "ts_epoch_ms": INIT_EPOCH_MS}))

def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
    start = time.perf_counter()
    handler_start_epoch_ms = int(time.time() * 1000)

    # Lambda code for measuring total invocation time,
    # not the topic of this post
    # ... 
Enter fullscreen mode Exit fullscreen mode

As mentioned, to make the benchmark meaningful, the Lambda handler method includes heavy initialization logic, importing big libraries which we’ve mentioned above. This is to simulate a real-world Lambda doing a big computational task that loads large dependencies to complete it’s task.

Results: SnapStart vs No SnapStart (10 Cold Starts)

The difference between SnapStart-enabled and non-SnapStart Lambdas becomes immediately obvious when comparing cold start durations across multiple runs.

Results of Testing Standard Lambda environment vs SnapStart

The average cold start initialization (so not total invocation, just initialization!) times were on average:

  • Lambda with a standard environment - 2929.99ms
  • Lambda with SnapStart enabled - 473.50ms

By only enabling one Lambda feature, we’ve cut the initialization time by ~84%! That’s 6.2x faster!

When SnapStart Is (and Isn’t) Worth It

We’ve seen how SnapStart makes a difference in cutting down on invocation times in Lambdas where library and global object initialization times are significant and can impact user experience.

It’s on you as a Lead Developer, or a Solutions Architect, to decide if implementing SnapStart in your Lambdas is worth it — if you are using AWS Lambda as your API handler to do CRUD operations in your application, more probably than not you are not going to need SnapStart enabled. Yet again, it’s on you to decide, based on the data you see.

Finally, there is some cost when using SnapStart (following cost is for eu-central-1, AWS region I’m using):

  • when caching the Lambda environment - $0.0000015046 per GB-second
  • when restoring the Lambda environment - $0.0001397998 for every GB restored

Cost is still very low, but something to keep in mind if you have Lambdas handling heavy traffic. More information about SnapStart pricing can be found on the link here.

Conclusion

AWS Lambda SnapStart offers a very powerful, yet a low-effort way of reducing cold start times for heavy serverless workloads. The benchmark provided in this blog post provides strong evidence that the Lambda functions, which require libraries that are time-consuming during Lambda environment initialization, can be mitigated by toggling on a single feature, making environment initialization times from a multi-second into sub-second starts.

For teams already using AWS CDK and Python Lambdas, enabling SnapStart is one of the highest-ROI optimizations available today.

Top comments (0)