[Backport 7.x] Use filter_path for improving the search helpers performances (#1201)
This commit is contained in:
committed by
GitHub
parent
8a1e9576aa
commit
35e587663c
@ -212,7 +212,8 @@ console.log(result)
|
|||||||
----
|
----
|
||||||
|
|
||||||
=== Search Helper
|
=== Search Helper
|
||||||
A simple wrapper around the search API. Instead of returning the entire `result` object it will return only the search documents result.
|
A simple wrapper around the search API. Instead of returning the entire `result` object it will return only the search documents source.
|
||||||
|
For improving the performances, this helper automatically adds `filter_path=hits.hits._source` to the querystring.
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
----
|
----
|
||||||
@ -281,6 +282,7 @@ for await (const result of scrollSearch) {
|
|||||||
=== Scroll Documents Helper
|
=== Scroll Documents Helper
|
||||||
|
|
||||||
It works in the same way as the scroll search helper, but it returns only the documents instead. Note, every loop cycle will return you a single document, and you can't use the `clear` method.
|
It works in the same way as the scroll search helper, but it returns only the documents instead. Note, every loop cycle will return you a single document, and you can't use the `clear` method.
|
||||||
|
For improving the performances, this helper automatically adds `filter_path=hits.hits._source` to the querystring.
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
----
|
----
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
/* eslint camelcase: 0 */
|
||||||
|
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
const { ResponseError, ConfigurationError } = require('./errors')
|
const { ResponseError, ConfigurationError } = require('./errors')
|
||||||
|
|
||||||
@ -29,11 +31,14 @@ class Helpers {
|
|||||||
/**
|
/**
|
||||||
* Runs a search operation. The only difference between client.search and this utility,
|
* Runs a search operation. The only difference between client.search and this utility,
|
||||||
* is that we are only returning the hits to the user and not the full ES response.
|
* is that we are only returning the hits to the user and not the full ES response.
|
||||||
|
* This helper automatically adds `filter_path=hits.hits._source` to the querystring,
|
||||||
|
* as it will only need the documents source.
|
||||||
* @param {object} params - The Elasticsearch's search parameters.
|
* @param {object} params - The Elasticsearch's search parameters.
|
||||||
* @param {object} options - The client optional configuration for this request.
|
* @param {object} options - The client optional configuration for this request.
|
||||||
* @return {array} The documents that matched the request.
|
* @return {array} The documents that matched the request.
|
||||||
*/
|
*/
|
||||||
async search (params, options) {
|
async search (params, options) {
|
||||||
|
appendFilterPath('hits.hits._source', params, true)
|
||||||
const response = await this[kClient].search(params, options)
|
const response = await this[kClient].search(params, options)
|
||||||
return this[kGetHits](response.body)
|
return this[kGetHits](response.body)
|
||||||
}
|
}
|
||||||
@ -63,6 +68,8 @@ class Helpers {
|
|||||||
options.ignore = [429]
|
options.ignore = [429]
|
||||||
}
|
}
|
||||||
params.scroll = params.scroll || '1m'
|
params.scroll = params.scroll || '1m'
|
||||||
|
appendFilterPath('_scroll_id', params, false)
|
||||||
|
const { method, body, index, ...querystring } = params
|
||||||
|
|
||||||
let response = null
|
let response = null
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
@ -74,33 +81,31 @@ class Helpers {
|
|||||||
throw new ResponseError(response)
|
throw new ResponseError(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollId = response.body._scroll_id
|
let scroll_id = response.body._scroll_id
|
||||||
let stop = false
|
let stop = false
|
||||||
const clear = async () => {
|
const clear = async () => {
|
||||||
stop = true
|
stop = true
|
||||||
await this[kClient].clearScroll(
|
await this[kClient].clearScroll(
|
||||||
{ body: { scroll_id: scrollId } },
|
{ body: { scroll_id } },
|
||||||
{ ignore: [400] }
|
{ ignore: [400] }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
while (response.body.hits.hits.length > 0) {
|
while (response.body.hits && response.body.hits.hits.length > 0) {
|
||||||
scrollId = response.body._scroll_id
|
scroll_id = response.body._scroll_id
|
||||||
response.clear = clear
|
response.clear = clear
|
||||||
response.documents = this[kGetHits](response.body)
|
response.documents = this[kGetHits](response.body)
|
||||||
|
|
||||||
yield response
|
yield response
|
||||||
|
|
||||||
if (!scrollId || stop === true) {
|
if (!scroll_id || stop === true) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
response = await this[kClient].scroll({
|
response = await this[kClient].scroll({
|
||||||
scroll: params.scroll,
|
...querystring,
|
||||||
body: {
|
body: { scroll_id }
|
||||||
scroll_id: scrollId
|
|
||||||
}
|
|
||||||
}, options)
|
}, options)
|
||||||
if (response.statusCode !== 429) break
|
if (response.statusCode !== 429) break
|
||||||
await sleep(wait)
|
await sleep(wait)
|
||||||
@ -120,11 +125,14 @@ class Helpers {
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
* Each document is what you will find by running a scrollSearch and iterating on the hits array.
|
* Each document is what you will find by running a scrollSearch and iterating on the hits array.
|
||||||
|
* This helper automatically adds `filter_path=hits.hits._source` to the querystring,
|
||||||
|
* as it will only need the documents source.
|
||||||
* @param {object} params - The Elasticsearch's search parameters.
|
* @param {object} params - The Elasticsearch's search parameters.
|
||||||
* @param {object} options - The client optional configuration for this request.
|
* @param {object} options - The client optional configuration for this request.
|
||||||
* @return {iterator} the async iterator
|
* @return {iterator} the async iterator
|
||||||
*/
|
*/
|
||||||
async * scrollDocuments (params, options) {
|
async * scrollDocuments (params, options) {
|
||||||
|
appendFilterPath('hits.hits._source', params, true)
|
||||||
for await (const { documents } of this.scrollSearch(params)) {
|
for await (const { documents } of this.scrollSearch(params)) {
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
yield document
|
yield document
|
||||||
@ -428,4 +436,14 @@ class Helpers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendFilterPath (filter, params, force) {
|
||||||
|
if (params.filter_path !== undefined) {
|
||||||
|
params.filter_path += ',' + filter
|
||||||
|
} else if (params.filterPath !== undefined) {
|
||||||
|
params.filterPath += ',' + filter
|
||||||
|
} else if (force === true) {
|
||||||
|
params.filter_path = filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Helpers
|
module.exports = Helpers
|
||||||
|
|||||||
@ -183,6 +183,8 @@ test('Scroll search (retry throws later)', async t => {
|
|||||||
var count = 0
|
var count = 0
|
||||||
const MockConnection = connection.buildMockConnection({
|
const MockConnection = connection.buildMockConnection({
|
||||||
onRequest (params) {
|
onRequest (params) {
|
||||||
|
// filter_path should not be added if is not already present
|
||||||
|
t.strictEqual(params.querystring, 'scroll=1m')
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
count += 1
|
count += 1
|
||||||
return { body: {}, statusCode: 429 }
|
return { body: {}, statusCode: 429 }
|
||||||
@ -232,6 +234,7 @@ test('Scroll search documents', async t => {
|
|||||||
var count = 0
|
var count = 0
|
||||||
const MockConnection = connection.buildMockConnection({
|
const MockConnection = connection.buildMockConnection({
|
||||||
onRequest (params) {
|
onRequest (params) {
|
||||||
|
t.strictEqual(params.querystring, 'filter_path=hits.hits._source%2C_scroll_id&scroll=1m')
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
_scroll_id: count === 3 ? undefined : 'id',
|
_scroll_id: count === 3 ? undefined : 'id',
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const { connection } = require('../../utils')
|
|||||||
test('Search should have an additional documents property', async t => {
|
test('Search should have an additional documents property', async t => {
|
||||||
const MockConnection = connection.buildMockConnection({
|
const MockConnection = connection.buildMockConnection({
|
||||||
onRequest (params) {
|
onRequest (params) {
|
||||||
|
t.strictEqual(params.querystring, 'filter_path=hits.hits._source')
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
hits: {
|
hits: {
|
||||||
@ -44,6 +45,7 @@ test('Search should have an additional documents property', async t => {
|
|||||||
test('kGetHits fallback', async t => {
|
test('kGetHits fallback', async t => {
|
||||||
const MockConnection = connection.buildMockConnection({
|
const MockConnection = connection.buildMockConnection({
|
||||||
onRequest (params) {
|
onRequest (params) {
|
||||||
|
t.strictEqual(params.querystring, 'filter_path=hits.hits._source')
|
||||||
return { body: {} }
|
return { body: {} }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -59,3 +61,73 @@ test('kGetHits fallback', async t => {
|
|||||||
})
|
})
|
||||||
t.deepEqual(result, [])
|
t.deepEqual(result, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Merge filter paths (snake_case)', async t => {
|
||||||
|
const MockConnection = connection.buildMockConnection({
|
||||||
|
onRequest (params) {
|
||||||
|
t.strictEqual(params.querystring, 'filter_path=foo%2Chits.hits._source')
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
hits: {
|
||||||
|
hits: [
|
||||||
|
{ _source: { one: 'one' } },
|
||||||
|
{ _source: { two: 'two' } },
|
||||||
|
{ _source: { three: 'three' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
node: 'http://localhost:9200',
|
||||||
|
Connection: MockConnection
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await client.helpers.search({
|
||||||
|
index: 'test',
|
||||||
|
filter_path: 'foo',
|
||||||
|
body: { foo: 'bar' }
|
||||||
|
})
|
||||||
|
t.deepEqual(result, [
|
||||||
|
{ one: 'one' },
|
||||||
|
{ two: 'two' },
|
||||||
|
{ three: 'three' }
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Merge filter paths (camelCase)', async t => {
|
||||||
|
const MockConnection = connection.buildMockConnection({
|
||||||
|
onRequest (params) {
|
||||||
|
t.strictEqual(params.querystring, 'filter_path=foo%2Chits.hits._source')
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
hits: {
|
||||||
|
hits: [
|
||||||
|
{ _source: { one: 'one' } },
|
||||||
|
{ _source: { two: 'two' } },
|
||||||
|
{ _source: { three: 'three' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
node: 'http://localhost:9200',
|
||||||
|
Connection: MockConnection
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await client.helpers.search({
|
||||||
|
index: 'test',
|
||||||
|
filterPath: 'foo',
|
||||||
|
body: { foo: 'bar' }
|
||||||
|
})
|
||||||
|
t.deepEqual(result, [
|
||||||
|
{ one: 'one' },
|
||||||
|
{ two: 'two' },
|
||||||
|
{ three: 'three' }
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user