When is the Lambda Init Phase Free, and when is it Billed?

It’s an open secret that the ‘init’ phase in AWS Lambda is free. This fact is not included in the AWS documentation, FAQ, or pricing pages, yet the Lambda Function logs clearly show the ‘Init Duration’ is not included in the ‘Billed Duration’. At least, most of the time. This article will demystify when the AWS Lambda init duration is free, and when it is billed.

The Lambda Service creates execution environments for your Lambda Functions. These micro-VMs boot up in milliseconds, then execute your Lambda Function code. When the call has completed, the execution environment is kept around by the Lambda Service so it can handle another call. A Lambda Function consists of handler code (executed for every invocation of the Lambda Function) and initialization code (executed when a new execution environment is created). The initialization code is commonly used to import libraries and initialize reusable classes, such as AWS Service Clients. For more details, see the article Lambda Cold Starts and Bootstrap Code.

Many people who regularly use AWS Lambda know that the initialization phase is both free and gets two unthrottled vCPUs, even at the lowest memory configurations. This leads to design patterns where as much reusable code as possible is moved outside the handler. But there are several cases where the initialization phase is not free.

To conclusively determine which Lambda Functions get a free init phase and which don’t, I created a CDK project (GitHub) that deploys many different Lambda configurations and measures if the init phase is free. Long story short, you can find the results below.

Package TypeRuntime / ImageArchitectureInit ConfigInit DurationDurationBilled DurationFree Init
Imagepublic.ecr.aws/lambda/nodejs:12x86Sleep 1 Sec1293.83 ms1049.63 ms2344 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/nodejs:14x86Sleep 1 Sec1307.68 ms1058.15 ms2366 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/python:3.7x86Sleep 1 Sec1258.24 ms1002.25 ms2261 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/python:3.7x86Sleep 10 Sec12079.53 ms12080 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/python:3.8x86Sleep 1 Sec1285.01 ms1002.07 ms2288 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/python:3.9x86Sleep 1 Sec1148.21 ms1001.62 ms2150 ms๐Ÿšซ
Imagepublic.ecr.aws/lambda/python:3.9-arm64arm64Sleep 1 Sec1147.77 ms1001.65 ms2150 ms๐Ÿšซ
Imagepython:3.9-alpine3.12x86Sleep 1 Sec1684.10 ms1002.00 ms2687 ms๐Ÿšซ
ZipNODEJS_12_Xx86Sleep 1 Sec1152.58 ms1031.01 ms1032 msโœ…
ZipNODEJS_12_Xarm64Sleep 1 Sec1144.01 ms1019.79 ms1020 msโœ…
ZipNODEJS_14_Xx86Sleep 1 Sec1273.41 ms1077.13 ms1078 msโœ…
ZipNODEJS_14_Xarm64Sleep 1 Sec1156.92 ms1011.18 ms1012 msโœ…
ZipPYTHON_3_7x86Sleep 1 Sec1125.03 ms1003.04 ms1004 msโœ…
ZipPYTHON_3_8x86Sleep 1 Sec1189.79 ms1001.90 ms1002 msโœ…
ZipPYTHON_3_8arm64Sleep 1 Sec1108.83 ms1001.59 ms1002 msโœ…
ZipPYTHON_3_9x86Sleep 1 Sec1101.84 ms1002.54 ms1003 msโœ…
ZipPYTHON_3_9arm64Sleep 1 Sec1096.78 ms1001.86 ms1002 msโœ…
ZipRUBY_2_7x86Sleep 1 Sec1142.84 ms1003.05 ms1004 msโœ…
ZipRUBY_2_7arm64Sleep 1 Sec1139.89 ms1003.10 ms1004 msโœ…
ZipGO_1_Xx86Sleep 1 Sec1083.49 ms1002.60 ms1003 msโœ…
ZipGo on PROVIDED_AL2x86Sleep 1 Sec1064.65 ms1001.72 ms2067 ms๐Ÿšซ
ZipGo on PROVIDED_AL2arm64Sleep 1 Sec1049.58 ms1002.49 ms2053 ms๐Ÿšซ
ZipRust on PROVIDED_AL2x86Sleep 1 Sec1023.89 ms1002.13 ms2027 ms๐Ÿšซ
ZipPYTHON_3_9 with internal extensionx86Extension 1 Sec, Init 1 Sec2124.46 ms1001.82 ms1002 msโœ…
ZipPYTHON_3_9 with external extensionx86Extension 1 Sec, Init 1 Sec2509.05 ms1002.16 ms1003 msโœ…
ZipPYTHON_3_7x86Sleep 10 Sec12100.60 ms12101 ms๐Ÿšซ
ZipPYTHON_3_8x86Sleep 10 Sec12121.07 ms12122 ms๐Ÿšซ
ZipPYTHON_3_8arm64Sleep 10 Sec12107.96 ms12108 ms๐Ÿšซ
ZipPYTHON_3_9x86Sleep 10 Sec12012.99 ms12013 ms๐Ÿšซ
ZipPYTHON_3_9arm64Sleep 10 Sec11937.29 ms11938 ms๐Ÿšซ

Test results

The table above is the result of executing dozens of Lambda Functions, in a wide variety of languages, configurations, runtimes, and architectures. If you want to check the results yourself, download out the CDK project which provides a single step to deploying all these functions in your AWS account.

From the results, we can conclude several things:

  1. Only Lambda Functions using zipped (.zip) code on managed runtimes get free init.
  2. The init phase for Lambda Functions packaged as container images and Lambda Functions on custom runtimes (Amazon Linux or Amazon Linux 2) is not free.
  3. The architecture (x86 or arm64) does not influence the free init.
  4. Init phases that take more than 10 seconds are always billed, even when using managed runtimes. More about this below.
  5. Lambda extensions count as part of the init phase and follow the rules above. This is true for both internal and external extensions.

10 second+ init phase

The AWS documentation states:

The Init phase ends when the runtime and all extensions signal that they are ready by sending a Next API request. The Init phase is limited to 10 seconds. If all three tasks do not complete within 10 seconds, Lambda retries the Init phase at the time of the first function invocation.

In practice, this means that as soon as the init phase reaches the 10 seconds mark, it is aborted. When this occurs, the init phase is treated as if it didn’t occur at all. Instead, the handler is called, which lands in an uninitialized execution environment. The handler then executes the init code as part of its own execution, however long it takes. This leads to a handler duration (and billed duration) of ‘init + handler’. However, the actual duration is ’10 seconds + init + handler’, as can be seen in the logs below.

2022-04-06T22:12:20.595+02:00	Init starting
2022-04-06T22:12:20.595+02:00	Sleep for 10 second(s)
2022-04-06T22:12:30.434+02:00	START RequestId: c7bf5500-383b-4910-81f9-69abcf72b177 Version: $LATEST
2022-04-06T22:12:31.516+02:00	Init starting
2022-04-06T22:12:31.516+02:00	Sleep for 10 second(s)
2022-04-06T22:12:41.511+02:00	Init done
2022-04-06T22:12:41.512+02:00	Handler starting
2022-04-06T22:12:41.512+02:00	Sleep for 1 second(s)
2022-04-06T22:12:42.513+02:00	Handler done
2022-04-06T22:12:42.517+02:00	END RequestId: c7bf5500-383b-4910-81f9-69abcf72b177
2022-04-06T22:12:42.517+02:00	REPORT RequestId: c7bf5500-383b-4910-81f9-69abcf72b177 Duration: 12079.53 ms Billed Duration: 12080 ms Memory Size: 128 MB Max Memory Used: 11 MB

Note the timestamps: the first init phase starts at 22:12:20 and runs until 22:12:30. Then the handler is called, which restarts init at 22:12:31. The init phase is done at 22:12:41, and the handler is done at 22:12:42. 22 seconds from start to finish, and we’re billed for 12080 ms. Luckily, once the first handler has successfully completed the init phase, it will remain available for the next execution.

Provisioned concurrency

Fun fact: Lambda Functions initialized under a provisioned concurrency configuration are not limited to a 10 second init duration. Because provisioned concurrency keeps the configured amount of execution environments ‘warm’ and ready to accept incoming requests, the init phase is completely isolated from the inbound request. To assure the execution environment is actually available, the init phase is not aborted after 10 seconds. See the logs below, which are for a Lambda Function with a managed runtime (Python 3.7) and provisioned concurrency.

2022-04-06T22:33:18.860+02:00	Init starting
2022-04-06T22:33:18.860+02:00	Sleep for 10 second(s)
2022-04-06T22:33:28.870+02:00	Init done
2022-04-06T22:35:22.883+02:00	START RequestId: c0c823ec-d175-4dce-b580-536e0551063f Version: 1
2022-04-06T22:35:22.886+02:00	Handler starting
2022-04-06T22:35:22.886+02:00	Sleep for 1 second(s)
2022-04-06T22:35:23.887+02:00	Handler done
2022-04-06T22:35:23.890+02:00	END RequestId: c0c823ec-d175-4dce-b580-536e0551063f
2022-04-06T22:35:23.890+02:00	REPORT RequestId: c0c823ec-d175-4dce-b580-536e0551063f Duration: 1003.30 ms Billed Duration: 1004 ms Memory Size: 128 MB Max Memory Used: 36 MB Init Duration: 11714.77 ms

The init phase took more than 10 seconds! The Billed Duration is only 1004 ms, which seems to indicate the entire init phase was free. Unfortunately, the official docs disprove this:

For provisioned concurrency instances, your function’s initialization code runs during allocation and every few hours, as running instances of your function are recycled. You can see the initialization time in logs and traces after an instance processes a request. However, initialization is billed even if the instance never processes a request. Provisioned concurrency runs continually and is billed separately from initialization and invocation costs. For details, see AWS Lambda pricing.

Looking at the same test, but with a container image, we also see an init phase of more than 10 seconds. But as with every container image, the init phase is included in the billed duration.

2022-04-06T22:23:40.011+02:00	Init starting
2022-04-06T22:23:40.011+02:00	Sleep for 10 second(s)
2022-04-06T22:23:50.021+02:00	Init done
2022-04-06T22:25:46.791+02:00	START RequestId: aebe0c6b-1893-41e7-8ff7-b62db35df34d Version: 1
2022-04-06T22:25:46.793+02:00	Handler starting
2022-04-06T22:25:46.793+02:00	Sleep for 1 second(s)
2022-04-06T22:25:47.794+02:00	Handler done
2022-04-06T22:25:47.798+02:00	END RequestId: aebe0c6b-1893-41e7-8ff7-b62db35df34d
2022-04-06T22:25:47.798+02:00	REPORT RequestId: aebe0c6b-1893-41e7-8ff7-b62db35df34d Duration: 1003.30 ms Billed Duration: 12990 ms Memory Size: 128 MB Max Memory Used: 35 MB Init Duration: 11986.34 ms

Performance boost

As discussed in the article Lambda Cold Starts and Bootstrap Code, the init phase gets two unthrottled vCPUs, even at very low memory configurations. You might wonder if this applies to all configurations, even those without free init. I’m here to assure you: it does.

The table below shows the amount of prime numbers calculated in a single second. The code is written in Go and executed in the init phase and the handler code of four 128 MB Lambda Functions.

Package TypeRuntime / ImageArchitecturePrimes in InitPrimes in Handler
Dockerpublic.ecr.aws/lambda/go:1x86389243557
ZipGo on PROVIDED_AL2x86402713689
ZipGo on PROVIDED_AL2arm64337403200
ZipGO_1_Xx86371743515

The init code was more than ten times as performant as the handler code in all cases, even though only the last test gets a free init phase.

Conclusion

In this article we have uncovered the exact scenarios under which AWS provides a free init phase for your Lambda Functions. The goal of this exercise is to clear out any confusion about the free init phase. But what should you do with this knowledge? It might come as a surprise, but the answer is: nothing. You shouldn’t choose your technology based on whether you get a free initialization phase. Instead, follow these general guidelines:

  • If you can use the zip format, do so. It is more efficient, easier to maintain, and less prone to security issues than the container image format.
  • Choose your language based on other aspects than the free init, such as performance requirements or familiarity within your team.
  • If you have an init duration of more than 10 seconds, reconsider your function design. A duration this long is a clear sign something is wrong and should be optimized, for example by packaging requirements in a Lambda Layer or by storing reusable large files on EFS.

If these guidelines lead to a design with free init, good! If they don’t, it’s also good. You made the best architectural decisions for your workload, and the result does not have free init. Finally, keep in mind that free init is not an official feature. AWS might change or remove it at any time, so do not depend on its indefinite availability.


Posted

in

Blog at WordPress.com.