Repository navigation
The managed store sets random_page_cost = 1.1, tuned for SSD storage - #4863
Merged
Merged
Conversation
With the default of 4 the planner prices a BRIN index's lossy heap pages as random reads and chose a sequential scan for a 12-hour Query Store window. The managed settings file now carries random_page_cost = 1.1; a file written by the previous version is re-rendered with the line and is not treated as hand-edited.
erikdarlingdata
marked this pull request as ready for review
September 30, 2026 15:57
…ore converges in one start Step A wrote darling-managed.conf from the before-snapshot and dropped every owned key the snapshot lacked. random_page_cost has no legacy block, so a store migrating off the legacy blocks lost the line and its next start rewrote the file. Step A now writes the derived value for keys only the managed file carries, and its verification accepts exactly that addition and nothing else.
This was referenced Oct 1, 2026
Closed
Merged
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.
The managed store's
darling-managed.confnow carriesrandom_page_cost = 1.1. It also adds two sentences to the bring-your-own-store section ofDarling/README.md.Why
random_page_costof 4 made the planner price a BRIN index's lossy heap pages as random reads. It chose a sequential scan overquery_store_interval_widefor a 12-hour window (3.2M blocks), even with a BRIN index oncollection_time.SET LOCAL random_page_cost = 1.1moved the 6 h, 12 h and 48 h windows onto the BRIN index (Build a BRIN index on query_store_interval_wide (collection_time) in the background #4862). A 24 h read took 4.3 s warm (about 37 s cold, at the volume's throughput cap) instead of 98.8 s cold on the full scan.seq_page_coststays at 1.0. The PG18 docs (runtime-config-query) say the 4.0 default assumes most random reads are cached. They say network-attached storage latency shrinks the relative cost of random access, and that lowering the value suits mostly cached data or high latency. They also say a value belowseq_page_cost"is not physically sensible", and that equal values only make sense for a database entirely in RAM. The docs give no SSD figure, so 1.1 is the measured value, not a documented one.What changed
DarlingManagedPostgres.BuildPlannerPageCostConfAppend()is a new block with no marker.ManagedConfFile.RenderBodyappends it, so the managed file is the only place the setting is written.postgresql.confblocks gets the line in the same migration that writes the managed file, so it converges in one start:ManagedConfFile.ManagedOnlyKeysnamesrandom_page_cost, andRenderWithValueswrites its derived value only when the store's own file settings have none, so an operator's value inpostgresql.confis still carried. The migration's verification (ManagedConfFileSettings.Compare) accepts exactly that one addition at exactly that value; any other added or changed setting still fails it. No legacy block is appended for it.random_page_cost = 1.1on SSD-backed storage. It also says the BRIN index on the per-interval Query Store table is skipped below PostgreSQL 16, because HOT updates past a BRIN index need PostgreSQL 16 or newer.Upgrade path
IsHandEditedis false. The new body differs, soShouldReplaceManagedConfis true and the next service-owned start rewrites the file with the line. The rewrite happens inEnsureManagedConfReadyAsync, beforepg_ctl start, so the start that renders it also applies it. Nothing reloads a running store on its own.random_page_costis user-context, so a reload would apply it, but the render only takes effect at the next service-owned start.random_page_cost(absent in the file, 1.1 rendered).postgresql.auto.conf: nothing reads or writes it. An operator'sALTER SYSTEMstill wins over the managed file.postgresql.conf: arandom_page_costset below the include line still wins. One set ABOVE the include line is now overridden by the managed file on a store whose conf already migrated, because the key was not managed when that migration ran. Stock files carry only the commented-out default. An operator who set it above the include should move it below the include or useALTER SYSTEM.random_page_cost = 1.1is valid on every supported PG, so this path is not new.Audit (PG18 docs)
Only
random_page_costchanges. The rest is deliberate:random_page_costseq_page_costrandom_page_costbelow it "is not physically sensible".effective_cache_sizeeffective_io_concurrencymaintenance_io_concurrencyio_methodworker(default),io_uring,sync. Postmaster-context, restart only.io_uringneeds a Linux build with liburing.worker)io_uringis not available there, andworkeris the only useful choice. Keep the default.io_workersio_method = worker.io_combine_limit/io_max_combine_limitio_combine_limitaboveio_max_combine_limitis silently capped to it.max_worker_processesmax_parallel_workersmax_worker_processes.max_parallel_workers_per_gather/max_parallel_maintenance_workersparallel_setup_cost/parallel_tuple_cost/min_parallel_table_scan_sizeNone of these has a clear, unmeasured win. Everything except
random_page_coststays out. Several need a restart, and each needs production measurement first.--check-settings(README, "Check Host/Store Sizing Settings") lists RAM-, hypertable- and disk-derived settings.random_page_costis a fixed constant, not a derivation, so that list is unchanged.Pins (RED and GREEN)
The tests are in
ManagedConfFileTests. I ran them in-process on macOS (runtime-config edit as documented).RenderBody_ContainsRandomPageCost_ExactlyOnce_At1Point1checks the body and the full file text.Expected "random_page_cost = '1.1'",Actual "random_page_cost = '4'".Assert.Singlefails on zero lines.PreviousVersionRender_IsNotHandEdited_AndIsReplacedWithTheNewLinebuilds the previous render from the new body minus the line, with a matching hash. It assertsIsHandEditedis false andShouldReplaceManagedConfis true.HandEditedPreviousVersionRender_IsLeftInForce_AndTheDiffNamesRandomPageCostasserts a hand edit is detected and not replaced, and the diff namesrandom_page_cost.ManagedConfRehearsalTestsmanaged-key count moved 26 to 27 (69 lines removed unchanged). GREEN, 6 of 6.ManagedConfFileTests: 38 total, 2 failures. Both arekernel32.dllP/Invoke inWriteManagedConfFile_*and fail the same way on dev; they are Windows-only.ManagedConfRehearsalTests,ManagedConfMigrationTests,ManagedConfMigrationStateTests,ManagedConfMigrationRunnerTests,DocCommentHygieneTests,CommentFilterAdoptionTests,RepoFileAdoptionTests,McpToolsListBudgetTests, and every*Census*,*AdoptionTestsand*CommandTimeoutTestsclass (33 classes).DarlingManagedPostgresTests(Windows paths orkernel32) and 1 inManagedConfUpgradePathTests(kernel32).DARLING_TEST_PGRUNTIMEis set), the existingManagedConfFileLiveTests.FirstStartMigrates_SecondStartSourcesFromManagedConf_Gatedstarts the bundled server on a fresh render and asserts that every key in the managed body sources fromdarling-managed.confinpg_settings. That set now includesrandom_page_cost, so CI proves the line loads and is in force. No new live test was added.Plan regression screen (production)
random_page_cost = 1.1and then an hour back at 4, back to back, at the same collection cadence, with no test load (a reversibleALTER SYSTEMplus reload; an earlier read was set aside because test load ran in both windows). Across the 171 statements with at least 5 calls in both windows, weighted at the same call counts, total statement time was 892 s at 1.1 against 913 s at 4 (−2.3%). Faster at 1.1, for example: the wait-type summary (0.30 against 5.64 ms per call over about 3,950 calls an hour, 36 against 1,426 buffers), the per-type wait trend (18 against 69 ms) and the plan-cache ranking (0.15 against 13.6 ms). Slower at 1.1, about 20 s an hour in all: the per-serverMAX(last_execution_time), MAX(collection_time)read ofquery_store_stats(30 against 22 ms; a plan change, 37,778 against 4,611 buffers per call), the plan-map liveness touch (92 against 61 ms), acollector_statedelete (71 against 33 ms, same buffers) and the plan-dimension digest fetch (307 against 216 ms). The heaviest statement, the Query Store raw dedupe, is unchanged. The 12-hour-and-longer Query Store panels switch from a full scan to the BRIN index. The slower statements are a tuning note for after the release.CHANGELOG
SECTION: Changed
ENTRY: - The managed store sets
random_page_cost = 1.1([#4863]) - the managed PostgreSQL store now rendersrandom_page_cost = 1.1, a value suited to SSD storage, in place of PostgreSQL's default of 4, which prices a random page read at four sequential ones. The planner now picks index and bitmap scans that the default priced out. Measured on a production store over an hour at each value, back to back, total statement time was 2.3% lower at 1.1, with some statements faster and a few slower. A store upgrading from 3.8 moves its settings into the managed file at the upgrade start and gets the line there, and the value is in force from the service's next start after that. Arandom_page_costset withALTER SYSTEMstill wins, and one already set inpostgresql.confwhen the store upgrades is carried into the managed file unchanged.REF: [#4863]: #4863
Findings by class
1 substantial (the planner cost, fixed here). 5 hygiene or polish fixed in this PR: a migrating store needed two starts to settle its managed file (the first start dropped the new line and the second rewrote it), a test assert that compared the new body with itself, a doc comment and two test comments. 0 dropped.