Reduce Existing Javascript Lambda Package by 49%? Yes please!

Reduce Existing Javascript Lambda Package by 49%? Yes please!

Photo by Markus Spiske on Unsplash

I was reading about the new aws-sdk for Javascript version 3 (aws-sdk-js-v3), and part of the advancement is the modularization of the SDK to reduce the resulting deployment package size — and by extension cold-start time.

You can read about the new v3 SDK, including the modular architecture, here.

What if you are working on a project though that is stabilizing for release, or you just aren’t ready to make the jump to v3 yet? That’s where I was, but out of curiosity I read the migration guide and learned that the first step is to convert the existing aws-sdk-js v2 imports away from things like:

import AWS from “aws-sdk”;
const dynamoDB = new AWS.DynamoDB();

into:

import DynamoDB from "aws-sdk/clients/dynamodb";
const dynamoDB = new DynamoDB();

I’m already using Webpack and Tree Shaking!

This would supposedly reduce the amount of code imported, even with v2. So maybe I could make this simple change, preparing the code base for v3 later and safely improve performance now? I wasn’t sure how much such a change would help, since the project was already tree-shaking with webpack. I was aware though that webpack doesn’t work well on aws-sdk-js… so maybe?

To try it out, I first added a new rule to tslint.json (I’m using Typescript):

"import-blacklist": [true, "aws-sdk"]

With that, tslint highlights all the places that need to be changed and bans anyone from unwittingly doing a top-level import later and undoing any benefits. Cool. 😎

Because the code base uses hexagonal architecture, tslint only found twelve files that needed to be updated. 👍 I made a few changes such as:

- import {ApiGatewayManagementApi} from "aws-sdk";

+ import ApiGatewayManagementApi from "aws-sdk/clients/apigatewaymanagementapi";

and

- import * as AWS from 'aws-sdk';

+ import Iot from 'aws-sdk/clients/iot';
+ import IotData from 'aws-sdk/clients/iotdata';
+ import {AWSError} from 'aws-sdk/lib/error';

A quick deploy and into the AWS Console I go to look in the stack deployment buckets in AWS S3 and compare the new and previous packages. What I found sealed the deal! Here area few of the results:

╔════════════╦═══════════════╦══════════╦═══════════╗
║ Stack Name ║ Previous Size ║ New Size ║ Reduction ║
╠════════════╬═══════════════╬══════════╬═══════════╣
║ ingest ║ 9.0 MB ║ 7.4 MB ║ 18% ║
║ provision ║ 4.2 MB ║ 1.8 MB ║ 57% ║
║ device-api ║ 14.3 MB ║ 6.5 MB ║ 55% ║
║ general-api║ 1.4 MB ║ 0.6 MB ║ 57% ║
║ account-api║ 5.6 MB ║ 2.4 MB ║ 57% ║
╚════════════╩═══════════════╩══════════╩═══════════╝

Overall average reduction in size: 49%

It was an easy change that took mere minutes to forever speed up cold starts.

Yes, bundle your AWS SDK!

One last note, as I’m sure someone will point out that you can reduce sizes even more by using the SDK already within the Lambda execution environment instead of bundling it with your code.

Don’t fall for that trap!

That goes against Best practices for working with AWS Lambda functions, which includes controlling your dependencies. The SDK in the execution environment is there for quick little functions with no other dependencies — often made from within the AWS Console. Relying on this ever-changing version of the SDK in your production environment means that you never know when your system will change from under you and mysteriously break. It isn’t worth it! Bundle your dependencies, but only what you need.