Skip to content

Commit 059223d

Browse files
authored
docs: create new Node.js performance docs section and clean up upgrading docs (#6466)
* docs: create new Node.js performance docs section and clean up upgrading docs * docs: create lambda -> Node.js performance doc cross-link
1 parent 6a539fe commit 059223d

File tree

6 files changed

+264
-14
lines changed

6 files changed

+264
-14
lines changed

UPGRADING.md

+24-7
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ This list is indexed by [v2 config parameters](https://docs.aws.amazon.com/AWSJa
5454
configure them by supplying a new `requestHandler`. Here's the example of setting http options in Node.js runtime. You
5555
can find more in [v3 reference for NodeHttpHandler](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-smithy-node-http-handler/).
5656

57-
All v3 requests use HTTPS by default. You only need to provide custom httpsAgent.
57+
All v3 requests use HTTPS by default. You can provide a custom agent via the `httpsAgent`
58+
field of the `NodeHttpHandler` constructor input.
5859

5960
```javascript
6061
const { Agent } = require("https");
61-
const { Agent: HttpAgent } = require("http");
6262
const { NodeHttpHandler } = require("@smithy/node-http-handler");
63+
6364
const dynamodbClient = new DynamoDBClient({
6465
requestHandler: new NodeHttpHandler({
6566
httpsAgent: new Agent({
@@ -71,19 +72,19 @@ This list is indexed by [v2 config parameters](https://docs.aws.amazon.com/AWSJa
7172
});
7273
```
7374

74-
If you are passing custom endpoint which uses http, then you need to provide httpAgent.
75+
If you are using a custom endpoint which uses http, then you can provide an `httpAgent`.
7576

7677
```javascript
7778
const { Agent } = require("http");
7879
const { NodeHttpHandler } = require("@smithy/node-http-handler");
7980

8081
const dynamodbClient = new DynamoDBClient({
82+
endpoint: "http://example.com",
8183
requestHandler: new NodeHttpHandler({
8284
httpAgent: new Agent({
8385
/*params*/
8486
}),
8587
}),
86-
endpoint: "http://example.com",
8788
});
8889
```
8990

@@ -92,6 +93,7 @@ This list is indexed by [v2 config parameters](https://docs.aws.amazon.com/AWSJa
9293

9394
```javascript
9495
const { FetchHttpHandler } = require("@smithy/fetch-http-handler");
96+
9597
const dynamodbClient = new DynamoDBClient({
9698
requestHandler: new FetchHttpHandler({
9799
requestTimeout: /*number in milliseconds*/
@@ -121,14 +123,16 @@ This list is indexed by [v2 config parameters](https://docs.aws.amazon.com/AWSJa
121123
- **v3**: **Deprecated**. Requests are _always_ asynchronous.
122124
- `xhrWithCredentials`
123125
- **v2**: Sets the "withCredentials" property of an XMLHttpRequest object.
124-
- **v3**: Not available. SDK inherits [the default fetch configurations](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
126+
- **v3**: the `fetch` equivalent field `credentials` can be set via constructor
127+
configuration to the `requestHandler` config when using the browser
128+
default `FetchHttpHandler`.
125129

126130
- [`logger`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#logger-property)
127131
- **v2**: An object that responds to .write() (like a stream) or .log() (like the console object) in order to log information about requests.
128132
- **v3**: No change. More granular logs are available in v3.
129133
- [`maxRedirects`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#maxRedirects-property)
130134
- **v2**: The maximum amount of redirects to follow for a service request.
131-
- **v3**: **Deprecated**. SDK _does not_ follow redirects to avoid unintentional cross-region requests.
135+
- **v3**: **Deprecated**. SDK _does not_ follow redirects to avoid unintentional cross-region requests. S3 region redirects can be enabled separately with `followRegionRedirects=true` in the S3 Client only.
132136
- [`maxRetries`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#maxRetries-property)
133137
- **v2**: The maximum amount of retries to perform for a service request.
134138
- **v3**: Changed to `maxAttempts`. See more in [v3 reference for RetryInputConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-smithy-middleware-retry/#maxattempts).
@@ -179,6 +183,19 @@ This list is indexed by [v2 config parameters](https://docs.aws.amazon.com/AWSJa
179183
- **v2**: Whether to use the Accelerate endpoint with the S3 service.
180184
- **v3**: No change.
181185

186+
## Error handling
187+
188+
Top level fields such as `error.code` and http response metadata like the
189+
status code have slightly moved locations within the thrown error object
190+
to subfields like `error.$metadata` or `error.$response`.
191+
192+
This is because v3 more accurately follows the service models and avoids
193+
adding metadata at the top level of the error object, which may conflict
194+
with the structural error shape modeled by the services.
195+
196+
See how error handling has changed in v3
197+
here: [ERROR_HANDLING](./supplemental-docs/ERROR_HANDLING.md).
198+
182199
## Credential Providers
183200

184201
In v2, the SDK provides a list of credential providers to choose from, as well as a credentials provider chain,
@@ -348,7 +365,7 @@ variable.
348365

349366
### File System Credentials
350367

351-
- **v2**: [`FileSystemCredentials`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/FileSystemCredentials.html)
368+
- **v2**: [`FileSystemCredentials`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/FileSystemCredentials.html)
352369
represents credentials from a JSON file on disk.
353370
- **v3**: **Deprecated**. You can explicitly read the JSON file and supply to the client. Please open a
354371
[feature request](https://github.com/aws/aws-sdk-js-v3/issues/new?assignees=&labels=feature-request&template=---feature-request.md&title=)

supplemental-docs/AWS_LAMBDA.md

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
## AWS Lambda provided AWS SDK
44

5-
Several AWS Lambda runtimes, including those for Node.js, include the AWS SDK at various versions.
5+
Several AWS Lambda runtimes, including those for Node.js, include the AWS SDK at various versions.
66

7-
The SDK is provided as a convenience for development. For greater control of the SDK version and its runtime characteristics such as
7+
The SDK is provided as a convenience for development. For greater control of the SDK version and its runtime characteristics such as
88
JavaScript bundling, upload your selection of the AWS SDK as part of your function code.
99

1010
To check the version of the SDK that is installed, you can log the package.json metadata of a package that you are using.
@@ -16,13 +16,13 @@ const pkgJson = require("@aws-sdk/client-s3/package.json");
1616
exports.handler = function (event) {
1717
console.log(pkgJson);
1818
return JSON.stringify(pkgJson);
19-
}
19+
};
2020
```
2121

2222
## Best practices for initializing AWS SDK Clients in AWS Lambda
2323

24-
Suppose that you have an `async` function called, for example `prepare`, that you need to initialize only once.
25-
You do not want to execute it for every function invocation.
24+
Suppose that you have an `async` function called, for example `prepare`, that you need to initialize only once.
25+
You do not want to execute it for every function invocation.
2626

2727
```js
2828
// Example: one-time initialization in the handler code path.
@@ -51,7 +51,7 @@ export async function handler(event) {
5151
}
5252
```
5353

54-
There is a potential complication with this style. This is a peculiarity of AWS Lambda's cold/warm states and provisioned concurrency.
54+
There is a potential complication with this style. This is a peculiarity of AWS Lambda's cold/warm states and provisioned concurrency.
5555
If you make network requests in the `prepare()` function, they may be frozen pre-flight as part of early provisioning. In a certain
5656
edge case, time-sensitive signed requests may become invalid due to the delay between provisioning and execution.
5757

@@ -65,7 +65,8 @@ let ready = false;
6565

6666
export async function handler(event) {
6767
if (!ready) {
68-
await prepare(); ready = true;
68+
await prepare();
69+
ready = true;
6970
}
7071
// ...
7172
}
@@ -94,3 +95,9 @@ export async function handler(event) {
9495
});
9596
}
9697
```
98+
99+
## Parallel request workloads with the AWS SDK on AWS Lambda
100+
101+
See also the section about parallel workloads in Node.js, which is
102+
applicable to AWS Lambda:
103+
[Performance/Parallel Workloads in Node.js](./performance//parallel-workloads-node-js.md).

supplemental-docs/CLIENTS.md

+39
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,45 @@ client.middlewareStack.add(
533533
await client.listBuckets({});
534534
```
535535
536+
### Middleware Caching `cacheMiddleware`.
537+
538+
> Available only in [v3.649.0](https://github.com/aws/aws-sdk-js-v3/releases/tag/v3.649.0) and later.
539+
540+
By default (false), the middleware function stack is resolved every request,
541+
because the user may modify the middleware stack by adding middleware to the
542+
`client` or `command` instances at any time.
543+
544+
By contrast, when `cacheMiddleware=true`, the creation of the middleware function stack
545+
is cached on a per-client, per-command-class basis.
546+
547+
In the following example, the S3 HeadObject Command is called 10 times, but
548+
its middleware function stack is only created once, instead of once per request.
549+
550+
```ts
551+
// example: middleware caching
552+
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
553+
554+
const client = new S3Client({ cacheMiddleware: true });
555+
556+
for (let i = 0; i < 10; ++i) {
557+
await client.send(
558+
new HeadObjectCommand({
559+
Bucket: "...",
560+
Key: String(i),
561+
})
562+
);
563+
}
564+
```
565+
566+
This caches the combination of `S3Client+HeadObjectCommand`'s resolved
567+
`middlewareStack` upon the first request. This has two key effects:
568+
569+
- request creation time is reduced by (up to) a few milliseconds per request
570+
- modifying the middleware stack after requests have begun will have no effect.
571+
572+
**Only enable this feature if you need the marginal increaese to
573+
request performance, and are aware of its side-effects.**
574+
536575
### Dual-stack `useDualstackEndpoint`
537576
538577
This is a simple `boolean` setting that is present in most SDK Clients.

supplemental-docs/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Upgrading from AWS SDK for JavaScript (v2) (https://github.com/aws/aws-sdk-js).
1414

1515
Best practices for working within AWS Lambda using the AWS SDK for JavaScript (v3).
1616

17+
#### [Performance](./performance/README.md)
18+
19+
Details what steps the AWS SDK team has taken to optimize performance of the SDK,
20+
and includes tips for configuring the SDK to run efficiently.
21+
1722
#### [TypeScript](./TYPESCRIPT.md)
1823

1924
TypeScript tips & FAQ related to this project.

supplemental-docs/performance/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Topics:
1414
- [Bundle Sizes](./bundle-sizes.md)
1515
- [Dynamic Imports](./dynamic-imports.md)
1616
- [Dependency File Count Reduction](./dependency-file-count-reduction.md)
17+
- [Parallel workloads in Node.js](./parallel-workloads-node-js.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Performance > Parallel workloads in Node.js
2+
3+
Other sections such as bundle sizing, dependency count, and dynamic imports
4+
cover aspects of performance related to the initial startup of your application.
5+
6+
This section focuses on post-startup performance of request throughput. Specifically,
7+
we cover performance configuration of the AWS SDK for JavaScript (v3)
8+
in Node.js using HTTP/1.1 and the `node:https` module via the SDK's requestHandler
9+
dependency, `@smithy/node-http-handler`.
10+
11+
## What is a parallel workload?
12+
13+
A parallel workload is any time you make more than one request
14+
before the first request has completed.
15+
16+
In single-threaded JavaScript, this is accomplished via the asynchronicity of `Promise`s.
17+
18+
## Configuration options related to throughput
19+
20+
Here is an example containing SDK Client configuration options that have
21+
an effect on request throughput.
22+
23+
```ts
24+
// example: configuring an SDK client for throughput.
25+
import { S3 } from "@aws-sdk/client-s3";
26+
import { NodeHttpHandler } from "@smithy/node-http-handler";
27+
import { Agent } from "node:https";
28+
29+
const s3 = new S3({
30+
/**
31+
* Default is false. Setting this to true caches
32+
* middleware resolution and prevents modifications
33+
* to the middlewareStack from taking effect.
34+
*
35+
* Use only if you are not adding custom middleware.
36+
*/
37+
cacheMiddleware: true,
38+
requestHandler: new NodeHttpHandler({
39+
httpsAgent: new Agent({
40+
/**
41+
* Default is true. This should be left as true
42+
* generally speaking, unless you have very specific
43+
* use-case needing the alternative.
44+
*/
45+
keepAlive: true,
46+
/**
47+
* See expanded note below about sockets.
48+
* You should use a number that is the size
49+
* of your parallel workload batch.
50+
*/
51+
maxSockets: 50,
52+
}),
53+
}),
54+
});
55+
56+
// shorthand syntax available since v3.521.0
57+
const client = new S3({
58+
requestHandler: {
59+
requestTimeout: 3_000,
60+
httpsAgent: { maxSockets: 50 },
61+
},
62+
});
63+
```
64+
65+
## Client instances
66+
67+
In this SDK, much functionality is cached for performance reasons, but
68+
the cache is usually associated with the client instance. In particular,
69+
the following are cached on the client instance:
70+
71+
- credentials fetched by async function calls
72+
- if your client is configured to source credentials from a provider that includes
73+
a network request and/or file-system read, this work is done once per client until
74+
expiration of the credentials. If you instantiate a new client for every request,
75+
this will slow things down substantially.
76+
- middleware function stack when `cacheMiddleware=true`
77+
- `node:https` Agent and its socket pool
78+
79+
If you do need multiple instances of an SDK client, but don't want to
80+
have separate credentials and socket pools, you can share
81+
credentials and requestHandlers between clients.
82+
83+
```ts
84+
// example: credential and socket pool sharing from primary client.
85+
import { S3 } from "@aws-sdk/client-s3";
86+
87+
const s3_east = new S3({ region: "us-east-1" });
88+
89+
const { credentials, requestHandler } = s3_east.config;
90+
91+
const s3_west = new S3({
92+
region: "us-west-2",
93+
credentials,
94+
requestHandler,
95+
});
96+
```
97+
98+
```ts
99+
// example: credential and socket pool sharing from user instantiated objects.
100+
import { S3 } from "@aws-sdk/client-s3";
101+
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
102+
import { NodeHttpHandler } from "@smithy/node-http-handler";
103+
104+
const credentials = fromNodeProviderChain();
105+
const requestHandler = new NodeHttpHandler({
106+
httpsAgent: {
107+
maxSockets: 100,
108+
},
109+
});
110+
111+
const s3_east = new S3({ region: "us-east-1", credentials, requestHandler });
112+
const s3_west = new S3({ region: "us-west-2", credentials, requestHandler });
113+
```
114+
115+
## Node.js Sockets
116+
117+
The `node:https` Agent class manages sockets on your behalf. The most impactful configuration you can make for parallel workloads is to set
118+
the value of `maxSockets`.
119+
120+
Configuring the `maxSockets` value for the SDK's requestHandler should
121+
be based on the parallelism or parallel workload batch size of your application
122+
and usage scenario.
123+
124+
- Configuring too few sockets leads to a slowdown as this is equivalent to
125+
setting a lower cap on the parallel workload batch size.
126+
- Configuring too many sockets can _also_ slow down your application. This is
127+
because the application may open a new socket, which takes some CPU time, when
128+
an existing socket was about to become free for reuse.
129+
- configuring too many sockets can cause you to hit the file descriptor limit of the
130+
operating system. This can manifest as `Error: EMFILE, too many open files`
131+
in Node.js.
132+
133+
## Example Scenario
134+
135+
You have 10,000 files to upload to S3.
136+
137+
- Uploading one at a time is too slow.
138+
- Uploading all at once risks crashing your application process, or
139+
being throttled by the server.
140+
141+
#### Recommendataion
142+
143+
Test your application to determine the right level of parallel request traffic.
144+
After that, configure the `maxSockets` value to be equal to the batch size, or
145+
a factor of it.
146+
147+
```ts
148+
// example: workload of 10,000 files, batch size of 100.
149+
import { S3 } from "@aws-sdk/client-s3";
150+
151+
const files = [
152+
/*... */
153+
];
154+
const BATCH_SIZE = 100;
155+
156+
const s3 = new S3({
157+
requestHandler: {
158+
httpsAgent: { maxSockets: 100 },
159+
},
160+
});
161+
162+
const promises = [];
163+
while (files.length) {
164+
promises.push(
165+
...files.slice(0, BATCH_SIZE).map((file) => {
166+
return s3.putObject({
167+
Bucket: "...",
168+
Key: file.name,
169+
Body: file.contents,
170+
});
171+
})
172+
);
173+
await Promise.all(promises);
174+
promises.length = 0;
175+
}
176+
```
177+
178+
In this example we've adhered to the best practices mentioned in this section:
179+
180+
- use one client instance for repeated requests
181+
- set a `maxSockets` value that is a factor of the batch size

0 commit comments

Comments
 (0)