Skip to content

Conversation

@flash1293
Copy link
Contributor

@flash1293 flash1293 commented Jul 31, 2025

When dealing with lots of streams, performance of the Kibana layer of streams can be an issue.

This PR is fixing the worst problems - there are further gains possible, but this is shifting the bottleneck to Elasticsearch in most situations.

More performant type guards

The zod-based Definition.is implementation basically did a complete parse, which is pretty expensive. This PR replaces it with a very simple typeguard. Since the argument passed to Definition.is is already limited to a base stream definition, this isn't losing any functionality.

Do not parse when loading from storage

We used to parse the documents coming from the storage adapter, but this can be pretty expensive if there are lots of streams, and it shouldn't be required since we make sure we only store good definitions on the way in, so we don't need to check again on the way out.

On the contrary, if there is a bug and the documents from storage don't 100% match the zod schema, with the parse in place on read streams stops working completely. Using the malformed doc in a best-effort way might cause other issues somewhere else, but it is minimizing the impact. If you feel strongly about it, maybe we can keep the additional check in test/dev mode, but disable it on prod.

Another subtle change this causes is that default values from the zod schema wouldn't be set anymore on definitions coming from the storage adapter - I'm not aware we rely on this right now, and it might be better not to anyway (changing defaults is very close to a breaking change anyway, but more hidden)

Do not re-fetch the same information if we have it already

This is the worst offender - in the validation logic for a wired stream upsert we would check whether there are non-wired streams as ancestors by doing requests to Elasticsearch and we would also re-fetch all ancestors and descendants again for definitions. This PR changes this logic to only check for conflicts for new streams and uses desiredState to check for ancestors and descendants instead of refetching them

Make it cheap to find whether a stream is an ancestor

isDescendantOf is called a lot to find ancestors and descendants, but it was written in a super verbose and inefficient way. This PR fixes that.

@flash1293
Copy link
Contributor Author

/ci

@flash1293
Copy link
Contributor Author

/ci

@flash1293
Copy link
Contributor Author

/ci

@flash1293 flash1293 added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Team:obs-onboarding Observability Onboarding Team Feature:Streams This is the label for the Streams Project v9.2.0 labels Jul 31, 2025
@flash1293
Copy link
Contributor Author

/ci

@flash1293
Copy link
Contributor Author

/ci

@flash1293 flash1293 marked this pull request as ready for review August 1, 2025 05:33
@flash1293 flash1293 requested a review from a team as a code owner August 1, 2025 05:33
@elasticmachine
Copy link
Contributor

Pinging @elastic/obs-ux-logs-team (Team:obs-ux-logs)

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
streamsApp 616.3KB 616.5KB +213.0B

History

Copy link
Contributor

@miltonhultgren miltonhultgren left a comment

Choose a reason for hiding this comment

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

This looks good to me, my only concern is that we now need to pay much more attention to any kind of schema change to those stored documents and make sure our migration code works well and is tested.

@flash1293 flash1293 merged commit 5038b9a into elastic:main Aug 6, 2025
12 checks passed
@wildemat wildemat mentioned this pull request Aug 7, 2025
10 tasks
NicholasPeretti pushed a commit to NicholasPeretti/kibana that referenced this pull request Aug 18, 2025
When dealing with lots of streams, performance of the Kibana layer of
streams can be an issue.

This PR is fixing the worst problems - there are further gains possible,
but this is shifting the bottleneck to Elasticsearch in most situations.

# More performant type guards

The zod-based `Definition.is` implementation basically did a complete
parse, which is pretty expensive. This PR replaces it with a very simple
typeguard. Since the argument passed to `Definition.is` is already
limited to a base stream definition, this isn't losing any
functionality.

# Do not parse when loading from storage

We used to parse the documents coming from the storage adapter, but this
can be pretty expensive if there are lots of streams, and it shouldn't
be required since we make sure we only store good definitions on the way
in, so we don't need to check again on the way out.

On the contrary, if there is a bug and the documents from storage don't
100% match the zod schema, with the parse in place on read streams stops
working completely. Using the malformed doc in a best-effort way might
cause other issues somewhere else, but it is minimizing the impact. If
you feel strongly about it, maybe we can keep the additional check in
test/dev mode, but disable it on prod.

Another subtle change this causes is that default values from the zod
schema wouldn't be set anymore on definitions coming from the storage
adapter - I'm not aware we rely on this right now, and it might be
better not to anyway (changing defaults is very close to a breaking
change anyway, but more hidden)

# Do not re-fetch the same information if we have it already

This is the worst offender - in the validation logic for a wired stream
upsert we would check whether there are non-wired streams as ancestors
by doing requests to Elasticsearch and we would also re-fetch all
ancestors and descendants _again_ for definitions. This PR changes this
logic to only check for conflicts for new streams and uses
`desiredState` to check for ancestors and descendants instead of
refetching them

# Make it cheap to find whether a stream is an ancestor

`isDescendantOf` is called a lot to find ancestors and descendants, but
it was written in a super verbose and inefficient way. This PR fixes
that.
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 Feature:Streams This is the label for the Streams Project release_note:skip Skip the PR/issue when compiling release notes Team:obs-onboarding Observability Onboarding Team v9.2.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants