-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ML] ML client and shared services optimizations #146155
[ML] ML client and shared services optimizations #146155
Conversation
Pinging @elastic/ml-ui (:ml) |
I don't think this is true: |
@@ -61,11 +62,16 @@ function disableAdminPrivileges(capabilities: MlCapabilities) { | |||
export type HasMlCapabilities = (capabilities: MlCapabilitiesKey[]) => Promise<void>; | |||
|
|||
export function hasMlCapabilitiesProvider(resolveMlCapabilities: ResolveMlCapabilities) { | |||
const resolveMlCapabilitiesMemo = memoize( | |||
async (request: KibanaRequest) => await resolveMlCapabilities(request), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this not a memory leak?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how this looks like it could be a potential memory leak, but hasMlCapabilitiesProvider
is still called within the lifespan of the request, so the memoize
cache will be wiped after the request.
Because of this potential confusion I've rewritten this code so to make this clearer. Plus I've removed the use of memoize
as only one request will ever be passed in. Instead I'm keeping a copy of the resolveMlCapabilities
promise for reuse with every capabilities check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks, that sounds good. What about creating a function and wrapping it with _.once
? Maybe a little cleaner than storing a promise in a variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, can you use router.registerRequestContext
to provide a shared function that returns capabilities?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find any reference to registerRequestContext
in kibana? Can you share an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry, it's core.http.registerRouteHandlerContext
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ok, it looks like this is just for customising the context for the router, rather than for functions we'd share out where we need the request object passed in.
But to be honest, our shared functions and their providers were written so long ago that I imagine there are better ways to do this now. I'd love to get rid of the providers which require the request object passed in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ok, it looks like this is just for customising the context for the router, rather than for functions we'd share out where we need the request object passed in.
Can you clarify what you mean here? Specifically "just for customising the context for the router".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm misunderstanding what you were suggesting, the context created by the registerRouteHandlerContext
is available inside the route handlers.
However the functions we're sharing with other plugins via our "provider" functions are not inside route handlers.
Pinging @elastic/apm-ui (Team:APM) |
@jgowdyelastic I read up on the conversation you had with Oleg. IMHO it should not be up to ML to work around the fact that for whatever reason the capabilities plugin has decided to do 7 (mostly completely unrelated to ML's capabilities) sequential calls to be able to build a capabilities object. That sounds like the bigger issue to me. Let's wait until Core/Security et al have a look, hopefully they can come up with a fix. If they don't let's go with your optimisation. |
I think the changes in this PR are still worthwhile as they reduce the total calls to |
@@ -6,6 +6,7 @@ | |||
*/ | |||
|
|||
import { KibanaRequest } from '@kbn/core/server'; | |||
// import { memoize } from 'lodash'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we can remove this line
The issue for Core: #146881 |
Code LGTM 🎉 |
💚 Build Succeeded
Metrics [docs]Unknown metric groupsESLint disabled in files
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
PR #146155 introduced a cache of the ML saved objects to the `mlSavedObjectService` to improve performance. This can cause a problem for the module `setup` function when called more than once from an external plugin in a single request. A single instance of the `mlSavedObjectService` is used per request and so for each `setup` call the same cache is used. Any saved objects created, updated or removed in the first `setup` call are missing from the cache in subsequent calls. This means any jobs being created in the second call to `setup` cannot be opened as do not exist in the cache. This PR clears the cache after every write action to the saved object client causing it to be repopulated the next time it is read.
PR elastic#146155 introduced a cache of the ML saved objects to the `mlSavedObjectService` to improve performance. This can cause a problem for the module `setup` function when called more than once from an external plugin in a single request. A single instance of the `mlSavedObjectService` is used per request and so for each `setup` call the same cache is used. Any saved objects created, updated or removed in the first `setup` call are missing from the cache in subsequent calls. This means any jobs being created in the second call to `setup` cannot be opened as do not exist in the cache. This PR clears the cache after every write action to the saved object client causing it to be repopulated the next time it is read. (cherry picked from commit f6bb0f4)
# Backport This will backport the following commits from `main` to `8.7`: - [[ML] Fixing ML saved object cache (#151122)](#151122) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"James Gowdy","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-02-20T09:41:14Z","message":"[ML] Fixing ML saved object cache (#151122)\n\nPR #146155 introduced a cache of\r\nthe ML saved objects to the `mlSavedObjectService` to improve\r\nperformance.\r\n\r\nThis can cause a problem for the module `setup` function when called\r\nmore than once from an external plugin in a single request.\r\nA single instance of the `mlSavedObjectService` is used per request and\r\nso for each `setup` call the same cache is used. Any saved objects\r\ncreated, updated or removed in the first `setup` call are missing from\r\nthe cache in subsequent calls.\r\n\r\nThis means any jobs being created in the second call to `setup` cannot\r\nbe opened as do not exist in the cache.\r\n\r\nThis PR clears the cache after every write action to the saved object\r\nclient causing it to be repopulated the next time it is read.","sha":"f6bb0f4fccab76791ef292ebd90df581c3fbdac6","branchLabelMapping":{"^v8.8.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","non-issue",":ml","Feature:Anomaly Detection","release_note:skip","v8.7.0","v8.8.0"],"number":151122,"url":"https://github.com/elastic/kibana/pull/151122","mergeCommit":{"message":"[ML] Fixing ML saved object cache (#151122)\n\nPR #146155 introduced a cache of\r\nthe ML saved objects to the `mlSavedObjectService` to improve\r\nperformance.\r\n\r\nThis can cause a problem for the module `setup` function when called\r\nmore than once from an external plugin in a single request.\r\nA single instance of the `mlSavedObjectService` is used per request and\r\nso for each `setup` call the same cache is used. Any saved objects\r\ncreated, updated or removed in the first `setup` call are missing from\r\nthe cache in subsequent calls.\r\n\r\nThis means any jobs being created in the second call to `setup` cannot\r\nbe opened as do not exist in the cache.\r\n\r\nThis PR clears the cache after every write action to the saved object\r\nclient causing it to be repopulated the next time it is read.","sha":"f6bb0f4fccab76791ef292ebd90df581c3fbdac6"}},"sourceBranch":"main","suggestedTargetBranches":["8.7"],"targetPullRequestStates":[{"branch":"8.7","label":"v8.7.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.8.0","labelRegex":"^v8.8.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/151122","number":151122,"mergeCommit":{"message":"[ML] Fixing ML saved object cache (#151122)\n\nPR #146155 introduced a cache of\r\nthe ML saved objects to the `mlSavedObjectService` to improve\r\nperformance.\r\n\r\nThis can cause a problem for the module `setup` function when called\r\nmore than once from an external plugin in a single request.\r\nA single instance of the `mlSavedObjectService` is used per request and\r\nso for each `setup` call the same cache is used. Any saved objects\r\ncreated, updated or removed in the first `setup` call are missing from\r\nthe cache in subsequent calls.\r\n\r\nThis means any jobs being created in the second call to `setup` cannot\r\nbe opened as do not exist in the cache.\r\n\r\nThis PR clears the cache after every write action to the saved object\r\nclient causing it to be repopulated the next time it is read.","sha":"f6bb0f4fccab76791ef292ebd90df581c3fbdac6"}}]}] BACKPORT--> Co-authored-by: James Gowdy <[email protected]>
…ser (#160266) Fixes a bug introduced in PR #146155 A user who cannot see all spaces will incorrectly be told that jobs which only exist in spaces they cannot see are in need of synchronisation. The problem was caused by an accident replacement of the `internalSavedObjectsClient` function (which can see all spaces) with the cached saved objects client which can only see the user's allowed spaces. The fix is to revert to the original code. This particular scenario was not covered by API tests. The tests have also been updated in this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
…ser (elastic#160266) Fixes a bug introduced in PR elastic#146155 A user who cannot see all spaces will incorrectly be told that jobs which only exist in spaces they cannot see are in need of synchronisation. The problem was caused by an accident replacement of the `internalSavedObjectsClient` function (which can see all spaces) with the cached saved objects client which can only see the user's allowed spaces. The fix is to revert to the original code. This particular scenario was not covered by API tests. The tests have also been updated in this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios (cherry picked from commit 7aa1dca)
Closes #144897
Improvements to the way we are using the saved objects client when interacting loading ML saved objects:
memoizes the calls to the saved object client's find method, based on request for the duration of the request. some calls make multiple requests for the full ml saved object list, and each one checks privileges, so by caching the list we reduce the total number of calls to
_has_privileges
triggered by saved object client interaction.Improvements to the way we are resolving the capabilities per request:
Memoizes calls to
resolveCapabilities
for the duration of the request. Our shared function providers receive the request object and can share out multiple functions, each using the same request object. This allows us to cache the capabilities check used for each function. e.g.getAnomalyDetectorsProvider
sharesjobs
,jobStats
,datafeeds
andatafeedStats
, if a plugin calls all of these in the same request, only one call toresolveCapabilities
will be made.This is what the APM plugin was originally doing, calling
jobs
,jobStats
anddatafeedStats
however this has also been changed to a single ML shared functiongetJobsState
which provides the minimum job state information.Before:
31 calls
After:
13 calls
The red highlighted section above are all calls that take place inside the
capabilities
plugin'sresolveCapabilities
method.These are caused by the various switchers registered by plugins which are called to build up the capabilities list.
We need these capabilities to ensure the user triggering the calls to our shared functions has the correct permissions to perform these checks.