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 Type | Runtime / Image | Architecture | Init Config | Init Duration | Duration | Billed Duration | Free Init |
---|---|---|---|---|---|---|---|
Image | public.ecr.aws/lambda/nodejs:12 | x86 | Sleep 1 Sec | 1293.83 ms | 1049.63 ms | 2344 ms | ๐ซ |
Image | public.ecr.aws/lambda/nodejs:14 | x86 | Sleep 1 Sec | 1307.68 ms | 1058.15 ms | 2366 ms | ๐ซ |
Image | public.ecr.aws/lambda/python:3.7 | x86 | Sleep 1 Sec | 1258.24 ms | 1002.25 ms | 2261 ms | ๐ซ |
Image | public.ecr.aws/lambda/python:3.7 | x86 | Sleep 10 Sec | – | 12079.53 ms | 12080 ms | ๐ซ |
Image | public.ecr.aws/lambda/python:3.8 | x86 | Sleep 1 Sec | 1285.01 ms | 1002.07 ms | 2288 ms | ๐ซ |
Image | public.ecr.aws/lambda/python:3.9 | x86 | Sleep 1 Sec | 1148.21 ms | 1001.62 ms | 2150 ms | ๐ซ |
Image | public.ecr.aws/lambda/python:3.9-arm64 | arm64 | Sleep 1 Sec | 1147.77 ms | 1001.65 ms | 2150 ms | ๐ซ |
Image | python:3.9-alpine3.12 | x86 | Sleep 1 Sec | 1684.10 ms | 1002.00 ms | 2687 ms | ๐ซ |
Zip | NODEJS_12_X | x86 | Sleep 1 Sec | 1152.58 ms | 1031.01 ms | 1032 ms | โ |
Zip | NODEJS_12_X | arm64 | Sleep 1 Sec | 1144.01 ms | 1019.79 ms | 1020 ms | โ |
Zip | NODEJS_14_X | x86 | Sleep 1 Sec | 1273.41 ms | 1077.13 ms | 1078 ms | โ |
Zip | NODEJS_14_X | arm64 | Sleep 1 Sec | 1156.92 ms | 1011.18 ms | 1012 ms | โ |
Zip | PYTHON_3_7 | x86 | Sleep 1 Sec | 1125.03 ms | 1003.04 ms | 1004 ms | โ |
Zip | PYTHON_3_8 | x86 | Sleep 1 Sec | 1189.79 ms | 1001.90 ms | 1002 ms | โ |
Zip | PYTHON_3_8 | arm64 | Sleep 1 Sec | 1108.83 ms | 1001.59 ms | 1002 ms | โ |
Zip | PYTHON_3_9 | x86 | Sleep 1 Sec | 1101.84 ms | 1002.54 ms | 1003 ms | โ |
Zip | PYTHON_3_9 | arm64 | Sleep 1 Sec | 1096.78 ms | 1001.86 ms | 1002 ms | โ |
Zip | RUBY_2_7 | x86 | Sleep 1 Sec | 1142.84 ms | 1003.05 ms | 1004 ms | โ |
Zip | RUBY_2_7 | arm64 | Sleep 1 Sec | 1139.89 ms | 1003.10 ms | 1004 ms | โ |
Zip | GO_1_X | x86 | Sleep 1 Sec | 1083.49 ms | 1002.60 ms | 1003 ms | โ |
Zip | Go on PROVIDED_AL2 | x86 | Sleep 1 Sec | 1064.65 ms | 1001.72 ms | 2067 ms | ๐ซ |
Zip | Go on PROVIDED_AL2 | arm64 | Sleep 1 Sec | 1049.58 ms | 1002.49 ms | 2053 ms | ๐ซ |
Zip | Rust on PROVIDED_AL2 | x86 | Sleep 1 Sec | 1023.89 ms | 1002.13 ms | 2027 ms | ๐ซ |
Zip | PYTHON_3_9 with internal extension | x86 | Extension 1 Sec, Init 1 Sec | 2124.46 ms | 1001.82 ms | 1002 ms | โ |
Zip | PYTHON_3_9 with external extension | x86 | Extension 1 Sec, Init 1 Sec | 2509.05 ms | 1002.16 ms | 1003 ms | โ |
Zip | PYTHON_3_7 | x86 | Sleep 10 Sec | – | 12100.60 ms | 12101 ms | ๐ซ |
Zip | PYTHON_3_8 | x86 | Sleep 10 Sec | – | 12121.07 ms | 12122 ms | ๐ซ |
Zip | PYTHON_3_8 | arm64 | Sleep 10 Sec | – | 12107.96 ms | 12108 ms | ๐ซ |
Zip | PYTHON_3_9 | x86 | Sleep 10 Sec | – | 12012.99 ms | 12013 ms | ๐ซ |
Zip | PYTHON_3_9 | arm64 | Sleep 10 Sec | – | 11937.29 ms | 11938 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:
- Only Lambda Functions using zipped (.zip) code on managed runtimes get free init.
- 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.
- The architecture (x86 or arm64) does not influence the free init.
- Init phases that take more than 10 seconds are always billed, even when using managed runtimes. More about this below.
- 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 Type | Runtime / Image | Architecture | Primes in Init | Primes in Handler |
---|---|---|---|---|
Docker | public.ecr.aws/lambda/go:1 | x86 | 38924 | 3557 |
Zip | Go on PROVIDED_AL2 | x86 | 40271 | 3689 |
Zip | Go on PROVIDED_AL2 | arm64 | 33740 | 3200 |
Zip | GO_1_X | x86 | 37174 | 3515 |
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.