How To: Reuse Database Connections in AWS Lambda
AWS Lambda lets us "freeze" and "thaw" database connections so that we can reuse them and minimize the time it takes to setup new connections. This post teaches you how to reuse database connections in your Node.js projects.
Update 9/2/2018: I wrote an NPM module that manages MySQL connections for you in serverless environments. Check it out here.
I work with AWS Lambda quite a bit. The ability to use this Functions-as-a-Service (FaaS) has dramatically reduced the complexity and hardware needs of the apps I work on. This is what's known as a "Serverless" architecture since we do not need to provision any servers in order to run these functions. FaaS is great for a number of use cases (like processing images) because it will scale immediately and near infinitely when there are spikes in traffic. There's no longer the need to run several underutilized processing servers just waiting for someone to request a large job.
AWS Lambda is event-driven, so it's also possible to have it respond to API requests through AWS's API Gateway. However, since Lambda is stateless, you'll most likely need to query a persistent datastore in order for it to do anything exciting. Setting up a new database connection is relatively expensive. In my experience it typically takes more than 200ms. If we have to reconnect to the database every time we run our Lambda functions (especially if we're responding to an API request) then we are already adding over 200ms to the total response time. Add that to your queries and whatever additional processing you need to perform and it becomes unusable under normal circumstance. Luckily, Lambda lets us "freeze" and then "thaw" these types of connections.
Update 4/5/2018: After running some new tests, it appears that "warm" functions now average anywhere between 4 and 20ms to connect to RDS instances in the same VPC. Cold starts still average greater than 100ms. Lambda does handle setting up DB connections really well under heavy load, but I still favor connection reuse as it cuts several milliseconds off your execution time.
The Lambda documentation tells you to keep your variable declarations inside your handler
function. For example:
javascript'use strict'; module.exports.handler = (event, context, callback) => { const someVar = "foo" callback(null, { result: someVar }) }
Any variable outside the handler
function will be frozen in between Lambda invocations and possibly reused. The documentation states to "not assume that AWS Lambda always reuses the container because AWS Lambda may choose not to reuse the container." See [here](http://How To: Reuse Database Connections in AWS Lambda) for AWS's introduction to Lambda. I've found that depending on the volume of executions, the container is almost always reused.
This "freezing" process allows us to maintain state between executions. For example, we could create a simple counter variable to see how many times the Lambda container was reused:
javascript'use strict'; let counter = 0 module.exports.handler = (event, context, callback) => { counter++ console.log(counter) callback(null, { count: counter }) }
This would increment the counter every time we called the Lambda function until AWS decided to expire the container. Lambda is able to freeze any type of variable, including the connection to a database like MySQL. We simply create our connection outside of our handler
function like so:
javascript'use strict'; const mysql = require('mysql'); // require mysql // If 'client' variable doesn't exist if (typeof client === 'undefined') { // Connect to the MySQL database var client = mysql.createConnection({ // your connection info }); client.connect() } module.exports.handler = (event, context, callback) => { client.query('SELECT * FROM `books`', function (error, results) { callback(null, results) }); }
This is all fine and good, but the problem is that this will never actually return unless you close the connection to the database. This is because Lambda waits for Node's Event Loop to finish before returning anything via the callback. However, Lambda has a "context" object that can be tweaked to make this work. All we need to do is update context.callbackWaitsForEmptyEventLoop
to false
and Lambda will return as soon as we execute the callback()
function.
javascript'use strict'; const mysql = require('mysql'); // require mysql // If 'client' variable doesn't exist if (typeof client === 'undefined') { // Connect to the MySQL database var client = mysql.createConnection({ // your connection info }); client.connect() } module.exports.handler = (event, context, callback) => { // This will allow us to freeze open connections to a database context.callbackWaitsForEmptyEventLoop = false; client.query('SELECT * FROM `books`', function (error, results) { callback(null, results) }); }
Another important thing to remember is that module references are also "frozen", so you can add all of your database connection and state management functionality into a separate module. I typically use a closure to store the state of my database connections and then use promises to manage my async calls. This is a great way to connect to the database ONLY when you need to instead of making sure it is enabled for every invocation.
How do I manage these connections, especially after the Lambda container expires? Great question! I've written a post to answer this question: How To: Manage RDS Connections from AWS Lambda Serverless Functions
Looking to build an serverless API with AWS Lambda? Read my post How To: Build a Serverless API with Serverless, AWS Lambda and lambda-api