Skip to content

Commit 6b0063f

Browse files
[Maps] do not track total hits for elasticsearch search requests (#91754) (#95455)
* [Maps] do not track total hits for elasticsearch search requests * set track_total_hits for es_search_source tooltip fetch * tslint * searchSource doc updates, set track_total_hits in MVT requests * revert changes made to searchsourcefields docs * tslint * review feedback * tslint * remove Hits (Total) from functional tests * remove sleep in functional test * tslint * fix method name Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent a49e0b7 commit 6b0063f

File tree

18 files changed

+198
-137
lines changed

18 files changed

+198
-137
lines changed

x-pack/plugins/maps/common/elasticsearch_util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
export * from './es_agg_utils';
99
export * from './convert_to_geojson';
1010
export * from './elasticsearch_geo_utils';
11+
export { isTotalHitsGreaterThan, TotalHits } from './total_hits';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { isTotalHitsGreaterThan, TotalHits } from './total_hits';
9+
10+
describe('total.relation: eq', () => {
11+
const totalHits = {
12+
value: 100,
13+
relation: 'eq' as TotalHits['relation'],
14+
};
15+
16+
test('total.value: 100 should be more than 90', () => {
17+
expect(isTotalHitsGreaterThan(totalHits, 90)).toBe(true);
18+
});
19+
20+
test('total.value: 100 should not be more than 100', () => {
21+
expect(isTotalHitsGreaterThan(totalHits, 100)).toBe(false);
22+
});
23+
24+
test('total.value: 100 should not be more than 110', () => {
25+
expect(isTotalHitsGreaterThan(totalHits, 110)).toBe(false);
26+
});
27+
});
28+
29+
describe('total.relation: gte', () => {
30+
const totalHits = {
31+
value: 100,
32+
relation: 'gte' as TotalHits['relation'],
33+
};
34+
35+
test('total.value: 100 should be more than 90', () => {
36+
expect(isTotalHitsGreaterThan(totalHits, 90)).toBe(true);
37+
});
38+
39+
test('total.value: 100 should be more than 100', () => {
40+
expect(isTotalHitsGreaterThan(totalHits, 100)).toBe(true);
41+
});
42+
43+
test('total.value: 100 should throw error when value is more than 100', () => {
44+
expect(() => {
45+
isTotalHitsGreaterThan(totalHits, 110);
46+
}).toThrow();
47+
});
48+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { i18n } from '@kbn/i18n';
9+
10+
export interface TotalHits {
11+
value: number;
12+
relation: 'eq' | 'gte';
13+
}
14+
15+
export function isTotalHitsGreaterThan(totalHits: TotalHits, value: number) {
16+
if (totalHits.relation === 'eq') {
17+
return totalHits.value > value;
18+
}
19+
20+
if (value > totalHits.value) {
21+
throw new Error(
22+
i18n.translate('xpack.maps.totalHits.lowerBoundPrecisionExceeded', {
23+
defaultMessage: `Unable to determine if total hits is greater than value. Total hits precision is lower then value. Total hits: {totalHitsString}, value: {value}. Ensure _search.body.track_total_hits is at least as large as value.`,
24+
values: {
25+
totalHitsString: JSON.stringify(totalHits, null, ''),
26+
value,
27+
},
28+
})
29+
);
30+
}
31+
32+
return true;
33+
}

x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
LAYER_STYLE_TYPE,
2323
FIELD_ORIGIN,
2424
} from '../../../../common/constants';
25+
import { isTotalHitsGreaterThan, TotalHits } from '../../../../common/elasticsearch_util';
2526
import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_source';
2627
import { canSkipSourceUpdate } from '../../util/can_skip_fetch';
2728
import { IESSource } from '../../sources/es_source';
@@ -323,13 +324,18 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
323324
syncContext.startLoading(dataRequestId, requestToken, searchFilters);
324325
const abortController = new AbortController();
325326
syncContext.registerCancelCallback(requestToken, () => abortController.abort());
327+
const maxResultWindow = await this._documentSource.getMaxResultWindow();
326328
const searchSource = await this._documentSource.makeSearchSource(searchFilters, 0);
329+
searchSource.setField('trackTotalHits', maxResultWindow + 1);
327330
const resp = await searchSource.fetch({
328331
abortSignal: abortController.signal,
329332
sessionId: syncContext.dataFilters.searchSessionId,
333+
legacyHitsTotal: false,
330334
});
331-
const maxResultWindow = await this._documentSource.getMaxResultWindow();
332-
isSyncClustered = resp.hits.total > maxResultWindow;
335+
isSyncClustered = isTotalHitsGreaterThan(
336+
(resp.hits.total as unknown) as TotalHits,
337+
maxResultWindow
338+
);
333339
const countData = { isSyncClustered } as CountData;
334340
syncContext.stopLoading(dataRequestId, requestToken, countData, searchFilters);
335341
} catch (error) {

x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle
368368
): Promise<GeoJsonWithMeta> {
369369
const indexPattern: IndexPattern = await this.getIndexPattern();
370370
const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
371+
searchSource.setField('trackTotalHits', false);
371372

372373
let bucketsPerGrid = 1;
373374
this.getMetricFields().forEach((metricField) => {

x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export class ESGeoLineSource extends AbstractESAggSource {
190190
// Fetch entities
191191
//
192192
const entitySearchSource = await this.makeSearchSource(searchFilters, 0);
193+
entitySearchSource.setField('trackTotalHits', false);
193194
const splitField = getField(indexPattern, this._descriptor.splitField);
194195
const cardinalityAgg = { precision_threshold: 1 };
195196
const termsAgg = { size: MAX_TRACKS };
@@ -250,6 +251,7 @@ export class ESGeoLineSource extends AbstractESAggSource {
250251
const tracksSearchFilters = { ...searchFilters };
251252
delete tracksSearchFilters.buffer;
252253
const tracksSearchSource = await this.makeSearchSource(tracksSearchFilters, 0);
254+
tracksSearchSource.setField('trackTotalHits', false);
253255
tracksSearchSource.setField('aggs', {
254256
tracks: {
255257
filters: {

x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class ESPewPewSource extends AbstractESAggSource {
109109
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
110110
const indexPattern = await this.getIndexPattern();
111111
const searchSource = await this.makeSearchSource(searchFilters, 0);
112+
searchSource.setField('trackTotalHits', false);
112113
searchSource.setField('aggs', {
113114
destSplit: {
114115
terms: {
@@ -168,6 +169,7 @@ export class ESPewPewSource extends AbstractESAggSource {
168169

169170
async getBoundsForFilters(boundsFilters, registerCancelCallback) {
170171
const searchSource = await this.makeSearchSource(boundsFilters, 0);
172+
searchSource.setField('trackTotalHits', false);
171173
searchSource.setField('aggs', {
172174
destFitToBounds: {
173175
geo_bounds: {
@@ -185,7 +187,10 @@ export class ESPewPewSource extends AbstractESAggSource {
185187
try {
186188
const abortController = new AbortController();
187189
registerCancelCallback(() => abortController.abort());
188-
const esResp = await searchSource.fetch({ abortSignal: abortController.signal });
190+
const esResp = await searchSource.fetch({
191+
abortSignal: abortController.signal,
192+
legacyHitsTotal: false,
193+
});
189194
if (esResp.aggregations.destFitToBounds.bounds) {
190195
corners.push([
191196
esResp.aggregations.destFitToBounds.bounds.top_left.lon,

x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
addFieldToDSL,
1919
getField,
2020
hitsToGeoJson,
21+
isTotalHitsGreaterThan,
2122
PreIndexedShape,
2223
} from '../../../../common/elasticsearch_util';
2324
// @ts-expect-error
@@ -313,6 +314,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
313314
};
314315

315316
const searchSource = await this.makeSearchSource(searchFilters, 0);
317+
searchSource.setField('trackTotalHits', false);
316318
searchSource.setField('aggs', {
317319
totalEntities: {
318320
cardinality: addFieldToDSL(cardinalityAgg, topHitsSplitField),
@@ -343,11 +345,10 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
343345
const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT;
344346
let areTopHitsTrimmed = false;
345347
entityBuckets.forEach((entityBucket: any) => {
346-
const total = _.get(entityBucket, 'entityHits.hits.total', 0);
347348
const hits = _.get(entityBucket, 'entityHits.hits.hits', []);
348349
// Reverse hits list so top documents by sort are drawn on top
349350
allHits.push(...hits.reverse());
350-
if (total > hits.length) {
351+
if (isTotalHitsGreaterThan(entityBucket.entityHits.hits.total, hits.length)) {
351352
areTopHitsTrimmed = true;
352353
}
353354
});
@@ -385,6 +386,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
385386
maxResultWindow,
386387
initialSearchContext
387388
);
389+
searchSource.setField('trackTotalHits', maxResultWindow + 1);
388390
searchSource.setField('fieldsFromSource', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields
389391
if (sourceOnlyFields.length === 0) {
390392
searchSource.setField('source', false); // do not need anything from _source
@@ -408,7 +410,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
408410
hits: resp.hits.hits.reverse(), // Reverse hits so top documents by sort are drawn on top
409411
meta: {
410412
resultsCount: resp.hits.hits.length,
411-
areResultsTrimmed: resp.hits.total > resp.hits.hits.length,
413+
areResultsTrimmed: isTotalHitsGreaterThan(resp.hits.total, resp.hits.hits.length),
412414
},
413415
};
414416
}
@@ -508,6 +510,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
508510
const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source
509511
const searchService = getSearchService();
510512
const searchSource = await searchService.searchSource.create(initialSearchContext as object);
513+
searchSource.setField('trackTotalHits', false);
511514

512515
searchSource.setField('index', indexPattern);
513516
searchSource.setField('size', 1);
@@ -520,7 +523,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
520523
searchSource.setField('query', query);
521524
searchSource.setField('fieldsFromSource', this._getTooltipPropertyNames());
522525

523-
const resp = await searchSource.fetch();
526+
const resp = await searchSource.fetch({ legacyHitsTotal: false });
524527

525528
const hit = _.get(resp, 'hits.hits[0]');
526529
if (!hit) {

x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
195195
resp = await searchSource.fetch({
196196
abortSignal: abortController.signal,
197197
sessionId: searchSessionId,
198+
legacyHitsTotal: false,
198199
});
199200
if (inspectorRequest) {
200201
const responseStats = search.getResponseInspectorStats(resp, searchSource);
@@ -247,6 +248,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
247248
}
248249
}
249250
const searchService = getSearchService();
251+
250252
const searchSource = await searchService.searchSource.create(initialSearchContext);
251253

252254
searchSource.setField('index', indexPattern);
@@ -272,6 +274,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
272274
registerCancelCallback: (callback: () => void) => void
273275
): Promise<MapExtent | null> {
274276
const searchSource = await this.makeSearchSource(boundsFilters, 0);
277+
searchSource.setField('trackTotalHits', false);
275278
searchSource.setField('aggs', {
276279
fitToBounds: {
277280
geo_bounds: {
@@ -284,7 +287,10 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
284287
try {
285288
const abortController = new AbortController();
286289
registerCancelCallback(() => abortController.abort());
287-
const esResp = await searchSource.fetch({ abortSignal: abortController.signal });
290+
const esResp = await searchSource.fetch({
291+
abortSignal: abortController.signal,
292+
legacyHitsTotal: false,
293+
});
288294

289295
if (!esResp.aggregations) {
290296
return null;

x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource
127127

128128
const indexPattern = await this.getIndexPattern();
129129
const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
130+
searchSource.setField('trackTotalHits', false);
130131
const termsField = getField(indexPattern, this._termField.getName());
131132
const termsAgg = {
132133
size: this._descriptor.size !== undefined ? this._descriptor.size : DEFAULT_MAX_BUCKETS_LIMIT,

0 commit comments

Comments
 (0)