Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Lite's portable ZIP is self-contained, which HALVED it** ([#2501]) - `Publish Lite` is now `-r win-x64 --self-contained` in both `build.yml` and `nightly.yml`, so neither Lite artifact has a .NET prerequisite any more and the failure [#2489] documented stops existing: a tester who unzips onto a stock Windows Server no longer meets the .NET host's bare `You must install .NET to run this application` before a line of our code runs. **The size went the opposite way from what bundling a runtime suggests.** The old publish was RID-agnostic, so it copied every platform its packages ship - **537 MB of `runtimes\` on a 565 MB tree** (osx 130, linux-x64 116, linux-arm64 70, win-arm64 56, then win-x86, musl, loongarch64 and riscv64), of which only the **52 MB `win-x64`** folder could ever load on Windows. `DuckDB.NET.Bindings.Full` is most of it, SkiaSharp and SqlClient behind it. Dropping ~485 MB of unloadable native payload beats the cost of bundling .NET, WPF and ASP.NET Core by roughly two to one: measured on one commit and one SDK, **565 MB tree / 212.7 MB zipped becomes 277 MB / 114.2 MB**. It matters most for the **nightly** ZIP, which is the UAT download and is not offered as a `Setup.exe` at all. **A RID-specific publish needed two more files than the flag.** `Lite/packages.lock.json` had only a `net10.0-windows7.0` target, and a RID restore adds `net10.0-windows7.0/win-x64` to it - after which the `dotnet restore --locked-mode` that BOTH workflows run before the publish fails `NU1004: the project's runtime identifiers have changed`, because locked mode compares the PROJECT's RID set (empty) against the lock file's (win-x64). Reproduced locally; that is a red CI run on every PR, not the future `--no-restore` trap it was filed as. The fix is `<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>` in `PerformanceMonitorLite.csproj`, so the project itself asks for that graph and one committed lock file satisfies the RID-less locked-mode restore and the RID publish alike; `RuntimeIdentifiers` (plural) sets no RID on the build, so a plain `dotnet build` stays RID-agnostic and `Lite.Tests` is untouched. **SignPath needed nothing** - the `Lite` artifact-configuration slug already receives both shapes today, and the signed re-zip reads `signed/Lite/*`, inheriting whatever shape `publish/Lite` has. Auto-update is unaffected; the ZIP is not a Velopack channel. `LiteRuntimePrerequisiteDocsTests` went red on the flag alone (3 of its 7 facts) and was rewritten to state every claim BOTH ways round: [#2499]'s version asserted only that the docs DID name the runtimes, so two of its facts stayed green while the prose went stale. It now also derives the lock file's RID coverage from the `-r` flags in the workflows, and every new assertion was proven red with its fix reverted.

### Fixed
- **Managed-store Postgres sizing now re-derives when the HOST changes, not only when the formula does** ([#2845]) - every conf heal block from v1 to v7 is keyed on "is this marker absent?", a question answered once in a cluster's life, so nothing re-derived when the machine underneath it was replaced. All three monitoring boxes were resized 16 GB -> 31.5 GB and kept `effective_cache_size` at 11.86 GB - 75% of the RAM they no longer had - which biases the planner toward sequential scans on a store serving on the order of 670k small index lookups a day. The new v8 block is keyed on a FINGERPRINT of the derivation inputs (RAM and hypertable count) rather than a version, so the two triggers compose: a version marker still heals "we changed our mind about the formula", and this heals "the machine changed". Precedence cannot be ambiguous even though both write the same file, because v8 calls the SAME helpers the version blocks do (`DeriveMemorySettings`, and `DeriveWorkerSettings` extracted here so v2 and v8 cannot drift) - it can only ever differ by being fresher, never by disagreeing. It compares the LAST fingerprint in the conf, not any: a host resized 16 -> 32 -> 16 GB has both, and a `Contains` test would find the stale first one and skip, leaving the 32 GB block still winning by last-occurrence-wins on a box that no longer has 32 GB - the check has to ask the question postgresql.conf itself answers. **The omissions are the load-bearing part.** `shared_buffers` is excluded STRUCTURALLY rather than by trusting its cap to hold: the 1 GB cap is the Windows error-487 mitigation ([#1559], pgsql-bugs BUG #14050 / #18954) and that condition is live on this fleet (measured 4-205 occurrences/day across the three boxes, zero `could not fork` - the retry path holding is exactly the margin a larger segment would spend), so if the cap is ever raised deliberately that is a reviewed formula change, not something a resize propagates on its own. `work_mem` is excluded because the formula would take it 31 -> 63 MB and the only measurements above 31 MB on this store's heaviest read are worse (PlanRegressionSql: default 26,565ms, 31 MB 25,617ms, 512 MB 59,323ms) - and more fundamentally because it is the wrong KIND of setting for a hardware block, a per-sort per-connection ceiling that follows from the query mix rather than from the machine. `max_parallel_workers` is untouched: this class has never set it, so it sits at the PostgreSQL default of 8 regardless of cores, and deriving it is a behaviour change that multiplies the same memory story (each parallel worker gets its own `work_mem`), wanting its own evidence. Re-stating the two worker settings is also what finally makes the v2 block's "never goes stale as collectors are added" claim true - that was true of the formula and false of its marker-keyed application.
- **Four enumerated-path phase stamps were skipped whenever the phase they timed threw** ([#2854]) - `PerItemPlanFetchMs`, `PerItemTextFetchMs`, `PerItemOpenMs` and `PerItemWatermarkMs` were assigned on the line after their `await`, so a phase that threw skipped its own stamp and reported zero. The two fetch stamps are the parents of the sub-split fixed in #2816, and they are the live half: their fetch methods catch their own exceptions and return normally, so the item still completes and still logs, and a failed fetch printed `plan_fetch:0ms` above non-zero `probe:`/`target:`/`write:` children while `PlanFetchOtherMs` clamped the impossible negative residual to zero - a line that reads as precise while being arithmetically impossible. The open and watermark stamps were bare for the same reason but are latent, not live: a throw there propagates out of the driver's per-item `try`, so the item is skipped and prints no split line at all. All four now stamp from `finally` regardless, since the field's honesty should not depend on a caller two files away continuing to discard the item. The log site gates on a new `PerItemPhasesMeasured` flag rather than `PerItemOpenMs > 0`, which suppressed the whole split for a genuinely instant open. The #2851 IL reachability pin is now DERIVED from `CollectorContext` rather than naming two setters, so it covers all twelve stamps on both paths and any stamp added later; naming them is why it did not catch these four.
- **`Collector Cost Regression` compared the day's TOTAL cost, so a cadence recovery read as a regression** ([#2846]) - total daily cost is `runs x cost-per-run`, and the predicate compared totals, so it could not tell "each run got more expensive" from "the same work ran more often". When use1's collection cadence recovered - it had been starved by `query_store` occupying the shared collection body - run counts rose sharply and daily totals rose with them while cost PER RUN fell, and the alert reported the improvement as a regression: 3,259 firings across 612 (server, collector) pairs in one day, 53% of them on collectors whose per-run cost had gone DOWN. Running the shipped predicate against that store, the total-cost rule matched 610 pairs and a per-run rule matched 14. The comparison is now cost per run, against a run-weighted baseline (`sum(sql_ms) / sum(runs)` over the prior days, not an average of daily averages, so a low-run day cannot dominate the baseline it contributes to). That fixes a second defect for free: `latest_day` is a PARTIAL day compared against FULL days, so a total grows monotonically until midnight and the tripping population could only grow within a day - which is exactly the observed shape, 510 -> 1,508 -> 1,894 -> 2,317 across four hourly samples with no decay possible. A per-run average is scale-free, so a half-finished day is directly comparable to a whole one. The total-cost floor stays on the daily total deliberately - it is what stops a 3 ms/run collector alerting on a doubling to 6 ms - and there is deliberately NO minimum run count on the latest day, because `index_object_stats` legitimately runs once per server per day and a min-runs guard would blind the alert to every daily-cadence collector. `metric_name` stays `Collector Cost Regression` (a webhook automation key the [#2768] triage map reads); the reported values change unit from ms/day to ms/run because what is measured changed, with the daily total and run count kept in the detail text. Pinned against live Postgres by an adversarial fixture whose two collectors have IDENTICAL latest-day totals and differ only in run count, so reverting the predicate fails on the cadence-recovery case specifically.

Expand Down Expand Up @@ -3173,5 +3174,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2840]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2840
[#2846]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2846
[#2841]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2841
[#2845]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2845
[#2851]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2851
[#2854]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2854
Loading
Loading