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,495