From f2da2a36405e46d09067209470d4585619c4d98e Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 9 Feb 2017 15:49:12 -0500 Subject: [PATCH] Batch mget requests on dashboard load and cache field_stat results (#10081) * defer loading visualization saved objects so they can be loaded in a single _mget * Don't request field stats more than once for the same index pattern * [ui/courier] batch fetch requests for all searches and docs * [ui/courier] remove remaining mentions of req.isFetchRequested() * [courier/fetch/request] remove unneceessary !! --- src/ui/public/courier/fetch/fetch.js | 29 ++++++++++------ .../public/courier/fetch/request/request.js | 34 +++++++++++++++++-- .../public/courier/fetch/strategy/search.js | 8 ++++- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/ui/public/courier/fetch/fetch.js b/src/ui/public/courier/fetch/fetch.js index edcc1a04cb483..001e7be1847f0 100644 --- a/src/ui/public/courier/fetch/fetch.js +++ b/src/ui/public/courier/fetch/fetch.js @@ -8,23 +8,32 @@ import ReqStatusProvider from './req_status'; export default function fetchService(Private, Promise) { const requestQueue = Private(RequestQueueProvider); - const fetchThese = Private(FetchTheseProvider); - + const immediatelyFetchThese = Private(FetchTheseProvider); const callResponseHandlers = Private(CallResponseHandlersProvider); const INCOMPLETE = Private(ReqStatusProvider).INCOMPLETE; - function fetchQueued(strategy) { - const requests = requestQueue.getStartable(strategy); - if (!requests.length) return Promise.resolve(); - else return fetchThese(requests); - } + const debouncedFetchThese = _.debounce(() => { + const requests = requestQueue.get().filter(req => req.isFetchRequestedAndPending()); + immediatelyFetchThese(requests); + }, { + wait: 10, + maxWait: 50 + }); - this.fetchQueued = fetchQueued; + const fetchTheseSoon = (requests) => { + requests.forEach(req => req._setFetchRequested()); + debouncedFetchThese(); + return Promise.all(requests.map(req => req.defer.promise)); + }; + + this.fetchQueued = (strategy) => { + return fetchTheseSoon(requestQueue.getStartable(strategy)); + }; function fetchASource(source, strategy) { const defer = Promise.defer(); - fetchThese([ + fetchTheseSoon([ source._createRequest(defer) ]); @@ -50,7 +59,7 @@ export default function fetchService(Private, Promise) { * @param {array} reqs - the requests to fetch * @async */ - this.these = fetchThese; + this.these = fetchTheseSoon; /** * Send responses to a list of requests, used when requests diff --git a/src/ui/public/courier/fetch/request/request.js b/src/ui/public/courier/fetch/request/request.js index 110d09e7564a8..6c9aa65c28645 100644 --- a/src/ui/public/courier/fetch/request/request.js +++ b/src/ui/public/courier/fetch/request/request.js @@ -15,12 +15,42 @@ export default function AbstractReqProvider(Private, Promise) { this.source = source; this.defer = defer || Promise.defer(); this._whenAbortedHandlers = []; - requestQueue.push(this); } + /** + * Called by the loopers to find requests that should be sent to the + * fetch() module. When a module is sent to fetch() it's _fetchRequested flag + * is set, and this consults that flag so requests are not send to fetch() + * multiple times. + * + * @return {Boolean} + */ canStart() { - return Boolean(!this.stopped && !this.source._fetchDisabled); + return !this._fetchRequested && !this.stopped && !this.source._fetchDisabled; + } + + /** + * Used to find requests that were previously sent to the fetch() module but + * have not been started yet, so they can be started. + * + * @return {Boolean} + */ + isFetchRequestedAndPending() { + return this._fetchRequested && !this.started; + } + + /** + * Called by the fetch() module when this request has been sent to + * be fetched. At that point the request is somewhere between `ready-to-start` + * and `started`. The fetch module then waits a short period of time to + * allow requests to build up in the request queue, and then immediately + * fetches all requests that return true from `isFetchRequestedAndPending()` + * + * @return {undefined} + */ + _setFetchRequested() { + this._fetchRequested = true; } start() { diff --git a/src/ui/public/courier/fetch/strategy/search.js b/src/ui/public/courier/fetch/strategy/search.js index e1b3cd6cd7d1f..dd7d7ba47068a 100644 --- a/src/ui/public/courier/fetch/strategy/search.js +++ b/src/ui/public/courier/fetch/strategy/search.js @@ -15,6 +15,8 @@ export default function FetchStrategyForSearch(Private, Promise, timefilter, kbn * @return {Promise} - a promise that is fulfilled by the request body */ reqsFetchParamsToBody: function (reqsFetchParams) { + const indexToListMapping = {}; + return Promise.map(reqsFetchParams, function (fetchParams) { return Promise.resolve(fetchParams.index) .then(function (indexList) { @@ -23,7 +25,11 @@ export default function FetchStrategyForSearch(Private, Promise, timefilter, kbn } const timeBounds = timefilter.getBounds(); - return indexList.toIndexList(timeBounds.min, timeBounds.max); + + if (!indexToListMapping[indexList.id]) { + indexToListMapping[indexList.id] = indexList.toIndexList(timeBounds.min, timeBounds.max); + } + return indexToListMapping[indexList.id]; }) .then(function (indexList) { let body = fetchParams.body || {};