@terascope/elasticsearch-api 3.3.7 → 3.5.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/index.js +16 -3
- package/package.json +1 -1
- package/test/bulk-send-dlq-spec.js +119 -0
- package/test/bulk-send-limit-spec.js +28 -19
- package/types/index.d.ts +1 -1
package/index.js
CHANGED
|
@@ -309,6 +309,12 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
309
309
|
) {
|
|
310
310
|
nonRetriableError = true;
|
|
311
311
|
reason = `${item.error.type}--${item.error.reason}`;
|
|
312
|
+
|
|
313
|
+
if (config._dead_letter_action === 'kafka_dead_letter') {
|
|
314
|
+
actionRecords[i].data.setMetadata('_bulk_sender_rejection', reason);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
312
318
|
break;
|
|
313
319
|
}
|
|
314
320
|
} else if (item.status == null || item.status < 400) {
|
|
@@ -317,8 +323,11 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
317
323
|
}
|
|
318
324
|
|
|
319
325
|
if (nonRetriableError) {
|
|
326
|
+
// if dlq active still attempt the retries
|
|
327
|
+
const retryOnError = config._dead_letter_action === 'kafka_dead_letter' ? retry : [];
|
|
328
|
+
|
|
320
329
|
return {
|
|
321
|
-
retry:
|
|
330
|
+
retry: retryOnError, successful, error: true, reason
|
|
322
331
|
};
|
|
323
332
|
}
|
|
324
333
|
|
|
@@ -376,7 +385,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
376
385
|
retry, successful, error, reason
|
|
377
386
|
} = _filterRetryRecords(actionRecords, results);
|
|
378
387
|
|
|
379
|
-
if (error) {
|
|
388
|
+
if (error && config._dead_letter_action !== 'kafka_dead_letter') {
|
|
380
389
|
throw new Error(`bulk send error: ${reason}`);
|
|
381
390
|
}
|
|
382
391
|
|
|
@@ -384,10 +393,14 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
|
384
393
|
return previousCount + successful;
|
|
385
394
|
}
|
|
386
395
|
|
|
396
|
+
return _handleRetries(retry, previousCount + successful, previousRetryDelay);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function _handleRetries(retry, affectedCount, previousRetryDelay) {
|
|
387
400
|
warning();
|
|
388
401
|
|
|
389
402
|
const nextRetryDelay = await _awaitRetry(previousRetryDelay);
|
|
390
|
-
return _bulkSend(retry,
|
|
403
|
+
return _bulkSend(retry, affectedCount, nextRetryDelay);
|
|
391
404
|
}
|
|
392
405
|
|
|
393
406
|
/**
|
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.0",
|
|
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": {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
debugLogger,
|
|
5
|
+
cloneDeep,
|
|
6
|
+
DataEntity
|
|
7
|
+
} = require('@terascope/utils');
|
|
8
|
+
const { ElasticsearchTestHelpers } = require('elasticsearch-store');
|
|
9
|
+
const elasticsearchAPI = require('../index');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
makeClient, cleanupIndex,
|
|
13
|
+
EvenDateData, TEST_INDEX_PREFIX,
|
|
14
|
+
createMappingFromDatatype
|
|
15
|
+
} = ElasticsearchTestHelpers;
|
|
16
|
+
|
|
17
|
+
jest.setTimeout(10000);
|
|
18
|
+
|
|
19
|
+
function formatUploadData(
|
|
20
|
+
index, data, isES8ClientTest = false
|
|
21
|
+
) {
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
data.forEach((record, i) => {
|
|
25
|
+
const meta = { _index: index, _id: i + 1 };
|
|
26
|
+
|
|
27
|
+
if (!isES8ClientTest) {
|
|
28
|
+
meta._type = '_doc';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
results.push({ action: { index: meta }, data: record });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('bulkSend', () => {
|
|
38
|
+
let client;
|
|
39
|
+
let api;
|
|
40
|
+
let isElasticsearch8 = false;
|
|
41
|
+
|
|
42
|
+
beforeAll(async () => {
|
|
43
|
+
client = await makeClient();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('can return non-retryable records', () => {
|
|
47
|
+
const logger = debugLogger('congested_test');
|
|
48
|
+
const index = `${TEST_INDEX_PREFIX}_non-retryable-records`;
|
|
49
|
+
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
await cleanupIndex(client, index);
|
|
52
|
+
api = elasticsearchAPI(client, logger, { _dead_letter_action: 'kafka_dead_letter' });
|
|
53
|
+
isElasticsearch8 = api.isElasticsearch8();
|
|
54
|
+
|
|
55
|
+
const overrides = {
|
|
56
|
+
settings: {
|
|
57
|
+
'index.number_of_shards': 1,
|
|
58
|
+
'index.number_of_replicas': 0,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const mapping = await createMappingFromDatatype(
|
|
63
|
+
client, EvenDateData.EvenDataType, '_doc', overrides
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
mapping.index = index;
|
|
67
|
+
|
|
68
|
+
await client.indices.create(mapping);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterAll(async () => {
|
|
72
|
+
await cleanupIndex(client, index);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should send records if no issues and dlq not set', async () => {
|
|
76
|
+
const diffApi = elasticsearchAPI(client, logger);
|
|
77
|
+
|
|
78
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
79
|
+
|
|
80
|
+
const result = await diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
81
|
+
|
|
82
|
+
expect(result).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw an error if dlq is not set', async () => {
|
|
86
|
+
const diffApi = elasticsearchAPI(client, logger);
|
|
87
|
+
|
|
88
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
89
|
+
|
|
90
|
+
docs[0].bytes = 'this is a bad value';
|
|
91
|
+
|
|
92
|
+
await expect(diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8)))
|
|
93
|
+
.rejects.toThrow();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should update metadata for records that are not retryable and return results for successful updates if dlq is set', async () => {
|
|
97
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2))
|
|
98
|
+
.map((doc, i) => DataEntity.make(doc, { _key: i + 1 }));
|
|
99
|
+
|
|
100
|
+
docs[0].bytes = 'this is a bad value';
|
|
101
|
+
|
|
102
|
+
const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
103
|
+
|
|
104
|
+
// 1 good doc - so only 1 row affected
|
|
105
|
+
expect(result).toBe(1);
|
|
106
|
+
|
|
107
|
+
expect(docs[0].getMetadata('_bulk_sender_rejection')).toInclude('mapper_parsing_exception--failed to parse field [bytes]');
|
|
108
|
+
expect(docs[1].getMetadata('_bulk_sender_rejection')).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return a count if not un-retryable records if dlq is set', async () => {
|
|
112
|
+
const docs = cloneDeep(EvenDateData.data.slice(0, 2));
|
|
113
|
+
|
|
114
|
+
const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
|
|
115
|
+
|
|
116
|
+
expect(result).toBe(2);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
debugLogger,
|
|
5
|
+
chunk,
|
|
6
|
+
pMap
|
|
7
|
+
} = require('@terascope/utils');
|
|
4
8
|
const { ElasticsearchTestHelpers } = require('elasticsearch-store');
|
|
5
9
|
const elasticsearchAPI = require('../index');
|
|
6
10
|
|
|
@@ -11,7 +15,7 @@ const {
|
|
|
11
15
|
|
|
12
16
|
const THREE_MINUTES = 3 * 60 * 1000;
|
|
13
17
|
|
|
14
|
-
jest.setTimeout(THREE_MINUTES +
|
|
18
|
+
jest.setTimeout(THREE_MINUTES + 60000);
|
|
15
19
|
|
|
16
20
|
function formatUploadData(
|
|
17
21
|
index, data, isES8ClientTest = false
|
|
@@ -31,33 +35,38 @@ function formatUploadData(
|
|
|
31
35
|
return results;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
describe('bulkSend
|
|
35
|
-
const logger = debugLogger('congested_test');
|
|
36
|
-
const index = `${TEST_INDEX_PREFIX}_congested_queues_`;
|
|
37
|
-
|
|
38
|
+
describe('bulkSend', () => {
|
|
38
39
|
let client;
|
|
39
40
|
let api;
|
|
40
41
|
let isElasticsearch8 = false;
|
|
41
42
|
|
|
42
43
|
beforeAll(async () => {
|
|
43
44
|
client = await makeClient();
|
|
44
|
-
await cleanupIndex(client, index);
|
|
45
|
-
api = elasticsearchAPI(client, logger);
|
|
46
|
-
isElasticsearch8 = api.isElasticsearch8();
|
|
47
45
|
});
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
describe('can work with congested queues', () => {
|
|
48
|
+
const logger = debugLogger('congested_test');
|
|
49
|
+
const index = `${TEST_INDEX_PREFIX}_congested_queues_`;
|
|
50
|
+
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
await cleanupIndex(client, index);
|
|
53
|
+
api = elasticsearchAPI(client, logger);
|
|
54
|
+
isElasticsearch8 = api.isElasticsearch8();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
await cleanupIndex(client, index);
|
|
59
|
+
});
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
it('can get correct data even with congested queues', async () => {
|
|
62
|
+
const chunkedData = chunk(EvenDateData.data, 50);
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
await pMap(chunkedData, async (cData) => {
|
|
65
|
+
const formattedData = formatUploadData(index, cData, isElasticsearch8);
|
|
66
|
+
return api.bulkSend(formattedData);
|
|
67
|
+
}, { concurrency: 9 });
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
await waitForData(client, index, EvenDateData.data.length, logger, THREE_MINUTES);
|
|
70
|
+
});
|
|
62
71
|
});
|
|
63
72
|
});
|
package/types/index.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ declare namespace elasticsearchAPI {
|
|
|
62
62
|
/**
|
|
63
63
|
* This is used for improved bulk sending function
|
|
64
64
|
*/
|
|
65
|
-
export interface AnyBulkAction
|
|
65
|
+
export interface AnyBulkAction {
|
|
66
66
|
update?: Partial<BulkActionMetadata>;
|
|
67
67
|
index?: Partial<BulkActionMetadata>;
|
|
68
68
|
create?: Partial<BulkActionMetadata>;
|