Photo by Nathan Riley from Unsplash

3 Things I Wish I Knew About AWS Lambda Functions Early On

Andrew Titenko
3 min readMay 24, 2020

So I’ve been building serverless applications for quite a lot during the last year and a half. It’s now time to share a few very important takeaways I’ve learned about the AWS Lambda engine.

Retaining of the execution context

It came as a surprise to me and my team when we realized that two absolutely unrelated executions of the same Lambda function might end up sharing the global execution context. Take a look at this example snippet:

/* my-lambda-function.js */const Logger = require('...');
const logger = new Logger('...');
exports.handler = () => {
// ...
}

You might be doing something similar in your code where you create a global instance of an object (for example a reusable logger instance) that is then passed around or exported for use in other listings.

Since it has no modifiable state you treat it as a global constant and don’t consider it an anti-pattern. And that’s where the problems begin: this is an anti-pattern in the context of the Lambda engine.

When someone comes around and unknowingly updates the Logger class to hold some modifiable state, it infests your code with one of the evilest bugs that you could ever encounter: implicit data mutations. You can’t understand the source of the mutations and spend hours trying to fix it.

It’s not immediately obvious because intuitively you treat each Lambda function as a separate application and, again intuitively, you expect it to start each code execution from a blank slate, with a brand new global context. It’s so intuitive that you don’t even think about it.

Well the truth is that Lambda can actually run the next function invocation request on an existing, running engine image, on the same Node.js process by simply invoking your Lambda function handler method with a new request payload. If you modified your global context in the previous execution, effects of that will propagate into the future executions that occur afterwards.

It’s worth noting that this “request bundling” happens only when two requests occur within a certain time threshold, that in my experience have been about 2–3 seconds.

Abandoned network connections

Another unobvious thing that you can encounter with is unexplainable Lambda function timeouts. This issue is also quite hard to debug simply because you don’t expect this behavior to occur.

When you write your Lambda function code, you use callbacks or Promises to signal the Lambda engine that your function is done doing what it’s been doing and a response can be returned back to the invocation request.

Intuitively you expect that when your handler’s returned Promise resolves or rejects, or when you call a handler callback() function, your function will stop executing and pass back the execution result. But it’s not that simple.

If your Node.js event loop contains something at the time of callback() invocation or Promise resolution Lambda engine won’t act on your signal until the event loop is cleared. Typically things that are found in the event loop once your script flow is done executing are open database connections. If you don’t terminate them and leave them hanging your function will timeout.

You can disable this behavior by setting the callbackWaitsForEmptyEventLoop flag to false on the context object passed to your Lambda function handler.

/* my-lambda-function.js */exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
}

Lambda layers limitations

Another thing I wish I knew earlier is the limitations of Lambda layers. You can attach only 5 layers to each Lambda function and the maximum size of each Lambda function’s bundle is 250mb. That includes the function’s code, dependencies, and all attached layers.

I wrongfully assumed that layer count is unlimited and that function size is not a thing. I jumped straight into it and ended up designing several serverless applications incorrectly which led me to have to redo my work.

So remember these limitations and plan your application architecture ahead, taking this information into consideration. Don’t repeat my mistakes!

If you have found this article valuable please share it! Thank you for reading!

--

--

Andrew Titenko

Writing full-stack React.js applications and building cloud solutions on AWS • Follow me @iamarkadyt