Repository navigation
Custom Views and custom alert rules scope server reads by server_id, not server name (#5525) - #5537
Merged
Merged
Conversation
…t server_name (#5525) The composer filtered every raw fact read on server_name = ANY(\). server_name is neither the compression segment column nor the leading index column, so every compressed chunk was read in full. The scope is now server_id = ANY(ARRAY(SELECT server_id FROM collect.servers WHERE server_name = ANY(\))), an InitPlan the planner pushes into each chunk's segment filter. A scoped name with no registry row keeps matching on the stored server_name. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZGNj3cTaf9E9jYjdAa9vA
…takes the same scope as the read, the runner seams are pinned, the semantics are stated (#5525)
…d by server id as by server name (#5525)
…ered name keeps matching what it matched before (#5525)
erikdarlingdata
marked this pull request as ready for review
October 8, 2026 05:49
12 of 14 tasks
This was referenced Oct 8, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Oct 8, 2026
Adds the changelog entries for the 3.10.0 PRs that merged after the first splice: #5528, #5532, #5537, #5542, #5543, #5552 and #5553. Each full entry goes into docs/changelog/3.10.md and its title-only line into the CHANGELOG.md index. The archive census row for 3.10.0 moves from 257 to 265 entries. Changelog entries for the 3.10.0 PRs merged after the first splice.
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.
Fixes #5525
Why
The Custom Views and custom alert rule compiler scoped every fact read with
server_name = ANY($n).server_nameis neither the compression segment column nor the leading index column (server_idis both), so every compressed chunk was read in full and the filter threw most of it away. Every Custom View panel,run_custom_view_panelcall, custom alert rule pass and alert notebook chart cell runs this read.What changes
ComposeCompiler.ServerScopeis the one place the scope is spelled now:f.server_id = ANY(ARRAY(SELECT reg.server_id FROM collect.servers AS reg WHERE reg.server_name = ANY($n))). The text comes fromServerScopeSql.Predicate, which the hourly-edges count guard shares. The$nname list keeps its position. When the run has unregistered names (below) one more boundtext[],$u, follows it. A run with none binds nothing extra, so its parameter list is unchanged.server_id-segmented table: the raw fact read (every source table, including the hourly-raw-edges union and every rollup route, scoped once on thefalias), the Query Store raw dedupe and its partition restriction, the procedure_stats module CTE, the annotation query, the server clock read, and now the hourly-raw-edges count guard (IntervalRollupCountGuard.SqlFor, both its ledger and rollup sides).collect.module_map(keyedserver_name, sql_handle, noserver_id, small) stays scoped by name.Semantics
A scoped name means the server or servers the registry (
collect.servers) holds under that name now, with all of their rows.server_namecan differ fromcollect.servers.server_namefor itsserver_id.server_idis a hash of host and database, so an edit that renames a server keeps the id. The registry row's name is rewritten on the next connect (DarlingObservability.UpsertServerSql,ON CONFLICT (server_id) DO UPDATE SET server_name), and fact rows keep the name they were written under. A removed server keeps its registry row (it is only flagged disabled, never deleted).collect.servershas only its primary key onserver_id; there is no uniqueserver_nameindex, so a name can be held twice.scope.serversare free text) resolves to no id. The runners (RunComposedPanelAsyncandCustomAlertEvaluator) callComposeServerScope.FindUnregisteredAsyncfirst, one read of the small registry table. Names with no registry row go inComposeRunContext.UnregisteredServers, and the compiler addsOR server_name = ANY($u)for those names only, at every scope in the statement (a test counts them). When every name is registered (the normal case) the SQL and parameters carry nothing extra. If the lookup faults, all names are treated as unregistered and the fault is logged as a warning, so a fault costs the old read's price and never loses a server. The alert sweep looks the names up once per server, not once per rule.(ad hoc)on that route, while the raw route labels the same rows correctly. Totals improve against before (those rows were missing entirely); only the labels differ.Measurements
Store numbers are the #5508 audit's reads, run with a hand-written
server_idfilter, not the shipped InitPlan SQL (cold / warm ms, by name vs byserver_id):server_idBy name, the audit's 30 day statement read touched 871 K buffers and removed 124 M rows; with the hand-written id filter 122 K buffers and none removed.
Rig numbers for the shipped InitPlan form (PostgreSQL 18, TimescaleDB 2.30.1, 50 servers, segmentby
server_id, a 3-server scope, 29 day window;EXPLAIN (ANALYZE, BUFFERS)):int[]idsint[]idsserver_idsegment index; by name it is a Parallel Seq Scan of every compressed row. On the many-chunk table the id list shows up asserver_id = ANY((InitPlan 1).col1)in each compressed chunk's scan filter.server_id,server_name, ...): 418 rows removed vs 279,730 by name, on the rig's stand-in table.Test plan
ComposeServerScopeByIdTests(unit): no compiled statement filters a fact onserver_name = ANYalone, over six emitters; the unregistered arm is asserted on EVERY by-id scope of EVERY compiled statement (count of scopes equals count of armed scopes); source pins that the panel runner and the alert sweep each look the names up before compiling and hand the answer on; the count guard's SQL scopes both sides by the shared predicate; fleet-wide runs carry no scope.ComposeServerScopeByIdLiveTests(live, rig): by-id and by-name return identical rows for a two-of-three-server scope; a renamed server keeps its whole history by id; an unregistered name returns its rows throughRunComposedPanelAsyncand throughEvaluateScalarNowAsync, and nothing when the runner's lookup is removed.IntervalRollupCountGuardLiveTests: a renamed server whose rollup hour is short under its old name is now caught by the guard (it passed before); the ghost-server case needs the unregistered arm.ComposeHourlyRawEdgesGuardLiveTests: the hourly-raw-edges statement scoped by id returns the rows it returned scoped by name, and the runner's payload.server_name = ANY($3)fails both guard live tests; each runner's lookup deleted fails only that runner's own live test, and the alert sweep's per-server lookup deleted fails the source pin;ServerScopeput back to the old shape (first round) failed 6 of 7 new tests.DarlingComposeTests,ComposeAnnotationServerClockTests,ServerLocalReadFrameDisciplineTests,ComposeParameterCoverageTests,ComposeHourlyEdgesRunnerTests. Live compose tests andTuningReaderBudgetLiveTestsregister their fixture server incollect.servers, because a scope resolves its names through the registry.*Compose*,*CustomAlert*,*IntervalRollupCountGuard*,*QueryStats*,*ReadScope*,*ServerLocal*,*TuningReader*,*AlertNotebook*,*RollupWindowEndBound*,*QueryStoreIntervalWide*,*DailyStitch*,*DailySummaryAndCompose*, then*Census*,*Ratchet*,*Hygiene*,*Pin*,*Guard*,*Scan*,RepoFileAdoptionTests,FleetIdentifierScrubTests,RegisteredServerSettingDriftTests,TsqlConventionGuardTests: 0 failed (a stale pin inComposeHourlyEdgesRunnerTestsand a stacked doc comment in my own new test were fixed on the way).Review round 1
server_nameindex is gone.Changelog
SECTION: Fixed
ENTRY:
REF:
[Custom Views and custom alert rules scope server reads by server_id, not server name (#5525) #5537]: Custom Views and custom alert rules scope server reads by server_id, not server name (#5525) #5537