Fan the service-map rollup out over projects that actually send spans - #482
Conversation
`trigger load-chat on #ai-response-container` failed to parse: hyperscript tokenizes the `-` as minus, so the whole feature block was rejected with three parse errors and neither handler was ever bound. The AI panel therefore never fetched its history — not on toggle, and not on reload with the panel remembered open. Quoting the event name is the existing convention here (`trigger 'update-query' on window`). Also put hx-swap on the drawer's lazy-load element instead of leaving it to be inherited from the wrapper. htmx 4 resolves inheritance explicitly, so a parent's value no longer reaches the child. No behaviour change: every drawer_ caller passes Nothing for the url, so the element is not rendered today, and htmx 4's defaultSwap is innerHTML anyway.
The dispatcher enqueued one rollup per active project — 1726 of them in production, every 5 minutes. The rollup's span self-join costs the same scan whether or not a project has traffic, and almost none do at any given tick, so ~all of that budget went to proving there was nothing to do: ~497k odd-jobs and ~5.8 TimeFusion self-joins per second, of the query shape that has OOM-killed it before. projectsWithSpansInRange answers for every project in one scan. It is deliberately rollupServiceEdges' own FROM clause with the per-project predicate lifted out — same store, window and kind filter — so a project is on the list exactly when the rollup would have found rows for it. project_id is read as text and parsed rather than cast with ::uuid, which DataFusion has no type for, and an unparseable value is dropped rather than costing every other project its bucket. Follows runHourlyJob, which already derives its project list from the span table with no projects.active intersection.
ReviewSolid, well-documented change — the Haddock comments carry real justification (window/kind-filter parity with Reuse: this is now the 3rd near-duplicate "distinct project_id from spans in a window" query
The divergence from the spec doc is defensible — Correctness
Performance / Test coverage / Security
Minor
Nice find on the hyperscript minus-sign parse failure — that's a nasty silent breakage (three parse errors, no exception surfaced to the user) and the fix is exactly right (matches the existing |
What
Two commits:
fix(ai panel)— quote theload-chatevent so the chat actually loads (htmx 4 follow-up).projectsWithSpansInRangeanswers for every project in one scan. It is deliberatelyrollupServiceEdges' ownFROMclause with the per-project predicate lifted out — same store, window andkindfilter — so a project is on the list exactly when the rollup would have found rows for it.Notes
project_idis read as text and parsed rather than cast with::uuid(DataFusion has no uuid type); an unparseable value is dropped rather than costing every other project its bucket.runHourlyJob, which already derives its project list from the span table with noprojects.activeintersection.ENABLE_SERVICE_MAP_ROLLUPis still off, soapis.service_dependency_edgesstays empty and every service map still renders "No service activity". This is prep for turning that flag on.Testing
projectDiscovery_listsOnlyProjectsThatSentSpansInTheBucketpins both directions — the fixture's bucket returns[testPid], and a window where that same still-active project sent nothing returns[]. All 8 ServiceMap specs pass.Unverified: the discovery query is exercised against Postgres in tests (
useTf = False). Its behaviour on TimeFusion — where it will actually run — has not been measured. It is a much gentler shape than the self-join (single column, no join, one 5-minute window, low-cardinality DISTINCT), but it should be timed against real TF beforeENABLE_SERVICE_MAP_ROLLUPis flipped on.🤖 Generated with Claude Code
https://claude.ai/code/session_014AteFpMusSMajKqCe2kPJa