Skip to content

[DataViews] - Add caching to fields.toSpec#224767

Closed
michaelolo24 wants to merge 3 commits intoelastic:mainfrom
michaelolo24:improve-fields-caching
Closed

[DataViews] - Add caching to fields.toSpec#224767
michaelolo24 wants to merge 3 commits intoelastic:mainfrom
michaelolo24:improve-fields-caching

Conversation

@michaelolo24
Copy link
Contributor

@michaelolo24 michaelolo24 commented Jun 21, 2025

Summary

The goal of this PR is primarily for performance improvements. When the index patterns in a given dataview have a significant enough number of fields, the performance of the application can slow down pretty significantly. When accessing the dataview saved object directly, we have the benefit of the network request being cached here.

This drastically helps with the performance when working with the dataview directly, but when working with the DataViewSpec produced by DataView.toSpec() here, there is no such optimization. This can be problematic given a significant number of fields and the nested iterations in the fields.toSpec() call seen here and here.

This PR introduces a simple in memory cache for this potentially expensive fields.toSpec() call.

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

Identify risks

  • Potential risk: since the cache is entirely in the UI, there could be a potential issue confirming the cache is invalidated, bust since it is entirely in memory, this is mitigated by just refreshing the given view.

@michaelolo24 michaelolo24 mentioned this pull request Jun 22, 2025
9 tasks
@michaelolo24 michaelolo24 force-pushed the improve-fields-caching branch from 90659fd to c50000e Compare June 22, 2025 11:13
@michaelolo24 michaelolo24 added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting v9.1.0 v8.19.0 Team:Threat Hunting:Investigations Security Solution Threat Hunting Investigations Team labels Jun 22, 2025
expect(shortDotsList[0].spec.shortDotsEnable).toBe(true);
});

it('does not fail if add is called with a field with the same name', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, but not sure if this should be valid?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm hard to say, but I'd be hesitant to change it now 😅

this.byName.delete(field.name);

const fieldIndex = findIndex(this, { name: field.name });
if (fieldIndex === -1) return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if a name is provided not actually in the list, the mutation still took place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, so did this used to just always remove the last field in the list if passed a not-found field? 🤔

@michaelolo24 michaelolo24 changed the title add caching to fields spec call [DataViews] - Add caching to fields.toSpec Jun 23, 2025
@michaelolo24 michaelolo24 added ci:cloud-deploy Create or update a Cloud deployment ci:cloud-redeploy Always create a new Cloud deployment ci:cloud-persist-deployment Persist cloud deployment indefinitely and removed ci:cloud-redeploy Always create a new Cloud deployment labels Jun 23, 2025
@michaelolo24 michaelolo24 marked this pull request as ready for review June 23, 2025 04:09
@michaelolo24 michaelolo24 requested a review from a team as a code owner June 23, 2025 04:09
@elasticmachine
Copy link
Contributor

Pinging @elastic/security-threat-hunting-investigations (Team:Threat Hunting:Investigations)

@elasticmachine
Copy link
Contributor

elasticmachine commented Jun 24, 2025

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] Investigations - Security Solution Cypress Tests #1 / Alert details expandable flyout right panel should mark as acknowledged should mark as acknowledged
  • [job] [logs] Serverless Investigations - Security Solution Cypress Tests #10 / Alert details expandable flyout right panel should mark as acknowledged should mark as acknowledged
  • [job] [logs] Investigations - Security Solution Cypress Tests #1 / Alert details expandable flyout right panel should mark as closed should mark as closed
  • [job] [logs] Serverless Investigations - Security Solution Cypress Tests #10 / Alert details expandable flyout right panel should mark as closed should mark as closed
  • [job] [logs] FTR Configs #100 / Rules Management - Rule Bulk Action API @ess @serverless @skipInServerless perform_bulk_action @skipInServerlessMKI fill gaps run action should trigger the backfilling of the gaps for the rules in the request

Metrics [docs]

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
dataViews 62.0KB 62.3KB +329.0B
Unknown metric groups

API count

id before after diff
dataViews 1241 1243 +2

History

Copy link
Contributor

@davismcphee davismcphee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes look good in general and the idea makes sense, but it's hard to have confidence in these types of changes since data views are so broadly relied on. I left a few comments and questions, and some ideas on how we might better gauge the impact. It's going to be a tricky one for sure.

Also what approach were you using for testing the changes locally? I'm not really sure where to begin with it tbh.

Comment on lines +451 to +453
const firstSpec = indexPattern.fields.toSpec();
const secondSpec = indexPattern.fields.toSpec();
expect(firstSpec).toEqual(secondSpec);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One risk here is if there's anywhere in the codebase that retrieves and modifies a spec, it could corrupt the cache value. Maybe we should use defaultFreeze on it like what's done on responses in this PR, which will freeze it in dev and help catch these issues.

Do you know if CI runs in dev? I'd feel a bit more confident if we had a full CI run with freezing enabled in case it reveals any issues. Otherwise we might want to let it go through CI once with deepFreeze explicitly to see if it passes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to actually have some tests for this now 👍

Comment on lines +226 to +234
it('does not throw if remove is called on a field not in the list', () => {
const field = new DataViewField({ ...baseField, name: 'notfound' });
expect(() => list.remove(field)).not.toThrow();
});

it('does not throw if update is called on a field not in the list', () => {
const field: FieldSpec = { ...baseField, name: 'notfound' };
expect(() => list.update(field)).not.toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these duplicates of the above tests with "handles _ on non-existent field gracefully"?

expect(shortDotsList[0].spec.shortDotsEnable).toBe(true);
});

it('does not fail if add is called with a field with the same name', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm hard to say, but I'd be hesitant to change it now 😅

Comment on lines 342 to +343
existingField.runtimeField = undefined;
this.fields.clearSpecCache();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sucks that we need to expose a public clearSpecCache method just for this case... But it also sucks that we do this by mutating the field (and that we expose mutable props on the field class in the first place). Kinda makes me feel like we should try sticking readonly on all the public field class props and run a CI build to see if we catch anywhere else in the codebase where we do this, since it could corrupt the cache value.

Thinking about it a bit more, wouldn't we need to invalidate the entire field list cache whenever any property of any field is modified? Most of them look to be mutable.

this.byName.delete(field.name);

const fieldIndex = findIndex(this, { name: field.name });
if (fieldIndex === -1) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, so did this used to just always remove the last field in the list if passed a not-found field? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of questions to better understand the security use case:

  • Why does security use data view specs so heavily? Would it not work better to use actual data view instances instead, which are already cached by the data views service?
  • Would using specs that don't include the fields, e.g. toSpec(false) or toMinimalSpec() help at all here? I'm guessing not since presumably you want to keep the fields around?

@michaelolo24
Copy link
Contributor Author

Closing this for now as we do not need to rely on this for our performance enhancements, but a noticeable benefit was seen for even Discover with this caching layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting ci:cloud-deploy Create or update a Cloud deployment ci:cloud-persist-deployment Persist cloud deployment indefinitely release_note:skip Skip the PR/issue when compiling release notes Team:Threat Hunting:Investigations Security Solution Threat Hunting Investigations Team v8.19.0 v9.1.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants