feat: seed UI collection watches from the list resourceVersion#6311
Merged
rpelczar merged 6 commits intoJun 22, 2026
Merged
Conversation
✅ Deploy Preview for docs-kargo-io ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## Marvin9/project-grpc-rest-migration #6311 +/- ##
=======================================================================
+ Coverage 58.49% 58.52% +0.03%
=======================================================================
Files 501 503 +2
Lines 42226 42390 +164
=======================================================================
+ Hits 24700 24809 +109
- Misses 16030 16075 +45
- Partials 1496 1506 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This comment was marked as outdated.
This comment was marked as outdated.
c667c73 to
b0bf5eb
Compare
b0bf5eb to
9518bc3
Compare
On large projects the UI's Stage/Warehouse/Promotion/Freight collection streams open their follow-up watch with no resourceVersion, so the API server replays every listed object as an individual ADDED event. On a big project that is thousands of redundant events the browser decodes one at a time, freezing the page for the better part of 20s after content appears. Implement standard Kubernetes list-then-watch seeding on the REST/SSE path. Server: - Return the list-level resourceVersion from the list/query endpoints (Stages, Warehouses, Promotions natively; Freight via a new resourceVersion field on the QueryFreights response). - Accept ?resourceVersion= on the SSE watch endpoints and start the watch from it via buildWatchListOptions. - Read seed lists through an uncached direct reader (listForWatchSeed), gated by an explicit SubjectAccessReview that mirrors the cached client, falling back to the cached client when no direct reader is available. - Surface an expired resourceVersion as an SSE error event so clients can relist, and emit synthetic DELETEs for client-side filtered watches. UI: - Centralize seeding in runSeededWatch: seed each watch from the matching list query's resourceVersion (kept out of effect deps to avoid reconnect churn), relist and reopen on an expired-resourceVersion error, and skip replayed ADDED events. On a ~1k-object project this drops ~2,261 replayed ADDED events per page load to 0 and reduces main-thread blocking during load by ~85%. Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
9518bc3 to
899ee68
Compare
The list+watch seed endpoints read through listForWatchSeed's uncached directReader, which always returns a real list-level resourceVersion. The effectiveResourceVersion max-item fallback only ran on the degraded no-directReader path (tests, or no rest.Config), where its best-effort RV is no better than opening the watch unseeded -- the documented pre-change behavior. Replace it with a small normalizeListResourceVersion that maps the cached client's "0" sentinel to an empty string, and collapse the three near-duplicate resourceVersionFor*List helpers into inline calls. The upsertOrDelete ADDED dedup guard is retained: it makes the degraded unseeded path (which now replays) cheap. Net -120 lines. No behavior change on the production directReader path. Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
11 tasks
Replace the bespoke uncached client built in the server package with the cluster's existing GetAPIReader, exposed via kubernetes.Client.APIReader. This is the same direct-read pattern the controllers and external webhooks server use, and it drops the parallel client.New, its RESTMapper/connection, and the RestConfig-into-server wiring. listForWatchSeed now reads through client.APIReader and authorizes the caller first, single-mode. Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
…ion' into jacobboykin/resource-version-watch Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io> # Conflicts: # ui/src/features/project/pipelines/use-watch-stages.ts # ui/src/features/project/pipelines/use-watch-warehouses.ts # ui/src/features/project/pipelines/watch-utils.ts
…ranch 'origin' into jacobboykin/resource-version-watch
…esource-version-watch
Marvin9
approved these changes
Jun 19, 2026
Marvin9
left a comment
Contributor
There was a problem hiding this comment.
UI LGTM. Tested and checked the UI code.
rpelczar
approved these changes
Jun 22, 2026
030fb79
into
Marvin9/project-grpc-rest-migration
14 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
On big projects the pipeline page can feel loaded-but-frozen: the content paints, and then for several seconds clicks and menus do nothing. The culprit is the collection watches. When the UI lists a collection — Stages, Warehouses, Promotions, Freight — and then opens its live watch, the watch starts from scratch and the API server replays every object it just listed back as an individual ADDED event. On a large project that's thousands of redundant events the browser has to decode and push through React one at a time, and that's what blocks the main thread.
This wires up the standard Kubernetes list-then-watch pattern so it doesn't happen. The list response carries its resourceVersion and the follow-up watch starts from it instead of replaying everything, so the watch opens and stays quiet.
How it works
Impact
On a project with roughly a thousand Stages and Freight, opening the pipeline page used to replay the whole collection — about 960 Stage and 1,300 Freight events on a single open. With seeding the watches replay nothing. The Freight stream alone drops from around 1,300 events and a megabyte and a half of data down to zero.
What matters is the effect on the page: main-thread blocking during load falls by roughly 85%, and the stretch where the UI is unresponsive shrinks from over ten seconds to a few. It's an interaction-responsiveness win rather than a faster first paint — the initial list still has to download and render — and it only shows up at scale. Small projects won't notice it; the large ones that motivated this go from frozen for the better part of twenty seconds to actually usable.