@terascope/elasticsearch-api 3.4.0 → 3.5.1
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/index.js +36 -41
- package/package.json +3 -3
- package/test/api-spec.js +2 -2
- package/test/{bulk-send-dlq.js → bulk-send-dlq-spec.js} +32 -17
- package/types/index.d.ts +2 -2
package/index.js
CHANGED
|
@@ -278,7 +278,6 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
278
278
|
*/
|
|
279
279
|
function _filterRetryRecords(actionRecords, result) {
|
|
280
280
|
const retry = [];
|
|
281
|
-
const deadLetter = [];
|
|
282
281
|
const { items } = result;
|
|
283
282
|
|
|
284
283
|
let nonRetriableError = false;
|
|
@@ -312,9 +311,10 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
312
311
|
reason = `${item.error.type}--${item.error.reason}`;
|
|
313
312
|
|
|
314
313
|
if (config._dead_letter_action === 'kafka_dead_letter') {
|
|
315
|
-
|
|
314
|
+
actionRecords[i].data.setMetadata('_bulk_sender_rejection', reason);
|
|
316
315
|
continue;
|
|
317
316
|
}
|
|
317
|
+
|
|
318
318
|
break;
|
|
319
319
|
}
|
|
320
320
|
} else if (item.status == null || item.status < 400) {
|
|
@@ -323,8 +323,11 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
if (nonRetriableError) {
|
|
326
|
+
// if dlq active still attempt the retries
|
|
327
|
+
const retryOnError = config._dead_letter_action === 'kafka_dead_letter' ? retry : [];
|
|
328
|
+
|
|
326
329
|
return {
|
|
327
|
-
retry:
|
|
330
|
+
retry: retryOnError, successful, error: true, reason
|
|
328
331
|
};
|
|
329
332
|
}
|
|
330
333
|
|
|
@@ -337,7 +340,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
337
340
|
|
|
338
341
|
/**
|
|
339
342
|
* @param data {Array<{ action: data }>}
|
|
340
|
-
* @returns {Promise<
|
|
343
|
+
* @returns {Promise<number>}
|
|
341
344
|
*/
|
|
342
345
|
async function _bulkSend(actionRecords, previousCount = 0, previousRetryDelay = 0) {
|
|
343
346
|
const body = actionRecords.flatMap((record, index) => {
|
|
@@ -349,7 +352,18 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
349
352
|
throw new Error(`Bulk send record is missing the action property${dbg}`);
|
|
350
353
|
}
|
|
351
354
|
|
|
352
|
-
if (!isElasticsearch6())
|
|
355
|
+
if (!isElasticsearch6()) {
|
|
356
|
+
const actionKey = getFirstKey(record.action);
|
|
357
|
+
const { _type, ...withoutTypeAction } = record.action[actionKey];
|
|
358
|
+
// if data is specified return both
|
|
359
|
+
return record.data ? [{
|
|
360
|
+
...record.action,
|
|
361
|
+
[actionKey]: withoutTypeAction
|
|
362
|
+
}, record.data] : [{
|
|
363
|
+
...record.action,
|
|
364
|
+
[actionKey]: withoutTypeAction
|
|
365
|
+
}];
|
|
366
|
+
}
|
|
353
367
|
|
|
354
368
|
// if data is specified return both
|
|
355
369
|
return record.data ? [record.action, record.data] : [record.action];
|
|
@@ -358,51 +372,41 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
358
372
|
const response = await _clientRequest('bulk', { body });
|
|
359
373
|
const results = response.body ? response.body : response;
|
|
360
374
|
|
|
361
|
-
if (!results.errors)
|
|
375
|
+
if (!results.errors) {
|
|
376
|
+
return results.items.reduce((c, item) => {
|
|
377
|
+
const [value] = Object.values(item);
|
|
378
|
+
// ignore non-successful status codes
|
|
379
|
+
if (value.status != null && value.status >= 400) return c;
|
|
380
|
+
return c + 1;
|
|
381
|
+
}, 0);
|
|
382
|
+
}
|
|
362
383
|
|
|
363
384
|
const {
|
|
364
|
-
retry, successful, error, reason
|
|
385
|
+
retry, successful, error, reason
|
|
365
386
|
} = _filterRetryRecords(actionRecords, results);
|
|
366
387
|
|
|
367
|
-
if (error) {
|
|
368
|
-
if (config._dead_letter_action === 'kafka_dead_letter') {
|
|
369
|
-
return {
|
|
370
|
-
recordCount: previousCount + successful,
|
|
371
|
-
deadLetter
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
388
|
+
if (error && config._dead_letter_action !== 'kafka_dead_letter') {
|
|
375
389
|
throw new Error(`bulk send error: ${reason}`);
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
if (retry.length === 0) {
|
|
379
|
-
return
|
|
393
|
+
return previousCount + successful;
|
|
380
394
|
}
|
|
381
395
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const nextRetryDelay = await _awaitRetry(previousRetryDelay);
|
|
385
|
-
return _bulkSend(retry, previousCount + successful, nextRetryDelay);
|
|
396
|
+
return _handleRetries(retry, previousCount + successful, previousRetryDelay);
|
|
386
397
|
}
|
|
387
398
|
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const { _type, ...withoutTypeAction } = record.action[actionKey];
|
|
392
|
-
// if data is specified return both
|
|
393
|
-
|
|
394
|
-
const body = [{ ...record.action, [actionKey]: withoutTypeAction }];
|
|
395
|
-
|
|
396
|
-
if (record.data != null) body.push(record.data);
|
|
399
|
+
async function _handleRetries(retry, affectedCount, previousRetryDelay) {
|
|
400
|
+
warning();
|
|
397
401
|
|
|
398
|
-
|
|
402
|
+
const nextRetryDelay = await _awaitRetry(previousRetryDelay);
|
|
403
|
+
return _bulkSend(retry, affectedCount, nextRetryDelay);
|
|
399
404
|
}
|
|
400
405
|
|
|
401
406
|
/**
|
|
402
407
|
* The new and improved bulk send with proper retry support
|
|
403
408
|
*
|
|
404
|
-
* @returns {Promise<
|
|
405
|
-
* the number of affected rows and records for kafka dead letter queue
|
|
409
|
+
* @returns {Promise<number>} the number of affected rows
|
|
406
410
|
*/
|
|
407
411
|
function bulkSend(data) {
|
|
408
412
|
if (!Array.isArray(data)) {
|
|
@@ -412,15 +416,6 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
412
416
|
return Promise.resolve(_bulkSend(data));
|
|
413
417
|
}
|
|
414
418
|
|
|
415
|
-
function _affectedRowsCount(results) {
|
|
416
|
-
return results.items.reduce((c, item) => {
|
|
417
|
-
const [value] = Object.values(item);
|
|
418
|
-
// ignore non-successful status codes
|
|
419
|
-
if (value.status != null && value.status >= 400) return c;
|
|
420
|
-
return c + 1;
|
|
421
|
-
}, 0);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
419
|
function _warn(warnLogger, msg) {
|
|
425
420
|
let _lastTime = null;
|
|
426
421
|
return () => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terascope/elasticsearch-api",
|
|
3
3
|
"displayName": "Elasticsearch API",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.5.1",
|
|
5
5
|
"description": "Elasticsearch client api used across multiple services, handles retries and exponential backoff",
|
|
6
6
|
"homepage": "https://github.com/terascope/teraslice/tree/master/packages/elasticsearch-api#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@terascope/types": "^0.11.2",
|
|
26
|
-
"@terascope/utils": "^0.45.
|
|
26
|
+
"@terascope/utils": "^0.45.6",
|
|
27
27
|
"bluebird": "^3.7.2",
|
|
28
28
|
"setimmediate": "^1.0.5"
|
|
29
29
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@opensearch-project/opensearch": "^1.1.0",
|
|
32
32
|
"@types/elasticsearch": "^5.0.40",
|
|
33
33
|
"elasticsearch": "^15.4.1",
|
|
34
|
-
"elasticsearch-store": "^0.
|
|
34
|
+
"elasticsearch-store": "^0.66.1",
|
|
35
35
|
"elasticsearch6": "npm:@elastic/elasticsearch@^6.7.0",
|
|
36
36
|
"elasticsearch7": "npm:@elastic/elasticsearch@^7.0.0",
|
|
37
37
|
"elasticsearch8": "npm:@elastic/elasticsearch@^8.0.0"
|
package/test/api-spec.js
CHANGED
|
@@ -768,7 +768,7 @@ describe('elasticsearch-api', () => {
|
|
|
768
768
|
{ delete: { _index: 'some_index', _type: 'events', _id: 5 } }
|
|
769
769
|
]
|
|
770
770
|
});
|
|
771
|
-
return expect(result).
|
|
771
|
+
return expect(result).toBe(2);
|
|
772
772
|
});
|
|
773
773
|
|
|
774
774
|
it('can remove type from bulkSend', async () => {
|
|
@@ -877,7 +877,7 @@ describe('elasticsearch-api', () => {
|
|
|
877
877
|
});
|
|
878
878
|
const result = await api.bulkSend(myBulkData);
|
|
879
879
|
|
|
880
|
-
expect(result).
|
|
880
|
+
expect(result).toBe(2);
|
|
881
881
|
|
|
882
882
|
bulkError = ['some_thing_else', 'some_thing_else'];
|
|
883
883
|
|
|
@@ -14,7 +14,9 @@ const {
|
|
|
14
14
|
createMappingFromDatatype
|
|
15
15
|
} = ElasticsearchTestHelpers;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const THREE_MINUTES = 3 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
jest.setTimeout(THREE_MINUTES + 60000);
|
|
18
20
|
|
|
19
21
|
function formatUploadData(
|
|
20
22
|
index, data, isES8ClientTest = false
|
|
@@ -72,35 +74,48 @@ describe('bulkSend', () => {
|
|
|
72
74
|
await cleanupIndex(client, index);
|
|
73
75
|
});
|
|
74
76
|
|
|
75
|
-
it('
|
|
77
|
+
it('should send records if no issues and dlq not set', async () => {
|
|
78
|
+
const diffApi = elasticsearchAPI(client, logger);
|
|
79
|
+
|
|
80
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
81
|
+
|
|
82
|
+
const result = await diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
83
|
+
|
|
84
|
+
expect(result).toBe(2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should throw an error if dlq is not set', async () => {
|
|
88
|
+
const diffApi = elasticsearchAPI(client, logger);
|
|
89
|
+
|
|
76
90
|
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
77
91
|
|
|
78
92
|
docs[0].bytes = 'this is a bad value';
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
await expect(diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8)))
|
|
95
|
+
.rejects.toThrow();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should update metadata for records that are not retryable and return results for successful updates if dlq is set', async () => {
|
|
99
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2))
|
|
100
|
+
.map((doc, i) => DataEntity.make(doc, { _key: i + 1 }));
|
|
81
101
|
|
|
82
|
-
|
|
102
|
+
docs[0].bytes = 'this is a bad value';
|
|
103
|
+
|
|
104
|
+
const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
83
105
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
userAgent: 'Mozilla/5.0 (Windows; U; Windows NT 6.1) AppleWebKit/533.1.2 (KHTML, like Gecko) Chrome/35.0.894.0 Safari/533.1.2',
|
|
87
|
-
url: 'http://lucious.biz',
|
|
88
|
-
uuid: 'b23a8550-0081-453f-9e80-93a90782a5bd',
|
|
89
|
-
created: '2019-04-26T15:00:23.225+00:00',
|
|
90
|
-
ipv6: '9e79:7798:585a:b847:f1c4:81eb:0c3d:7eb8',
|
|
91
|
-
location: '50.15003, -94.89355',
|
|
92
|
-
bytes: 'this is a bad value'
|
|
93
|
-
}));
|
|
106
|
+
// 1 good doc - so only 1 row affected
|
|
107
|
+
expect(result).toBe(1);
|
|
94
108
|
|
|
95
|
-
expect(
|
|
109
|
+
expect(docs[0].getMetadata('_bulk_sender_rejection')).toInclude('mapper_parsing_exception--failed to parse field [bytes]');
|
|
110
|
+
expect(docs[1].getMetadata('_bulk_sender_rejection')).toBeUndefined();
|
|
96
111
|
});
|
|
97
112
|
|
|
98
|
-
it('should return a count if not un-retryable records', async () => {
|
|
113
|
+
it('should return a count if not un-retryable records if dlq is set', async () => {
|
|
99
114
|
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
100
115
|
|
|
101
116
|
const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
102
117
|
|
|
103
|
-
expect(result).
|
|
118
|
+
expect(result).toBe(2);
|
|
104
119
|
});
|
|
105
120
|
});
|
|
106
121
|
});
|
package/types/index.d.ts
CHANGED
|
@@ -32,9 +32,9 @@ declare namespace elasticsearchAPI {
|
|
|
32
32
|
/**
|
|
33
33
|
* The new and improved bulk send with proper retry support
|
|
34
34
|
*
|
|
35
|
-
* @returns the number of affected rows
|
|
35
|
+
* @returns the number of affected rows
|
|
36
36
|
*/
|
|
37
|
-
bulkSend: (data: BulkRecord[]) => Promise<
|
|
37
|
+
bulkSend: (data: BulkRecord[]) => Promise<number>;
|
|
38
38
|
nodeInfo: (query: any) => Promise<any>;
|
|
39
39
|
nodeStats: (query: any) => Promise<any>;
|
|
40
40
|
buildQuery: (opConfig: Config, msg: any) => ClientParams.SearchParams;
|