The derived-parent recompute in buildChildCounts may hit the agent
fallback scan once per guest, and that scan walked every resource with
per-entry string checks, an O(n^2) pass per registry ingest. The pass
now buckets agent resources by node name up front and the fallback
walks only the matching bucket. Selection is order-independent (best
score with smallest-ID tie-break) and scores read live through shared
pointers, so results are unchanged; callers outside the pass keep the
full scan.
Contract-Neutral: performance-only scan bucketing in the parent recompute pass, selection semantics unchanged
The incident recorder asks for current metrics for every monitored
resource every five seconds, and the adapter answered each request by
scanning all views with a formatted VMID comparison per candidate. At
thousands of resources that is millions of comparisons and allocations
per tick, forever. The adapter now exposes a batch method that builds
the full lookup in one pass over the views, keyed by every ID form the
per-ID method matches with the same first-match precedence, and the
recorder uses it when the provider offers it. Per-ID behavior is pinned
to the batch by an equivalence test, including VMID collisions.
Contract-Neutral: performance-only batch lookup addition, per-ID metrics resolution is pinned unchanged by equivalence tests
The fallback attachment pass compared every group against every other
group with pairwise hostname normalization, allocating sorted root and
host sets per call. Resolving top-level systems runs inside the state
request path, so large estates paid a quadratic normalization storm per
request. Hostname equivalence requires the two names to share either a
comparable form or a short form, so an index of groups by those forms
prunes each group's candidates to the provable superset and the
original pairwise matcher still decides every surviving pair. Attachment
decisions are unchanged.
Contract-Neutral: performance-only candidate pruning, the original matcher still decides every attachment
Rebuilding the registry view caches resolved each resource's metrics
target by scanning every bySource mapping, an O(resources x mappings)
pass holding the write lock. At about five thousand resources that is
tens of millions of map iterations per rebuild, and rebuilds run on
every poll cycle. The rebuild now inverts the mappings once and the
cached index also serves single-resource lookups while the views are
clean, with the legacy scan kept for dirty-state callers. An
equivalence test pins index results to the legacy scan.
The agent-parent fallback scan also ran its per-entry string
canonicalization before the type check, so every guest in the estate
paid allocation cost in a scan that only ever matches agents. The
cheap checks now run first.
Contract-Neutral: performance-only rework of metrics-target resolution and scan ordering, resolved values are pinned unchanged by equivalence tests
A handler that flushes before its first write commits the response
headers with an implicit 200. The gzip wrapper deferred its
compress-or-not decision to the first write, so such a handler would
send headers without Content-Encoding and then a compressed body,
which clients cannot decode. Every current flusher is an SSE handler
whose content type never compresses, so this was latent, but any
future streaming JSON endpoint would have corrupted silently. Flush
now runs the decision first, matching net/http header-commit
semantics.
The broadcast ticker logged seven fields at Info level every tick and
built a full state snapshot to do it, before checking whether any
WebSocket subscriber existed. An idle server paid a snapshot copy plus
roughly 8600 log lines a day for nothing. The log is now Debug and both
the log and the snapshot build sit behind the subscriber check.
The poll task worker pool was also fixed at ten workers no matter how
many instances an estate monitors. POLL_TASK_WORKERS now overrides the
count and the cap, bounded at 128, following the existing env knob
pattern. Behavior without the variable is unchanged.
Contract-Neutral: operational log demotion and an opt-in worker pool env knob, no monitoring or agent-lifecycle contract semantics change
The browser advertises max_message_bytes on the upgrade request and the
server withholds any state frame larger than that, falling back to REST
recovery. The advertised value was still the historical 8 MiB, which
pushed estates past roughly 3100 resources off the cheap socket delta
path and onto a 30 second full-state REST poll loop. 32 MiB keeps
estates about four times that size on socket deltas. The server honors
whatever the client advertises, so no backend change is needed.
Contract-Neutral: transport frame-limit constant, no alert lifecycle semantics change
Large estates that exceed the WebSocket frame ceiling recover over an
uncompressed /api/state poll loop, which made the biggest deployments
pay the heaviest transfer cost. Wrap the main HTTP handler in a gzip
middleware that compresses JSON and other text responses roughly an
order of magnitude, while leaving WebSocket upgrades, SSE streams,
Range requests, small declared bodies, and binary content untouched.
Contract-Neutral: transport-only gzip response middleware, no RBAC or persistence semantics change
checkZFSPoolHealth only runs while storage.ZFSPool is attached, so its
clear paths never fire once the attachment goes away and previously raised
zfs-pool-state, zfs-pool-errors, and zfs-device alerts linger until the
multi-day stale cleanup. Clear them on the spot when a storage is checked
without a pool. This is the recovery path for the shared storages that were
wrongly matched to a node-local pool before the matcher's type gate.
Refs #1731
matchZFSPoolForStorage's sole-pool fallback attached the node's only ZFS
pool to every storage the per-node endpoint returned, including NFS, CIFS,
PBS, and RBD entries that can never be backed by a local pool. One failing
device then raised a duplicate ZFS device alert per shared storage. Gate
the matcher on isInherentlySharedStorageType so remote-backed storages are
never matched, while dir-type storages keep the single-pool fallback.
Refs #1731