@cumulus/aws-client 21.0.0 → 21.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/S3.js +23 -6
- package/SNS.js +2 -1
- package/package.json +7 -7
- package/s3-jitter.d.ts +11 -0
- package/s3-jitter.js +29 -0
package/README.md
CHANGED
|
@@ -83,6 +83,10 @@ for allowed params and return value.</p>
|
|
|
83
83
|
<dt><a href="#decryptBase64String">decryptBase64String(ciphertext)</a> ⇒ <code>string</code></dt>
|
|
84
84
|
<dd><p>Decrypt a KMS-encrypted string, Base 64 encoded</p>
|
|
85
85
|
</dd>
|
|
86
|
+
<dt><a href="#applyS3Jitter">applyS3Jitter(maxJitterMs, operation)</a> ⇒</dt>
|
|
87
|
+
<dd><p>Introduces random jitter delay to stagger concurrent S3 operations.
|
|
88
|
+
This helps prevent AWS S3 SlowDown errors when many operations occur simultaneously.</p>
|
|
89
|
+
</dd>
|
|
86
90
|
</dl>
|
|
87
91
|
|
|
88
92
|
<a name="module_CloudFormation"></a>
|
|
@@ -1122,6 +1126,20 @@ Decrypt a KMS-encrypted string, Base 64 encoded
|
|
|
1122
1126
|
| --- | --- | --- |
|
|
1123
1127
|
| ciphertext | <code>string</code> | a KMS-encrypted value, Base 64 encoded |
|
|
1124
1128
|
|
|
1129
|
+
<a name="applyS3Jitter"></a>
|
|
1130
|
+
|
|
1131
|
+
## applyS3Jitter(maxJitterMs, operation) ⇒
|
|
1132
|
+
Introduces random jitter delay to stagger concurrent S3 operations.
|
|
1133
|
+
This helps prevent AWS S3 SlowDown errors when many operations occur simultaneously.
|
|
1134
|
+
|
|
1135
|
+
**Kind**: global function
|
|
1136
|
+
**Returns**: A Promise that resolves after the random delay
|
|
1137
|
+
|
|
1138
|
+
| Param | Description |
|
|
1139
|
+
| --- | --- |
|
|
1140
|
+
| maxJitterMs | Maximum jitter time in milliseconds (0-59000). If 0, no delay is applied. |
|
|
1141
|
+
| operation | Optional operation name for logging context |
|
|
1142
|
+
|
|
1125
1143
|
|
|
1126
1144
|
## About Cumulus
|
|
1127
1145
|
|
package/S3.js
CHANGED
|
@@ -46,10 +46,13 @@ const checksum_1 = require("@cumulus/checksum");
|
|
|
46
46
|
const errors_1 = require("@cumulus/errors");
|
|
47
47
|
const logger_1 = __importDefault(require("@cumulus/logger"));
|
|
48
48
|
const S3MultipartUploads = __importStar(require("./lib/S3MultipartUploads"));
|
|
49
|
+
const s3_jitter_1 = require("./s3-jitter");
|
|
49
50
|
const services_1 = require("./services");
|
|
50
51
|
const test_utils_1 = require("./test-utils");
|
|
51
52
|
const utils_1 = require("./utils");
|
|
52
53
|
const log = new logger_1.default({ sender: 'aws-client/s3' });
|
|
54
|
+
// S3 jitter configuration from environment variable
|
|
55
|
+
const s3JitterMaxMs = Number(process.env.S3_JITTER_MAX_MS || 0);
|
|
53
56
|
const buildDeprecationMessage = (name, version, alternative) => {
|
|
54
57
|
let message = `${name} is deprecated after version ${version} and will be removed in a future release.`;
|
|
55
58
|
if (alternative)
|
|
@@ -148,6 +151,7 @@ exports.deleteS3Objects = deleteS3Objects;
|
|
|
148
151
|
* @returns returns response from `S3.headObject` as a promise
|
|
149
152
|
**/
|
|
150
153
|
const headObject = (Bucket, Key, retryOptions = { retries: 0 }) => (0, p_retry_1.default)(async () => {
|
|
154
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `headObject(${Bucket}/${Key})`);
|
|
151
155
|
try {
|
|
152
156
|
return await (0, services_1.s3)().headObject({ Bucket, Key });
|
|
153
157
|
}
|
|
@@ -212,7 +216,10 @@ exports.waitForObjectToExist = waitForObjectToExist;
|
|
|
212
216
|
*
|
|
213
217
|
* @param params - same params as https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
|
|
214
218
|
**/
|
|
215
|
-
const s3PutObject = (params) =>
|
|
219
|
+
const s3PutObject = async (params) => {
|
|
220
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `putObject(${params.Bucket}/${params.Key})`);
|
|
221
|
+
return (0, services_1.s3)().putObject(params);
|
|
222
|
+
};
|
|
216
223
|
exports.s3PutObject = s3PutObject;
|
|
217
224
|
/**
|
|
218
225
|
* Upload a file to S3
|
|
@@ -227,10 +234,13 @@ exports.putFile = putFile;
|
|
|
227
234
|
/**
|
|
228
235
|
* Copy an object from one location on S3 to another
|
|
229
236
|
**/
|
|
230
|
-
const s3CopyObject = (params) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
const s3CopyObject = async (params) => {
|
|
238
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `copyObject(${params.Bucket}/${params.Key})`);
|
|
239
|
+
return (0, services_1.s3)().copyObject({
|
|
240
|
+
TaggingDirective: 'COPY',
|
|
241
|
+
...params,
|
|
242
|
+
});
|
|
243
|
+
};
|
|
234
244
|
exports.s3CopyObject = s3CopyObject;
|
|
235
245
|
/**
|
|
236
246
|
* Upload data to S3
|
|
@@ -238,6 +248,7 @@ exports.s3CopyObject = s3CopyObject;
|
|
|
238
248
|
* see https://github.com/aws/aws-sdk-js-v3/tree/main/lib/lib-storage
|
|
239
249
|
*/
|
|
240
250
|
const promiseS3Upload = async (params) => {
|
|
251
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `upload(${params.params?.Bucket}/${params.params?.Key})`);
|
|
241
252
|
const parallelUploads = new lib_storage_1.Upload({
|
|
242
253
|
...params,
|
|
243
254
|
client: (0, services_1.s3)(),
|
|
@@ -290,6 +301,7 @@ const downloadS3File = async (s3Obj, filepath) => {
|
|
|
290
301
|
if (!s3Obj.Bucket || !s3Obj.Key) {
|
|
291
302
|
throw new Error('Bucket and Key are required');
|
|
292
303
|
}
|
|
304
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `downloadS3File(${s3Obj.Bucket}/${s3Obj.Key})`);
|
|
293
305
|
const fileWriteStream = fs_1.default.createWriteStream(filepath);
|
|
294
306
|
const objectStream = await (0, exports.getObjectReadStream)({
|
|
295
307
|
bucket: s3Obj.Bucket,
|
|
@@ -309,6 +321,7 @@ exports.downloadS3File = downloadS3File;
|
|
|
309
321
|
*/
|
|
310
322
|
const getObjectSize = async (params) => {
|
|
311
323
|
// eslint-disable-next-line no-shadow
|
|
324
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `getObjectSize(${params.bucket}/${params.key})`);
|
|
312
325
|
const { s3: s3Client, bucket, key } = params;
|
|
313
326
|
const headObjectResponse = await s3Client.headObject({
|
|
314
327
|
Bucket: bucket,
|
|
@@ -348,7 +361,10 @@ exports.s3PutObjectTagging = s3PutObjectTagging;
|
|
|
348
361
|
/**
|
|
349
362
|
* Gets an object from S3.
|
|
350
363
|
*/
|
|
351
|
-
const getObject = (s3Client, params) =>
|
|
364
|
+
const getObject = async (s3Client, params) => {
|
|
365
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `getObject(${params.Bucket}/${params.Key})`);
|
|
366
|
+
return s3Client.getObject(params);
|
|
367
|
+
};
|
|
352
368
|
exports.getObject = getObject;
|
|
353
369
|
/**
|
|
354
370
|
* Get an object from S3, waiting for it to exist and, if specified, have the
|
|
@@ -754,6 +770,7 @@ const uploadPartCopy = async (params) => {
|
|
|
754
770
|
*/
|
|
755
771
|
const multipartCopyObject = async (params) => {
|
|
756
772
|
const { sourceBucket, sourceKey, destinationBucket, destinationKey, ACL, copyTags = false, chunkSize, } = params;
|
|
773
|
+
await (0, s3_jitter_1.applyS3Jitter)(s3JitterMaxMs, `multipartCopyObject(${destinationBucket}/${destinationKey})`);
|
|
757
774
|
const sourceObject = params.sourceObject ?? await (0, exports.headObject)(sourceBucket, sourceKey);
|
|
758
775
|
// Create a multi-part upload (copy) and get its UploadId
|
|
759
776
|
const uploadId = await createMultipartUpload({
|
package/SNS.js
CHANGED
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.createSnsTopic = exports.publishSnsMessageWithRetry = void 0;
|
|
10
10
|
const p_retry_1 = __importDefault(require("p-retry"));
|
|
11
11
|
const logger_1 = __importDefault(require("@cumulus/logger"));
|
|
12
|
+
const errors_1 = require("@cumulus/errors");
|
|
12
13
|
const client_sns_1 = require("@aws-sdk/client-sns");
|
|
13
14
|
const services_1 = require("./services");
|
|
14
15
|
const log = new logger_1.default({ sender: 'aws-client/sns' });
|
|
@@ -33,7 +34,7 @@ const publishSnsMessageWithRetry = async (snsTopicArn, message, retryOptions = {
|
|
|
33
34
|
await (0, services_1.sns)().send(new client_sns_1.PublishCommand(publishInput));
|
|
34
35
|
}, {
|
|
35
36
|
maxTimeout: 5000,
|
|
36
|
-
onFailedAttempt: (err) => log.debug(`publishSnsMessageWithRetry('${snsTopicArn}', '${JSON.stringify(message)}') failed with ${err.retriesLeft} retries left: ${
|
|
37
|
+
onFailedAttempt: (err) => log.debug(`publishSnsMessageWithRetry('${snsTopicArn}', '${JSON.stringify(message)}') failed with ${err.retriesLeft} retries left: ${(0, errors_1.errorify)(err)}`),
|
|
37
38
|
...retryOptions,
|
|
38
39
|
});
|
|
39
40
|
exports.publishSnsMessageWithRetry = publishSnsMessageWithRetry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cumulus/aws-client",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.1.0",
|
|
4
4
|
"description": "Utilities for working with AWS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"GIBS",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"@aws-sdk/s3-request-presigner": "^3.621.0",
|
|
70
70
|
"@aws-sdk/signature-v4-crt": "^3.621.0",
|
|
71
71
|
"@aws-sdk/types": "^3.609.0",
|
|
72
|
-
"@cumulus/checksum": "21.
|
|
73
|
-
"@cumulus/errors": "21.
|
|
74
|
-
"@cumulus/logger": "21.
|
|
75
|
-
"@cumulus/types": "21.
|
|
72
|
+
"@cumulus/checksum": "21.1.0",
|
|
73
|
+
"@cumulus/errors": "21.1.0",
|
|
74
|
+
"@cumulus/logger": "21.1.0",
|
|
75
|
+
"@cumulus/types": "21.1.0",
|
|
76
76
|
"lodash": "~4.17.21",
|
|
77
77
|
"mem": "^8.0.2",
|
|
78
78
|
"p-map": "^1.2.0",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"uuid": "^8.2.0"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@cumulus/test-data": "21.
|
|
86
|
+
"@cumulus/test-data": "21.1.0",
|
|
87
87
|
"@types/uuid": "^8.0.0"
|
|
88
88
|
},
|
|
89
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "5d443a04647ed537903c85b48992d08ce3c3cd1d"
|
|
90
90
|
}
|
package/s3-jitter.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Introduces random jitter delay to stagger concurrent S3 operations.
|
|
3
|
+
* This helps prevent AWS S3 SlowDown errors when many operations occur simultaneously.
|
|
4
|
+
*
|
|
5
|
+
* @param maxJitterMs - Maximum jitter time in milliseconds (0-59000).
|
|
6
|
+
* If 0, no delay is applied.
|
|
7
|
+
* @param operation - Optional operation name for logging context
|
|
8
|
+
* @returns A Promise that resolves after the random delay
|
|
9
|
+
*/
|
|
10
|
+
export declare const applyS3Jitter: (maxJitterMs: number, operation?: string) => Promise<void>;
|
|
11
|
+
//# sourceMappingURL=s3-jitter.d.ts.map
|
package/s3-jitter.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyS3Jitter = void 0;
|
|
7
|
+
const logger_1 = __importDefault(require("@cumulus/logger"));
|
|
8
|
+
const log = new logger_1.default({ sender: 's3-jitter' });
|
|
9
|
+
/**
|
|
10
|
+
* Introduces random jitter delay to stagger concurrent S3 operations.
|
|
11
|
+
* This helps prevent AWS S3 SlowDown errors when many operations occur simultaneously.
|
|
12
|
+
*
|
|
13
|
+
* @param maxJitterMs - Maximum jitter time in milliseconds (0-59000).
|
|
14
|
+
* If 0, no delay is applied.
|
|
15
|
+
* @param operation - Optional operation name for logging context
|
|
16
|
+
* @returns A Promise that resolves after the random delay
|
|
17
|
+
*/
|
|
18
|
+
const applyS3Jitter = async (maxJitterMs, operation) => {
|
|
19
|
+
if (maxJitterMs <= 0) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const jitterMs = Math.ceil(Math.random() * maxJitterMs);
|
|
23
|
+
const logContext = operation ? ` for ${operation}` : '';
|
|
24
|
+
log.info(`Applying S3 jitter: ${jitterMs}ms${logContext} (max: ${maxJitterMs}ms)`);
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, jitterMs));
|
|
26
|
+
log.debug(`S3 jitter delay completed: ${jitterMs}ms${logContext}`);
|
|
27
|
+
};
|
|
28
|
+
exports.applyS3Jitter = applyS3Jitter;
|
|
29
|
+
//# sourceMappingURL=s3-jitter.js.map
|