<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AJ Stuyvenberg</title>
    <description>The latest articles on DEV Community by AJ Stuyvenberg (@astuyve).</description>
    <link>https://dev.to/astuyve</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F95513%2Ffa4a2b59-c2d5-484b-85a9-e97634fdd4cf.jpg</url>
      <title>DEV Community: AJ Stuyvenberg</title>
      <link>https://dev.to/astuyve</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/astuyve"/>
    <language>en</language>
    <item>
      <title>Ultimate guide to Secrets in Lambda</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Sat, 30 Mar 2024 14:19:11 +0000</pubDate>
      <link>https://dev.to/aws-heroes/ultimate-guide-to-secrets-in-lambda-2db7</link>
      <guid>https://dev.to/aws-heroes/ultimate-guide-to-secrets-in-lambda-2db7</guid>
      <description>&lt;p&gt;We all have secrets. Some are small secrets which we barely hide (sometimes I roll through stop signs on my bike). Others are so sensitive that we don't even want to think about them (&lt;em&gt;serverless actually has servers&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Managing and securing secrets in your applications have similar dimensions! As a result, handling a random 3rd party API key is different from handling the root signing key for an operating system or nuclear launch codes.&lt;/p&gt;

&lt;p&gt;This work is a fundamental requirement for any production-quality software system. Unfortunately, AWS doesn't make it easy to select a secrets management tool within their ecosystem. For Serverless developers, this is even more difficult! Lambda is simply one service in a constellation of multiple supporting services which you can use to control application secrets. This guide lays out the most common ways to store and manage secrets for Lambda, the performance impacts of each option, and a framework for considering your specific use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick best practices primer
&lt;/h2&gt;

&lt;p&gt;Plaintext secrets should &lt;strong&gt;NEVER&lt;/strong&gt; be hardcoded in your application code or source control. Typically you want to follow the &lt;code&gt;principle of least privilege&lt;/code&gt; and limit the access of any runtime secret to only the runtime environment (Lambda, in this case).&lt;/p&gt;

&lt;p&gt;This means passing &lt;em&gt;references&lt;/em&gt; or &lt;em&gt;encrypted&lt;/em&gt; data to configuration files or infrastructure as code tools whenever possible. It also means that decrypting or fetching secrets from a secure storage system at runtime will be the most secure option. This post is geared to deploying your Lambda applications along this dimension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Secret Options
&lt;/h2&gt;

&lt;p&gt;Within Lambda, there are four major options for storing configuration parameters and secrets. They are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lambda Environment Variables&lt;/li&gt;
&lt;li&gt;AWS Systems Manager Parameter Store (Formerly known as Simple Systems Manager, or SSM)&lt;/li&gt;
&lt;li&gt;AWS Secrets Manager&lt;/li&gt;
&lt;li&gt;AWS Key Management Service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post will rate each option along the following dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ease of use&lt;/li&gt;
&lt;li&gt;Cost&lt;/li&gt;
&lt;li&gt;Auditability&lt;/li&gt;
&lt;li&gt;Rotation Simplicity&lt;/li&gt;
&lt;li&gt;Capability&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll also cover the &lt;a href="https://aws.amazon.com/blogs/compute/using-the-aws-parameter-and-secrets-lambda-extension-to-cache-parameters-and-secrets/"&gt;AWS Lambda Parameter and Secret extension&lt;/a&gt;, which is used to retrieve secrets from both Parameter Store and Secrets Manager from within a Lambda function.&lt;/p&gt;

&lt;p&gt;Then, we'll consider several example secrets with various blast radii, and decide which service best suits our needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service breakdown Tl;dr
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Ease of Use&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Auditability&lt;/th&gt;
&lt;th&gt;Rotation Complexity&lt;/th&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Environment Variables&lt;/td&gt;
&lt;td&gt;Easiest&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free!&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Poor&lt;/td&gt;
&lt;td&gt;Requires UpdateFunctionConfiguration or deployment&lt;/td&gt;
&lt;td&gt;Encrypted at rest Decrypted when getFunctionConfiguration called.&lt;br&gt; Limited to 4KB total&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameter Store Standard&lt;/td&gt;
&lt;td&gt;Some assembly required&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Free storage&lt;/strong&gt;&lt;br&gt;&lt;br&gt;Free calls up to 40 calls/second.&lt;br&gt;$0.05/10,000 calls after&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Easy manual rotation, not automatic&lt;/td&gt;
&lt;td&gt;4KB size limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameter Store Advanced&lt;/td&gt;
&lt;td&gt;Some assembly required&lt;/td&gt;
&lt;td&gt;$0.05 per month per secret.&lt;br&gt;&lt;br&gt;$0.05/10,000 calls&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Easy manual rotation, not automatic&lt;/td&gt;
&lt;td&gt;Supports TTL for secrets. 8KB size limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Manager&lt;/td&gt;
&lt;td&gt;Some assembly required&lt;/td&gt;
&lt;td&gt;$0.40 per secret per month $0.05/10,000 calls.&lt;br&gt;30 day free tier.&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Easiest &amp;amp; Automatic&lt;br&gt;Built into the product&lt;/td&gt;
&lt;td&gt;Largest binary size, 65KB per secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
Key Management Service (KMS)&lt;/td&gt;
&lt;td&gt;Most work&lt;/td&gt;
&lt;td&gt;$1 per key per month $0.03/10,000 requests&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Depends on ciphertext storage.&lt;br&gt;Easy with DynamoDB/S3, more manual with env vars.&lt;/td&gt;
&lt;td&gt;Most flexible option.&lt;br&gt; 4KB per &lt;code&gt;encrypt&lt;/code&gt; operation.&lt;br&gt;Binary size is limited by storage mechanism.&lt;br&gt;Roll your own Secrets Manager or Parameter Store.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Lambda Environment Variables
&lt;/h2&gt;

&lt;p&gt;Environment variables in Lambda are where most folks start out in their journey. They're baked right in, and can be fetched easily (using something like &lt;code&gt;process.env.MY_SECRET&lt;/code&gt; for Node or &lt;code&gt;os.environ.get('MY_SECRET')&lt;/code&gt; for Python). Unfortunately they are not the &lt;em&gt;most&lt;/em&gt; secure option.&lt;/p&gt;

&lt;p&gt;However one common misconception is that environment variables are &lt;code&gt;stored as plain text&lt;/code&gt; by AWS Lambda. This is &lt;strong&gt;false&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lambda environment variables are &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html"&gt;encrypted at rest&lt;/a&gt;, and only decrypted when the Lambda Function initializes, or you take an action resulting in a call to &lt;code&gt;GetFunctionConfiguration&lt;/code&gt;. This includes visiting the &lt;code&gt;Environment Variables&lt;/code&gt; section of the Lambda page in the AWS Console. It startles some people to see their secrets on this page, but you can easily prevent this by denying &lt;code&gt;lambda:GetFunctionConfiguration&lt;/code&gt;, or &lt;code&gt;kms:Decrypt&lt;/code&gt; permissions from your AWS console user.&lt;/p&gt;

&lt;p&gt;Auditability is another challenge of Lambda environment variables. For the principle of least privilege to be effective, we should limit access to secrets only to when they are needed. To ensure this is followed, or investigate and remediate a leaked secret, we need to know which Lambda function used a specific secret and at what time.&lt;/p&gt;

&lt;p&gt;Environment variables are automatically decrypted and injected into every function sandbox upon initialization. Given that CloudTrail reflects one call to &lt;code&gt;kms:Decrypt&lt;/code&gt;, I presume the entire 4KB environment variable package is encrypted together. This means you lack the ability to audit an individual secret - it's all or nothing.&lt;/p&gt;

&lt;p&gt;If you're in a regulated environment, or otherwise distrust Amazon; you can create a Consumer-Managed Key (CMK) and use that to encrypt your environment variables instead.&lt;/p&gt;

&lt;p&gt;It's important to note that when you update environment variables, you will trigger a cold start (as long as you're using the &lt;code&gt;$LATEST&lt;/code&gt; function alias). Your function sandbox is automatically shut down permanently. Then when a new request arrives, you will experience a cold start and that sandbox will pull the latest environment variables into scope.&lt;/p&gt;

&lt;p&gt;Environment variables are also the best-performing option. Systems Manager Parameter Store, Secrets Manager, Lambda environment variables, and KMS all fundamentally rely on KMS and thus a call to &lt;code&gt;kms:Decrypt&lt;/code&gt; at some point.&lt;/p&gt;

&lt;p&gt;Lambda Function environment variables add around 25ms to your cold start duration, according to an article David Behroozi &lt;a href="https://speedrun.nobackspacecrew.com/blog/2024/03/13/lambda-environment-variables-impact-on-coldstarts.html"&gt;just wrote&lt;/a&gt;. These calls are logged in CloudTrail whenever your function starts.&lt;/p&gt;

&lt;p&gt;However, purely storing secrets as environment variables is not the most secure option. Although they are encrypted at rest, environment variables and &lt;code&gt;lambda:GetFunctionConfiguration&lt;/code&gt; permissions are treated by Lambda as part of the &lt;code&gt;ReadOnly&lt;/code&gt; policy used by AWS internally, auditors, and cloud security SaaS products. This broadens your risk for a vendor or 3rd party auditor becoming compromised and leaking your secrets.&lt;/p&gt;

&lt;p&gt;One risk is that you may accidentally leak a secret when sharing your screen while viewing or modifying a Lambda environment variable. It's unfortunate that AWS automatically decrypts and displays these values in plain text. AWS has no excuse for this, and should absolutely hide environment variable values unless toggled on, which is how Parameter Store and Secrets Manager both work.&lt;/p&gt;

&lt;p&gt;Furthermore, CloudFormation treats environment variables as regular parts of a template, so they are available when looking at the full template or historical templates for a given stack. Additionally, AWS does not recommend storing &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html"&gt;anything secret in an environment variable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can improve that somewhat for no (or little) cost using a pattern I lay out further on. Before we get there, you should be familiar with the first-class products AWS offers to store your secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Systems Manager Parameter Store
&lt;/h2&gt;

&lt;p&gt;The title is a mouthful, and the service is equally Byzantine. It includes features for managing nodes, patching systems, handling feature flags, and so much more. Earlier it was called the Simple Systems Manager, however it's truly anything but simple.&lt;/p&gt;

&lt;p&gt;Today we'll focus only on Lambda and exclusively on the Parameter Store feature which allows us to store a plaintext or secure string either as a simple value or structured item.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;always want to use SecureString&lt;/strong&gt; for secrets.&lt;/p&gt;

&lt;p&gt;Parameter Store offers the choice between Standard and Advanced Parameters. Standard Parameters are free to store, Advanced Parameters incur a $0.05 per month per parameter charge.&lt;/p&gt;

&lt;p&gt;Standard parameters are limited to 4KB in size (each), with 10,000 total per region. Advanced Parameters have higher limits of 8KB per item and 100,000 total per region. They come with the bonus of attaching &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-policies.html"&gt;Parameter Policies&lt;/a&gt;, which are effectively TTLs for a given parameter.&lt;/p&gt;

&lt;p&gt;Standard Parameters are free up to 40 requests per second (for all values stored in Parameter Store). Beyond that, the cost is $0.05 per 10,000 Parameter Store API Interactions. Advanced Parameters are always billed at $0.05/10,000 requests. Fetching each parameter counts as an interaction, so 10 parameters triggers 10 interactions. Parameters are individually versioned, and you can fetch by version or &lt;code&gt;$LATEST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Historically one major advantage of Secrets Manager over Parameter Store is the ability to share secrets across AWS accounts using a resource-based policy. This is now &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/02/aws-systems-manager-parameter-store-cross-account-sharing/"&gt;supported by Parameter Store for Advanced Parameters&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;Finally, individual Parameter calls are auditable in CloudTrail so you can prove who accessed a Parameter and when.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;For a new TCP connection, Parameter Store fetched a parameter in around 217ms, including 99ms to set up the connection itself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2pxu90ymvot1unyzbjsj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2pxu90ymvot1unyzbjsj.png" alt="Systems Manager Parameter Store cold request" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With an existing connection, fetching the parameter took around 39.3ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2e7bf4c4jhx4rw82hlk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2e7bf4c4jhx4rw82hlk8.png" alt="Systems Manager Parameter Store warm request" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Secrets Manager
&lt;/h2&gt;

&lt;p&gt;Secrets Manager is purpose-built for encrypting and storing secrets for your application. It also has the largest cost at $0.40 per secret per month. This cost is multiplied by the number of regions you choose to replicate each secret to, so this can add up quickly. Fetching a secret costs $0.05 per 10,000 API calls, and there is a free 30-day trial.&lt;/p&gt;

&lt;p&gt;The big features you'll gain over Parameter Store are the ability to automatically replicate secrets across regions, automatically (or manually) rotate secrets. This feature often satisfies requirements for applications subject to regulations like PCI-DSS or HIPAA. If these are must-have features for your application, it makes sense to use Secrets Manager.&lt;/p&gt;

&lt;p&gt;Secret values can be up to 65KB in size, which is far larger than environment variables or Parameter Store. Like Parameter Store, calls for &lt;code&gt;GetSecretValue&lt;/code&gt; are logged in CloudTrail. The big advantage Secrets have over Parameter Store is the ability to simply rotate or change a secret everywhere it's used. You can do this on a schedule if you're in an environment which demands this, or ad-hoc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Similar to Parameter Store, it takes Secrets Manager a bit to warm up. 177ms was the duration to create this TCP connection and make the request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx31ejeby8typn6c6dxaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx31ejeby8typn6c6dxaj.png" alt="Secrets Manager cold request" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a warm connection, fetching a secret from Secrets Manager took only 29.4ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh8vjevrq8mprbguhvlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh8vjevrq8mprbguhvlq.png" alt="Secrets Manager warm request" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Management Service
&lt;/h2&gt;

&lt;p&gt;AWS Key Management Service (KMS) is the system which underpins &lt;em&gt;all of these other services&lt;/em&gt;. If you look carefully at either the documentation or CloudTrail logs, you'll see KMS!&lt;/p&gt;

&lt;p&gt;KMS allows us to create an encryption key, securely store it within AWS, and then use IAM to grant access to resource-based policies used by Lambda to decrypt the ciphertext when your function runs. Instead of passing around a reference to a secret, you'll need to pass your Lambda function the encrypted ciphertext.&lt;/p&gt;

&lt;p&gt;Storing and fetching the ciphertext can be implemented many ways, and should generally track the size of the encrypted blob. Small strings can be easily encrypted and stored as environment variables. If you need to share the same secret, you can store the ciphertext in DynamoDB. For large shared secrets, ciphertexts can be stored in S3.&lt;/p&gt;

&lt;p&gt;Most often these secrets are decrypted during the initialization phase of a Lambda function. Fun fact, you don't need to store or pass the ID of the key used to encrypt data. That key ID is &lt;a href="https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html"&gt;encoded&lt;/a&gt; right along with the encrypted data in the ciphertext! Simply call &lt;code&gt;kms:Decrypt&lt;/code&gt; on the blob, and KMS takes care of the rest. Neat!&lt;/p&gt;

&lt;p&gt;KMS bills $1 per key per month. There is no charge for the keys created and used by Parameter Store, Secrets Manager, or AWS Lambda. You're also charged $0.03 per 10,000 requests to &lt;code&gt;kms:Decrypt&lt;/code&gt; (or other API actions). These calls are individually auditable in CloudTrail.&lt;/p&gt;

&lt;p&gt;You'll have to implement rotation yourself, but if you store ciphertexts in DynamoDB, this can be relatively straightforward and cheaper than either Parameter Store or Secrets Manager, especially if you want to distribute a secret across multiple regions.&lt;/p&gt;

&lt;p&gt;I see KMS used most frequently to encrypt slowly changing items like certificates, .PEM files, or to securely store signing keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Decrypting one small (~200b) ciphertext with KMS is notably faster than Parameter Store or Secrets Manager. This request took 64.4ms, including creating the TCP connection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn26yr0x57hqg0azr38bj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn26yr0x57hqg0azr38bj.png" alt="KMS cold request" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a warm connection, KMS decrypted my secret in a blistering &lt;strong&gt;6.45ms&lt;/strong&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea4kqcx1b4z65g5md93e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea4kqcx1b4z65g5md93e.png" alt="KMS warm request" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Presumably a big advantage here is that my ciphertext was already present in Lambda (as an environment variable) and didn't need to be fetched from a remote datastore call. KMS merely needed to decrypt the ciphertext and return!&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Parameter and Secrets Lambda Extension
&lt;/h2&gt;

&lt;p&gt;To more easily use either Parameter Store or Secrets Manager in Lambda, AWS has published a &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html"&gt;Lambda extension&lt;/a&gt; which handles API calls to the underlying services for you, along with caching and refreshing secrets. You can &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html"&gt;tune&lt;/a&gt; these parameters to your liking as well.&lt;/p&gt;

&lt;p&gt;Your function interacts with this extension via a lightweight API running on &lt;code&gt;localhost&lt;/code&gt;. It's reasonably well designed, although I find it a bit clumsy overall. This really feels like the type of feature Lambda should implement themselves, and then &lt;code&gt;magically&lt;/code&gt; make secrets appear in your function runtime. In contrast, ECS &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html"&gt;has this behavior built in&lt;/a&gt; and I find the experience far superior compared to Lambda.&lt;/p&gt;

&lt;p&gt;Furthermore, this extension isn't open source. Because extensions are indistinguishable from your own function code, it leaves a bit of a foul taste in my mouth that I'm completely blessing a random extension with carte-blanche access to both my function code and secrets.&lt;/p&gt;

&lt;p&gt;I'm of the firm opinion that we as users shouldn't seriously consider any Lambda Extension unless the code is open source (and can be built/published to my own account if I choose). If AWS changes this behavior, I'll happily update the post.&lt;/p&gt;

&lt;p&gt;For these reasons, I prefer interacting with the Parameter Store or Secrets Manager APIs instead, using the &lt;code&gt;aws-sdk&lt;/code&gt;. The (excellent) AWS Lambda &lt;a href="https://github.com/aws-powertools"&gt;PowerTools project&lt;/a&gt; also supports fetching parameters from &lt;a href="https://docs.powertools.aws.dev/lambda/python/latest/utilities/parameters/"&gt;multiple sources&lt;/a&gt; and is absolutely worth considering. &lt;/p&gt;

&lt;p&gt;Now let's consider three example secrets. We'll look at the attack vectors, the blast radius for a leak/compromise, and identify the best cost/benefit solution for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns and Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Safely securing environment variables
&lt;/h3&gt;

&lt;p&gt;The biggest issue storing sensitive data in environment variables isn't Lambda itself - it's CloudFormation (and your CI pipeline)! When your stack is created or updated, those environment variables &lt;strong&gt;are&lt;/strong&gt; plaintext values in the CloudFormation stack template. Templates are also stored and retrievable in the CloudFormation UI.&lt;/p&gt;

&lt;p&gt;To avoid using sensitive information in your CloudFormation Template but avoid the cost overhead of Parameter Store being used &lt;strong&gt;at function runtime&lt;/strong&gt;, you can adopt the following strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store your secrets as SecureStrings in Systems Manager Parameter Store.&lt;/li&gt;
&lt;li&gt;Use CloudFormation &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html"&gt;dynamic references&lt;/a&gt; to pass a &lt;em&gt;reference&lt;/em&gt; to your secret to CloudFormation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now your secret will land safely encrypted at rest in a Lambda environment variable, and never be visible in CloudFormation.&lt;/p&gt;

&lt;p&gt;Standard Parameters are free to store and free to use under 40 req/s, if you're only fetching secrets at deploy time via CloudFormation references, you'll likely never receive a bill for these secrets.&lt;/p&gt;

&lt;p&gt;The downside is that your secrets are still viewable in the Lambda Console via &lt;code&gt;lambda:GetFunctionConfiguration&lt;/code&gt;, and if you update your secret in Parameter Store, it won't be updated in Lambda until you redeploy your functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Envelope Encryption
&lt;/h3&gt;

&lt;p&gt;Consider a case where you may have ~100kb of secrets to store. A handful of signing keys, a couple tokens, maybe an mTLS certificate. Here's where you can use a technique called &lt;a href="https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping"&gt;envelope encryption&lt;/a&gt; to secure your data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a KMS key&lt;/li&gt;
&lt;li&gt;Generate 256-bit AES key for each customer, application, or secrets payload&lt;/li&gt;
&lt;li&gt;Encrypt all of your secrets with the AES key. This is the "envelope"&lt;/li&gt;
&lt;li&gt;Include the encrypted secrets in your function zip.&lt;/li&gt;
&lt;li&gt;Finally, encrypt the AES key with your KMS key and pass the encrypted key to your function in an environment variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You've just encrypted an envelope, and passed the encrypted key to your Lambda Function securely! This also helps save money on KMS keys, as you can re-use one KMS key for multiple AES keys. This pattern is also useful if you need to secure keys for customers in a multi-tenant environment, but laying that out is beyond the scope of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitive Data Exercise
&lt;/h2&gt;

&lt;p&gt;We've covered the fundamental building blocks for securing sensitive information within AWS and using it within Lambda. We've also composed a few patterns you can use to reduce costs or handle specific use cases.&lt;/p&gt;

&lt;p&gt;Now, let's consider 4 common secrets used in Lambda and think about how best to secure them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telemetry API Key
&lt;/h3&gt;

&lt;p&gt;First up is a telemetry API key. Consider an ELK stack, or any provider you prefer. These keys are free to create, so it's best to create one key per application to limit blast radius and, as a bonus - better track costs. Telemetry keys are also usually write-only. Leaking this key can only cause an attacker to send additional data to the API.&lt;/p&gt;

&lt;p&gt;With this in mind, &lt;em&gt;environment variables&lt;/em&gt; are likely a good enough option here. They have minimal performance overhead, no cost, and minimal blast radius.&lt;/p&gt;

&lt;p&gt;Keys can be easily created for exactly one Lambda function, or CloudFormation stack. If someone peers over your shoulder at a coffee shop, or inadvertently leaks the environment variable - it's simple to change with a few clicks and a re-deploy.&lt;/p&gt;

&lt;p&gt;You can also use dynamic references and limit the read permissions for console users or 3rd party roles to further prevent access.&lt;/p&gt;

&lt;p&gt;Using a SecureString with Parameter Store would also be a good option as it would likely be free - especially if your application doesn't have any users.&lt;/p&gt;

&lt;p&gt;In this case, the blast-radius is small, the rotation complexity is easy, and a key encrypted at rest is likely more than suitable for our use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Username and Password
&lt;/h3&gt;

&lt;p&gt;Your RDBMs may only allow one username and password string, to be shared across all applications - or maybe you just need to share a secret for the sake of simplicity. If you're not using a stateful connection pooler (like &lt;code&gt;pgbouncer&lt;/code&gt;), you may need to share this secret with all your functions.&lt;/p&gt;

&lt;p&gt;Here's where Parameter Store is probably also a great fit. If you ever have to change it, your functions can reference an unversioned Parameter and get the latest key. For one key, it's pretty affordable. However this math changes if you have a larger bundle of secrets, which exceed the 4KB or 8KB size limits of Parameter Store.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Application Private Key
&lt;/h3&gt;

&lt;p&gt;For our second example, consider building and deploying a GitHub Application. Authenticating as a GitHub Application is not quite as simple as a 128bit UUID.&lt;/p&gt;

&lt;p&gt;Instead, you must download and save an &lt;a href="https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps"&gt;application key in PEM format&lt;/a&gt;. These keys can be a bit large, around ~2KB which may push you close to the 4KB environment variable limit.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; create multiple keys for the same application at no cost, so deploying one key per stack is still tenable.&lt;/p&gt;

&lt;p&gt;If the key were to be leaked, someone could conceivably authenticate as your application and access &lt;strong&gt;ANY&lt;/strong&gt; of the repositories your application is installed into (with whatever permissions your application is configured to use). This is risky!&lt;/p&gt;

&lt;p&gt;In this case, you'd probably want to use something like Parameter Store if you choose to create multiple keys and rotate them yourself. You'll help avert the size limit for Lambda environment variables, but it won't be too costly.&lt;/p&gt;

&lt;p&gt;If you're dealing with a larger key but don't want to eat the cost of Secrets Manager, KMS or DynamoDB can make sense as well.&lt;/p&gt;

&lt;p&gt;I'd be remiss if I didn't mention that like Lambda environment variables, DynamoDB records are also &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html"&gt;encrypted at rest&lt;/a&gt;, optionally with your own consumer-managed key. I assume this is mostly at the hardware (disk) level, so data in memory may not be encrypted. But generally if you're also concerned with someone peeking over your shoulder as you browse DynamoDB items in the AWS console, you could also encrypt them with your own key.&lt;/p&gt;

&lt;h3&gt;
  
  
  PCI-DSS or HIPAA credential rotation
&lt;/h3&gt;

&lt;p&gt;If you're in a regulated environment with mandated credential rotation, Secrets Manager makes this so easy. As this post has mentioned several times, it's certainly possible to build this yourself. However - it's often worth the cost of $0.40 per secret to have the peace of mind that Secrets Manager will automatically rotate your secrets on a regular cadence. Your auditor will thank you as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;My hot take after writing this guide is that Lambda environment variables are generally fine for a one-off API key with a small blast radius. They're fast, free, and easy to use.&lt;/p&gt;

&lt;p&gt;For secrets with larger blast radii, use SecureStrings from Parameter Store. If you're working in a regulated environment or you'd like to regularly rotate a secret, it's probably easiest to use Secrets Manager.&lt;/p&gt;

&lt;p&gt;Reach for KMS and another storage mechanism if your use case doesn't quite fit into these boxes, or if doing so would be prohibitively expensive. &lt;/p&gt;

&lt;p&gt;Ultimately security is a balancing act. I realize best practices are all about limiting risks at every turn, but it still feels wrong to crow about environment variables when so many developers run around with &lt;code&gt;Administrator&lt;/code&gt; IAM roles (and can easily read any secret anyway).&lt;/p&gt;

&lt;p&gt;At the same time, AWS should do more to restrict the values of environment variables to a permission more restricted than &lt;code&gt;lambda:getFunctionConfiguration&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This post would not exist without &lt;a href="https://speedrun.nobackspacecrew.com/blog/index.html"&gt;David Behroozi&lt;/a&gt; challenging me to finish it, and helping out with his CloudTrail digging. You should follow him on &lt;a href="https://twitter.com/rooToTheZ"&gt;twitter&lt;/a&gt;. Thanks, David!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/Frichette_n"&gt;Nick Frichette&lt;/a&gt;, &lt;a href="https://twitter.com/alexbdebrie"&gt;Alex DeBrie&lt;/a&gt;, and &lt;a href="http://awsteele.com/"&gt;Aidan Steele&lt;/a&gt; also helped review this, thanks friends! &lt;/p&gt;

&lt;p&gt;If you like this type of content please subscribe to my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; or follow me on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; and send me any questions or comments. You can also ask me questions directly if I'm &lt;a href="//twitch.tv/aj_stuyvenberg"&gt;streaming on Twitch&lt;/a&gt; or &lt;a href="https://www.youtube.com/channel/UCsWwWCit5Y_dqRxEFizYulw"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>aws</category>
      <category>serverless</category>
      <category>secrets</category>
    </item>
    <item>
      <title>How Lambda starts containers 15x faster (deep dive)</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Tue, 23 Jan 2024 20:26:53 +0000</pubDate>
      <link>https://dev.to/aws-heroes/how-lambda-starts-containers-15x-faster-deep-dive-5077</link>
      <guid>https://dev.to/aws-heroes/how-lambda-starts-containers-15x-faster-deep-dive-5077</guid>
      <description>&lt;p&gt;This is also available as a YouTube video!&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/qAYY9df2hVQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://aaronstuyvenberg.com/posts/containers-on-lambda"&gt;first post&lt;/a&gt; of this series, we demonstrated that container-based Lambda functions can initialize as fast or faster than zip-based functions. This is counterintuitive as zip-based functions are usually much smaller (up to 250mb), while container images typically contain far more data and are supported up 10gb in size. So how is this technically possible?&lt;/p&gt;

&lt;p&gt;"On demand container loading on AWS Lambda" was &lt;a href="https://arxiv.org/abs/2305.13162"&gt;published&lt;/a&gt; on May 23rd, 2023 by Marc Brooker et al. I suggest you read the full paper, as it's quite approachable and extremely interesting, but I'll break it down here.&lt;/p&gt;

&lt;p&gt;The key to this performance improvement can be summarized in four steps, all performed during &lt;strong&gt;function creation&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deterministically serialize container layers (which are tar.gz files) onto an ext4 file system&lt;/li&gt;
&lt;li&gt;Divide filesystem into 512kb chunks&lt;/li&gt;
&lt;li&gt;Encrypt each chunk&lt;/li&gt;
&lt;li&gt;Cache the chunks and share them &lt;em&gt;across all customers&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these chunks stored and shared safely in a multi-tier cache, they can be fetched more quicky during &lt;strong&gt;function cold start&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But how can one safely encrypt, cache, and share actual bits of a container image &lt;em&gt;between&lt;/em&gt; users?!&lt;/p&gt;

&lt;h2&gt;
  
  
  Container images are sparse
&lt;/h2&gt;

&lt;p&gt;One interesting fact about container images is that they're an objectively inefficient method for distributing software applications. It's true!&lt;/p&gt;

&lt;p&gt;Container images are sparse blobs, with only a fraction of the contained bytes required to actually run the packaged application. &lt;a href="https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter"&gt;Harter et al&lt;/a&gt; found that only 6.5% of bytes on average were needed at startup.&lt;/p&gt;

&lt;p&gt;When we consider a collection of container images, the frequency and quantity of similar bytes is very high between images. This means there are lots of duplicated bytes copied over the wire every time you push or pull an image!&lt;/p&gt;

&lt;p&gt;This is attributed to the fact that container images include a ton of stuff that doesn't vary between us as users. These are things like the kernel, the operating system, system libraries like libc or curl, and runtimes like the jvm, python, or nodejs.&lt;/p&gt;

&lt;p&gt;Not to mention all of the code in your app which you copied from Chat GPT (like everyone else).&lt;/p&gt;

&lt;p&gt;The reality is that we're all shipping ~80% of the same code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic serialization onto ext4
&lt;/h2&gt;

&lt;p&gt;Container images are stacks of tarballs, layered on top of each other to form a filesystem like the one on your own computer. This process is typically done at container runtime, using a &lt;a href="https://docs.docker.com/storage/storagedriver/"&gt;storage driver&lt;/a&gt; like &lt;a href="https://docs.docker.com/storage/storagedriver/overlayfs-driver/"&gt;overlayfs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa84e9vt6qsb3z7uzgw77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa84e9vt6qsb3z7uzgw77.png" alt="Containers are layers of tarballs" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a typical filesystem, this process of copying files from the tar.gz file to the filesystem's underlying block device is &lt;em&gt;nondeterministic&lt;/em&gt;. Files always land in the same directory, but those locations &lt;em&gt;on disk&lt;/em&gt; may land on different parts of the block device over the course of multiple instantiations of the container.&lt;br&gt;
This is a concurrency-based performance optimization used by filesystems, which introduces nondeterminism.&lt;/p&gt;

&lt;p&gt;In order to de-duplicate and cache function container images, Lambda also needs a filesystem. This process is done when a function is created or updated. But for Lambda to efficiently cache chunks of a function container image, this process needed to be deterministic. So they made filesystem creation a serial operation, and thus the creation of Lambda filesystem blocks are deterministic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zcz6lw07vc4zfiuchsc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zcz6lw07vc4zfiuchsc.png" alt="An example filesystem created by the tarballs" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Filesystem chunking
&lt;/h2&gt;

&lt;p&gt;Now that each byte of a container image will land in the same block each time a function is created, Lambda can divide the blocks into 512kb chunks. They specifically call out that larger chunks reduce metadata duplication, and smaller chunks lead to better deduplication and thus cache hit rate, so they expect this exact value to change over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lpoh7pqbbgkwsxirw8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lpoh7pqbbgkwsxirw8j.png" alt="The Lambda filesystem divided into chunks and hashed" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next two steps are the most important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convergent encryption
&lt;/h2&gt;

&lt;p&gt;Lambda code is considered unsafe, as any customer can upload anything they want. But then how can AWS deduplicate and share chunks of function code between customers?&lt;br&gt;
The answer is something called Convergent Encryption, which sounds scarier than it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash each 512kb chunk, and from that, derive an encryption key.&lt;/li&gt;
&lt;li&gt;Encrypt each block with the derived key.&lt;/li&gt;
&lt;li&gt;Create a manifest file containing a SHA256 hash of each chunk, the key, and file offset for the chunk.&lt;/li&gt;
&lt;li&gt;Encrypt the keys list in the manifest file using a per-customer key managed by KMS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0apdx7r1lyymluve2sq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0apdx7r1lyymluve2sq.png" alt="The encrypted chunks and manifest file for a Lambda container function" width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These chunks are then de-duplicated and stored in a s3 when a Lambda function is created.&lt;/p&gt;

&lt;p&gt;Now that each block is hashed and encrypted, they can be efficiently de-duplicated and shared across customers. The manifest and chunk key list are decrypted by the Lambda worker during a cold start, and only chunks matching those keys are downloaded and decrypted.&lt;br&gt;
This is safe because for any customer's manifest to contain a chunk hash (and the key derived from it) in the manifest file, that customer's function must have created and sent that chunk of bytes to Lambda.&lt;/p&gt;

&lt;p&gt;Put another way, all users with an identical chunk of bytes also all share the identical key.&lt;/p&gt;

&lt;p&gt;This is key to sharing chunks of container images without trust. Now if you and I both run a node20.x container on Lambda, the bytes for nodejs itself (and it's dependencies like libuv) can be shared, so they may already be on the worker before my function runs or is even created!&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tiered cache strategy
&lt;/h2&gt;

&lt;p&gt;The last component to this performance improvement is creating a multi-tiered cache. Tier three is the source cache, and lives in an S3 bucket controlled by AWS.&lt;/p&gt;

&lt;p&gt;The second tier is an AZ-level cache, which is replicated and separated into an in-memory system for hot data, and flash storage for colder chunks.&lt;br&gt;
Fun fact - to reduce p99 outliers, this cache data is stored using erasure coding in a 4-of-5 code strategy. This is the same sharding technique &lt;a href="https://youtu.be/v3HfUNQ0JOE?t=508"&gt;used in s3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This allows workers to make redundant requests to this cache while fetching chunks, and abandon the slowest request as soon as 4 of the 5 chunks return. This is a &lt;a href="https://dl.acm.org/doi/10.1145/2796314.2745873"&gt;common pattern&lt;/a&gt;, which AWS also uses when fetching zip-based Lambda function code from s3 (among many other applications).&lt;/p&gt;

&lt;p&gt;Finally the tier-one cache lives on each Lambda worker and is entirely in-memory. This is the fastest cache, and most performant to read from when initializing a new Lambda function.&lt;/p&gt;

&lt;p&gt;In a given week, 67% of chunks were served from on-worker caches!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5ur6fr876msag7qjk53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5ur6fr876msag7qjk53.png" alt="For a given week, 67% of chunks were served from the worker" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;During a cold start, these chunk IDs are looked up using the manifest, and then fetched from the cache(s) and decrypted. The Lambda worker reassembles the chunks and then the function initialization begins. It doesn't matter who uploaded the chunk, they're all shared safely!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbztp2azg0boff0jdfyyd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbztp2azg0boff0jdfyyd.png" alt="The encrypted chunks fetched from the cache during a cold start and reassembled." width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Crazy stat
&lt;/h2&gt;

&lt;p&gt;This leads to a staggering statistic. If (after subscribing and sharing this post), you close this page and create a brand new container-based Lambda function right now, there is an &lt;strong&gt;80% chance&lt;/strong&gt; that new container image will contain &lt;em&gt;zero unique bytes&lt;/em&gt; compared to what Lambda already has seen.&lt;/p&gt;

&lt;p&gt;AWS has seen the code and dependencies you are likely to deploy before you have even deployed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole paper is excellent and includes many other interesting topics like cache eviction, and how this was implemented (in Rust!), so I suggest you &lt;a href="https://arxiv.org/abs/2305.13162"&gt;read the full paper&lt;/a&gt; to learn more. The Lambda team even had to contend with some cache fragements being &lt;strong&gt;too popular&lt;/strong&gt;, so they had to salt the chunk hashes!&lt;/p&gt;

&lt;p&gt;It's interesting to me that the Fargate team went a totally different direction here with &lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/07/aws-fargate-container-startup-seekable-oci/"&gt;SOCI&lt;/a&gt;. My understanding is that SOCI is less effective for images smaller than 1GB, so I'd be curious if some lessons from this paper could further improve Fargate launches.&lt;/p&gt;

&lt;p&gt;At the same time, I'm curious if this type of multi-tenant cache would make sense to improve launch performance of something like GCP Cloud Run, or Azure Container Instances.&lt;/p&gt;

&lt;p&gt;If you like this type of content please subscribe to my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; or reach out on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; with any questions. You can also ask me questions directly if I'm &lt;a href="//twitch.tv/aj_stuyvenberg"&gt;streaming on Twitch&lt;/a&gt; or &lt;a href="https://www.youtube.com/channel/UCsWwWCit5Y_dqRxEFizYulw"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>docker</category>
      <category>lambda</category>
      <category>aws</category>
    </item>
    <item>
      <title>The case for containers on Lambda (with benchmarks)</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Tue, 02 Jan 2024 14:45:03 +0000</pubDate>
      <link>https://dev.to/aws-heroes/the-case-for-containers-on-lambda-with-benchmarks-2alf</link>
      <guid>https://dev.to/aws-heroes/the-case-for-containers-on-lambda-with-benchmarks-2alf</guid>
      <description>&lt;p&gt;When AWS Lambda first introduced support for container-based functions, the initial reactions from the community were mostly negative. Lambda isn't meant to run large applications, it is meant to run small bits of code, scaled widely by executing many functions simultaneously.&lt;/p&gt;

&lt;p&gt;Containers were not only antithetical to the philosophy of Lambda and the serverless mindset writ large, they were also far slower to initialize (or cold start) compared with their zip-based function counterparts.&lt;/p&gt;

&lt;p&gt;If we're being honest, I think the &lt;strong&gt;biggest roadblock to adoption&lt;/strong&gt; was the cold start performance penalty associated with using containers. That penalty has now all but evaporated.&lt;/p&gt;

&lt;p&gt;The AWS Lambda team put in tremendous amounts of work and improved the cold-start times by a shocking &lt;strong&gt;15x&lt;/strong&gt;, according to the paper and &lt;a href="https://www.youtube.com/watch?v=Wden61jKWvs"&gt;talk given by Marc Brooker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post focuses on analyzing the performance of container-based Lambda functions with simple, reproducible tests. It also lays out the pros and cons for containers on Lambda. The next post will delve into how the Lambda team pulled off this performance win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Tests
&lt;/h2&gt;

&lt;p&gt;I set off to test this new container image strategy by creating several identical functions across zip and container-based packaging schemes. These varied from 0mb of additional dependencies, up to the 250mb limit of zip-based Lambda functions. I'm &lt;strong&gt;not&lt;/strong&gt; directly comparing the size of the final image with the size of the zip file, because containers include an OS and system libraries, so they are natively much larger than zip files.&lt;/p&gt;

&lt;p&gt;As usual, I'm testing the &lt;strong&gt;round trip&lt;/strong&gt; request time for a cold start from within the same region. I'm not using init duration, which &lt;a href="https://youtu.be/2EDNcPvR45w?t=1421"&gt;does not include the time to load bytes into the function sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I created a cold start by updating the function configuration (setting a new environment variable), and then sending a simple test request. The code for this project is &lt;a href="https://github.com/astuyve/cold-start-benchmarker"&gt;open source&lt;/a&gt;. I also streamed this entire process &lt;a href="https://twitch.tv/aj_stuyvenberg"&gt;live on twitch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These results were based on the p99 response time, but I've included the p50 times for python below.&lt;/p&gt;

&lt;p&gt;This first test contains a set of NodeJS functions running Node18.x. After several days and thousands of invocations, we see the final result. The top row represents zip-based Lambda functions, and the bottom row reports container-based Lambda functions (lower is better):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm8p2n32v0e67tc42i9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm8p2n32v0e67tc42i9u.png" alt="Round trip cold start request time for thousands of invocations over several days" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An earlier version of this post reversed the rows. I've changed this to be consistent with the python result format. Thanks to those who corrected me!&lt;/p&gt;

&lt;p&gt;It's easier to read a bar chart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij7nlr01o7gjtrizh1q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij7nlr01o7gjtrizh1q3.png" alt="Round trip cold start request time for thousands of invocations over several days, as a bar chart" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second test was similar and performed with Python functions running Python 3.11. We see a very similar pattern, with slightly more variance and overlap on the lower end of function sizes. Here is the p99:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F344ozjyabceui2m9j7dh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F344ozjyabceui2m9j7dh.png" alt="Round trip cold start request time for python functions, p99" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and here is the p50:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1x9qtaku18oadrm8j6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1x9qtaku18oadrm8j6j.png" alt="Round trip cold start request time for python functions, p50" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here it is in chart form, once again looking at p99 over a week:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1mnoipoq225kedbec8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1mnoipoq225kedbec8q.png" alt="Round trip cold start request time for python functions, p99, in chart form" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see the closer variance at the 100mb and 150mb marks. For the 150mb test I was using Pandas, Flask, and PsycoPG as dependencies. I'm not familiar with the internals of these libraries, so I don't want to speculate on why these results are slightly unexpected.&lt;/p&gt;

&lt;p&gt;My simplest answer is that this is a "real world" test using real dependencies. On top of a managed service like Lambda as well as some amount of network latency in a shared multi-tenant system - many variables could be confounding here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Takeaways
&lt;/h2&gt;

&lt;p&gt;For NodeJS, beyond ~30mb, container images &lt;em&gt;outperform&lt;/em&gt; zip based Lambda functions in cold start performance.&lt;/p&gt;

&lt;p&gt;For Python, container images &lt;strong&gt;vastly outperform&lt;/strong&gt; zip based Lambda functions beyond 200mb in size.&lt;/p&gt;

&lt;p&gt;This result is incredible, because Lambda container images (in total) are much much larger than the comparative zip files.&lt;/p&gt;

&lt;p&gt;I want to stress that the size of dependencies is only one factor that plays into cold starts. Besides size, other factors impact static initialization time including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Size and number of heap allocations&lt;/li&gt;
&lt;li&gt;Computations performed during init&lt;/li&gt;
&lt;li&gt;Network requests made during init&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These nuances are covered in my &lt;a href="https://youtu.be/2EDNcPvR45w"&gt;talk at AWS re:Invent&lt;/a&gt; if you want to dig deeper on the topic of cold starts.&lt;br&gt;
All of these individual projects are &lt;a href="https://github.com/astuyve/benchmarks"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use containers on Lambda?
&lt;/h2&gt;

&lt;p&gt;I am not advocating that you choose containers as a packaging mechanism for your Lambda function based &lt;em&gt;solely&lt;/em&gt; on cold start performance.&lt;/p&gt;

&lt;p&gt;That said, &lt;strong&gt;you should be using containers on Lambda&lt;/strong&gt; anyway. With these cold start performance improvements, there are very few reasons &lt;em&gt;not&lt;/em&gt; to.&lt;/p&gt;

&lt;p&gt;While it's technically true that container images are objectively less efficient means of deploying software applications, container images should be the standard for Lambda functions going forward.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containers are ubiquitous in software development, and so many tools and developer workflows already revolve around them. It's easy to find and hire developers who already know how to use containers.&lt;/li&gt;
&lt;li&gt;Multi-stage builds are clear and easy to understand, allowing you easily create the lightest and smallest image possible.&lt;/li&gt;
&lt;li&gt;Graviton on Lambda is quickly becoming the preferred architecture, and container images make x86/ARM cross-compilation easy. This is even more relevant now, as Apple silicon becomes a popular choice for developers. &lt;/li&gt;
&lt;li&gt;Base images for Lambda are updated frequently, and it's easy enough to auto-deploy the latest image version containing security updates&lt;/li&gt;
&lt;li&gt;Containers allow support larger functions, up to 10gb&lt;/li&gt;
&lt;li&gt;You can use custom runtimes like Bun, Deno, as well as use new runtime versions more easily&lt;/li&gt;
&lt;li&gt;Using the excellent &lt;a href="https://github.com/awslabs/aws-lambda-web-adapter"&gt;Lambda web adapter extension&lt;/a&gt; with a container, you can very easily move a function from Lambda to Fargate or Apprunner if cost becomes an issue. This optionality is of high value, and shouldn't be overlooked.&lt;/li&gt;
&lt;li&gt;AWS and the broader software development community continues to invest heavily in the container image standard. These improvements to Lambda represent the result of this investment, and I expect that to continue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To update dependencies managed by Lambda runtimes, you'll need to re-build your container image and re-deploy your function occasionally. This is something dependabot can easily do, but it could be painful if you have thousands of functions. These updates come free with managed runtimes anyway.&lt;/li&gt;
&lt;li&gt;You do pay for the init duration. Today, Lambda documentation claims that init duration is &lt;a href="https://aws.amazon.com/lambda/pricing/"&gt;always billed&lt;/a&gt;, but in practice we see that init duration for managed runtimes is not included in the billed duration, logged in the REPORT log line at the end of every execution.&lt;/li&gt;
&lt;li&gt;Slower deployment speeds&lt;/li&gt;
&lt;li&gt;The very first cold start for a new function or function update seems to be quite slow (p99 ~5+ seconds for a large function). This makes the iterate + test loop feel slow. In any production environment, this should be mitigated by invoking an alias (other than &lt;code&gt;$LATEST&lt;/code&gt;). In practice I've noticed this goes away if I wait a bit between deployment and invocation. This isn't great and ideally the Lambda team fixes it soon, but in production it shouldn't be a problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all of your functions are under 30mb and you're team is comfortable with zip files, then it may be worth continuing with zip files.&lt;br&gt;
For me personally, all new Lambda-backed APIs I create are based on container images using the Lambda web adapter.&lt;/p&gt;

&lt;p&gt;Ultimately your team and anyone you hire likely &lt;strong&gt;already knows how to use containers&lt;/strong&gt;. Containers start as fast or faster than zip functions, have more powerful build configurations, and more easily support existing workflows. Finally, containers make it easy to optionally move your application to something like Fargate or AppRunner if costs become a primary concern.&lt;/p&gt;

&lt;p&gt;It's time to use containers on Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;The next post in this series explores how this performance improvement was designed. It's an example of excellent systems engineering work, and it represents why I'm so bullish on serverless in the long term.&lt;/p&gt;

&lt;p&gt;If you like this type of content please subscribe to my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; or reach out on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; with any questions. You can also ask me questions directly if I'm &lt;a href="//twitch.tv/aj_stuyvenberg"&gt;streaming on Twitch&lt;/a&gt; or &lt;a href="https://www.youtube.com/channel/UCsWwWCit5Y_dqRxEFizYulw"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>docker</category>
    </item>
    <item>
      <title>Stop using Lambda Layers (use this instead)</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Wed, 08 Nov 2023 13:38:26 +0000</pubDate>
      <link>https://dev.to/aws-heroes/stop-using-lambda-layers-use-this-instead-46o0</link>
      <guid>https://dev.to/aws-heroes/stop-using-lambda-layers-use-this-instead-46o0</guid>
      <description>&lt;p&gt;This post is also available on YouTube:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Y4EJPIpqmuk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html"&gt;Lambda layers&lt;/a&gt; are a special packaging mechanism provided by AWS Lambda to manage dependencies for zip-based Lambda functions. Layers themselves are nothing more than a &lt;em&gt;sparkling&lt;/em&gt; zip file, but they have a few interesting properties which prove useful in some cases. Unfortunately Lambda layers are also difficult to work with as a developer, tricky to deploy safely, and typically don't offer benefits over native package managers. These downsides frequently outweigh the upsides, and we'll examine both in detail.&lt;/p&gt;

&lt;p&gt;By the end of this post, you'll understand the pitfalls of general Lambda layer use as well as the niche cases where layers may make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Busting Lambda layer Myths
&lt;/h2&gt;

&lt;p&gt;When I ask developers why they are using Lambda layers I often learn the underlying reasons are misguided. It's not their fault entirely, the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html"&gt;documentation&lt;/a&gt; makes some imprecise claims which may perpetuate these myths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda layers do not circumvent the 250mb size limit
&lt;/h3&gt;

&lt;p&gt;I frequently hear folks say they are leveraging Lambda layers to "raise the 250mb limit placed on zip-based Lambda functions". That's simply &lt;em&gt;not true&lt;/em&gt;. The size of the unzipped function &lt;em&gt;and all attached layers&lt;/em&gt; &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html"&gt;must be less than 250mb&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This misunderstanding springs from the very first point in the documentation which states that Lambda layers "reduce the size of your deployment packages". While technically it is true that the specific &lt;em&gt;function code&lt;/em&gt; you deploy can be reduced with layers, the overall size of the function when it runs in Lambda does not change.&lt;/p&gt;

&lt;p&gt;This leads me to my next point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda layers do not improve or reduce cold start initialization duration
&lt;/h3&gt;

&lt;p&gt;Developers often mistake that a "reduced deployment package" size will reduce cold start latency. This is also untrue, as we already know that the &lt;a href="https://twitter.com/astuyve/status/1716125268060860768"&gt;code you load&lt;/a&gt; is the single largest contributor to cold start latency. Whether or not these bytes come from a layer or simply the function zip itself is irrelevant to the resulting initialization duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development pain with Layers
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges for developers leveraging Lambda layers is that they appear &lt;code&gt;magically&lt;/code&gt; when a handler executes. While that feat is impressive technically, it poses an issue for developers as text editors and IDEs expect dependencies to be locally available, as do bundlers, test runners, and lint tools. If you run your function code locally or use an emulator, only a subset of those tools cooperate with layers. Although solving these issues is possible, external dependencies provided by Lambda layers require special consideration and handling for limited benefit.&lt;/p&gt;

&lt;p&gt;Often, the process of building and deploying Layers separately is enough to avoid them, but there are other reasons to avoid Lambda layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-architecture woes
&lt;/h2&gt;

&lt;p&gt;We're writing software for a world which is increasingly powered by ARM chips. It may be your shiny new M3 laptop, or Amazon's own (admittedly excellent) &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/"&gt;Graviton&lt;/a&gt; processor. Your Lambda functions are likely running on x86 or a combination of ARM and x86 processors today.&lt;/p&gt;

&lt;p&gt;Lambda layers &lt;em&gt;do&lt;/em&gt; support metadata attributes called "supported runtimes" and "supported architectures", but these are merely &lt;em&gt;labels&lt;/em&gt;. They don't prevent or enforce any runtime or deployment time compatibility. Imagine your surprise when you attach a binary compiled for x86 to your arm-based Lambda function and receive &lt;code&gt;exec format&lt;/code&gt; errors!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/LrenCkwFhZs?t=4917"&gt;I demonstrated this failure live&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment difficulties
&lt;/h2&gt;

&lt;p&gt;Lambda layers do not support semantic versioning. Instead, they are immutable and versioned incrementally. While this does help prevent unintentional upgrading, incremental versioning offers no clues as to backwards compatibility or changes in the updated layer package. Additionally, Lambda layers are completely runtime agnostic and offer no manifest, lockfile, or packaging hints. Layers don't provide a &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;, or &lt;code&gt;gemspec&lt;/code&gt; file to ensure adequate dependency resolution. Instead it's incumbant on the authors to only package compatible code.&lt;/p&gt;

&lt;p&gt;One of the main selling points of Lambda layers is that they can share common dependencies between many functions, which is great if every function requires exactly the same compatible version of a dependency. But what happens when you want to upgrade a major version?&lt;/p&gt;

&lt;p&gt;You'll need to release a new version of the layer with the new major version, ensure that no developer accidentally applies the incrementally-adjusted layer (remember – no semantic versioning, manifest files, or lockfiles!), and then simultaneously upgrade the Lambda function code and layer at the same time.&lt;/p&gt;

&lt;p&gt;But even &lt;em&gt;that&lt;/em&gt; doesn't work out automatically, as I've &lt;a href="https://aaronstuyvenberg.com/posts/lambda-arch-switch"&gt;already documented&lt;/a&gt;. Deploying a function + layer results in two separate, asynchronous API calls. &lt;code&gt;updateFunction&lt;/code&gt; updates the function &lt;em&gt;code&lt;/em&gt; while &lt;code&gt;updateFunctionConfiguration&lt;/code&gt; updates the &lt;em&gt;configured layers&lt;/em&gt;, and both of these are &lt;em&gt;separate&lt;/em&gt; control plane operations which can happen in parallel. This means that invoking &lt;code&gt;$LATEST&lt;/code&gt; will fail until both calls complete. To avoid this you'll need to create a new function &lt;em&gt;version&lt;/em&gt;, apply the new layer, and then update your integration (eg: ApiGateway) to point to the new alias, after both steps are complete.&lt;/p&gt;

&lt;p&gt;Now semantic versioning is not perfect, and flexible specification (eg: &lt;code&gt;~&lt;/code&gt; or &lt;code&gt;^&lt;/code&gt; for relative versions) means that the combination of bits executing your Lambda function may run together for the very first time in a staging or production environment. This has caused enough issues that package managers have solutions like &lt;code&gt;npm shrinkwrap&lt;/code&gt;, but this can be even worse with Lambda layers.&lt;/p&gt;

&lt;p&gt;And that's the gist of my point – this is what your package manager should be doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency collisions
&lt;/h2&gt;

&lt;p&gt;Lambda layers can cause a particular nasty bug and it stems from how Lambda creates a filesystem from your deployment artifacts. If you've followed this blog, you know that &lt;a href="https://aaronstuyvenberg.com/posts/impossible-assumptions"&gt;zip archives themselves&lt;/a&gt; can already create interesting edge cases when unpacking a zip file onto a file system, and Lambda is not immune to that. When a Lambda function sandbox is created, the main function package is copied into the sandbox and then each layer is copied &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/adding-layers.html"&gt;in order&lt;/a&gt; into the same filesystem directory. This means that layers containing files with the same path and filename are squashed.&lt;/p&gt;

&lt;p&gt;Although Lambda handler code is copied into a different directory than layer code, the runtime will decide where to look &lt;em&gt;first&lt;/em&gt; for dependencies. This is typically handled by the order of directories listed in the &lt;code&gt;PATH&lt;/code&gt; environment variable, or the runtime-specific variant like &lt;code&gt;NODE_PATH&lt;/code&gt;, Ruby's &lt;code&gt;GEM_PATH&lt;/code&gt;, or Java's &lt;code&gt;CLASS_PATH&lt;/code&gt; as &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/packaging-layers.html"&gt;documented here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider a Lambda function and two layers which all depend on different versions of the same library. Layers don't provide lockfiles or content metadata, so as a developer you may not be aware of this dependency conflict at build time or deployment time.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuawzby9sfcdes8iag7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuawzby9sfcdes8iag7k.png" alt="Lambda function code requiring A @ 1.0, layer 1 requiring A @ 2.0, and layer 2 requiring A @ 3.0" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At runtime, the layer code and function code are copied to their respective directories, but when the handler begins processing a request; it crashes with a syntax error! But your code ran fine locally?! What happened?&lt;/p&gt;

&lt;p&gt;The code and dependencies in the Lambda layer expect to have access to version 2 of library ABC, but the runtime has already loaded version 1 of library ABC from the function zip file!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favqqsq5lnkljtve5l6ze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favqqsq5lnkljtve5l6ze.png" alt="Lambda function code loading library A @ 3.0!" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this seems farfetched, it can happen to you – because it &lt;a href="https://github.com/DataDog/serverless-plugin-datadog/issues/321#issuecomment-1349044506"&gt;happened to me&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lambda layers can do for you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lambda layers &lt;em&gt;can&lt;/em&gt; improve function deployment speeds (but so can your CI pipeline)
&lt;/h3&gt;

&lt;p&gt;Consider two Lambda functions of identical dependencies, one with using layers (A), and one without (B).&lt;br&gt;
It's true that you can expect relatively shorter deployments for A, if you aren't also modifying and deploying the associated layer(s). However the vast majority of CI/CD pipelines support dependency caching, so most users have clear paths towards fast deployments regardless of their use of layers. Yes, your CloudFormation deployment will be a bit longer but ultimately there is not a distinct advantage here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda layers can share code across functions
&lt;/h3&gt;

&lt;p&gt;Within the same region, one layer can be used across different Lambda functions. This admittedly can be super useful to share libraries for authentication or other cross-functional dependencies. This is especially useful if you (like me) need to &lt;a href="https://github.com/datadog/datadog-lambda-extension"&gt;share layers&lt;/a&gt; for other users, even publicly.&lt;/p&gt;

&lt;p&gt;I don't really agree with the other two points in the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html"&gt;documentation&lt;/a&gt;. Layers may "separate core function logic from dependencies", but only as much as putting that dependency in another file and &lt;code&gt;import&lt;/code&gt;ing it. Your runtime does this already so this point falls a bit flat.&lt;/p&gt;

&lt;p&gt;Finally, I don't think it's best to edit your production Lambda function code live in the console editor, and I &lt;em&gt;especially&lt;/em&gt; don't think you should modify your software development process to support this. (Cloud9 IDE is a good product, just don't use the version in the Lambda console.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you should use Lambda layers
&lt;/h2&gt;

&lt;p&gt;Lambda layers aren't all bad, they're a tool with some sharp edges (which AWS should fix!). There are a couple exceptions which you can and should use Lambda layers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared binaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a commonly used binary like &lt;code&gt;ffmpeg&lt;/code&gt; or &lt;code&gt;sharp&lt;/code&gt;, it may be easier to compile those projects once and deploy them as a layer. It's handy to share them across functions, and this specific layer will rarely need to be rebuilt and updated. Layers are best with established binaries containing solid API contracts, so you won't need to deal with the deployment difficulties I listed earlier pertaining to major version upgrades.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The immensely popular &lt;a href="https://bref.sh/docs/runtimes#aws-lambda-layers"&gt;Bref&lt;/a&gt; PHP runtime is available as a Layer. Bref is available precompiled for both arm and x86, so it can make sense to use as a layer. The same is true for the &lt;a href="https://bun.sh"&gt;Bun&lt;/a&gt; javascript runtime. That being said - container images have become &lt;a href="https://twitter.com/astuyve/status/1715789135804354734"&gt;far more performant&lt;/a&gt; recently and are worth reconsidering, but that's a subject for another post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda Extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extensions are a special type of Layer but have access to extra lifecycle events, async work, and post processing which regular Lambda handlers cannot access. Extensions can perform work asynchronously from the main handler function, and can execute code &lt;em&gt;after&lt;/em&gt; the handler has returned a result to the caller. This makes Lambda Extensions a worthwhile exception to the above risks, especially if they are also pre-compiled, statically linked binary executables which won't suffer from dependency collisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In specific cases it can be worthwhile to use Lambda layers. Specifically for Lambda extensions, or heavy compiled binaries. However Lambda layers should not replace the runtime-specific packaging and ecosystem you already have. Layers don't offer semantic versioning, make breaking changes difficult to synchronize, cause headaches during development, and leave your software susceptible to dependency collisions.&lt;/p&gt;

&lt;p&gt;If or when AWS offered semantic versioning, support for layer lockfiles, and integration with native package managers, I'll happily reconsider these thoughts.&lt;/p&gt;

&lt;p&gt;Use your package manager wherever you can, it's a more capable tool and already solves these issues for you.&lt;/p&gt;

&lt;p&gt;If you like this type of content please subscribe to my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; or reach out on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; with any questions. You can also ask me questions directly if I'm &lt;a href="//twitch.tv/aj_stuyvenberg"&gt;streaming on Twitch&lt;/a&gt; or &lt;a href="https://www.youtube.com/channel/UCsWwWCit5Y_dqRxEFizYulw"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Understanding AWS Lambda Proactive Initialization</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Sun, 16 Jul 2023 18:52:21 +0000</pubDate>
      <link>https://dev.to/aws-heroes/understanding-aws-lambda-proactive-initialization-477g</link>
      <guid>https://dev.to/aws-heroes/understanding-aws-lambda-proactive-initialization-477g</guid>
      <description>&lt;p&gt;This post was first published on my &lt;a href="https://aaronstuyvenberg.com/posts/understanding-proactive-initialization"&gt;blog&lt;/a&gt; and shared on &lt;a href="https://twitter.com/astuyve/status/1679531741860577280"&gt;twitter&lt;/a&gt;, so if you like this post – please subscribe!&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Lambda can warm your functions (for free)
&lt;/h2&gt;

&lt;p&gt;In March 2023, AWS updated the documentation for the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html"&gt;Lambda Function Lifecycle&lt;/a&gt;, and included this interesting new statement:&lt;/p&gt;

&lt;p&gt;"For functions using unreserved (on-demand) concurrency, Lambda may proactively initialize a function instance, even if there's no invocation."&lt;/p&gt;

&lt;p&gt;It goes on to say:&lt;/p&gt;

&lt;p&gt;"When this happens, you can observe an unexpected time gap between your function's initialization and invocation phases. This gap can appear similar to what you would observe when using provisioned concurrency."&lt;/p&gt;

&lt;p&gt;This sentence, buried in the docs, indicates something not widely known about AWS Lambda; that AWS may warm your functions to reduce the impact and frequency of cold starts, even when used on-demand!&lt;/p&gt;

&lt;p&gt;Today, July 13th - they clarified this &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/troubleshooting-invocation.html#troubleshooting-invocation-initialization-gap"&gt;further&lt;/a&gt;:&lt;br&gt;
"For functions using unreserved (on-demand) concurrency, Lambda occasionally pre-initializes execution environments to reduce the number of cold start invocations. For example, Lambda might initialize a new execution environment to replace an execution environment that is about to be shut down. If a pre-initialized execution environment becomes available while Lambda is initializing a new execution environment to process an invocation, Lambda can use the pre-initialized execution environment."&lt;/p&gt;

&lt;p&gt;This update is no accident. In fact it's the result of several months I spent working closely with the AWS Lambda service team:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk5hcv9ktel6mboo2xnh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk5hcv9ktel6mboo2xnh.png" alt="Screenshot of a support ticket I filed with AWS, showing that they've added documentation about Proactive Initialization" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html"&gt;1 - Execution environments (see 'Init Phase' section)&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/troubleshooting-invocation.html#troubleshooting-invocation-initialization-gap"&gt;2 - Invocation Initialization gap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post we'll define what a Proactively Initialized Lambda Sandbox is, how they differ from cold starts, and measure how frequently they occur.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tracing Proactive Initialization
&lt;/h2&gt;

&lt;p&gt;This adventure began when I noticed what appeared to be a bug in a distributed trace. The trace correctly measured the Lambda initialization phase, but appeared to show the first invocation occurring several minutes after initialization. This can happen with SnapStart, or Provisioned Concurrency - but this function wasn't using either of these capabilities and was otherwise entirely unremarkable.&lt;/p&gt;

&lt;p&gt;Here's what the flamegraph looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F898ldf8rd6kpk8yf17c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F898ldf8rd6kpk8yf17c8.png" alt="Screenshot of a flamegraph showing a large gap between initialization and invocation" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see a massive gap between function initialization and invocation - in this case the invocation request wasn't even made by the client until ~12 seconds after the sandbox was warmed up.&lt;/p&gt;

&lt;p&gt;We've also observed cases where Initialization occurs several minutes before the first invocation, in this case the gap was nearly 6 minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblxsu2vr60xwtz8fzjl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblxsu2vr60xwtz8fzjl8.png" alt="Screenshot of a flamegraph showing an even larger gap between initialization and invocation" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After much discussion with the AWS Lambda Service team - I learned that I was observing a Proactively Initialized Lambda Sandbox.&lt;/p&gt;

&lt;p&gt;It's difficult to discuss Proactive Initialization at a technical level without first defining a cold start, so let's start there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining a Cold Start
&lt;/h2&gt;

&lt;p&gt;AWS Lambda defines a cold start in the &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/"&gt;documentation&lt;/a&gt; as the time taken to download your application code and start the application runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F370stju8soblkr2lk31v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F370stju8soblkr2lk31v.png" alt="AWS's diagram showing the Lambda initialization phase" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until now, it was understood that cold starts would happen for any function invocation where there is no idle, initialized sandbox ready to receive the request (absent using SnapStart or Provisioned Concurrency).&lt;/p&gt;

&lt;p&gt;When a function invocation experiences a cold start, users experience something ranging from 100ms to several additional seconds of latency, and developers observe an &lt;code&gt;Init Duration&lt;/code&gt; reported in the CloudWatch logs for the invocation.&lt;/p&gt;

&lt;p&gt;With cold starts defined, let's expand this to understand the definition of Proactive Initialization.&lt;/p&gt;
&lt;h2&gt;
  
  
  Technical Definition of Proactive Initialization
&lt;/h2&gt;

&lt;p&gt;Proactive Initialization occurs when a Lambda Function Sandbox is initialized without a pending Lambda invocation. &lt;/p&gt;

&lt;p&gt;As a developer this is desirable, because each proactively initialized sandbox means one less painful cold start which otherwise a user would experience.&lt;/p&gt;

&lt;p&gt;As a user of the application powered by Lambda, it's as if there were never any cold starts at all.&lt;/p&gt;

&lt;p&gt;It's like getting Lambda Provisioned Concurrency - for free.&lt;/p&gt;
&lt;h2&gt;
  
  
  Aligned interests in the Shared Responsibility Model
&lt;/h2&gt;

&lt;p&gt;According to the AWS Lambda service team, Proactive Initialization is the result of aligned interests by both the team running AWS Lambda and developers running applications on Lambda.&lt;/p&gt;

&lt;p&gt;We know that from an economic perspective, AWS Lambda wants to run as many functions on the same server as possible (yes, serverless has servers...). We also know that developers want their cold starts to be as infrequent and fast as possible.&lt;/p&gt;

&lt;p&gt;Understanding the fact that cold starts absorb valuable CPU time in a shared, multi-tenant system, (time which is currently not billed) it's clear that any option AWS has to minimize this time is mutually beneficial.&lt;/p&gt;

&lt;p&gt;AWS Lambda is a distributed service. Worker fleets need to be redeployed, scaled out, scaled in, and respond to failures in the underlying hardware. After all - &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzldgk7yumbl3luo4296.png"&gt;everything fails all the time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This means that even with steady-state throughput, Lambda will need to rotate function sandboxes for users over the course of hours or days. AWS does not publish minimum or maximum lease durations for a function sandbox, although in practice I've observed ~7 minutes on the low side and several hours on the high side.&lt;/p&gt;

&lt;p&gt;The service also needs to run efficiently, combining as many functions onto one machine as possible. In distributed systems parlace, this is known as &lt;code&gt;bin packing&lt;/code&gt; (aka shoving as much stuff as possible into the same bucket).&lt;/p&gt;

&lt;p&gt;The less time spent initializing functions which AWS &lt;em&gt;knows&lt;/em&gt; will serve invocations, the better for everyone.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Lambda will Proactively Initialize your function
&lt;/h2&gt;

&lt;p&gt;On a call with the AWS Lambda Service Team, they confirmed some logical cases of Proactive Initialization - deployments and eager assignments.&lt;/p&gt;

&lt;p&gt;Consider we're working with a function which at steady state experiences 100 concurrent invocations. When you deploy a change to your function (or function configuration), AWS can make a pretty reasonable guess that you'll continue to invoke that same function 100 times concurrently after the deployment finishes.&lt;/p&gt;

&lt;p&gt;Instead of waiting for each invocation to trigger a cold start, AWS will automatically re-provision (roughly) 100 sandboxes to absorb that load when the deployment finishes. Some users will still experience the full cold start duration, but some won't (depending on the request duration and when requests arrive).&lt;/p&gt;

&lt;p&gt;This can similarly occur when Lambda needs to rotate or roll out new Lambda Worker hosts.&lt;/p&gt;

&lt;p&gt;These aren't novel optimizations in the realm of distributed systems, but this is the first time AWS has confirmed they make these optimizations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proactive Initialization due to Eager Assignments
&lt;/h2&gt;

&lt;p&gt;In certain cases, Proactive Initialization is a consequence of natural traffic patterns in your application where an internal system called the AWS Lambda Placement Service will assign pending lambda invocation requests to sandboxes as they become available.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;p&gt;Consider a running Lambda function which is currently processing a request. In this case, only one sandbox is running. When a new request triggers a Lambda function, AWS's Lambda Control Plane will check for available &lt;code&gt;warm&lt;/code&gt; sandboxes to run your request.&lt;/p&gt;

&lt;p&gt;If none are available, a new sandbox is initialized by the Control Plane:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41ntqjlsjz65x9g7z0p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41ntqjlsjz65x9g7z0p4.png" alt="Step one where the Lambda control plane has assigned a pending request to a warm sandbox" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However it's possible that in this time that a warm sandbox completes a request and is ready to receive a new request.&lt;br&gt;
In this case, Lambda will assign the request to the newly-free warm sandbox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gsiyc8fvg3qwgsqyycu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gsiyc8fvg3qwgsqyycu.png" alt="Step two where the Lambda control plane has assigned a pending request to a newly-freed sandbox" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new sandbox which was created now has no request to serve. It is still kept warm, and can serve new requests - but a user did not wait for the sandbox to warm up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyc93365mfj42ub7wf59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyc93365mfj42ub7wf59.png" alt="Proactive init after being assigned a warm sandbox!" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a proactive initialization.&lt;/p&gt;

&lt;p&gt;When a new request arrives, it can be routed to this warm container with no delay!&lt;/p&gt;

&lt;p&gt;Request B did spend some time waiting for a sandbox (but less than the full duration of a cold start). This latency is not reflected in the duration metric, which is why it’s important to monitor the end to end latency of any synchronous request through the calling service! (Like API Gateway)&lt;/p&gt;
&lt;h2&gt;
  
  
  Detecting Proactive Initializations
&lt;/h2&gt;

&lt;p&gt;We can leverage the fact that AWS Lambda functions must &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html"&gt;initialize within 10 seconds&lt;/a&gt;, otherwise the Lambda runtime is re-initialized from scratch. Using this fact, we can safely infer that a Lambda Sandbox is proactively initialized when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Greater than 10 seconds has passed between the earliest part of function initialization first invocation processed
and&lt;/li&gt;
&lt;li&gt;We're processing the first invocation for a sandbox.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both of these are easily tested, here's the code for Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coldStartSystemTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;functionDidColdStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functionDidColdStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlerWrappedTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proactiveInitialization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerWrappedTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;coldStartSystemTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;proactiveInitialization&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;functionDidColdStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;init_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_ns&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
&lt;span class="n"&gt;cold_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;cold_start&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cold_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_ns&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
        &lt;span class="n"&gt;cold_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;proactive_initialization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;init_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;proactive_initialization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{proactiveInitialization: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;proactive_initialization&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Go Serverless v1.0! Your function executed successfully!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frequency of Proactive Initializations
&lt;/h2&gt;

&lt;p&gt;At low throughput, there are virtually no proactive initializations for AWS Lambda functions. But I called this function over and over in an endless loop (thanks to AWS credits provided by the AWS Community Builder program), and noticed that almost &lt;em&gt;65%&lt;/em&gt; of my cold starts were actually proactive initializations, and did not contribute to user-facing latency.&lt;/p&gt;

&lt;p&gt;Here's the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, @message.proactiveInitialization
| filter proactiveInitialization == 0 or proactiveInitialization == 1
| stats count() by proactiveInitialization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the detailed breakdown, note that each bar reflects the sum of initializations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mqneq0x6mjfms0ulhcf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mqneq0x6mjfms0ulhcf.jpg" alt="Count of proactively initialized Lambda Sandboxes showing 56 proactive initializations and 33 cold starts." width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running this query over several days across multiple runtimes and invocation methods, I observed between 50% and 75% of initializations were Proactive (versus 50% to 25% which were true Cold Starts):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlfr7oormtz3wc3gvge2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlfr7oormtz3wc3gvge2.jpg" alt="Count of proactively initialized Lambda Sandboxes across node and python (including API Gateway)." width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see this reflected in the cumulative sum of invocations for a one day window. Here’s a python function invoked at a very high frequency:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5vmmrmlkn81doqtv6vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5vmmrmlkn81doqtv6vg.png" alt="Count of proactively initialized Lambda Sandboxes versus cold starts for a python function" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see after one day, we’ve had 63 Proactively Initialized Lambda Sandboxes, with only 11 Cold Starts. 85% of initializations were proactive!&lt;/p&gt;

&lt;p&gt;AWS Serverless Hero &lt;a href="https://github.com/metaskills"&gt;Ken Collins&lt;/a&gt; maintains a very popular &lt;a href="https://github.com/rails-lambda"&gt;Rails-Lambda&lt;/a&gt; package. After some discussion, he &lt;a href="https://github.com/rails-lambda/lamby/pull/169"&gt;added the capability&lt;/a&gt; to track Proactive Initializations and came to a similar conclusion - in his case after a 3-day test using Ruby with a custom runtime, 80% of initializations were proactive:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkytbz56szfqask8sbowc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkytbz56szfqask8sbowc.png" alt="Count of proactively initialized Lambda Sandboxes versus cold starts for a ruby function" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirming what we suspected
&lt;/h2&gt;

&lt;p&gt;This post confirms what we've all speculated but never knew with certainty - AWS Lambda is warming your functions. We've demonstrated how you can observe this behavior, and even spoken with the AWS Lambda service team to confirm some triggers for this warming.&lt;/p&gt;

&lt;p&gt;But that begs the question - what should you do about AWS Lambda Proactive Initialization?&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should do about Proactive Initialization
&lt;/h2&gt;

&lt;p&gt;Nothing.&lt;/p&gt;

&lt;p&gt;This is the fulfillment of the promise of Serverless in a big way. You'll get to focus on your own application while AWS improves the underlying infrastructure. Cold starts become something managed out by the cloud provider, and you never have to think about them.&lt;/p&gt;

&lt;p&gt;We use Serverless services because we offload undifferentiated heavy lifting to cloud providers. Your autoscaling needs and my autoscaling needs probably aren't that similar, but workloads taken in aggregate with millions of functions across thousands of customers, AWS can predictively scale out functions and improve performance for everyone involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;I hope you enjoyed this first look at Proactive Initialization, and learned a bit more about how to observe and understand your workloads on AWS Lambda. If you want to track metrics and/or APM traces for proactively initialized functions, it's available for anyone using Datadog.&lt;/p&gt;

&lt;p&gt;This was also my first post as an &lt;a href="https://aws.amazon.com/developer/community/heroes/aj-stuyvenberg/"&gt;AWS Serverless Hero!&lt;/a&gt;, so if you like this type of content please subscribe to my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; or reach out on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; with any questions.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Thawing your Lambda Cold Starts with Lazy Loading</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Tue, 30 May 2023 12:48:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/thawing-your-lambda-cold-starts-with-lazy-loading-2mop</link>
      <guid>https://dev.to/aws-builders/thawing-your-lambda-cold-starts-with-lazy-loading-2mop</guid>
      <description>&lt;p&gt;If you've heard anything about Serverless Applications or AWS Lambda Functions, you've certainly heard of the dreaded Cold Start. I've written a lot about Cold Starts, and I spend a great deal of time measuring them as I did for my post on &lt;a href="//aaronstuyvenberg.com/aws-sdk-comparison/"&gt;Benchmarking the AWS SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post we'll recap what a Cold Start is, then we'll define a technique called Lazy Loading, show you when and how to use it, and measure the outcome!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Cold Start?
&lt;/h2&gt;

&lt;p&gt;Lambda sandboxes are created on demand when a new request arrives, but live for multiple sequential invocations of a function. When an application experiences an increase in traffic, Lambda must create additional sandboxes.&lt;/p&gt;

&lt;p&gt;The additional latency caused by this sandbox creation (which the user also experiences) is known as a Cold Start:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ws401e9emsx7uhiogef.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ws401e9emsx7uhiogef.jpg" alt="Cold Start diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample App
&lt;/h2&gt;

&lt;p&gt;This application is a Todo list, which is built for multiple tenants. This application is built using AWS Lambda, API Gateway, and DynamoDB.&lt;/p&gt;

&lt;p&gt;One particular user (we can pick on me, AJ, in this case), demands that he is notified by SNS any time a new &lt;code&gt;Todo item&lt;/code&gt; is added to his list. &lt;br&gt;
The architecture of this application looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw57ft4z8r3oz1vfpsph8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw57ft4z8r3oz1vfpsph8.jpg" alt="Lazy Load Todo Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To view full-resolution flamegraphs, I suggest reading this post on my &lt;a href="https://aaronstuyvenberg.com/posts/lambda-lazy-loading" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eager Loading
&lt;/h2&gt;

&lt;p&gt;Eager loading happens when you load a dependency by calling &lt;code&gt;require&lt;/code&gt;, or &lt;code&gt;import&lt;/code&gt; at the top of your function code.&lt;/p&gt;

&lt;p&gt;Normally, dependencies in your function are Eager loaded - or loaded during initialization. For Node, Python, and Ruby runtimes - your dependencies are loaded when the runtime begins reading your handler files and processing each &lt;code&gt;require&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt; in the order they are written. If you're writing Rust or Go, this is the default behavior as well because binaries are statically compiled into one file.&lt;/p&gt;

&lt;p&gt;This code is very typical and you've probably seen it many times. At the top of the file, we load a DynamoDB client along with a SNS client, then we move on to process the payload:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamoClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ddbClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dynamoClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublishBatchCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// handler code in gist&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The full code is available &lt;a href="https://gist.github.com/astuyve/2e7fe4b39a7ffcfa0646deb9e147802d" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eager Loading Cold Start
&lt;/h2&gt;

&lt;p&gt;We can measure the duration of this Cold Start Trace and see that loading DynamoDB loads in around 360ms. The DynamoDB client also depends on the AWS STS client, which is true of SNS and most other services. The trace looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gdxqk89tpre5zat317y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gdxqk89tpre5zat317y.png" alt="Eager Load DynamoDB Cold Start Trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Further down the flamegraph we see SNS loads in another 50ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmdt4m4ktdt3q0m94p54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmdt4m4ktdt3q0m94p54.png" alt="Eager Load SNS Cold Start Trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazy Loading to improve performance
&lt;/h2&gt;

&lt;p&gt;If we have hundreds or thousands of users; AJ's &lt;code&gt;todo&lt;/code&gt; items may represent only 5% or 1% of calls to this endpoint. However we load the SNS client on &lt;em&gt;every single initialization&lt;/em&gt;, regardless of if we'll use SNS!&lt;/p&gt;

&lt;p&gt;Let's fix this!&lt;/p&gt;

&lt;p&gt;To improve this performance we can move our &lt;code&gt;require&lt;/code&gt; statement into a method which we'll call only when a &lt;code&gt;Todo item&lt;/code&gt; item from AJ is received. Don't worry that we reassign this variable - in NodeJS, calls to &lt;code&gt;require&lt;/code&gt; are cached so this module load will only occur once on the first call to &lt;code&gt;loadSns()&lt;/code&gt;. We could also check if the snsClient variable is nil before calling the method, but brevity is preferred here.&lt;/p&gt;

&lt;p&gt;This strategy is also effective for Ruby and Python (as well as Java and other languages).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamoClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ddbClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dynamoClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublishBatchCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SNSClient&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadSns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublishBatchCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newItemId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// It's for AJ - load the SNS client!&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aj&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;loadSns&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// ... rest of handler code in gist&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The full code is available &lt;a href="https://gist.github.com/astuyve/94029d6206eaf144903579cb5d1ea843" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lazy Loading means that we only load the &lt;code&gt;SNS&lt;/code&gt; client when we need it - so let's take a look at the Cold Start Trace when a normal user creates a &lt;code&gt;Todo item&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpww721zav2u6vker4ktu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpww721zav2u6vker4ktu.png" alt="Lazy Load DynamoDB Cold Start Trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the handler loads in 401ms compared to the previous 478ms - that's a 16% decrease in latency for normal users experiencing a Cold Start! &lt;/p&gt;

&lt;p&gt;So what happens when a &lt;code&gt;Todo item&lt;/code&gt; is created for AJ? You can see that the ~80ms is shifted to the AWS Lambda Handler function span, where AJ has to wait for the SNS client to load:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtmq4j00ytkzmaut5k54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtmq4j00ytkzmaut5k54.png" alt="Lazy Load SNS Cold Start Trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Keen observers would point out that the &lt;code&gt;init&lt;/code&gt; portion of a Lambda execution lifecycle is free. And they're right! For now. AWS doesn't promise that the init duration is free (although this is &lt;a href="https://bitesizedserverless.com/bite/when-is-the-lambda-init-phase-free-and-when-is-it-billed/" rel="noopener noreferrer"&gt;widely observed&lt;/a&gt; and has been for some time).&lt;/p&gt;

&lt;p&gt;Cost in dollars shouldn't really be a factor here, as the overall number of cold starts is limited and shifting this dependency to the user with a special case is worth saving everyone other use the initialization time.&lt;/p&gt;

&lt;p&gt;This technique is especially applicable to &lt;a href="https://aaronstuyvenberg.com/posts/monolambda-vs-individual-function-api" rel="noopener noreferrer"&gt;mono-lambda APIs&lt;/a&gt; where dependencies can vary by route, or specific users like in this simple example. I'd also make a strong case that this type of atypical behavior ought to be refactored out into a separate Lambda Function, but that will be a topic for a different day.&lt;/p&gt;

&lt;p&gt;As you embark on your Serverless journey, keep an eye out for opportunities to be lazy!&lt;/p&gt;

&lt;p&gt;Hopefully you enjoyed this post. If you're interested in other Serverless minutia, be sure to check out the rest of my &lt;a href="https://aaronstuyvenberg.com" rel="noopener noreferrer"&gt;blog&lt;/a&gt;, where this article was first published, and my &lt;a href="https://twitter.com/astuyve" rel="noopener noreferrer"&gt;twitter feed&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Introducing AWS Lambda Response Streaming</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Fri, 07 Apr 2023 22:23:49 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-streaming-response-from-aws-lambda-511f</link>
      <guid>https://dev.to/aws-builders/introducing-streaming-response-from-aws-lambda-511f</guid>
      <description>&lt;p&gt;Today, AWS has announced support for &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/"&gt;Streaming Responses from Lambda Functions&lt;/a&gt;. This long-awaited capability helps developers stream responses from their functions to their users without necessarily waiting for the entire response to be finished. It's especially useful for server-side rendering, commonly used by modern javascript frameworks.&lt;/p&gt;

&lt;p&gt;This capability reduces Time to First Byte, which makes your application feel snappier, and load more quickly - especially for users who are geographically far from the AWS datacenter you’re using, or users with poor connections.&lt;/p&gt;

&lt;p&gt;Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling
&lt;/h2&gt;

&lt;p&gt;To enable Streaming Responses, developers will have to modify their function code slightly. Your handler will need to use a new decorator available in the Lambda runtime for Node 14, 16, or 18, which wraps your handler. This decorator is injected directly in the runtime, so you don't need to import any packages. A user &lt;a href="https://gist.github.com/magJ/63bac8198469b6a25d5697ad490d31e6#file-index-mjs-L925"&gt;extracted the method from the base lambda image&lt;/a&gt; quite some time ago, so this launch has clearly been planned for a while.&lt;/p&gt;

&lt;p&gt;Here's an example from the launch post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're familiar with Node's &lt;a href="https://nodejs.org/docs/latest-v14.x/api/stream.html#stream_writable_streams"&gt;writable stream API&lt;/a&gt;, then you'll recognize that this decorator implements one. AWS suggests you use stream pipelines to write to the stream - again, here's the example from the launch post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zlib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zlib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Readable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// As an example, convert event to a readable stream.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGzip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from something like server-side HTML rendering, this feature also helps transmit media back to API callers. Here's an example of a Lambda function rendering an image, using response streaming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Response streaming function which loads a large image.
 */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large-photo.jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the response streaming to the browser, which looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94qd6vbf8wqk6ymx43bz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94qd6vbf8wqk6ymx43bz.gif" alt="Streaming response to browser" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling these functions
&lt;/h2&gt;

&lt;p&gt;Next, if you're going to call a function which issues a Streaming Response programmatically using the NodeJS AWS SDK, you'll need to use v3. I've &lt;a href="https://aaronstuyvenberg.com/aws-sdk-comparison/"&gt;written about this change extensively&lt;/a&gt;, but most importantly for this feature - it doesn't seem that the v2 SDK is supported at all. So you'll need to upgrade before you can take advantage of Streaming Responses. If you're looking to invoke a function using Streaming Responses with other languages, it's also now supported using the AWS SDK for Java 2.x, and AWS SDKs for Go version 1 and version 2. I'd hope Python's boto3 support is coming soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  But wait, one catch
&lt;/h2&gt;

&lt;p&gt;Finally, developers can use the streaming response capability only with the newer Lambda Function URL integration. Function URLs are one of several ways to trigger a Lambda Function via an HTTP request, which I've covered &lt;a href="https://dev.to/aws-builders/introducing-lambda-function-urls-4ahd"&gt;previously, in another post&lt;/a&gt;. This will be a bit limiting in terms of authentication mechanisms.&lt;/p&gt;

&lt;p&gt;API Gateway and ALB are more common HTTP Integration methods for Lambda, and they do not support chunked transfer encoding - so you can't stream responses directly from a Lambda function to API Gateway or ALB using this feature.&lt;/p&gt;

&lt;p&gt;You can use API Gateway in front of Lambda Function URL, and use that to increase the response size from the previous limit of 10mb, up to the new soft limit of 20mb - but users won't see an improvement in Time to First Byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;If you're using Lambda to serve media such as images, videos, or audio - Streaming Responses will help immensely. That's not been a core use case for me personally, but I suspect this will be most leveraged by developers using Lambda to serve frontend applications using server-side rendering. For those users, I think this launch is particularly exciting.&lt;br&gt;
Ultimately, Streaming Response for Lambda is an important step in bringing the capability of Lambda closer to what users can get in other, traditional server-ful compute environments. It's an exciting new feature, and I'm looking forward to seeing the capabilities it unlocks for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;As always, if you liked this post you can find more of my thoughts on my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; and on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>nextjs</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Benchmarking the AWS SDK</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Mon, 20 Mar 2023 14:32:30 +0000</pubDate>
      <link>https://dev.to/aws-builders/benchmarking-the-aws-sdk-2pd4</link>
      <guid>https://dev.to/aws-builders/benchmarking-the-aws-sdk-2pd4</guid>
      <description>&lt;p&gt;If you're building Serverless applications on AWS, you will likely use the AWS SDK to interact with some other service. You may be using DynamoDB to store data, or publishing events to SNS, SQS, or EventBridge.&lt;/p&gt;

&lt;p&gt;Today the NodeJS runtime for AWS Lambda is at a bit of a crossroads. Like all available Lambda runtimes, NodeJS includes the &lt;code&gt;aws-sdk&lt;/code&gt; in the base image for each supported version of Node.&lt;/p&gt;

&lt;p&gt;This means Lambda users don't need to manually bundle the commonly-used dependency into their applications. This reduces the deployment package size, which is key for Lambda. Functions packaged as zip files can be a maximum of &lt;code&gt;250mb&lt;/code&gt; including code + layers, and container-based functions support up to &lt;code&gt;10GB&lt;/code&gt; image sizes.&lt;/p&gt;

&lt;p&gt;The decision about which SDK you use and how you use it in your function seems simple at first - but it's actually a complex multidimensional engineering decision.&lt;/p&gt;

&lt;p&gt;In the Node runtime, &lt;code&gt;Node12.x&lt;/code&gt;, &lt;code&gt;14.x&lt;/code&gt;, and &lt;code&gt;16.x&lt;/code&gt; each contain the AWS SDK v2 packaged in the runtime. This means virtually all Lambda functions up until recently have been built to use the v2 SDK. When AWS launched the &lt;code&gt;Node18.x&lt;/code&gt; runtime for Lambda, they packaged the AWS SDK v3 by default. Since the AWS SDK is likely the most commonly used library in Lambda, I decided to break down the cold start performance of each version across a couple of dimensions.&lt;/p&gt;

&lt;p&gt;We'll trace the cold start and measure the time to load the SDK via the following configurations from the runtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The entire v2 SDK&lt;/li&gt;
&lt;li&gt;One v2 SDK client&lt;/li&gt;
&lt;li&gt;One v3 SDK client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then we'll use &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; to tree-shake and minify each client, and run the tests again:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tree-shaken v2 SDK&lt;/li&gt;
&lt;li&gt;One tree-shaken v2 SDK client&lt;/li&gt;
&lt;li&gt;One tree-shaken v3 SDK client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these tests were performed in my local AWS region (us-east-1), using x86 Lambda Functions configured with 1024mb of RAM. The client I selected was SNS. I ran each test 3 times and screengrabbed one. Limitations are noted at the end.&lt;/p&gt;

&lt;p&gt;To view full-resolution flamegraphs, I suggest reading this post on my &lt;a href="https://aaronstuyvenberg.com/aws-sdk-comparison/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading the entire v2 SDK
&lt;/h2&gt;

&lt;p&gt;There are a few common ways to use the v2 SDK.&lt;br&gt;
In most blogs and documentation (including AWS's &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-examples-using-tables.html" rel="noopener noreferrer"&gt;own&lt;/a&gt;, but not &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-2/" rel="noopener noreferrer"&gt;always&lt;/a&gt;), you'll find something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="c1"&gt;// ... some handler code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although functional, this code is suboptimal as it loads the entire AWS SDK into memory. Let's take a look at that flamegraph for the cold start trace:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faaronstuyvenberg.com%2Fassets%2Fimages%2Faws-sdk%2Fv2_all.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faaronstuyvenberg.com%2Fassets%2Fimages%2Faws-sdk%2Fv2_all.png" alt="Loading the entire v2 AWS SDK"&gt;&lt;/a&gt;&lt;br&gt;
In this case we can see that this function loaded the entire &lt;code&gt;aws-sdk&lt;/code&gt; in &lt;code&gt;324ms&lt;/code&gt;. Check out all of this extra &lt;em&gt;stuff&lt;/em&gt; that we're loading!&lt;/p&gt;

&lt;p&gt;Here we see that we're loading not only SNS, but also a smattering of every other client in the &lt;code&gt;/clients&lt;/code&gt; directory, like DynamoDB, S3, Kinesis, Sagemaker, and so many small files that I don't even trace them in this cold start trace:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu9rq3r3t1d5r3d4apuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu9rq3r3t1d5r3d4apuc.png" alt="Loading the entire v2 AWS SDK - focusing on client"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 324ms
Second run: 344ms
Third run: 343ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Packaging and loading the entire v2 SDK
&lt;/h2&gt;

&lt;p&gt;One common piece of advice I've read suggests that users should pin a specific version of the &lt;code&gt;aws-sdk&lt;/code&gt;, and package it into their application.&lt;/p&gt;

&lt;p&gt;Although the &lt;code&gt;aws-sdk&lt;/code&gt; is already provided by AWS in the Lambda runtime, the logic is that AWS can roll out changes to the SDK at any point with no changes on your side. These changes &lt;em&gt;should&lt;/em&gt; be backwards compatible, but unless you're specifically &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-runtime-management-controls/" rel="noopener noreferrer"&gt;managing runtime updates&lt;/a&gt;, those new SDK changes will be applied automatically - potentially breaking your application.&lt;/p&gt;

&lt;p&gt;But does manually packaging the &lt;code&gt;aws-sdk&lt;/code&gt; impact the cold start duration? In this test, the code is still the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="c1"&gt;// ... some handler code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yet the flame graph is not:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbozbspvbz650yas73o9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbozbspvbz650yas73o9v.png" alt="Loading the entire v2 sdk, packaged by us"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the difference from the first example. When we load node modules from the runtime, the span labels are &lt;code&gt;aws.lambda.require_runtime&lt;/code&gt;. Now that we're packaging our own version of the sdk, we see the same general composition of spans labeled &lt;code&gt;aws.lambda.require&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We also see that packaging our own v2 &lt;code&gt;aws-sdk&lt;/code&gt; clocks in at &lt;code&gt;540ms&lt;/code&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 540ms
Second run: 531ms
Third run: 502ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The v3 &lt;code&gt;aws-sdk&lt;/code&gt; is modularized by default, so we won't test importing the entire v3 SDK, so we'll move on to sub-client imports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading one v2 SDK client
&lt;/h2&gt;

&lt;p&gt;Let's consider a more efficient approach. We can instead simply pluck the SNS client (or whichever client you please) from the library itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/sns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should save us a fair amount of time, check out the flame graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzloo1a5ce2fcsw6dsca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzloo1a5ce2fcsw6dsca.png" alt="Loading only the v2 SNS client"&gt;&lt;/a&gt;&lt;br&gt;
This is much nicer, &lt;code&gt;110ms&lt;/code&gt;. Since we're not loading clients we won't use,that saves us around &lt;code&gt;238&lt;/code&gt; milliseconds!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 110ms
Second run: 104ms
Third Run: 109ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AWS SDK v3
&lt;/h2&gt;

&lt;p&gt;The v3 SDK is entirely client-based, so we have to specify the SNS client specifically. Here's what that looks like in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublishBatchCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in a pretty deep cold start trace:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb7yg03kcfktxrq766es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb7yg03kcfktxrq766es.png" alt="Loading only the v3 SNS client"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the SNS client in v3 loaded in &lt;code&gt;250ms&lt;/code&gt;.&lt;br&gt;
The Simple Token Service (STS) contributed &lt;code&gt;84ms&lt;/code&gt; of this time:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppr2436q6r0trbeclnrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppr2436q6r0trbeclnrh.png" alt="Loading only the v3 SNS client, zooming in on STS"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 250ms
Second run: 259ms
Third run: 236ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bundled JS benchmarks
&lt;/h2&gt;

&lt;p&gt;The other option I want to highlight is packaging the project using something like &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt; or &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt;. JS Bundlers transpile all of your separate files and classes (along with all node_modules) into one single file, a practice originally developed to reduce package size for frontend applications. This helps improve the cold start time in Lambda, as unimported files can be pruned and the entire handler becomes one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS SDK v2 - minified in its entirety
&lt;/h2&gt;

&lt;p&gt;Now we'll load the entire AWS SDK v2 again, this time using esbuild to transpile the handler and SDK v2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="c1"&gt;// ... some handler code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the cold start trace:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7l5pq7ftfyqh0gmfxvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7l5pq7ftfyqh0gmfxvg.png" alt="Loading the entire minified v2 AWS SDK"&gt;&lt;/a&gt;&lt;br&gt;
You'll note that now we only have one span tracing the handler (as the entire SDK is now included in the same output file) - but the interesting thing is that the load time is almost &lt;em&gt;600ms&lt;/em&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 597ms
Second run: 570ms
Third run: 621ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why is this so much slower than the non-bundled version?
&lt;/h2&gt;

&lt;p&gt;Handler-contributed cold start duration is primarily driven by syscalls used by the runtime (NodeJS) to open files; eg &lt;code&gt;fs.readSync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To break this down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your code tells NodeJS to &lt;code&gt;require&lt;/code&gt; the file.&lt;/li&gt;
&lt;li&gt;NodeJS finds the file (this happens inside the &lt;code&gt;require&lt;/code&gt; method)&lt;/li&gt;
&lt;li&gt;NodeJS makes a system call, which tells the Firecracker VM instance to open the file.&lt;/li&gt;
&lt;li&gt;Firecracker opens the file.&lt;/li&gt;
&lt;li&gt;NodeJS reads the file entirely.&lt;/li&gt;
&lt;li&gt;Your function code continues running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The handler file is now &lt;code&gt;7.5mb&lt;/code&gt; uncompressed, and Node has to load it entirely.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2r3kl6xfprryaem0dhf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2r3kl6xfprryaem0dhf9.png" alt="Loading the entire minified v2 AWS SDK"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally I suspect that AWS can separately cache the built-in SDK with better locality (on each worker node) than your individual handler package, which must be fetched after a Lambda Worker is assigned to run your function.&lt;/p&gt;

&lt;p&gt;In simple terms - AWS knows most functions will need to load the AWS SDK, so the library is cached on each machine before your function is even created.&lt;/p&gt;
&lt;h2&gt;
  
  
  Minified v2 SDK - loading only the SNS client
&lt;/h2&gt;

&lt;p&gt;Once again we're importing only the SNS client, but this time we've minified it, so the code is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/sns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see in the cold start trace that the SDK is no longer being loaded from the runtime, rather it's all part of the handler:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y5y0us9gobo6euzl6lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y5y0us9gobo6euzl6lh.png" alt="Loading the minified v2 AWS SDK, containing only the SNS client"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;63ms&lt;/code&gt; is &lt;em&gt;much&lt;/em&gt; better than the entire minified SDK from the previous test. Here are all three runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 63ms
Second run: 71ms
Third run: 67ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Minified v3 SDK
&lt;/h2&gt;

&lt;p&gt;Next, let's look at a minified project using the SNS client from the v3 SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublishBatchCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the flamegraph:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijofzjhilcdbohku1ddv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijofzjhilcdbohku1ddv.png" alt="Loading only the SNS client, minified v3 AWS SDK"&gt;&lt;/a&gt;&lt;br&gt;
Far better now, &lt;code&gt;104ms&lt;/code&gt;. After repeating this test a few times, I saw that &lt;code&gt;104ms&lt;/code&gt; tended towards the high end and measured some as low as &lt;code&gt;83ms&lt;/code&gt;. No surprise that this will vary a bit (see the caveats), but I thought it was interesting that we got around the same performance as the minified v2 sns client code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run: 104ms 
Second run: 83ms
Third run: 110ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also find it fun to see the core modules, which are provided by Node Core, are also traced:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnj5oj30vka2l9d43wr51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnj5oj30vka2l9d43wr51.png" alt="Loading a core module from the v3 AWS SDK"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring
&lt;/h2&gt;

&lt;p&gt;Here's the list of fastest to slowest packaging strategies for the AWS SDK:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;esbuild + individual v2 SNS client&lt;/td&gt;
&lt;td&gt;Node16x&lt;/td&gt;
&lt;td&gt;63ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;esbuild + individual v3 SNS client&lt;/td&gt;
&lt;td&gt;Node18x&lt;/td&gt;
&lt;td&gt;83ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2 SNS client from the runtime&lt;/td&gt;
&lt;td&gt;Node16x&lt;/td&gt;
&lt;td&gt;104ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3 SNS client from the runtime&lt;/td&gt;
&lt;td&gt;Node18x&lt;/td&gt;
&lt;td&gt;250ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entire v2 client from the runtime&lt;/td&gt;
&lt;td&gt;Node16x&lt;/td&gt;
&lt;td&gt;324ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entire v2 client, packaged by us&lt;/td&gt;
&lt;td&gt;Node16x&lt;/td&gt;
&lt;td&gt;540ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;esbuild + entire v2 SDK&lt;/td&gt;
&lt;td&gt;Node16x&lt;/td&gt;
&lt;td&gt;570ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Caveats, etc
&lt;/h2&gt;

&lt;p&gt;Measuring the cold start time of a Lambda function and drawing concrete conclusions at the millisecond level is a bit of a perilous task. Deep below a running Lambda function lives an actual server whose load factor is totally unknowable to us as users. There could be an issue with noisy neighbors, where other Lambda functions are stealing too many resources. The host could have failing hardware, older components, etc. It could be networked via an overtaxed switch or subnet, or simply have a bug somewhere in the millions of lines of code needed to run a Lambda function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Most importantly, users should &lt;em&gt;know&lt;/em&gt; how long their function takes to initialize and understand specifically &lt;em&gt;which&lt;/em&gt; modules are contributing to that duration.&lt;/p&gt;

&lt;p&gt;As the old adage goes:&lt;br&gt;
&lt;em&gt;You can't fix what you can't measure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Based on this experiment, I can offer a few key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load only the code you need. Consider adding a lint rule to disallow loading the entire v2 sdk.&lt;/li&gt;
&lt;li&gt;Small, focused Lambda functions will experience less painful cold starts. If your application has a number of dependencies, consider breaking it up across several functions.&lt;/li&gt;
&lt;li&gt;Bundling can be worthwhile, but may not always make sense.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For me, this means using the runtime-bundled SDK and import clients directly. For you, that might be different.&lt;/p&gt;

&lt;p&gt;As far as the Node18.x runtime and v3 SDK, AWS has already said they're &lt;a href="https://github.com/aws/aws-lambda-base-images/issues/47#issuecomment-1423915498" rel="noopener noreferrer"&gt;aware of this issue and working on it&lt;/a&gt;. I'll happily re-run this test when there's a notable change in the performance.&lt;/p&gt;

&lt;p&gt;Keep in mind, the AWS SDK is only one dependency! Most applications have several, or even dozens of dependencies in each function. Optimizing the AWS SDK may not have a large impact on your service, which brings me to my final point:&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this on your own functions
&lt;/h2&gt;

&lt;p&gt;I traced these functions using a feature I built for Datadog called &lt;a href="https://www.datadoghq.com/blog/serverless-cold-start-traces/" rel="noopener noreferrer"&gt;Cold Start Tracing&lt;/a&gt;, it's available now for Python and Node, and I'd encourage you to try this yourself with your own functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;You can find more of my thoughts on my &lt;a href="https://aaronstuyvenberg.com" rel="noopener noreferrer"&gt;blog&lt;/a&gt; and on &lt;a href="https://twitter.com/astuyve" rel="noopener noreferrer"&gt;twitter&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This post has been updated to add a new example, packaging the entire v2 SDK. You can view the diff on &lt;a href="https://github.com/astuyve/astuyve.github.io/commit/eda5dd061bcff5231f3e8c36de3271cbb4d8846e" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>node</category>
    </item>
    <item>
      <title>AJ's re:Invent Recap - 2022</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Mon, 05 Dec 2022 15:06:50 +0000</pubDate>
      <link>https://dev.to/aws-builders/ajs-reinvent-recap-2022-332k</link>
      <guid>https://dev.to/aws-builders/ajs-reinvent-recap-2022-332k</guid>
      <description>&lt;p&gt;AWS re:Invent was a whirlwind! I had a great time meeting a number of AWS Community Builders, Heroes, and cloud enthusiasts. A huge part of re:Invent is the highly anticipated product launches, and there were far &lt;a href="https://techcrunch.com/2022/11/30/heres-everything-aws-announced-today/"&gt;too many&lt;/a&gt; for me to discuss individually. Instead, here are three new features that I'm most excited about.&lt;/p&gt;

&lt;h2&gt;
  
  
  EventBridge Pipes
&lt;/h2&gt;

&lt;p&gt;EventBridge is one of my favorite serverless services. It's made building event-driven applications quite simple. You can easily create an Event Bus, define a few events, and set up targets to receive those events. This gave users a clear path to build loosely coupled, fully serverless systems.&lt;/p&gt;

&lt;p&gt;However - I often found the need to use a Lambda function as a target to filter events in some way. Occasionally I'd do some transformation and re-publish an event back into a bus. This is easy enough, but there are operational and development considerations to adding any additional Lambda function to your application.&lt;/p&gt;

&lt;p&gt;Therefore, I'm happy to use any service which allows me to remove custom Lambda functions and replace them entirely with something managed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnia7m7zxz2eggp9nngww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnia7m7zxz2eggp9nngww.png" alt="EventBridge Pipes, image from AWS Blog" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter EventBridge Pipes. Pipes allow you to define optional filter, transform, or enrich stages between sources and target destinations. The Pipe will maintain order for you, and doesn't have to be used with an Event Bus.&lt;/p&gt;

&lt;p&gt;Perhaps the most important impact on EventBridge Pipes is &lt;a href="https://aws.amazon.com/eventbridge/pricing/"&gt;pricing&lt;/a&gt;. Events published to a pipe are $.40/million &lt;em&gt;after filtering&lt;/em&gt;, where EventBridge is $1/million. More on this later.&lt;/p&gt;

&lt;p&gt;You can learn more about EventBridge Pipes in the &lt;a href="https://aws.amazon.com/blogs/aws/new-create-point-to-point-integrations-between-event-producers-and-consumers-with-amazon-eventbridge-pipes"&gt;blog post&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SnapStart
&lt;/h2&gt;

&lt;p&gt;I've already written &lt;a href="https://dev.to/aws-builders/introducing-lambda-snapstart-46no"&gt;extensively&lt;/a&gt; about SnapStart, so I won't dive in here. That said, SnapStart for Lambda is how Lambda should have been from the very beginning.&lt;/p&gt;

&lt;p&gt;I discussed this opinion in depth with Tarun, the Lambda Product Manager behind this feature, who understands my perspective (although I won't say he necessarily agrees. This blog is my opinion, not his).&lt;/p&gt;

&lt;p&gt;SnapStart is the result of many years of work, requiring infrastructure changes, new caching system deployments, and runtime changes to make the hooks function. It was a heavy lift, and I'm pleased to see this one land.&lt;/p&gt;

&lt;p&gt;Hopefully we see SnapStart for more runtimes very soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Composer
&lt;/h2&gt;

&lt;p&gt;I had never used Stackery for a production deployment, but given how complex some of my CloudFormation templates have been - I think I probably should have. Stackery was SaaS product that helped you build Serverless applications with a simple drag and drop interface.&lt;/p&gt;

&lt;p&gt;Stackery was acqui-hired by AWS in 2021, and the product was shut down. It seems that some of those innovations and influences have been rolled into a new feature called Application Composer, and the UX seems surprisingly polished.&lt;/p&gt;

&lt;p&gt;AWS is infamous for building reliable, scalable infrastructure tools with a clunky developer experience. But from the videos I've seen so far, Application Composer looks excellent. I haven't played with it yet, but I'm looking forward to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn5daua21y04nfoya1ak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn5daua21y04nfoya1ak.png" alt="Application Composer, image from AWS Blog" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can import existing CloudFormation or SAM templates and visualize them, make changes, and then re-export them without ever having to use another intrinsic function like &lt;code&gt;!ref&lt;/code&gt; or &lt;code&gt;!getAtt&lt;/code&gt;.&lt;br&gt;
Check out the &lt;a href="https://aws.amazon.com/blogs/compute/visualize-and-create-your-serverless-workloads-with-aws-application-composer/"&gt;blog post&lt;/a&gt; from Julian Wood to learn more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Javascript resolvers for AppSync
&lt;/h2&gt;

&lt;p&gt;AppSync helps developers write GraphQL APIs on AWS, which I haven't used seriously - mainly due to my aversion to authoring VTL.&lt;/p&gt;

&lt;p&gt;Now I'll need to give it a serious second look, as we can use a subset of Javascript to implement business logic in AppSync.&lt;/p&gt;

&lt;p&gt;There are limitations, but this release likely helps many users remove Lambda functions which they previously used between AppSync and other AWS resources. For that alone, it deserves a mention here. Learn more &lt;a href="https://aws.amazon.com/blogs/mobile/getting-started-with-javascript-resolvers-in-aws-appsync-graphql-apis/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra Bonus: Step Functions Distributed Map
&lt;/h2&gt;

&lt;p&gt;I'm not an avid Step Functions user, but given that &lt;a href="https://youtu.be/RfvL_423a-I?t=1354"&gt;Werner Vogels&lt;/a&gt; mentioned this in his keynote - I assume it's huge; and it's always safe to assume that I'm wrong, and that this is game-changing.&lt;/p&gt;

&lt;p&gt;I look forward to being wrong on this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Alright, if you've made it to the end, I assume I have either deeply offended you, or piqued your interest. You can find more of my thoughts on my &lt;a href="https://aaronstuyvenberg.com"&gt;blog&lt;/a&gt; and on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; and let me know!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>reinvent</category>
    </item>
    <item>
      <title>Introducing Lambda SnapStart</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Tue, 29 Nov 2022 04:44:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-lambda-snapstart-46no</link>
      <guid>https://dev.to/aws-builders/introducing-lambda-snapstart-46no</guid>
      <description>&lt;p&gt;Today, AWS announced a new feature called SnapStart for Lambda. SnapStart is a feature aimed at reducing the duration of Cold Starts, which can be especially painful for Java environments. SnapStart is now available for Java 11 functions.&lt;/p&gt;

&lt;p&gt;SnapStart works by taking a snapshot of your function just before the invocation begins, and then storing that snapshot in a multi-tiered cache in order to make subsequent Lambda container initializations much faster.&lt;/p&gt;

&lt;p&gt;This snapshot capability is powered by &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/snapshot-support.md#about-microvm-snapshotting" rel="noopener noreferrer"&gt;MicroVM Snapshot&lt;/a&gt; technology inside FireCracker, the underlying MicroVM framework used by Lambda as well as AWS Fargate. In practice this means function handler code can start running with sub-second latency (up to 10x faster).&lt;/p&gt;

&lt;p&gt;SnapStart is available initially for Java (Amazon Corretto 11), but given that the underlying system providing this capability is runtime-agnostic, it seems likely we'll see SnapStart support for other runtimes very soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;Let's recall this slide from Julian Wood's 2021 re:Invent talk, &lt;a href="https://www.youtube.com/watch?v=dnFm6MlPnco" rel="noopener noreferrer"&gt;Best practices of advanced serverless developers&lt;/a&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fzjcfutbxcljcfq212y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fzjcfutbxcljcfq212y.png" alt="Julian talking about Cold Starts" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see that a traditional Lambda invocation (known as an on-demand invocation) begins by the Lambda placement service creating a new execution environment. Your code (or open-container image) is downloaded to the environment, and the runtime is initialized. Then your handler is loaded, and finally your handler is executed.&lt;/p&gt;

&lt;p&gt;Now with SnapStart, a snapshot is taken after a new version of the function is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrdypxs221cjiuk1984k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrdypxs221cjiuk1984k.png" alt="SnapStart vs Cold Starts" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating and publishing a new Version takes some additional time, compared to simply using &lt;code&gt;$LATEST&lt;/code&gt;. Thankfully snapshots are somewhat long-lived. They are only reaped by Lambda if the function is not invoked for a couple of weeks, then the next invocation would be on-demand and generate a new snapshot.&lt;/p&gt;

&lt;p&gt;Once the snapshot is recorded, all new concurrent invocations &lt;em&gt;to fully qualified Lambda ARNs&lt;/em&gt; will utilize the snapshot to resume. This is where the payoff occurs, as resuming a snapshot can be up to 10x faster than creating and initializing a new Lambda execution environment.&lt;/p&gt;

&lt;p&gt;One important note is that Snap Start doesn't change anything for serial "warm" invocations. Only a new request or event triggering a concurrent invocation (where there is not a warm Lambda container to receive a new invocation) will use SnapStart.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's in a snapshot?
&lt;/h2&gt;

&lt;p&gt;Snapshots contain both memory and disk state of the function after it's been initialized (but before the invocation has begun). Snapshot data is chunked into 512kb fragments, and cached in a multi-tier strategy.&lt;/p&gt;

&lt;p&gt;When a Function snapshot resumes, it will only load chunks required by the function code itself. This is pretty clever, and I presume this is done using mmap's &lt;a href="https://man7.org/linux/man-pages/man2/mmap.2.html" rel="noopener noreferrer"&gt;MAP_PRIVATE&lt;/a&gt;, as documented in the firecracker &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/snapshot-support.md#about-microvm-snapshotting" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. However - reads to the snapshot memory or disk are lazy-loaded. This means there may be some latency when referencing variables or other data, as the entire function code may not be loaded when resumed, and don't occur until after a specific location is accessed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Some important caveats
&lt;/h2&gt;

&lt;p&gt;SnapStart is only usable when invoking fully qualified Lambda ARNs, which means publishing and invoking an specific function version. AWS always recommends using versions for your Lambda integrations as a matter of best practice, but the simple fact is that our development tools (including AWS-backed CDK and SAM) don't do this as a default.&lt;/p&gt;

&lt;p&gt;This means you'll likely need to make some changes to your infrastructure-as-code tool if you want to take advantage of SnapStart.&lt;br&gt;
As a quick reminder, here's the difference between an unqualified and qualified function ARN.&lt;br&gt;
Qualified ARN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:lambda:aws-region:acct-id:function:helloworld:42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unqualified ARN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:lambda:aws-region:acct-id:function:helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Free!! Free is good. I like free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Randomness and Uniqueness
&lt;/h2&gt;

&lt;p&gt;MicroVM Snapshots have inherent &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/random-for-clones.md" rel="noopener noreferrer"&gt;Uniqueness and Randomness&lt;/a&gt; concerns, as a snapshot of memory from a singular invocation will be re-used across multiple (perhaps concurrent) invocations. Fortunately this is mitigated by using cryptographically-secure pseudo-random number generators, instead of PRNGs.&lt;/p&gt;

&lt;p&gt;AWS also provides a tool to help check to ensure your function doesn't assume uniqueness, it's available &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/snapstart-uniqueness.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ephemeral Data and Temporary Credentials
&lt;/h2&gt;

&lt;p&gt;Another consequence of snapshot-resuming is that ephemeral data or temporary credentials have no expiry guarantees. For example; a library which creates an expiring token at function may find that the token is expired when a new container spins up via SnapStart. Therefore, it's best practice to verify that any ephemeral tokens or data is valid before using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network connections
&lt;/h2&gt;

&lt;p&gt;The last likely pitfall that serverless developers may fall into is storing and resuming network connections. It's common practice to &lt;code&gt;memoize&lt;/code&gt; a database or network connection outside of the function handler, so it's available for subsequent invocations. This won't work with SnapStart, because although the the HTTP or Database library is still initialized, the actual socket connection can't be transferred or multiplexed to the new containers. So you'll have to re-establish these connections.&lt;/p&gt;

&lt;p&gt;The documentation doesn't cover VPC connections, but I anticipate SnapStart won't help here either; as function containers are created and then their network devices are &lt;em&gt;attached&lt;/em&gt; to a VPC, versus the somewhat common theory that functions will be created &lt;em&gt;inside&lt;/em&gt; a VPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  My thoughts
&lt;/h2&gt;

&lt;p&gt;To me, SnapStart feels like the way Lambda should have been designed &lt;em&gt;from the very beginning&lt;/em&gt;. If the claimed performance improvements hold up, it'll change the way Lambda scaling is perceived in the Serverless space and the industry at large. That said, while SnapStart seems truly compelling, I can't help but consider the developer experience.&lt;/p&gt;

&lt;p&gt;Although I think SnapStart likely represents the defacto standard for all new Lambda functions going forward, our tools need to adapt before SnapStart is easy to use.&lt;/p&gt;

&lt;p&gt;Using SnapStart means only invoking qualified ARNs (via versioning). As I previously &lt;a href="https://dev.to/aws-builders/serverless-tools-cut-both-ways-7o2"&gt;discussed&lt;/a&gt;, this isn't the default for our tools and likely means building more complex deployment processes. It also means we, as Serverless developers, need to improve how we build and ship Serverless applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about SnapStart, you can check out the full &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all I've got for you today. If you've got questions, or I missed something - feel free to reach out to me on &lt;a href="https://twitter.com/astuyve" rel="noopener noreferrer"&gt;twitter&lt;/a&gt; and let me know!&lt;/p&gt;

&lt;p&gt;Update:&lt;br&gt;
An earlier version of this article included an &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4knpxarwymug50situ9f.png" rel="noopener noreferrer"&gt;graphic&lt;/a&gt; which, upon reflection, was confusing. I've updated the graphic to be more clear.&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Safely migrating Lambda functions from x86 to ARM</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Fri, 18 Nov 2022 16:26:14 +0000</pubDate>
      <link>https://dev.to/aws-builders/serverless-tools-cut-both-ways-7o2</link>
      <guid>https://dev.to/aws-builders/serverless-tools-cut-both-ways-7o2</guid>
      <description>&lt;p&gt;As Serverless developers, we often take our tools for granted. We press &lt;code&gt;"serverless deploy"&lt;/code&gt; or &lt;code&gt;"cdk deploy"&lt;/code&gt;, sip some coffee, and it all &lt;code&gt;"just works"&lt;/code&gt;. But in reality we're wielding powerful managed services and infrastructure as code; the underlying systems which actually run our software are abstracted away from us - and that's kind of the point. These tools give us zero-downtime deployments, rollbacks, and zero-to-large scale compute &lt;em&gt;right out of the box&lt;/em&gt;. It's amazing!&lt;/p&gt;

&lt;p&gt;These magical abstractions also mean that we often forget that our tools are sharp. Like a knife used absentmindedly, we'll occasionally leave unsafe defaults in place from development to production. That's not a bad thing necessarily! But sharp tools can unpredictably cut us. And unlike chef knives, software is constantly shifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  I love getting stuff for free
&lt;/h2&gt;

&lt;p&gt;Serverless developers have the benefit of cloud providers deploying new features which improve our experience and reduce costs. Recently AWS introduced Graviton for Lambda, which leverages their custom ARM-based processor. Using Graviton, AWS says that users can see &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/"&gt;19% better performance at 20% lower cost&lt;/a&gt; - and many users wouldn't even have to change any of their code at all! At my day job at DataDog, we quickly rolled out ARM-compatible versions of the &lt;a href="https://github.com/DataDog/datadog-agent"&gt;DataDog Extension&lt;/a&gt; and our IaC integrations like the &lt;a href="https://www.github.com/DataDog/serverless-plugin-datadog"&gt;Serverless Plugin&lt;/a&gt; and the &lt;a href="https://github.com/DataDog/datadog-cdk-constructs"&gt;CDK Construct&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before long a &lt;a href="https://github.com/DataDog/datadog-cdk-constructs/issues/110"&gt;bug was reported&lt;/a&gt; by some folks from Vercel, and I started digging in. At first it seemed like a simple bug; switching from x86 to arm64-based Lambda functions caused unix launch errors. It appeared as though an x86-based binary extension was being applied to an arm64-based function. These binaries are incompatible, as x86 and arm64 have different instruction sets. I was able to reproduce the issue, and started to suspect the CloudFormation template generated by the CDK construct.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's impossible!
&lt;/h2&gt;

&lt;p&gt;But the CloudFormation template was correct! I couldn't create a condition where we'd erroneously match up the ARM function with x86 Lambda Extension, or vice-versa! It was frustrating. No matter what the template said, for a few seconds during the deployment, the Lambda function would fail to initialize with a unix process launch error.&lt;/p&gt;

&lt;p&gt;At this point I had a hunch that this wasn't a bug per-se, rather a sharp edge around CloudFormation and the Lambda control plane. I decided to try to reproduce this issue with the Serverless Framework. It also relies on CloudFormation, but generates different CloudFormation templates, and would rule out the existence of a bug in the CDK construct. I created a &lt;a href="https://github.com/astuyve/lambda-architecture-bug"&gt;demo project&lt;/a&gt; and was able to reproduce this immediately.&lt;/p&gt;

&lt;p&gt;With two reproducible cases, I filed an AWS support ticket. After some back and forth with the support team, my case ended with a Chime call to the Lambda Workers team. They were super helpful, and pointed out that CloudFormation deployments result in two important API calls to different systems: an &lt;code&gt;updateFunction&lt;/code&gt; call and &lt;code&gt;updateFunctionConfiguration&lt;/code&gt; call. These API calls happen in parallel, so the &lt;code&gt;updateFunction&lt;/code&gt; call is updating the Lambda Function code and architecture, while the &lt;code&gt;updateFunctionConfiguration&lt;/code&gt; call is setting layers/extensions, as well as environment variables, tags, and things like timeout value and memory.&lt;/p&gt;

&lt;p&gt;This race condition is inherent to Lambda and CloudFormation today, and can occur for Layers, Extensions, Environment Variables, or tags! Ultimately we can entirely avoid this failure mode by adhering to best practices: For any production system, you should &lt;em&gt;never&lt;/em&gt; directly invoke the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html"&gt;unqualified function ARN&lt;/a&gt;. This means specifically rolling out changes to a new Lambda function version or alias, and then mapping that version to your integration (API Gateway, SQS, SNS, EventBridge, etc).&lt;/p&gt;

&lt;p&gt;Qualified ARN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:lambda:aws-region:acct-id:function:helloworld:42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unqualified ARN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:lambda:aws-region:acct-id:function:helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Developer Ergonomics
&lt;/h2&gt;

&lt;p&gt;I freely admit that this is harder to do than simply invoking &lt;code&gt;$LATEST&lt;/code&gt;. There's a reason multi-phase deployments are not the default for these IaC tools. In many cases you'll need to deploy twice, once to update the function code/configuration, and once to update your integration to point to the new function. Of course if you've &lt;a href="https://dev.to/aws-builders/serverless-at-team-scale-a8"&gt;split your stacks&lt;/a&gt;, this would already be your deployment practice. But fundamentally I don't think this is your fault, developer. We need sensible defaults that include best practices. We need less risky IAM policies, and we need safer deployments.&lt;/p&gt;

&lt;p&gt;We need to demand more ergonomic tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Although we probably can use &lt;code&gt;serverless deploy&lt;/code&gt; or &lt;code&gt;cdk deploy&lt;/code&gt; with regular, unversioned function ARNs in a lot of cases; we need to remember that we're orchestrating load balancers, messaging queues, and complex integrations. These are complex systems with complex failure modes, and these failure modes don't often appear in development environments, and will only burn us in production.&lt;/p&gt;

&lt;p&gt;Our tools need to improve as well. Both CDK and Serverless could have interactive deployments to roll out new Lambda function versions. CloudFormation can and should detect when function code and function configuration changes are being deployed simultaneously, and warn or require versioned functions be used. I'd love to see this particular sharp edge documented in the &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/best-practices.html"&gt;cdk best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until then, let's agree not to point all our traffic straight to &lt;code&gt;$LATEST&lt;/code&gt;. Our tools are sharp and we use them regularly, but it's important not to forget that we can cut ourselves.&lt;/p&gt;

&lt;p&gt;That's all I've got for you today. If you've been burned by this or other Serverless side-effects, feel free to reach out to me on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; and let me know!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>aws</category>
    </item>
    <item>
      <title>Introducing Lambda Function URLs</title>
      <dc:creator>AJ Stuyvenberg</dc:creator>
      <pubDate>Wed, 06 Apr 2022 21:07:29 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-lambda-function-urls-4ahd</link>
      <guid>https://dev.to/aws-builders/introducing-lambda-function-urls-4ahd</guid>
      <description>&lt;p&gt;AWS has just launched a new, not entirely unfamiliar feature - there is now a new way to invoke a Lambda function via HTTP API call.&lt;/p&gt;

&lt;p&gt;Lambda Function URLs are built into Lambda itself, so there's no need to configure an external API Gateway (V1) or HTTP Api (V2).&lt;/p&gt;

&lt;p&gt;You can create one right now through the AWS console, either by creating a new function or editing an existing function:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8lw2k48uv17ab2eltdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8lw2k48uv17ab2eltdv.png" alt="Function URL" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This short post will help you understand what Lambda Function URLs are, when to choose them, and when to reach for a more traditional API integration.&lt;/p&gt;
&lt;h2&gt;
  
  
  At a glance
&lt;/h2&gt;

&lt;p&gt;Lambda Function URLs allow your function to be called via a HTTP request. This capability isn't new, previously you'd need to pair Lambda with API Gateway (v1 or v2) to invoke a function via HTTP request. API Gateway had a free tier, but after that you'd be charged $1.00/million requests (not including the time your Lambda function required to execute).&lt;/p&gt;

&lt;p&gt;The key distinction between API Gateway and Lambda Function URLs is that Function URLs are a &lt;em&gt;free&lt;/em&gt;* way to invoke your Lambda function via HTTP request *(you only pay for the very small additional running time incurred by serializing the request and response).&lt;/p&gt;
&lt;h2&gt;
  
  
  That's right, Lambda Function URLs are free
&lt;/h2&gt;

&lt;p&gt;This is clearly the biggest selling point for Function URLs because it's not uncommon for API Gateway to be the biggest part of a Serverless bill!&lt;/p&gt;

&lt;p&gt;There are also more significant advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Function timeout is 15 minutes, instead of API Gateway's 29 seconds&lt;/li&gt;
&lt;li&gt;Ease of setup and use&lt;/li&gt;
&lt;li&gt;Performance seems to be &lt;em&gt;really&lt;/em&gt; good for an API-based Lambda integration. With a vanilla Node.JS App, cold starts take about 900ms until the function is invoked, and warm starts are a &lt;em&gt;blistering&lt;/em&gt; 8.35ms 🤯
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gg0qwhchexbj2rfhy2y.png" alt="Cold Start" width="718" height="562"&gt;
and
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftijosywcewg4bodmjx0a.png" alt="Warm Start" width="800" height="601"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are some drawbacks over a API Gateway/HTTP API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No specified routes or payload formatting options&lt;/li&gt;
&lt;li&gt;No custom domain names. Your URL is randomly assigned an ID: &lt;code&gt;https://&amp;lt;url-id&amp;gt;.lambda-url.&amp;lt;region&amp;gt;.on.aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IAM authorization or public endpoints only, no authorizers are supported.&lt;/li&gt;
&lt;li&gt;Only synchronous invocation is supported&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Routing
&lt;/h2&gt;

&lt;p&gt;Function URLs are similar to the &lt;code&gt;proxy+&lt;/code&gt; integration you may be familiar with in API Gateway.&lt;br&gt;
This means that any HTTP method to any endpoint will route to your function, eg:&lt;br&gt;
&lt;code&gt;POST https://&amp;lt;url-id&amp;gt;.lambda-url.&amp;lt;region&amp;gt;.on.aws/foo/123/bar&lt;/code&gt;&lt;br&gt;
and&lt;br&gt;
&lt;code&gt;GET https://&amp;lt;url-id&amp;gt;.lambda-url.&amp;lt;region&amp;gt;.on.aws/biz/456/&lt;/code&gt;&lt;br&gt;
will both invoke your function.&lt;/p&gt;

&lt;p&gt;If you want to serve multiple resources from the same Function URL, you'll need to parse the route from the requestOptions in your Lambda Function. This effectively places you into a &lt;a href="https://dev.to/aws-builders/the-what-why-and-when-of-mono-lambda-vs-single-function-apis-5cig"&gt;Mono-Lambda&lt;/a&gt; API pattern.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authorization Options
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpuozfsyd9qdtlss6gx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpuozfsyd9qdtlss6gx1.png" alt="Function URL Authorization" width="762" height="512"&gt;&lt;/a&gt;&lt;br&gt;
Your authorization choices are limited to Public, or IAM authorized. This lets you write IAM policies to restrict which users or services can invoke your Lambda Function via the Function URL. It's worth noting that you can still use IAM to limit who can invoke the function explicitly via the &lt;code&gt;aws sdk&lt;/code&gt; or CLI, which opens up some interesting configuration choices. &lt;/p&gt;
&lt;h2&gt;
  
  
  Payload Specification
&lt;/h2&gt;

&lt;p&gt;As there is no method for specifying Lambda integration method, like with API Gateway, Lambda Function URLs infer response format and use the API Gateway payload v2 request format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your function returns a string, API Gateway will return a HTTP 200 status code and your message.&lt;/li&gt;
&lt;li&gt;If your function returns valid JSON, it will be sent (along with a HTTP 200 status code).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most users will want more control over the full HTTP response, and thus specific keys like &lt;code&gt;headers&lt;/code&gt;, &lt;code&gt;statusCode&lt;/code&gt;, and &lt;code&gt;isBase64Encoded&lt;/code&gt; are properly assigned to the API response. &lt;code&gt;cookies&lt;/code&gt; can also be set, and are represented as a string array.&lt;/p&gt;

&lt;p&gt;Function output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"specified-header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"specified-header-value"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cookies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"User_Id=abcd1234; Expires 19 Nov 2021 20:22 GMT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Client response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 201
content-type: application/json
specified-header: specified-header-value
set-cookie: User_Id=abcd1234; Expires=19 Nov 2021 20:22 GMT
{
  "result": "success"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full documentation is available &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html"&gt;here&lt;/a&gt;, and goes into several more examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Having played with Lambda Function URLs, I think they're useful in a couple of important cases - Mono-Lambda APIs, Service to Service communication, and lightweight webhooks. I think with a few iterations, Function URLs could get much better - and possibly be the default integration mechanism for HTTP-based Lambda invocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mono-Lambda API
&lt;/h2&gt;

&lt;p&gt;Given the caveat that your authentication and authorization is already handled via IAM, or you can resolve it in your function against a provider like Auth0 - Lambda Function URLs are a cheap and easy way spin up a Mono-Lambda API. I've written extensively about why you might consider this pattern, so dig in to this &lt;a href="https://dev.to/aws-builders/the-what-why-and-when-of-mono-lambda-vs-single-function-apis-5cig"&gt;blog post&lt;/a&gt; if you're curious to learn more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Webhook use case
&lt;/h2&gt;

&lt;p&gt;Sometimes I just need a darn lambda function to talk to Slack, or to receive a webhook from Github. Gluing workflows together has been one of the key attractions of Serverless technology, and Function URLs fit a great niche as they are easy to set up when I don't care to have an &lt;code&gt;api.company.com&lt;/code&gt; domain name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service-to-Service communication
&lt;/h2&gt;

&lt;p&gt;Serverless APIs often use Cognito or Auth0 to authenticate requests from users, but in a service oriented architecture, one system often needs to authenticate with another system as a service (not acting as a user). Usually this is for things like bulk processing of records, or fetching data asynchronously.&lt;/p&gt;

&lt;p&gt;Function URLs protected with IAM roles fill a gap here, as previously you'd either need to pass user authentication context (which is not desirable, especially if the downstream service is being invoked via some persistent mechanism like DynamoDB Streams), or call the Lambda function directly with the AWS SDK (which is either a slight hassle or massive headache).&lt;/p&gt;

&lt;h2&gt;
  
  
  New for 2023 - Response Streaming
&lt;/h2&gt;

&lt;p&gt;Almost exactly a year later AWS has launched Streaming Responses for NodeJS. This feature helps reduce time to first byte by allowing your function to start streaming the beginning of a response to the user before waiting for the entire response to be finished. Function URLs are the only way to get streaming responses out of Lambda, so you'll need to use them if you'd like to harness this new capability. You can read more about Response Streaming &lt;a href="https://aaronstuyvenberg.com/posts/introducing-response-streaming"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Long term I see Function URLs fitting a pattern of service discover via Outputs, where public APIs are served with API Gateway, and internal API endpoints are surfaced with Function URLs and shared via CloudFormation Outputs (which I &lt;a href="https://dev.to/aws-builders/serverless-at-team-scale-a8"&gt;suggest&lt;/a&gt; you to use for sharing configuration between services).&lt;/p&gt;

&lt;p&gt;Good luck out there. Feel free to reach out on &lt;a href="https://twitter.com/astuyve"&gt;twitter&lt;/a&gt; with specific questions, or to share something you're building!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
