Route API catalog & endpoint-list telemetry reads to TimeFusion - #462
Conversation
The catalog and endpoint-list stat queries read the Postgres pool unconditionally, so projects with TimeFusion reads enabled saw empty event counts, last-seen, and activity charts. TimeFusion is telemetry-only and can't serve apis.* tables, so the single joined query is split: endpoint/host metadata stays on Postgres, telemetry aggregates route via withHasqlTimefusion, and the two are merged, sorted, and paginated in Haskell. The telemetry SQL uses only TF-safe constructs (floor-epoch bucketing, #>> path extraction, no width_bucket/generate_series/ARRAY_AGG); hostCoalesceExpr switched to the equivalent #>> path form. Also fixes two pre-existing test compile breaks (AgenticSpec, ProcessMessageSpec) from an earlier processAIQuery/endpoint signature change.
Review: Route API catalog & endpoint-list telemetry reads to TimeFusionOverviewSolid fix for the underlying problem: metadata ( Correctness / bugs
Performance
Test coverage
Style / conventions
SecurityNo injection concerns — all dynamic SQL fragments go through bound Overall: a well-reasoned split of an unsupportable single-query design, with clean pure-function extraction. Main thing I'd want addressed before merge is bounding the now-unbounded fetch-then-paginate queries for large projects, and ideally a quick staging check against real TimeFusion given the local test limitation. |
Verified against a live TimeFusion instance: the earlier #>> path form
errors ("Operator #>> is not yet supported") — TF's VariantAwareExprPlanner
only rewrites chained ->/->> on the Variant attributes column. Reverted
hostCoalesceExpr and the route expression to ->/->>.
Also restructured both telemetry queries to a filtered CTE + outer GROUP BY
on CTE column names (DataFusion doesn't group by same-level output aliases),
dropped LEAST/GREATEST clamping (denseBuckets ignores out-of-range indices),
switched to integer epoch bucket params, and inlined the window start as an
ISO literal (matching the proven buildDateRange pattern). All confirmed
running on real TF.
Addresses PR review: Map.fromListWith (<>) over lazy Data.Map built up thunk chains under high per-key row counts. Strict Map forces values on insert.
|
Thanks for the review. Addressed / responded per point:
Lazy Map thunks — fixed in Unbounded fetch-then-paginate — real tradeoff, but note the old single query already had to aggregate + sort all matching endpoints in Postgres before Sort by
|
ReviewNice write-up in the PR description — the metadata/telemetry split is a reasonable way to work around TF not serving Bug: empty-string service names are no longer filtered outBoth COALESCE(ARRAY_AGG(DISTINCT service) FILTER (WHERE service IS NOT NULL AND service != ''), ARRAY[]::text[])
-- and for hosts:
... WHERE service IS NOT NULL AND service != '' AND service != host ...The new Haskell-side merge only does: services = V.fromList $ ordNub $ mapMaybe (.service) rs -- endpointRequestStatsByProject
services = V.fromList $ filter (/= h) $ ordNub $ mapMaybe (.service) rs -- dependenciesAndEventsCount
services = V.fromList $ ordNub $ filter (not . T.null) $ mapMaybe (.service) rs
services = V.fromList $ filter (`notElem` [h, ""]) $ ordNub $ mapMaybe (.service) rsPerformance: endpoint metadata query lost its
|
ReviewSolid piece of work — cleanly splits identity (Postgres) from telemetry (TF-routable), and the SQL rewrite to TF-safe constructs (epoch-floor bucketing, no Bug: empty-string
|
…by endpoint hash Threads enableTimefusionReads through the trace/span detail read paths (getSpanRecordsByTraceId, getTraceDetails, getDataPointsData) in Anomalies, Share, Telemetry pages and ApiHandlers, so shared links, issue detail, and trace pages read from TimeFusion when TF reads are enabled. Also bounds the endpoint-catalog telemetry query: instead of scanning every span and evaluating the host JSON fallback, it filters by the endpoint hashes already stamped onto telemetry by the extraction worker (hashes && endpointHashes), addressing the fan-out concern from PR #462.
Problem
The API Catalog and endpoint-list pages showed empty event counts, last-seen, and activity charts for projects with TimeFusion reads enabled. Their traffic-stats queries (
dependenciesAndEventsCount,endpointRequestStatsByProject) read the Postgres pool unconditionally, while dashboard/AI reads were already migrated to TF.Fix
TimeFusion is telemetry-only and can't serve
apis.*tables, so the single joined query can't just be routed. Each query is split:withHasqlTimefusion useTf.The telemetry SQL uses only TF/DataFusion-proven constructs (floor-epoch bucketing instead of
width_bucket/generate_series, per-row services instead ofARRAY_AGG,#>>path extraction instead of chained->/->>).hostCoalesceExprswitched to the equivalent#>>form. The two page handlers threadenableTimefusionReadsfromAuthContext.Also fixes two pre-existing test compile breaks (
AgenticSpec,ProcessMessageSpec) from an earlier signature change.Testing
All 17
ApiCatalogSpecexamples pass, including per-host event attribution and pagination disjointness/total. The real-TF dialect can't be exercised locally (tests point the "timefusion" pool at the same Postgres), so that rests on using only prod-TF-proven constructs.🤖 Generated with Claude Code