Repository navigation
Hold alert threshold overrides for a server tag (#6926) - #6939
Merged
Merged
Conversation
A threshold override can now be set for a server tag. Precedence is database, then server, then the deepest tag the server is under (the notification routes' own rule), then global. Adds V227, the MCP tag argument, the get_alert_settings tag source, the Viewer and web Admin tag rows, and tests.
This was referenced Oct 29, 2026
… use Assert.Single in the tag scope test
… rule in the server view (#6926) LoadAsync now throws when the tag tables could not be read, so the worker keeps the whole snapshot in force instead of installing one with no tag winners until the next config change. get_alert_settings filtered tag rows through its own known-key and integer checks. It now calls the loader's Accept, so a deeper tag whose value is outside the key's bounds is skipped there as it is in the engine, and the view names the tag the engine applies.
erikdarlingdata
marked this pull request as ready for review
October 30, 2026 00:03
…e read from the store source (#6926) V227 adds a nullable column, two CHECKs and a partial unique index to the operator-written config.config_threshold_overrides table, a handful of rows, so it is declared SetsTheFloor false and the lock-wait multiple stays 5. The tag-name read in the override tool now uses postgres.CreateCommand like its sibling reads, which is the store receiver the read-deadline census recognises.
1 of 3 tasks
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.
Claude posting for Erik Darling.
Closes #6926.
Draft on purpose: this must not merge before the current release is published.
Why
Overrides (#5933) were per server or per database. Production and development needed different thresholds, which meant one override per key per server, and a server added to a tag later started on the global values. A tag scope fixes both.
What changes
PgMigrations.cs,StorageVersion227):config_threshold_overridesgains nullablescope_tag_id,server_idbecomes nullable, a CHECK says exactly one of the two is set (existing rows have a server, so they stay valid), a second CHECK says a tag row has no database, and a partial unique index keeps one value per tag and key. No foreign key on the tag, as V181 did for routes, so a deleted tag leaves the row as an orphan. All statements are idempotent. The viewer's schema probe has a V227 sentinel,MapProbedSchemaVersionhas its arm, andRungRewindArtifactshas the V227 rewind.docs/darling/admin.mdhas the V227 rolling-back line: an older build's loader stops on a row with no server, so it applies no override until tag rows are removed.provision-roles.sqlandDarlingManagedRolesneed no new line. The V191 table-level grants (admin and mcp write, viewer read) already cover the new column.TagThresholdResolver(Storage) runsNotificationRouteScopeExpander.Expand, the routes' own code, on one synthetic route per tag. A tag covers its servers and every tag below it, a deleted tag is orphaned and covers nothing, unreadable tag tables cover nothing. The deepest tag wins (the routes'ScopeTagDepth). The routes break a same-depth tie on the lowest route id, so an override breaks it on the lowest row id (the oldest override).DarlingThresholdOverrides): database, server, tag, global. Tag winners are resolved per server at load, soResolveWirestays a dictionary lookup. Every evaluator reads throughResolveWire(the alert engine, the PostgreSQL count checks, the two worker call sites), so all of them see tag overrides.set_alert_threshold_overridetakestag(name or id) in place ofserver_name,clearworks the same. Writes arm only (an investigation request's change is for its server only), nodatabase_name. A name two tags share is refused until the id is used. An override whose tag was deleted can still be cleared by id.get_alert_settingswithserver_namereportssource: tag_overridewith thetagandtag_id, and liststag_overrides.Test plan
Darling.Tests.exe -class "*ThresholdOverrideTagScopeTests*"on a throwaway PostgreSQL cluster: Total 9, Failed 0. Covers precedence, nested tags, the same-depth tie, an orphaned tag and unreadable tag tables matching nothing, the MCP set/clear/get round trip, misuse refusals, orphan list and clear, and V227 on a store that already held server overrides (rows kept, constraints, second run changes nothing).dotnet build Darling/Darling.Tests: 0 errors.MigrationDataMovingRungCensusPins) with SetsTheFloor false, since it only touches the small operator-written overrides table, soMigrationLockWaitTimeoutSecondsis unchanged. The tag-name read inDarlingMcpThresholdOverrideToolsnow usespostgres.CreateCommand(the store receiverMcpReadCommandTimeoutTestsrecognises). Census, Ratchet, Guard, Migration, Rung, SchemaVersion, McpReadCommandTimeout and threshold override classes: Total 483 and 753, Failed 0.Notes
tag_writes_arm_only, because the investigation arm's change is scoped to the request's server and its journal and undo key on a server id.Click-through (web Admin, Threshold Overrides)
Built the service from this branch and ran it on a throwaway PostgreSQL cluster with its own config file (first log line named that file). Three servers under two tags (prod holds two servers, staging one). Driven with a headless browser at 1400 wide and at 390 wide.
cpu.threshold_percent, 70): row showsTag: prod, saved, no server needed.get_alert_settingsfor a prod server then readsource: globalagain.effective_value 75, source tag_override, tag prod, a server outside the tag readglobal. A server-level override of 90 on one prod server readeffective_value 90, source server_override, so the server value still wins over its tag.Also fixed: CA1068 in
ViewerDataService.CollectionHealth.cs.GetSweepPressureAsyncnow takes the cancellation token last, and its two callers inViewerSweepPressureLiveTestspass it by name. The tag scope test usedAssert.Equal(1, ...Count), which raised xUnit2013, nowAssert.Single.Run:
ThresholdOverrideTagScopeTests,ViewerSweepPressureLiveTests,ViewerSchemaVersionGateTestson the throwaway cluster: Total 44, Failed 0.Changes after a code check
DarlingThresholdOverrides.LoadAsyncthrew nothing whenReadRouteScopeInputsAsyncswallowed a tag read error, so a snapshot with no tag winners was installed and stayed until the next config version bump. It now throws when the tag tables could not be read, so the worker keeps the whole snapshot in force, the same as a failed read of the overrides table. Trigger: a reload that coincides with a connection reset or statement timeout on theserver_tagsorserver_tag_mapread.get_alert_settingsnow filters tag rows through the loader's own accept rule (DarlingThresholdOverrides.Accept, made internal) instead of its own known-key and integer checks. Before, a deeper tag whose value was outside the key's bounds (direct SQL, or a bound tightened later) could be named in the view while the engine skipped it and applied a shallower tag.ThresholdOverrideTagScopeTests: a live test where the tag map cannot be read (the loader throws, the previous tag and server values stay), and a live test where the deeper tag holds 5000 for a percent key (the loader and the view both name the shallower tag at 85).ThresholdOverrideTagScopeTestsTotal 11, Failed 0. All threshold override classes (*ThresholdOverride*) ran Total 90 with 1 failure, a wrong count in my new test, fixed and re-run in the class above. Not re-run after the final rebase onto the branch.