Minimizing Cold Starts with SnapStart
This lab references the code in the aws-connectedcar-java-serverless repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.
For Java, there’s now another solution that AWS offers for the cold start problem called “SnapStart”. The provisioned concurrency feature that we tested in the previous lab basically keeps an entire Lambda provisioned and in an initialized state. With SnapStart, Lambdas are not kept provisioned, but their fully initialized state is cached so that the delay following the provisioning step is much less. Let’s see how this feature performs for the Java version of the sample code.
Deploying the Java Sample Code
Step 1: Delete the existing .Net deployment from the console
Up to now we’ve been working with the .Net version of the sample code. The SnapStart feature is currently only supported for Java Lambdas, so our first step here is to delete the existing .Net deployment. Navigate to CloudFormation in the console and delete the parent stack. As with the first cold starts lab, don’t move on to the next step until the parent and all its child stacks have been removed, like below:
Step 2: Deploy the java sample code from the terminal
If you haven’t done so already, clone the “aws-connectedcar-java-serverless” repository in VS Code. You will, of course, need to have the Java 11 JDK and Maven installed and configured. Also ensure that the workspacePath and bucket variable values in the config.zsh script are updated as required for the SAM deployment.
With all of the above in place, run the SAM deployment scripts in sequence from the VS Code terminal. As always, the stack is successfully deployed when its outputs are written to the console:
Testing Java Lambda Cold Starts without SnapStart
Step 3: Send a “Create Dealer” request to generate sample data from Postman
As with previous labs, we’re going to be using the “Get Dealers” test in Postman, so you’ll have generate some sample data. First, follow the steps to obtain and apply the adminApi and apiKey global variables in Postman, as you’ve done previously. Then, send a request for the “Create Dealer” test, after which you should see a “201 Created” response, like this:
Step 4: Send two requests for the Get Dealers Lambda
As you did in the previous two labs, send two consecutive “Get Dealers” requests from Postman. You’ll see from the results of the first request that cold starts for real-world Java Lambdas can be quite slow. In the example shown below, this request took nearly 4.5 seconds:
As usual, the second request is much faster at 145ms:
Step 5: Review the X-Ray trace for the first Get Dealers request
Open the X-Ray trace for the first request. It should look something like this:
What’s noticeable is the difference between this trace and the equivalent for the C# version of the code that we saw in the “Benchmarking Lambda Cold Starts” lab. That trace showed the following sequence of steps:
- 400ms to provision the Lambda
- 400ms to initialize the Lambda
- 1 second for the first invocation
Looking at the above trace, the equivalent steps for the Java Lambda are:
- 400ms to provision the Lambda
- 1.36 seconds to initialize the Lambda
- 2.39 seconds for the first invocation
The initialization of the JVM is slower, as is the process of profiling and JIT compiling the code for the first invocation. Let’s see what the SnapStart feature can do to improve this.
Updating the Lambda Code & Configuration
Step 6: Add the SnapStart property to the GetDealers resource
To enable SnapStart for a Java Lambda, all you have to do is add the SnapStart property to the resource using the code shown below:
SnapStart:
ApplyOn: PublishedVersions
As with the ProvisionedConcurrency property, you can add these elements to the bottom of the GetDealers Lambda resource in the admin.yaml template, as shown below:
Step 7: Add a call to DynamoDB in the Lambda constructor
Next, like you did in the previous lab, you need to add a service call to the default constructor for the Lambda so that the result of all the profiling, JIT compilation, and DynamoDB client initialization will be cached by SnapStart at the time of deployment.
For this, add the call to the dealer service in the default constructor, as shown below on lines 19-21 of the GetDealersFunction.java class:
Step 8: Deploy the code and configuration updates
With this new element in place, increment the “version” variable in the config.zsh script and run the three scripts in sequence for the SAM deployment. One deployed, you can see the SnapStart feature in use on the “General Configuration” page for the Lambda, as shown below, bottom center:
Testing Java Lambda Cold Starts with SnapStart
Step 9: Send another “Get Dealers“ request from Postman
With the SnapStart feature now enabled, send another “Get Dealers” request from Postman to see how the cold start performance has changed:
The answer, from looking at the response time above, is that the performance has improved considerably. Let’s see how this breaks down in the X-Ray trace.
Step 10: Review the X-Ray trace for the SnapStart-optimized Get Dealers request
Once more, open the X-Ray trace for the request. Here’s the trace for the request shown above:
The results you can see in this trace are interesting. The provisioning step, inferred from the delay between the invoke of the Lambda in API Gateway and the start of the restore step, takes longer here, at about 800ms. You also have the extra restore step, which takes 262ms. The invocation step, however, is much faster at 667ms compared to 2.39 seconds in the baseline cold start from above.
Summarizing the Results
You see outlandish claims for the performance gains that this feature unlocks for Java Lambdas. What we’ve witnessed with the tests in this lab is that the performance gains are good, but not miraculous.
Overall, what SnapStart achieves for Java Lambdas is cold start performance that’s more in line with what you see out-of-the-box with .Net Lambdas, which is probably good enough for most cases. Bear in mind that there’s no extra cost for this feature. For genuinely fast cold starts for Java Lambdas, however, you’ll still want to use the provisioned concurrency feature.