[APM] Harden Services, Overview, Operations, Dependencies, and Topology pages for scale - #2860
[APM] Harden Services, Overview, Operations, Dependencies, and Topology pages for scale#2860ps48 wants to merge 5 commits into
Conversation
…gy pages for scale Hardens the APM pages for high service and environment cardinality (hundreds of services across many environments), addressing the query, concurrency, and rendering issues that surface at that scale. Query construction - Services home and Topology no longer embed a service=~"a|b|c|..." regex built from the full service list. The queries rely on the existing sum by (service) grouping instead, so query length is constant regardless of fleet size. This removes the 10,000 character PromQL rejection that blanked the failure rate and (via a shared Promise.all) the throughput columns. - Application and group builders now filter remoteService="" so server span metrics are not double counted with client spans and latency histograms are not mixed across span kinds. - Added escape helpers for PromQL label, PromQL regex, and PPL string contexts and applied them to interpolated service, environment, operation, and remote values. - Added a row cap (| head) to the unbounded PPL list and service map queries and removed two dead PPL builders. Concurrency and correctness - Replaced all-or-nothing Promise.all with Promise.allSettled in the Services home, Operations, Dependencies, and Topology metric hooks so one failing query no longer blanks unrelated columns. - Added AbortController based stale response guards to every APM data hook, and threaded an optional AbortSignal through the PromQL and PPL search services, so responses from superseded refreshes or time range changes cannot overwrite fresh state. - Fixed a Topology refresh that fetched the map twice. Rendering - Services home no longer collapses to a single service on refresh: the metric range filters are now gated on whether the user actually moved the slider, and the bounds sync waits for metrics to settle instead of clobbering the selection from partial data. The same gating was applied to Operations and Dependencies. - Topology memoizes the graph layout so a metrics tick or an edge selection no longer forces a full dagre relayout, and adds a node cap with a clear notice above it. - Reconciled the environment value passed to the Operations list and metrics hooks so both query the same environment.
PR Reviewer Guide 🔍(Review updated until commit 9a71e59)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 9a71e59 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 49034aa
Suggestions up to commit a7b10e5
Suggestions up to commit 6f8c14c
Suggestions up to commit b239601
Suggestions up to commit 8bfc76a
|
…, C6) C3: Services home metric queries grouped by service only, so a service running in multiple environments had its throughput/error/latency summed into one value shown on every environment row. Group the throughput, total, failure-ratio, and latency builders by (environment, service[, le]); match both service and environment when extracting series; and key the catalog metrics map on (service, environment) via a shared serviceNodeKey helper used by the services-home lookups. C6: The catalog failure rate averaged the per-step ratio, which over-weights low-traffic steps. Compute it as a ratio of summed totals over the range ((error+fault)/request) via a new windowed instant query, matching how the topology and top-services widgets already compute it.
|
Persistent review updated to latest commit 8bfc76a |
… card Averaging per-step P99 values (showTotal) is statistically meaningless. The card now shows the most-recent P99 with a "Latest" label instead of an "Avg" of percentiles. Fault/error/availability cards keep "Avg" (averaging a rate is valid).
|
Persistent review updated to latest commit b239601 |
Follow-up to the label fix: instead of the most-recent-scrape P99, the card now computes the P99 over the selected time range (histogram_quantile over sum_over_time(latency_seconds_bucket[range])), matching the catalog. Dropped the subtitle entirely (title + time picker already convey it, like the throughput card). Shows a real value for services with latency data and a clean "-" only when a service genuinely has none.
|
Persistent review updated to latest commit 6f8c14c |
|
Persistent review updated to latest commit a7b10e5 |
a7b10e5 to
49034aa
Compare
|
Persistent review updated to latest commit 49034aa |
At fleet scale the per-step range queries (sparklines) dominated cost: they returned ~services x ~100 points and extraction was O(services x points), delaying catalog metrics. Sparklines are only shown for the ~10/25/50 rows on the current page, so fetching them for every service was waste. Split the metrics hook: instant queries (throughput total, failure-ratio, latency P99) stay global so the numbers, sorting, and range filters cover all services; the three range/sparkline queries now run only for the visible page via a bounded service=~ filter (small, well under the 10k-char limit). services_home mirrors the table page + sort to derive the visible slice. The visible-page fetch is cached and incremental: revisiting a page already in memory issues no new request, only the not-yet-fetched services on a page are queried, page changes are debounced so skimming past pages does not fire a request per intermediate page, and superseded fetches are aborted. A time range, percentile, or refresh change clears the cache so no stale series remain.
49034aa to
9a71e59
Compare
|
Persistent review updated to latest commit 9a71e59 |
Summary
This PR hardens all five APM pages (Services home, Service overview, Service operations, Service dependencies, Topology map) at high service and environment cardinality (hundreds of services across many environments), where query construction, request concurrency, and rendering issues surface. The changes came from an audit of every PromQL and PPL query builder plus the data hooks and page components.
Problems addressed
1. Oversized PromQL queries
The Services home and Topology pages built a
service=~"a|b|c|..."filter from the entire service list and embedded it into each metric query. The failure rate query embedded it three times, so at a few hundred services it crossed the datasource query-length limit and returned HTTP 400. That blanked the failure rate column, and because throughput shared an all-or-nothingPromise.allwith it, throughput went blank too.Fix: rely on the existing
sum by (service)grouping instead of a service list filter, so query length is constant regardless of fleet size. The service filter is now optional and omitted when empty.2. All-or-nothing metric fetching
Services home, Operations, Dependencies, and Topology fetched their metric queries with
Promise.all, so one failing query blanked every column, not just its own.Fix:
Promise.allSettledwith per-metric handling, so a single failure only affects its own column.3. Racy renders on refresh
No APM data hook cancelled or guarded in-flight requests, so a response from a superseded refresh or time range change could overwrite fresh state. On Services home this manifested as the catalog collapsing to a single service on some refreshes, because an auto-derived filter range computed from partial data was copied into the active filter.
Fix: AbortController based stale-response guards in every data hook (matching the existing pattern in
use_apm_config.ts), an optionalAbortSignalthreaded through the PromQL and PPL search services, gating the Services home / Operations / Dependencies range filters on whether the user actually moved the slider, and waiting for metrics to settle before syncing bounds. Also fixed a Topology refresh that fetched the map twice.4. Query correctness and safety
remoteService=""so server span metrics are not double counted with client spans, and latency histograms are not mixed across span kinds.5. Multi-environment metrics and failure-rate accuracy
(environment, service)and the metrics map keys on both.6. Service overview P99 latency card
The P99 card averaged per-step P99 values (labelled "Avg"), which is not meaningful for a percentile. It now shows the P99 over the selected range and drops the misleading subtitle.
7. Topology rendering
The graph rebuilt all nodes and ran a full dagre relayout on every metrics tick and every edge selection.
Fix: memoize the structural layout separately from the metric and selection overlays so a metrics update or edge click no longer triggers relayout, plus a node cap with a clear notice above it.
8. Catalog sparklines fetched per visible page
The per-step range queries that back the row sparklines were fetched for every service, even though sparklines are only shown for the rows on the current page. Instant metrics (numbers, sort, filter) still load for all services; the range/sparkline queries now run only for the visible page via a bounded, escaped
service=~filter.The visible-page fetch is cached and incremental: revisiting a page whose series are already in memory issues no new request, only the not-yet-fetched services on a page are queried, page changes are debounced so skimming past pages does not fire a request per intermediate page, and superseded fetches are aborted. A time range, percentile, or refresh change clears the cache so no stale series remain.
Follow ups (intentionally not in this PR)
These were found in the audit but need product decisions or live data validation and are better as separate changes: aligning the throughput unit across all surfaces, windowing the remaining Service overview RED cards over the selected range, and reconciling the availability (5xx only) vs failure ratio (4xx and 5xx) definitions.
Testing
Notes
No user facing strings changed beyond the Topology "too many services" notice and the Service overview P99 card subtitle.