praks5432
praks5432

Reputation: 7792

Sharing DB Connection across AWS Lambda function calls

So I'm following the example here https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs, to optimize my lambda functions.

I've tried two approaches and tested them locally using serverless-offline and both don't seem to work.

First Approach

// endpoint file

import {connectToDatabase} from "lib/dbUtils.js";

let cachedDb = null;

export function post(event, context, callback) {
  let response;
  context.callbackWaitsForEmptyEventLoop = false;
  connectToDatabase()
    .then(//do other stuff

// lib/dbUtils.js

export async function connectToDatabase() {
  if (cachedDb && cachedDb.serverConfig.isConnected()) {
    console.log(" using cached db instance");
    return cachedDb;
  }
  cachedDb = await mongoose.createConnection(
    process.env.DB_URL,
    async err => {
      if (err) {
        throw err;
      }
    }
  );
  return cachedDb;
}

Second Approach

global.cachedDb = null;

export function post(event, context, callback) {
  let response;
  context.callbackWaitsForEmptyEventLoop = false;
  connectToDatabase()
    .then(connection => createUser(event.body, connection))


// lib/dbUtils.js

export async function connectToDatabase() {
  // eslint-disable-next-line
  if (global.cachedDb && global.cachedDb.serverConfig.isConnected()) {
    // eslint-disable-next-line
    console.log(" using cached db instance");
    // eslint-disable-next-line
    return global.cachedDb;
  }
  // eslint-disable-next-line
  global.cachedDb = await mongoose.createConnection(
    process.env.DB_URL,
    async err => {
      if (err) {
        throw err;
      }
    }
  );
  // eslint-disable-next-line
  return global.cachedDb;
}

In both cases the using cached db instance console log does not run.

Why does this not work? Is this because of serverless-offline?

Upvotes: 2

Views: 1984

Answers (1)

mikemaccana
mikemaccana

Reputation: 123680

The answer is simple: serverless-offline doesn't simulate the full AWS. Use the AWS console to to make a real Lambda

The MongoDB Atlas guide is OK, but it's also worth checking the official AWS Lambda documentation describing the context option in each lambda:

callbackWaitsForEmptyEventLoop – Set to false to send the response right away when the callback executes, instead of waiting for the Node.js event loop to be empty. If false, any outstanding events will continue to run during the next invocation.

It's possible to run your code on a real Lambda and see using cached db instance on the console. Since MongoDB's JavaScript code is fairly poor, I've written out my own version below:

var MongoClient = require("mongodb").MongoClient

let db = null

var log = console.log.bind(console)

var print = function(object) {
    return JSON.stringify(object, null, 2)
}

// Use your own credentials (and better yet, put them in environment variables)
const password = `notactuallyapassword`
const uri = `mongodb+srv://lambdauser:${password}@fakedomain.mongodb.net/test?retryWrites=true`

exports.handler = function(event, context, callback) {
    log(`Calling MongoDB Atlas from AWS Lambda with event: ${print(event)}`)
    var document = JSON.parse(JSON.stringify(event))

    const databaseName = "myDatabase",
        collectionName = "documents"

    // See https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs
    // and https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html#nodejs-prog-model-context-properties
    context.callbackWaitsForEmptyEventLoop = false

    return createDoc(databaseName, collectionName, document)
}

async function createDoc(databaseName, collectionName, document) {
    var isConnected = db && db.serverConfig.isConnected()
    if (isConnected) {
        log(`Already connected to database, warm start!`)
    } else {
        log(`Connecting to database (cold start)`)
        var client = await MongoClient.connect(uri)
        db = client.db(databaseName)
    }

    var result = await db.collection(collectionName).insertOne(document)
    log(`just created an entry into the ${collectionName} collection with id: ${result.insertedId}`)
    // Don't close the connection thanks to context.callbackWaitsForEmptyEventLoop = false - this will re-use the connection on the next called (if it can re-use the same Lambda container)
    return result
}

Use the Test button to run the lambda above twice in the AWS Lambda console.

  • The first time you run it you'll see Connecting to database (cold start)

  • The second time you'll see Already connected to database, warm start!

See the log output section in screenshot below:

enter image description here

Upvotes: 2

Related Questions