Make your applications faster by using

JavaScript SDK v3

Thank you

#BellevueJS

#AWS

#JavaScriptSDK

Trivikram Kamat

SDE, AWS JavaScript SDK team

@trivikram

@trivikr

What will we learn today?

  • What is AWS SDK for JavaScript

  • What's new in JS SDK v3

  • Examine a note taking application written in JS SDK v2

  • Improve it's performance while moving it to JS SDK v3

What is AWS SDK for JavaScript?

  • JavaScript API for AWS services

  • Helps you build libraries or apps in:

    • modern browsers

    • Node.js

    • Electron

    • mobile (ReactNative)

What's new in JS SDK v3?

  • Modularized packages

  • Better stack traces

  • Types from TypeScript

  • Middleware

Example application

Amazon DynamoDB

Is a key-value and document database that delivers single-digit millisecond performance at any scale

scan,

putItem,

getItem,

deleteItem,

updateItem

Amazon S3

Is an object storage service that offers industry-leading scalability, data availability, security, and performance.

putObject,

getObject,

deleteObject

If you want to build and play with this application on your machine, visit

on GitHub

Instructions are in README

Note

I've written some CloudFormation templates to build and deploy AWS resources which you don't need to know or worry about in this talk

import dynamoDB from "./libs/dynamoDB";
import { success, failure } from "./libs/response";

export async function main() {
  const params = {
    TableName: process.env.tableName
  };

  try {
    const result = await dynamoDB.scan(params).promise();
    // Return the matching list of items in response body
    return success(result.Items);
  } catch (e) {
    return failure({ status: false });
  }
}

list.ts

import AWS from "aws-sdk";

export default new AWS.DynamoDB();

libs/dynamoDB.ts

Lambda sizes on deployment

What if you want to reduce your package size in SDK v2 itself?

Just import the client instead of entire SDK!

import AWS from "aws-sdk";

export default new AWS.DynamoDB();

libs/dynamoDB.ts

import DynamoDB from "aws-sdk/clients/dynamodb";

export default new DynamoDB();

Lambda sizes on deployment

before:

after:

Hey Trivikram, we can reduce the bundle size in SDK v2 itself. What's so special with v3?

Let us see by trying v3!

import DynamoDB from "aws-sdk/clients/dynamodb";

export default new DynamoDB();

libs/dynamoDB.ts

import { DynamoDB } from "@aws-sdk/client-dynamodb-node";

export default new DynamoDB({});
import dynamoDB from "./libs/dynamoDB";
import { success, failure } from "./libs/response";

export async function main() {
  const params = {
    TableName: process.env.tableName
  };

  try {
    const result = await dynamoDB.scan(params).promise();
    // Return the matching list of items in response body
    return success(result.Items);
  } catch (e) {
    return failure({ status: false });
  }
}

list.ts

import dynamoDB from "./libs/dynamoDB";
import { success, failure } from "./libs/response";

export async function main() {
  const params = {
    TableName: process.env.tableName
  };

  try {
    const result = await dynamoDB.scan(params);
    // Return the matching list of items in response body
    return success(result.Items);
  } catch (e) {
    return failure({ status: false });
  }
}

Lambda sizes on deployment

before:

after:

That was so easy!

Yes, we can!

By just importing the client and the specific command required 😉

Can we reduce size further?

import { DynamoDB } from "@aws-sdk/client-dynamodb-node";

export default new DynamoDB({});

libs/dynamoDB.ts

import { DynamoDBClient } from "@aws-sdk/client-dynamodb-node";

export default new DynamoDBClient({});

list.ts

import dynamoDB from "./libs/dynamoDB";
import { success, failure } from "./libs/response";

export async function main() {
  const params = {
    TableName: process.env.tableName
  };

  try {
    const result = await dynamoDB.scan(params);
    // Return the matching list of items in response body
    return success(result.Items);
  } catch (e) {
    return failure({ status: false });
  }
}
import dynamoDB from "./libs/dynamoDB";
import { success, failure } from "./libs/response";
import { ScanCommand } from "@aws-sdk/client-dynamodb-node";

export async function main() {
  const params = {
    TableName: process.env.tableName
  };

  try {
    const result = await dynamoDBClient.send(new ScanCommand(params));
    // Return the matching list of items in response body
    return success(result.Items);
  } catch (e) {
    return failure({ status: false });
  }
}

Lambda sizes on deployment

before:

after:

Size reduction is good, but does it actually reflect with performance?

Let us measure lambda execution time!

How do we do it?

  • We get metrics by enabling Active tracing under AWS X-Ray for ListNotes lambda

  • The comparison is between original bundle which imports entire v2 (size ~470KB) with final one which imports client+command in v3 (size ~18KB)

cold start

entire v2 import

v3 client+command import

warm start

entire v2 import

v3 client+command import

Hey, these metrics are just for one invocation

How can we trust them?

You're right! Let's write a Cloudwatch event to analyze these metrics 

Note

  • cold start: A Cloudwatch event was written to trigger both lambdas every 20 mins and 18 values (in ms) for Duration of AWS::Lambda::Function were analyzed over 6 hours

  • warm start: Cloudwatch event was written to trigger both lambdas every minute and 50 values (in ms) for Duration of AWS::Lambda::Function were analyzed

Metrics from the experiment

cold start

warm start

Learnings

  • SDK v3 client+command import reduces size significantly in lambda!

  • In our example, we reduced lambda size from 470kB which import entire v2 SDK to 18kB which imports v3 SDK+command

  • Reduction in size is helpful for lambda cold start (duration reduces by ~40%), but not so much for warm starts (duration reduces only ~1%)

Can we reduce bundle size with SDK v3 in the frontend too?

Yes, we can! 😊

I'm glad you asked!

import { s3Client } from "./s3Client";
import { config } from "../config";

const deleteObject = async (fileName: string) =>
  s3Client
    .deleteObject({
      Key: fileName,
      Bucket: config.s3Bucket
    })
    .promise();

export { deleteObject };

deleteObject.ts

import AWS from "aws-sdk";
import { config } from "../config";

const s3Client = new AWS.S3({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
});

export { s3Client };

libs/s3Client.ts

File sizes:
  344.77 KB  /2.0191bd07.chunk.js
  2.94 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

production build

What happens when you just import the client in v2 SDK?

import AWS from "aws-sdk";
import { config } from "../config";

const s3Client = new AWS.S3({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
});

export { s3Client };

libs/s3Client.ts

import AWS from "aws-sdk/global";
import s3 from "aws-sdk/clients/s3";
import { config } from "../config";

const s3Client = new s3({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
});

export { s3Client };

File sizes before:
  344.77 KB  /2.0191bd07.chunk.js
  2.94 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

production build

File sizes after:
  134.24 KB  /2.0191bd07.chunk.js
  2.95 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

Let's try SDK v3 to reduce the size further?

libs/s3Client.ts

import AWS from "aws-sdk/global";
import s3 from "aws-sdk/clients/s3";
import { config } from "../config";

const s3Client = new s3({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
});

export { s3Client };
import { S3 } from "@aws-sdk/client-s3-browser";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity-browser";
import { config } from "../config";

const s3Client = new S3({
  region: "us-west-2",
  credentials: fromCognitoIdentityPool({
    client: cognitoIdentityClient,
    identityPoolId: config.IdentityPoolId
  })
});

const cognitoIdentityClient = new CognitoIdentityClient({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
  credentials: () => Promise.resolve({} as any),
  signer: {} as any //this is required
});
cognitoIdentityClient.middlewareStack.remove("SIGNATURE");
import { s3Client } from "./s3Client";
import { config } from "../config";

const deleteObject = async (fileName: string) =>
  s3Client
    .deleteObject({
      Key: fileName,
      Bucket: config.s3Bucket
    })
    .promise();

export { deleteObject };

deleteObject.ts

import { s3Client } from "./s3Client";
import { config } from "../config";

const deleteObject = async (fileName: string) =>
  s3Client
    .deleteObject({
      Key: fileName,
      Bucket: config.s3Bucket
    });

export { deleteObject };

File sizes before:
  134.24 KB  /2.0191bd07.chunk.js
  2.94 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

production build

File sizes after:
  106.81 KB  /2.0191bd07.chunk.js
  2.95 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

How about just SDK v3 client+command?

libs/s3Client.ts

import { S3 } from "@aws-sdk/client-s3-browser";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity-browser/CognitoIdentityClient";
import { config } from "../config";

const s3Client = new S3({
  region: "us-west-2",
  credentials: fromCognitoIdentityPool({
    client: cognitoIdentityClient,
    identityPoolId: config.IdentityPoolId
  })
});

const cognitoIdentityClient = new CognitoIdentityClient({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
  credentials: () => Promise.resolve({} as any),
  signer: {} as any //this is required
});
cognitoIdentityClient.middlewareStack.remove("SIGNATURE");
import { S3Client } from "@aws-sdk/client-s3-browser";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity-browser";
import { config } from "../config";

const s3Client = new S3Client({
  region: "us-west-2",
  credentials: fromCognitoIdentityPool({
    client: cognitoIdentityClient,
    identityPoolId: config.IdentityPoolId
  })
});

const cognitoIdentityClient = new CognitoIdentityClient({
  region: "us-west-2",
  credentials: new AWS.CognitoIdentityCredentials(
    {
      IdentityPoolId: config.IdentityPoolId
    },
    {
      region: "us-west-2"
    }
  )
  credentials: () => Promise.resolve({} as any),
  signer: {} as any //this is required
});
cognitoIdentityClient.middlewareStack.remove("SIGNATURE");

deleteObject.ts

import { s3Client } from "./s3Client";
import { config } from "../config";

const deleteObject = async (fileName: string) =>
  s3Client
    .deleteObject({
      Key: fileName,
      Bucket: config.s3Bucket
    });

export { deleteObject };
import { DeleteObjectCommand } from "@aws-sdk/client-s3-browser";
import { s3Client } from "./s3Client";
import { config } from "../config";

const deleteObject = async (fileName: string) =>
  s3Client.send(
    new DeleteObjectCommand({
      Key: fileName,
      Bucket: config.s3Bucket
    })
  );

export { deleteObject };

File sizes before:
  106.81 KB  /2.0191bd07.chunk.js
  2.94 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

production build

File sizes after:
  90.07 KB  /2.0191bd07.chunk.js
  2.95 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

Hey Trivikram, now we're importing commands separately. Can we dynamically import dependencies?

Yes, we can! 😊

I'm glad you asked!

Routes.tsx

import React from "react";
import { Router } from "@reach/router";
import {
  ListNotes,
  CreateNote,
  ShowNote,
  NotFound
} from "./content";

const Routes = () => (
  <Router>
    <ListNotes path="/" />
    <CreateNote path="/note/new" />
    <ShowNote path="/notes/:noteId" />
    <NotFound default />
  </Router>
);

export { Routes };
import React, { lazy, Suspense } from "react";
import { Router } from "@reach/router";

const ListNotes = lazy(() => import("./content/ListNotes"));
const CreateNote = lazy(() => import("./content/CreateNote"));
const ShowNote = lazy(() => import("./content/ShowNote"));
const NotFound = lazy(() => import("./content/NotFound"));

const Routes = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <Router>
      <ListNotes path="/" />
      <CreateNote path="/note/new" />
      <ShowNote path="/notes/:noteId" />
      <NotFound default />
    </Router>
  </Suspense>
);

export { Routes };

CreateNote.tsx

export { CreateNote };
export default CreateNote;

React.lazy doesn't support named exports

production build

File sizes before:
  90.07 KB  /2.0191bd07.chunk.js
  2.95 KB    /main.f878258f.chunk.js
  790 B      /runtime~main.e82a7b61.js

File sizes after:

  45.72 KB  /4.76a075be.chunk.js
  35.33 KB  /1.9ecab0dd.chunk.js
  6.41 KB   /0.214c92a2.chunk.js
  5.78 KB   /5.d006f9d4.chunk.js
  2.56 KB   /6.9bdcbc25.chunk.js
  2.21 KB   /7.e5abe528.chunk.js
  ... smaller chunks

Does size reduction for frontend bundle reflect with improved performance?

Of course!

Let us see it live! 😊

What's next?

Try it out on your own to believe it!

on GitHub

If you have any questions/suggestions, do cut an issue on GitHub!

Thank you for listening!

Trivikram Kamat

@trivikram

@trivikr

SDE@

Make your applications faster by using modularized AWS JavaScript SDK

By Trivikram Kamat

Make your applications faster by using modularized AWS JavaScript SDK

Slides for BellevueJS meetup talk in September 2019 https://www.meetup.com/BellevueJS/events/llvkklyzmbgc/

  • 1,480