From 54572e48ba3af7dee1f93e9a8753ba6f5ead1f9b Mon Sep 17 00:00:00 2001 From: "Joshua (D) Drake" <136637981+ChronicallyJD@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:38:20 +0000 Subject: [PATCH 1/4] fix: do not price a base scan from sibling projection pages Projections share the relation file, so rel->pages was smgrnblocks of base plus every projection. A base scan reads only the base storage. Co-authored-by: Cursor --- CHANGELOG.md | 13 +++ src/columnar_tableam.c | 58 ++++++++++++ test/base_scan_io.sh | 107 ++++++++++++++++++++++ test/check_ledger.tsv | 9 ++ test/pytest/TESTS.md | 18 ++++ test/pytest/expected_tests.txt | 6 +- test/pytest/test_base_scan_io.py | 134 ++++++++++++++++++++++++++++ test/pytest/test_compare_to_bash.py | 1 + test/run_all_versions.sh | 1 + 9 files changed, 346 insertions(+), 1 deletion(-) create mode 100755 test/base_scan_io.sh create mode 100644 test/pytest/test_base_scan_io.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d30a0f..3d1235fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -474,6 +474,19 @@ Measured by the identity of the postmaster each edit could move the test and leave the message quoting the old threshold. That is the same defect one level along: a number the reader is told and nothing checks. `IO_BOUND` now feeds both. +- A base columnar scan was priced from the whole relation file, so adding a + covering projection made the base scan look more expensive even though it + still reads only the base storage. + + `pgcolumnar_relation_estimate_size` reported `smgrnblocks` of the main + fork. That file holds the base plus every projection. The planner's + `rel->pages` now subtracts the page-rounded footprints of sibling + projections. Tables with no extra projection keep the same page count. + + Measured on PG18 with `seq_page_cost = 1000` and CPU terms zeroed, 20000 + rows: the base-scan run stayed 22000 after a covering projection grew the + file from 180224 to 344064 bytes (ratio 1.000). Unfixed, the same scan + jumped to 42000 (ratio 1.909). - Five premise arms carried a verdict about a number they never printed (#1164). diff --git a/src/columnar_tableam.c b/src/columnar_tableam.c index 698dee57..acf03b5f 100644 --- a/src/columnar_tableam.c +++ b/src/columnar_tableam.c @@ -1030,6 +1030,45 @@ pgcolumnar_relation_needs_toast_table(Relation rel) return false; } +/* + * pgcolumnar_sibling_projection_pages + * Pages occupied by every non-base projection stored in this + * relation's file. + * + * The main fork holds the base plus every projection. The planner's + * rel->pages is smgrnblocks of that file, so a BASE scan is charged + * for pages it will not read. Subtracting this count is a no-op + * when the table has no extra projection. + */ +static BlockNumber +pgcolumnar_sibling_projection_pages(uint64 baseStorageId, Snapshot snapshot) +{ + List *projs; + ListCell *lc; + uint64 bytes = 0; + + projs = PgColumnarListProjections(baseStorageId); + foreach(lc, projs) + { + PgColumnarProjection *pr = (PgColumnarProjection *) lfirst(lc); + List *rgs; + ListCell *rgc; + + if (pr->projectionId == 0) + continue; + if (pr->projStorageId == 0 || pr->projStorageId == baseStorageId) + continue; + rgs = PgColumnarReadRowGroupList(pr->projStorageId, snapshot); + foreach(rgc, rgs) + { + NativeRowGroupMetadata *rg = (NativeRowGroupMetadata *) lfirst(rgc); + + bytes += COLUMNAR_PAGE_ROUND_UP(rg->byteLength); + } + } + return (BlockNumber) (bytes / COLUMNAR_BYTES_PER_PAGE); +} + static void pgcolumnar_relation_estimate_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, @@ -1097,6 +1136,25 @@ pgcolumnar_relation_estimate_size(Relation rel, int32 *attr_widths, liveRows = (double) (physicalRows - deleted); } + /* + * rel->pages is smgrnblocks of the one shared file: base row groups + * plus every projection stored beside them. A base scan reads only + * the base storage. Subtract the sibling projection footprints so + * the planner does not charge that scan for pages it will not visit. + * + * Without a projection this is a no-op, so tables that never grew a + * second copy keep the same page count they had. + */ + { + BlockNumber projPages; + + projPages = pgcolumnar_sibling_projection_pages(storageId, snapshot); + if (nblocks > projPages) + nblocks -= projPages; + else + nblocks = 1; + } + *pages = Max(nblocks, 1); *tuples = Max(liveRows, 0); diff --git a/test/base_scan_io.sh b/test/base_scan_io.sh new file mode 100755 index 00000000..bdf82cd5 --- /dev/null +++ b/test/base_scan_io.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# +# pgColumnar: a base scan must not be priced from sibling projection pages. +# +# Projections share the relation's main fork. relation_estimate_size reports +# smgrnblocks of that file as rel->pages, so a scan of the BASE storage is +# charged for every projection stored beside it. Adding a covering projection +# does not make the base scan read more bytes; the planner must not quote it +# as if it did. +# +# This suite pins the PLANNER number, not a runtime. Independent of +# test/pytest/test_base_scan_io.py: same public seam (EXPLAIN cost of a base +# scan before and after a sibling projection lands), own fixture, own +# observations. +# +# Usage: test/base_scan_io.sh [PG_CONFIG] +# Written fresh for pgColumnar. + +set -uo pipefail +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +pgc_setup "${1:-/usr/lib/postgresql/18/bin/pg_config}" + +N=20000 +psql_run "CREATE TABLE bsio (nid int, blob text) USING pgcolumnar;" +psql_run "SELECT pgcolumnar.set_options('bsio', stripe_row_limit => 1000, chunk_group_row_limit => 250);" +psql_run "INSERT INTO bsio SELECT nid, repeat('p', 850) FROM generate_series(1, $N) nid ORDER BY md5(nid::text);" +psql_run "ANALYZE bsio;" + +explain_base() { + env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres \ + -d "$PGC_DB" -Atq \ + -c "SET max_parallel_workers_per_gather = 0;" \ + -c "SET pgcolumnar.enable_ungrouped_vector_agg = off;" \ + -c "SET pgcolumnar.enable_group_vectorization = off;" \ + -c "SET jit = off;" \ + -c "SET seq_page_cost = 1000;" \ + -c "SET cpu_tuple_cost = 0;" \ + -c "SET cpu_operator_cost = 0;" \ + -c "SET cpu_index_tuple_cost = 0;" \ + -c "SET pgcolumnar.enable_projection_scan = off;" \ + -c "EXPLAIN (COSTS ON) $1" \ + | grep -v '^SET$' +} + +scan_cost_pair() { + echo "$1" | grep -F "Custom Scan (PgColumnarScan)" | head -1 \ + | grep -oE "cost=[0-9.]+\.\.[0-9.]+" | head -1 \ + | sed -E "s/cost=([0-9.]+)\\.\\.([0-9.]+)/\\1 \\2/" +} + +run_of() { + local pair start total + pair="$(scan_cost_pair "$1")" + start="${pair%% *}" + total="${pair##* }" + awk -v t="$total" -v s="$start" "BEGIN{ print t-s }" +} + +SQL="SELECT nid, blob FROM bsio" +before_plan="$(explain_base "$SQL")" +before_run="$(run_of "$before_plan")" +before_bytes="$(q "SELECT pg_relation_size('bsio')")" + +check "premise: the table holds every inserted row" \ + "$(q "SELECT count(*) FROM bsio")" "$N" + +check "premise: the plan is a base columnar scan" \ + "$(echo "$before_plan" | grep -c 'Custom Scan (PgColumnarScan)')" "1" + +check "premise: the base scan does not name a covering projection" \ + "$(echo "$before_plan" | grep -c 'Columnar Projection:')" "0" + +check "premise: the base scan has a positive run cost" \ + "$(awk -v c="$before_run" "BEGIN{ print (c>0) ? \"yes\" : \"no\" }")" "yes" + +psql_run "SELECT pgcolumnar.add_projection('bsio', 'bynid', ARRAY['nid','blob'], ARRAY['nid']);" + +after_bytes="$(q "SELECT pg_relation_size('bsio')")" +after_plan="$(explain_base "$SQL")" +after_run="$(run_of "$after_plan")" +ratio="$(awk -v a="$after_run" -v b="$before_run" "BEGIN{ if (b<=0) print 0; else printf \"%.3f\", a/b }")" + +echo "-- before_run=$before_run after_run=$after_run ratio=$ratio" +echo "-- before_bytes=$before_bytes after_bytes=$after_bytes" + +check "premise: a covering projection exists" \ + "$(q "SELECT count(*) FROM pgcolumnar.projection_declaration WHERE rel = 'bsio'::regclass AND name = 'bynid'")" "1" + +# Without this, a pass could mean the projection wrote nothing and both +# formulae agree because the file did not grow. +check "premise: adding the projection enlarged the relation file" \ + "$(awk -v a="$after_bytes" -v b="$before_bytes" "BEGIN{ print (b>0 && a > b*1.3) ? \"grew\" : \"stayed\" }")" \ + "grew" + +check "premise: the later plan is still a base columnar scan" \ + "$(echo "$after_plan" | grep -c 'Custom Scan (PgColumnarScan)')" "1" + +check "premise: the later plan still does not name a covering projection" \ + "$(echo "$after_plan" | grep -c 'Columnar Projection:')" "0" + +# Unfixed: after_run tracks the whole file, so ratio is about the size jump. +# Fixed: the base scan still charges the base storage, so ratio stays near 1. +check "a base scan is not priced from sibling projection pages" \ + "$(awk -v r="$ratio" "BEGIN{ print (r+0 > 1.25) ? \"inflated\" : \"stable\" }")" \ + "stable" + +pgc_summary diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index b2fc5b1b..f3657194 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -1,3 +1,12 @@ +base_scan_io base_scan_io a base scan is not priced from sibling projection pages 15;16;17;18;19 2026-09-21 projPages = 0 (keep whole-file pages) +base_scan_io base_scan_io premise: a covering projection exists 15;16;17;18;19 never - +base_scan_io base_scan_io premise: adding the projection enlarged the relation file 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the base scan does not name a covering projection 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the base scan has a positive run cost 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the later plan is still a base columnar scan 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the later plan still does not name a covering projection 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the plan is a base columnar scan 15;16;17;18;19 never - +base_scan_io base_scan_io premise: the table holds every inserted row 15;16;17;18;19 never - differential differential agg avg 15;16;17;18;19 never - differential differential agg count 15;16;17;18;19 never - differential differential agg minmax 15;16;17;18;19 never - diff --git a/test/pytest/TESTS.md b/test/pytest/TESTS.md index b6d96a52..e43dcb4d 100644 --- a/test/pytest/TESTS.md +++ b/test/pytest/TESTS.md @@ -119,6 +119,7 @@ behaviour, the source of that number is named. - [71. test_native_groupagg.py: the grouped vectorized aggregate must answer what core answers](#71-test_native_groupaggpy-the-grouped-vectorized-aggregate-must-answer-what-core-answers) - [72. test_analyze_function.py: statistics collected by reading, not by sampling](#72-test_analyze_functionpy-statistics-collected-by-reading-not-by-sampling) - [73. test_assertion_carries_its_measurement.py: a failure must say what it measured](#73-test_assertion_carries_its_measurementpy-a-failure-must-say-what-it-measured) +- [74. test_base_scan_io.py: a base scan is not priced from sibling projection pages](#74-test_base_scan_iopy-a-base-scan-is-not-priced-from-sibling-projection-pages) ## 1. How to read a test in here @@ -5777,3 +5778,20 @@ the corpus; none is live, and the file's header carries the measurement behind e | `test_the_carve_out_is_driven_at_its_boundary` | `> 0` and `>= 1` exactly, written either way round; `r > 1` and `r >= 0` are not the same shape and were being excused | | `test_a_comparison_wrapped_in_anything_is_still_examined` | `any(r <= 0 for r in runs)` is the natural rewrite of `min(runs) > 0`, so the escape hatch is closed rather than left beside the door | | `test_a_boolean_combination_is_examined_operand_by_operand` | one lossy operand is enough; a determinate one beside it is no excuse | + +## 74. test_base_scan_io.py: a base scan is not priced from sibling projection pages + +Port of `base_scan_io.sh`. A base columnar scan inherited `rel->pages` from +`smgrnblocks` of the relation file. That file holds the base plus every +projection stored beside it. Adding a covering projection does not make the +base scan read more bytes; the planner must not quote it as if it did. + +Public seam: `EXPLAIN` cost of a base scan (`pgcolumnar.enable_projection_scan += off`) before and after a sibling projection lands, with `seq_page_cost` +raised and CPU terms zeroed so the run is pages. The shell twin uses `bsio` / +`bynid` / 20000 rows; this file uses `bpages` / `onrid` / 30000 rows. +Assertion names match. + +| test | what it holds | +| --- | --- | +| `test_base_scan_io` | the table exists; the plan is a base columnar scan with no covering projection name and a positive run cost; a covering projection then exists and enlarged the file; the later plan is still a base scan; the run cost is not priced from sibling projection pages | diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index 48e084db..5b84db95 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -470,4 +470,8 @@ guard_tests 398 # # Re-derived by collection on the rebuilt branch: `462 tests collected`. `guard_tests` # was re-derived in the same run and did NOT move: 382. -cluster_tests 463 +# 463 -> 464 when test_base_scan_io.py landed (main already at 463 from +# #1189). Re-derived by collection on this tree, never by adding one: +# PLACEHOLDER until collection after rebase completes. +# `guard_tests` was re-derived in the same run and did NOT move: 393. +cluster_tests 464 diff --git a/test/pytest/test_base_scan_io.py b/test/pytest/test_base_scan_io.py new file mode 100644 index 00000000..84cd2963 --- /dev/null +++ b/test/pytest/test_base_scan_io.py @@ -0,0 +1,134 @@ +"""A base scan must not be priced from sibling projection pages. + +Projections share the relation's main fork. The planner's page count is +smgrnblocks of that file, so a scan of the BASE storage is charged for +every projection stored beside it. + +This file asserts the PLANNER ratio, not a runtime. Independent of +test/base_scan_io.sh: same public seam (EXPLAIN cost of a base scan before +and after a sibling projection lands), own fixture, own observations. +Assertion names match the shell suite so the two can be compared by name, +not by importing each other. +""" + + +def _nodes(plan): + stack = [plan[0]["Plan"]] + while stack: + node = stack.pop(0) + yield node + stack.extend(node.get("Plans") or ()) + + +def _custom_scan(plan): + for node in _nodes(plan): + if node.get("Node Type") == "Custom Scan": + return node + return None + + +def _plan(conn, sql): + with conn.cursor() as cur: + cur.execute("SET max_parallel_workers_per_gather = 0") + cur.execute("SET pgcolumnar.enable_ungrouped_vector_agg = off") + cur.execute("SET pgcolumnar.enable_group_vectorization = off") + cur.execute("SET jit = off") + cur.execute("SET seq_page_cost = 2000") + cur.execute("SET cpu_tuple_cost = 0") + cur.execute("SET cpu_operator_cost = 0") + cur.execute("SET cpu_index_tuple_cost = 0") + cur.execute("SET pgcolumnar.enable_projection_scan = off") + cur.execute("EXPLAIN (FORMAT JSON, COSTS ON) " + sql) + return cur.fetchone()[0] + + +def test_base_scan_io(pgc_conn, expect): + n = 30000 + with pgc_conn.cursor() as cur: + cur.execute("CREATE TABLE bpages (rid int, body text) USING pgcolumnar") + cur.execute( + "SELECT pgcolumnar.set_options('bpages', stripe_row_limit => 1500, " + "chunk_group_row_limit => 500)" + ) + # Compressible payload, independent of the shell twin: different + # table, N, stripe, column names, and repeat length. + cur.execute( + f"INSERT INTO bpages SELECT rid, repeat('q', 1000) " + f"FROM generate_series(1, {n}) rid ORDER BY md5((rid * 2)::text)" + ) + cur.execute("ANALYZE bpages") + cur.execute("SELECT count(*) FROM bpages") + expect.num( + cur.fetchone()[0], n, "premise: the table holds every inserted row" + ) + + sql = "SELECT rid, body FROM bpages" + before = _plan(pgc_conn, sql) + node = _custom_scan(before) + expect.text( + "Custom Scan" if node is not None else (before[0]["Plan"].get("Node Type") or "none"), + "Custom Scan", + "premise: the plan is a base columnar scan", + ) + expect.text( + (node or {}).get("Columnar Projection") or "none", + "none", + "premise: the base scan does not name a covering projection", + ) + before_run = node["Total Cost"] - node["Startup Cost"] + expect.text( + "yes" if before_run > 0 else "no", + "yes", + "premise: the base scan has a positive run cost", + ) + + with pgc_conn.cursor() as cur: + cur.execute("SELECT pg_relation_size('bpages')") + before_bytes = cur.fetchone()[0] + cur.execute( + "SELECT pgcolumnar.add_projection('bpages', 'onrid', " + "ARRAY['rid','body'], ARRAY['rid'])" + ) + cur.execute( + "SELECT count(*) FROM pgcolumnar.projection_declaration " + "WHERE rel = 'bpages'::regclass AND name = 'onrid'" + ) + expect.num( + cur.fetchone()[0], + 1, + "premise: a covering projection exists", + ) + cur.execute("SELECT pg_relation_size('bpages')") + after_bytes = cur.fetchone()[0] + + expect.text( + "grew" if before_bytes > 0 and after_bytes > before_bytes * 1.3 + else f"stayed before={before_bytes} after={after_bytes}", + "grew", + "premise: adding the projection enlarged the relation file", + ) + + after = _plan(pgc_conn, sql) + after_node = _custom_scan(after) + expect.text( + "Custom Scan" if after_node is not None else (after[0]["Plan"].get("Node Type") or "none"), + "Custom Scan", + "premise: the later plan is still a base columnar scan", + ) + expect.text( + (after_node or {}).get("Columnar Projection") or "none", + "none", + "premise: the later plan still does not name a covering projection", + ) + after_run = after_node["Total Cost"] - after_node["Startup Cost"] + ratio = (after_run / before_run) if before_run > 0 else 0.0 + print( + f"-- before_run={before_run} after_run={after_run} ratio={ratio:.3f}" + ) + print(f"-- before_bytes={before_bytes} after_bytes={after_bytes}") + expect.text( + f"inflated ratio={ratio:.3f} (after_run={after_run}, before_run={before_run})" + if ratio > 1.25 else "stable", + "stable", + "a base scan is not priced from sibling projection pages", + ) diff --git a/test/pytest/test_compare_to_bash.py b/test/pytest/test_compare_to_bash.py index 94ff3a5a..0eff5b6f 100644 --- a/test/pytest/test_compare_to_bash.py +++ b/test/pytest/test_compare_to_bash.py @@ -89,6 +89,7 @@ "analyze_differential", "analyze_function", "analyze_reltuples", + "base_scan_io", "differential", "encode_post_codec", "hilbert_cluster", diff --git a/test/run_all_versions.sh b/test/run_all_versions.sh index ad9e1aa0..6cdfc74f 100755 --- a/test/run_all_versions.sh +++ b/test/run_all_versions.sh @@ -54,6 +54,7 @@ SUITES=( autovacuum autovacuum_yield avro_manifest + base_scan_io batch_fold_explain bench_guards bloom_lazy From 1bea8f2e2705a495cf15dc3bbfc2b8848e23a414 Mon Sep 17 00:00:00 2001 From: "Joshua (D) Drake" <136637981+ChronicallyJD@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:41:05 +0000 Subject: [PATCH 2/4] test: carry base-scan ratios in the failing shell branch harness_selftest 540 refuses two-constant awk verdicts. Passing branches stay the named verdict; failing branches now include the ratio. Co-authored-by: Cursor --- test/base_scan_io.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/base_scan_io.sh b/test/base_scan_io.sh index bdf82cd5..4b7efa42 100755 --- a/test/base_scan_io.sh +++ b/test/base_scan_io.sh @@ -89,7 +89,7 @@ check "premise: a covering projection exists" \ # Without this, a pass could mean the projection wrote nothing and both # formulae agree because the file did not grow. check "premise: adding the projection enlarged the relation file" \ - "$(awk -v a="$after_bytes" -v b="$before_bytes" "BEGIN{ print (b>0 && a > b*1.3) ? \"grew\" : \"stayed\" }")" \ + "$(awk -v a="$after_bytes" -v b="$before_bytes" "BEGIN{ print (b>0 && a > b*1.3) ? \"grew\" : \"stayed before=\" b \" after=\" a }")" \ "grew" check "premise: the later plan is still a base columnar scan" \ @@ -101,7 +101,7 @@ check "premise: the later plan still does not name a covering projection" \ # Unfixed: after_run tracks the whole file, so ratio is about the size jump. # Fixed: the base scan still charges the base storage, so ratio stays near 1. check "a base scan is not priced from sibling projection pages" \ - "$(awk -v r="$ratio" "BEGIN{ print (r+0 > 1.25) ? \"inflated\" : \"stable\" }")" \ + "$(awk -v r="$ratio" "BEGIN{ print (r+0 > 1.25) ? \"inflated ratio=\" r : \"stable\" }")" \ "stable" pgc_summary From 994e682f5db92ccbff6518c692b66dfbc281ac82 Mon Sep 17 00:00:00 2001 From: "Joshua (D) Drake" <136637981+ChronicallyJD@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:05:11 +0000 Subject: [PATCH 3/4] test: band the base-scan I/O ratio and re-derive after rebase Over-subtraction passed a one-sided ceiling. Require 0.8..1.25, document the clamp and planning walk, and re-count cluster_tests and the never census on current main. Co-authored-by: Cursor --- src/columnar_tableam.c | 15 +++++++++++++++ test/base_scan_io.sh | 4 +++- test/pytest/expected_tests.txt | 4 ++-- test/pytest/test_base_scan_io.py | 7 +++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/columnar_tableam.c b/src/columnar_tableam.c index acf03b5f..16b81877 100644 --- a/src/columnar_tableam.c +++ b/src/columnar_tableam.c @@ -1039,6 +1039,11 @@ pgcolumnar_relation_needs_toast_table(Relation rel) * rel->pages is smgrnblocks of that file, so a BASE scan is charged * for pages it will not read. Subtracting this count is a no-op * when the table has no extra projection. + * + * When projections DO exist this walks every projection's row-group + * list on every estimate_size call (every plan of the table). That is + * planning-time catalog work proportional to projections times groups; + * the no-projection case remains free. */ static BlockNumber pgcolumnar_sibling_projection_pages(uint64 baseStorageId, Snapshot snapshot) @@ -1152,7 +1157,17 @@ pgcolumnar_relation_estimate_size(Relation rel, int32 *attr_widths, if (nblocks > projPages) nblocks -= projPages; else + { + /* + * Sibling footprints meeting or exceeding the file should not + * happen (they live in the same file), but stale or orphaned + * projection row groups, a rewrite, or PAGE_ROUND_UP can reach + * it. Floor at one page rather than underflow; a one-page + * estimate for a large table is the wrong answer that would + * otherwise look like a planner bug somewhere else. + */ nblocks = 1; + } } *pages = Max(nblocks, 1); diff --git a/test/base_scan_io.sh b/test/base_scan_io.sh index 4b7efa42..cbfbccbc 100755 --- a/test/base_scan_io.sh +++ b/test/base_scan_io.sh @@ -100,8 +100,10 @@ check "premise: the later plan still does not name a covering projection" \ # Unfixed: after_run tracks the whole file, so ratio is about the size jump. # Fixed: the base scan still charges the base storage, so ratio stays near 1. +# Band, not a ceiling: over-subtraction (ratio too small) is the failure mode +# this code newly makes reachable, and a one-sided bound would green it. check "a base scan is not priced from sibling projection pages" \ - "$(awk -v r="$ratio" "BEGIN{ print (r+0 > 1.25) ? \"inflated ratio=\" r : \"stable\" }")" \ + "$(awk -v r="$ratio" "BEGIN{ print (r+0 > 1.25 || r+0 < 0.8) ? \"moved ratio=\" r : \"stable\" }")" \ "stable" pgc_summary diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index 5b84db95..4584b9ac 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -472,6 +472,6 @@ guard_tests 398 # was re-derived in the same run and did NOT move: 382. # 463 -> 464 when test_base_scan_io.py landed (main already at 463 from # #1189). Re-derived by collection on this tree, never by adding one: -# PLACEHOLDER until collection after rebase completes. -# `guard_tests` was re-derived in the same run and did NOT move: 393. +# `464 tests collected`. `guard_tests` was re-derived in the same run and +# did NOT move: 393. cluster_tests 464 diff --git a/test/pytest/test_base_scan_io.py b/test/pytest/test_base_scan_io.py index 84cd2963..2e18f877 100644 --- a/test/pytest/test_base_scan_io.py +++ b/test/pytest/test_base_scan_io.py @@ -126,9 +126,12 @@ def test_base_scan_io(pgc_conn, expect): f"-- before_run={before_run} after_run={after_run} ratio={ratio:.3f}" ) print(f"-- before_bytes={before_bytes} after_bytes={after_bytes}") + # Band, not a ceiling: over-subtraction (ratio too small) is newly + # reachable once sibling pages are subtracted, and a one-sided bound + # would green it. expect.text( - f"inflated ratio={ratio:.3f} (after_run={after_run}, before_run={before_run})" - if ratio > 1.25 else "stable", + f"moved ratio={ratio:.3f} (after_run={after_run}, before_run={before_run})" + if ratio > 1.25 or ratio < 0.8 else "stable", "stable", "a base scan is not priced from sibling projection pages", ) From baab53591f385a9bc774754f9d5b9a25bffdf502 Mon Sep 17 00:00:00 2001 From: "Joshua (D) Drake" <136637981+ChronicallyJD@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:11:28 +0000 Subject: [PATCH 4/4] docs: re-count never census after rebase onto 133c3fbd Main moved past a5c7d5d (#1193). Re-derive checks_never_observed_red by counting field 5 on the rebased ledger; do not carry 1459. Co-authored-by: Cursor --- test/check_ledger_budget.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index e01a4aa7..220a24fa 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -202,4 +202,8 @@ suites_not_covered 249 # comment about a moving census goes stale by construction, and this comment sits # directly above the value it contradicts. The command is the durable half. # Reported by @OffgridwithJD. -checks_never_observed_red 1453 +# RESEATED onto origin/main 133c3fbd (post-#1193). Main stated 1453; this +# branch previously stated 1459 against a5c7d5d. Re-counted on THIS tree, +# never by adding: +# awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l +checks_never_observed_red 1461