Repository navigation
Raise managed store maintenance_work_mem to the measured compression floor (#1777) - #1780
Merged
Merged
Conversation
…floor (#1777) Field measurement on a production field instance (16 GB RAM class) showed TimescaleDB compression throughput rising ~70% when maintenance_work_mem went from the old formula's ~800 MB landing point to 1536 MB, and gaining nothing measurable at 4096 MB. The formula becomes min(max(5% RAM, 1536MB), 25% RAM, 2048MB): the 1536 MB floor is the measured capture point, the 25%-of-RAM term keeps the floor from overcommitting a small host, and the 2 GB cap bounds the big-RAM case where the data showed nothing further to gain. A formula change alone would only ever reach a fresh initdb, and the stores that need this are already collecting -- so a v7 conf block (ConfMarkerV7) re-states maintenance_work_mem the same way v5 re-states shared_buffers. postgresql.conf takes the LAST assignment, so an existing store adopts the raised value on its next service-owned start without the v3 block ever being rewritten. Also corrects three stale claims in the Darling README's memory-sizing paragraph that predate #1559 (shared_buffers cap and its 8 GB example, "all three appends").
…rest on (#1777) Comment only. Both of today's maintenance_work_mem consumers grow their allocation to fit the work (tuplesort spills past the ceiling; PG 17+ TidStore grows), so the setting bounds what an operation MAY use rather than what it WILL use -- which is what makes the 4 GB host's 1024MB landing safe. A future consumer that PRE-ALLOCATES would break that reasoning, so the note lives at the formula, where it would be violated.
erikdarlingdata
enabled auto-merge
July 28, 2026 03:48
This was referenced Jul 28, 2026
Merged
erikdarlingdata
added a commit
to ianwalkeruk/PerformanceMonitor
that referenced
this pull request
Jul 31, 2026
Three maintainer fixes on top of ianwalkeruk's contribution, which got the hard parts right (secret tiers in both apps, V42 claimed correctly, viewer projections carved): - The added credential-load block re-read every sibling secret through the REAL store inside the settings.json guard, after the legacy plaintext migration - re-breaking exactly what erikdarlingdata#1832's hoist fixed and bypassing the injectable readSecret seam (the two red AlertSettingsCredentialLoadTests were the contract catching it). The PagerDuty key now loads at the hoist like its siblings; the duplicate block is gone. - V42's doc cited erikdarlingdata#1780, which is the maintenance_work_mem issue; corrected to erikdarlingdata#1943. - AppAlertSettings' comment claimed a no-enable-flag rule its own code (correctly) does not follow; the comment now describes the actual sibling-channel shape. Plus the CHANGELOG entry with contributor credit. Lite 1951/1951 green including the two that were red; Darling 3823/3823 fast suite green; all builds 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Closes #1777.
Field measurement on a production field instance (16 GB RAM class) showed TimescaleDB compression throughput rising about 70% when
maintenance_work_memwent from the old formula's landing point (~800 MB) to 1536 MB, and gaining nothing measurable at 4096 MB. TimescaleDB's compression sort runs onmaintenance_work_mem, notwork_mem, so this setting directly gates how fast the background compression job moves.Per the issue's own sequencing note: the controlled repro (same chunk, only the setting varying, to find the exact threshold between ~800 MB and 1536 MB) is deliberately not done here and stays open on #1777. This ships the formula change now because the numbers already bound it from both sides.
The formula
DarlingManagedPostgres.DeriveMemorySettings(Darling/PerformanceMonitor.Darling.Service/DarlingManagedPostgres.cs:494, themaintenanceWorkMemterm at:506) goes frommin(5% RAM, 1 GB)to:Landing table
SHOWoutput is listed separately on purpose: PostgreSQL normalizes memory units on the way out, so a conf line of2048MBreads back as2GB. Same setting, different string, and an operator checking the value should not be surprised by it.SHOW maintenance_work_mem;1024MB1GB1536MB1536MB1536MB1536MB1638MB1638MB2048MB2GBEach row exercises a different one of the three terms, and
DeriveMemorySettings_PerTier(Darling/Darling.Tests/DarlingManagedPostgresTests.cs:399) pins all of them so the interaction cannot drift silently: 4 GB is the 25% guard winning, 8/16 GB is the floor winning, 32 GB is raw 5% having overtaken the floor, 64 GB is the cap.Flagged for review, not hidden: the floor raises small hosts proportionally more than the 16 GB host it was measured on (4 GB goes 204 -> 1024 MB, 8 GB goes 409 -> 1536 MB). That is bounded and I believe it is fine, but it is a judgement call and worth a second opinion.
maintenance_work_memis a per-operation ceiling, not a reservation: PostgreSQL grows the sort/TidStore allocation to fit the work, and a small host's chunks are small, so the ceiling is simply never reached there. PostgreSQL 17+ (the bundle pins 18.4) also made vacuum's dead-TID store grow incrementally instead of allocating the full limit up front, which is what made the old comment's "several autovacuum workers can each take up to this" the binding worry it no longer is.Propagation: the half that reaches the field
A formula change alone only ever affects a fresh
initdb, and every store that needs this is already collecting. The product already has the mechanism for exactly this:postgresql.confis built from independently versioned marker blocks (ConfMarkerv1 through v6), each checked on its own inEnsureConfAppendedand appended if absent, so an existing cluster gains a missing block on its next service-owned start.postgresql.conftakes the last assignment of a setting, which is how v5 already re-statesshared_buffersover v3's line without ever rewriting v3.This adds
ConfMarkerV7(:181),BuildCompressionMemoryConfAppend(:551) and the heal branch (:981) on the same pattern: it re-statesmaintenance_work_memalone. The v3 block is never touched. The append happens beforepg_ctl start, so an existing store picks the new value up on that very start rather than one restart later.Live before/after on a real store
Against the bundled runtime (PostgreSQL 18.4 + TimescaleDB 2.28.1, verified from
pg_ctl.exe --versionrather than the filename). A real store was provisioned through the production bootstrap, then rewound to its pre-#1777 shape: v7 block removed, and the v3 block's value set to819MB, which is what the old formula produced on a 16 GB host.Before - server started on the rewound conf, read straight from the server:
The service's next start, verbatim from the log:
After - the healed conf, showing the override rather than a rewrite:
The legacy line is still there and simply outvoted, which is the whole design. This evidence box is a 128 GB host, so it lands on the 2 GB cap; the 16 GB landing is pinned by the theory rows above.
How an operator verifies it post-upgrade
In the service log, on the first start after upgrading, one line:
Appended v7 compression memory to postgresql.conf (maintenance_work_mem = <N>MB from min(max(5% RAM, 1536MB), 25% RAM, 2048MB); TimescaleDB compression sorts on this setting)It appears once per store, ever. A store that already healed will not log it again.
Against the store:
On a 16 GB-class host that returns
1536MB. On a 4 GB host it returns1GBand on a 64 GB host2GB- both correct, both the unit normalization described above, not a failed upgrade.Tests
Full
Darling.Tests: 3489 passed, 0 failed, 169 skipped (the skips are theDARLING_TEST_PGconnection-string-gated live classes; theDARLING_TEST_PGRUNTIME-gated ones below all ran). Build:0 Warning(s)on a-t:Rebuildsweep ofPerformanceMonitor.Darling.ServiceandDarling.Tests.New/changed:
DeriveMemorySettings_PerTier- landing rows added for 4 GB and re-pinned for 2/8/16/32/64 GB.CompressionMemoryConfAppend_PinsV7Marker_AndOverridesAnOlderV3Line(:224) - builds a pre-maintenance_work_mem formula lands too low for compression throughput: +70% measured from raising it, plateaus by 1.5 GB #1777 v3 block and proves the effective value moves 819MB -> 1536MB by append, with the old line preserved.ExistingStore_AdoptsRaisedMaintenanceWorkMem_OnNextStart_Gated(:752) - the propagation E2E above, against a real server.DarlingStoreUpgradeTestsasserts one v7 marker after a major upgrade (the upgrade path sharesEnsureConfAppended, so v7 rides along).Comparisons are made in bytes via
pg_size_bytes, not on the setting string. The first version of the propagation test compared strings and went red on this host withExpected: "2048MB", Actual: "2GB"- a real property of the server, caught only because the test ran against one.Mutation evidence
Each applied, watched red, then restored:
min(5% RAM, 1 GB)Also in this diff
The Darling README's memory-sizing paragraph carried three stale claims that predate #1559 and had nothing to do with this issue:
shared_buffers = min(25% RAM, 8GB)(it has been capped at 1 GB since #1559), the worked example "on an 8 GB box that isshared_buffers 2048MB" (it is 1024MB), and "All three appends" (there are seven blocks). Corrected while rewriting the same sentence for the new formula.