At EverQuote, we are passionate about our DevOps toolchain and improving our lead time. Teams are encouraged to share best practices and curate patterns that can be replicated across the company.
The following suggestions are my own and subjective, but they are based on my personal experiences with using AWS Lambda as a compute platform over many projects and learning from many mistakes along the way. As of the time of writing; this article contains valid URLs and all repositories are in active development and/or maintenance.
Cold & Warm Boots
AWS Lambdas, when first invoked, will be loaded into the Lambda architecture - this first boot time impacts the first invocation and then subsequent invocations are warm booted. Lambdas that are idle within a timescale are removed (AWS have been suspected to move the goalposts on idle function expirations anywhere from 30mins to 2h). If cold boots are an issue and there is a guarantee of execution times, consider investigating Provisional Concurrency to warm up a pool of instances in preparation for your compute workload.
Always allocate at least 1024MB for functions
There is a temptation to run functions at the minimum memory required for efficiency and cost savings. Benchmarking has found that functions using 1024MB or greater tend to run on a higher class AWS instance. This can result in faster performance for what very may well be the same price point. https://www.youtube.com/watch?v=sSSMTSn2xmA&t=2342s
For more fine tuned optimizations, AWS Lambda Power Tuner is available to further analyse lambda functions. However my experience is to recommend starting at 1024MB and increase the function usage as required, and use Power Tuner as an optimization when your function is running as expected.
Keep function code and library loads small
AWS loads function code from S3 into their lambda architecture; so the smaller the function the faster the cold boot will be. Also, any libraries brought in have to be loaded by the language of choice which can affect cold boot times (do you really need to load that massive library for one method?). This speed of transfer has increased over recent years but any size reduction will impact cold boot times. https://www.youtube.com/watch?v=ddg1u5HLwg8
Don’t run a lambda within a VPC (where possible) - If your lambda function does not have to interact with any resources inside of a VPC - using the shared ENI (Elastic Network Interface) pool is much more performant than using an ENI from your VPC pool - but if you are connecting to resources inside of a VPC, sacrificing security for the sake of invocation time or cost is not worth the trade off. This load speed has been improved in recent years but it can still shave precious milliseconds off your functions. https://mikhail.io/serverless/coldstarts/aws/
Debugging lambdas has always been an issue due to the lack of a known stable environment to test upon. For local debugging I would recommend using AWS SAM https://aws.amazon.com/serverless/sam/ - it has good plugins for IDEs and allows local runtime. It also ties in nicely with LambCI for creation of integration tests around your functions.
Using LambCI as a container allows the replication of a testing environment almost identical to AWS. Pairing this with something like TestContainers will make creating a fully reproducible testing environment for your functions very straightforward to create and maintain across your SDLC.
Use AWS SDK2 client libraries
When using SDK v1 client libraries, a default HTTP connections pool is set up on a per client basis with little or no customization available; using SDK v2 allows one client to be shared across any/all AWS clients required. You can reuse a common set of http communication libraries and there are a variety of client options (sync using Apache, asynchronous using Netty or one synchronous specifically built for Lambda usage). Note that feature parity from SDK v1 to SDK v2 is not 100% yet, so I would recommend checking if your API call is exposed in the AWS developer documentation.
Save the environments & AWS Regions
The Lambda running region is passed as an environment variable for free - use this when initializing clients as this will be a faster lookup than region auto-detection and will shave off some milliseconds for your cold boots. It is also worth noting that all environment variables declared within a function are also encrypted at rest; so consider passing secrets via environment rather than making a call out to a secrets manager.
Using the JDK 11 Corretto JVM makes for efficient Lambdas when warmed up. Various Lambda benchmarking tests have shown that Go or Python lambdas generally perform better than Java in most cases; but having a polyglot of languages might not be worth the performance boost that having multiple languages to maintain costs in terms of tech debt? There really is no right or wrong answer but it should be something to consider.
AWS Corretto Crypto - improve SSL handshaking risk free
AWS have created a crypto implementation for the JVM that is production ready - Amazon Corretto Crypto Provider. If you’ve been enjoying the squid games; these libraries have been handling your traffic. Useful if connecting to a lot of TLS instances and fully supports the AWS JVM Coretto. This can also be used for all JVMs and not just exclusive to Lambda.