AWS Compute Blog
Caching data and configuration settings with AWS Lambda extensions
This post is written by Hari Ohm Prasath Rajagopal, Senior Modernization Architect and Vamsi Vikash Ankam, Technical Account Manager
In this post, I show how to build a flexible in-memory AWS Lambda caching layer using Lambda extensions. Lambda functions use REST API calls to access the data and configuration from the cache. This can reduce latency and cost when consuming data from AWS services such as Amazon DynamoDB, AWS Systems Manager Parameter Store, and AWS Secrets Manager.
Applications making frequent API calls to retrieve static data can benefit from a caching layer. This can reduce the function’s latency, particularly for synchronous requests, as the data is retrieved from the cache instead of an external service. The cache can also reduce costs by reducing the number of calls to downstream services.
There are two types of cache to consider in this situation:
- Data cache for caching data from databases such as Amazon Relational Database Service (Amazon RDS), or DynamoDB, etc.
- Configuration cache for caching data from a configuration management system such as Parameter Store, AWS AppConfig, or AWS Secrets Manager.
Lambda extensions are a new way for tools to integrate more easily into the Lambda execution environment and control and participate in Lambda’s lifecycle. They use the Extensions API, a new HTTP interface, to register for lifecycle events during function initialization, invocation, and shutdown.
They can also use environment variables to add options and tools to the runtime, or use wrapper scripts to customize the runtime startup behavior. The Lambda cache uses Lambda extensions to run as a separate process.
To learn more about how to use extensions with your functions, read “Introducing AWS Lambda extensions”.
Creating a cache using Lambda extensions
To set up the example, visit the GitHub repo, and follow the instructions in the README.md file.
The demo uses AWS Serverless Application Model (AWS SAM) to deploy the infrastructure. The walkthrough requires AWS SAM CLI (minimum version 0.48) and an AWS account.
To install the example:
- Create an AWS account if you do not already have one and login.
- Clone the repo to your local development machine:
- If you are not running in a Linux environment, ensure that your build architecture matches the Lambda execution environment by compiling with
GOOS=linux
andGOARCH=amd64
. - Build the Go binary extension with the following command:
- Ensure that the extensions files are executable:
- Update the parameters
region
value in../example-function/config.yaml
with the Region where you are deploying the function. - Build the function dependencies.
- Deploy the AWS resources specified in the template.yml file:
- During the prompts:
- Enter a stack name
cache-extension-demo
. - Enter the same AWS Region specified previously.
- Accept the default
DatabaseName
. You can specify a custom database name, and also update the../example-function/config.yaml
andindex.js
files with the new database name. - Enter
MySecret
as the Secrets Manager secret. - Accept the defaults for the remaining questions.
- A DynamoDB table.
- The Lambda function
ExtensionsCache-DatabaseEntry
, which puts a sample item into the DynamoDB table. - An AWS Systems Manager Parameter Store parameter called
CacheExtensions_Parameter1
with a value ofMyParameter
. - An AWS Secrets Manager secret called
secret_info
with a value ofMySecret
. - A Lambda layer called
Cache_Extension_Layer
. - A Lambda function using Nodejs.12 called
ExtensionsCache-SampleFunction
. This reads the cached values via the extension from either the DynamoDB table, Parameter Store, or Secrets Manager. - IAM permissions
- On start-up, the extension reads the
config.yaml
file, which determines which resources to cache. The file is deployed as part of the Lambda function. - The boolean
CACHE_EXTENSION_INIT_STARTUP
Lambda environment variable specifies whether to load into cache the items specified inconfig.yaml
. Iffalse
, the extension initializes an empty map with the names. - The extension retrieves the required data based on the resources in the
config.yaml
file. This includes the data from DynamoDB, the configuration from Parameter Store, and the secret from Secrets Manager. The data is stored in memory. - The extension starts a local HTTP server using TCP port 4000, which serves the cache items to the function. The Lambda function accesses the local in-memory cache by invoking the following endpoint:
http://localhost:4000/<cachetype>?name=<name
>
. - If the data is not available in the cache, or has expired, the extension accesses the corresponding AWS service to retrieve the data. It is cached first and then returned to the Lambda function. The
CACHE_EXTENSION_TTL
Lambda environment variable defines the refresh interval (defined based on Go time format, for example: 30s, 3m, etc.) - Select the function starting with the name ExtensionsCache-SampleFunction. Within the function code, the
options
array specifies which data to return from the cache. This is initially set topath: '/dynamodb?name=DynamoDbTable-pKey1-sKey1'
- Choose Configure test events to configure a test event.
- Enter a name for the Event name, accept the default payload, and select Create.
- Select Test to invoke the function. This returns the cached data from DynamoDB and logs the output.
- In the
index.js
file, amend the path statement to retrieve the Parameter Store configuration: - Select Deploy to save the function configuration and select Test. The function returns the Parameter Store configuration item:
- In the function code, amend the path statement to retrieve the Secrets Manager secret:
- Select Deploy to save the function configuration and select Test. The function returns the secret:
- Improved Lambda function performance as data is cached in memory by the extension during initialization.
- Fewer AWS API calls to external services, this can reduce costs and helps avoid throttling limits if services are accessed frequently.
- Cache data is stored in memory and not in a file within the Lambda execution environment. This means that no additional process is required to manage the lifecycle of the file. In-memory is also more secure, as data is not persisted to disk for subsequent function invocations.
- The function requires less code, as it only needs to communicate with the extension via HTTP to retrieve the data. The function does not have to have additional libraries installed to communicate with DynamoDB, Parameter Store, Secrets Manager, or the local file system.
- The cache extension is a Golang compiled binary and the executable can be shared with functions running other runtimes like Node.js, Python, Java, etc.
- Using a YAML template to store the details of what to cache makes it easier to configure and add additional services.
- A Golang Lambda function that accesses a secret from AWS Secrets Manager for every invocation.
- The ExtensionsCache-SampleFunction, previously deployed using AWS SAM. This uses the cache extension to access the secrets from Secrets Manager, the function reads the value from the cache.
git clone https://github.com/aws-samples/aws-lambda-extensions
cd aws-lambda-extensions/cache-extension-demo/
GOOS=linux GOARCH=amd64
go build -o bin/extensions/cache-extension-demo main.go
chmod +x bin/extensions/cache-extension-demo
parameters:
- region: us-west-2
cd SAM
sam build
sam deploy --guided
AWS SAM deploys:
The cache extension is delivered as a Lambda layer and added to ExtensionsCache-SampleFunction
.
It is written as a self-contained binary in Golang, which makes the extension compatible with all of the supported runtimes. The extension caches the data from DynamoDB, Parameter Store, and Secrets Manager, and then runs a local HTTP endpoint to service the data. The Lambda function retrieves the configuration data from the cache using a local HTTP REST API call.
Here is the architecture diagram.
Once deployed, the extension performs the following steps:
This sequence diagram explains the data flow:
Testing the example application
Once the AWS SAM template is deployed, navigate to the AWS Lambda console.
const options = {
"hostname": "localhost",
"port": 4000,
"path": "/parameters?name=CacheExtensions_Parameter1",
"method": "GET"
}
const options = {
"hostname": "localhost",
"port": 4000,
"path": "/parameters?name=/aws/reference/secretsmanager/secret_info",
"method": "GET"
}
The benefits of using Lambda extensions
There are a number of benefits to using a Lambda extension for this solution:
Comparing the performance benefit
To test the performance of the cache extension, I compare two tests:
Both functions are configured with 512 MB of memory and the function timeout is set to 30 seconds.
I use Artillery to load test both Lambda functions. The load runs for 100 invocations over 2 minutes. I use Amazon CloudWatch metrics to view the function average durations.
Test 1 shows a duration of 43 ms for the first invocation as a cold start. Subsequent invocations average 22 ms.
Test 2 shows a duration of 16 ms for the first invocation as a cold start. Subsequent invocations average 3 ms.
Using the Lambda extensions caching layer shows a significant performance improvement. Cold start invocation duration is reduced by 62% and subsequent invocations by 80%.
In this example, the CACHE_EXTENSION_INIT_STARTUP
environment variable flag is not configured. With the flag enabled for the extension, data is pre-fetched during extension initialization and the cold start time is further reduced.
Conclusion
Using Lambda extensions is an effective way to cache static data from external services in Lambda functions. This reduces function latency and costs. This post shows how to build both a data and configuration cache using DynamoDB, Parameter Store, and Secrets Manager.
To set up the walkthrough demo in this post, visit the GitHub repo, and follow the instructions in the README.md file.
The extension uses a local configuration file to determine which values to cache, and retrieves the items from the external services. A Lambda function retrieves the values from the local cache using an HTTP request, without having to communicate with the external services directly. In this example, this results in an 80% reduction in function invocation time.
For more serverless learning resources, visit https://serverlessland.com.