Alpha4's second item, from design/RELEASE_PLAN_1.0.md:100. Target 2026-09-15.
Why it is alpha work. It changes the bytes the writer emits, and the plan's rule is that
anything changing written bytes lands in an alpha even when no signature changes.
Read the first section before scheduling this as "low effort". The plan calls it low effort
and describes it as making a default depend on the storage tier. Measured against the tree, the
premise it rests on does not hold yet, and the finding behind it has never been measured on
pgColumnar.
The blocker: there is no storage tier to key a default off
Verified on main 9628414. grep -rni tier src/*.c src/*.h pgcolumnar--1.0-alpha3.sql
returns two hits, and neither is the word. Both are the letters tier inside a longer word:
src/columnar_encoding.c:255: ... is far nastier to
src/columnar_tableam.c:1699: ... at the append frontier is
There is no tier concept anywhere in the extension.
Verified: object storage is not a location for native table storage. It is an
import/export/interop surface. pgcolumnar_objstore handles s3:// and gs:// URLs for
external Parquet, Iceberg, and the parallel export sink (src/columnar_objstore.c,
src/columnar_parallel_export.c, test/objstore_sink_write.sh). A native columnar table's own
blocks always live in the PostgreSQL data directory. The objstore_stash_recovery suite is about
a test harness moving a module file aside, not about tiered storage.
So "make the default depend on the tier" has no input available. This is a scoping decision
the issue cannot make for itself, and there are two honest readings:
- Declared tier. Add a per-table or per-tablespace declaration of what the storage is
(fast_local, remote), and let the compression default follow it. This is a new
user-visible option and therefore genuinely alpha surface — larger than "low effort".
- Change the local default only. Native storage is always local, so if the finding holds
here, the change is to the existing global default and per-table options, with block
compression left on for the export and Iceberg paths that genuinely write to object storage.
This is small, and it is probably what the roadmap entry meant.
Reading 2 is my recommendation, because it needs no new concept and matches where our bytes
actually go. It should still be ruled on rather than assumed.
The finding behind this item has not been measured on our engine
Verified. design/ROADMAP.md:192-198 sources the claim to Zeng et al., VLDB 2024
(https://www.vldb.org/pvldb/vol17/p148-zeng.pdf). Grepping design/, docs/ and CHANGELOG.md
for a pgColumnar measurement of compression CPU against I/O saving returns only that roadmap
entry and the release-plan line that restates it.
So we would be changing a default that affects every table on the strength of someone else's
benchmark on someone else's engine. Our encoder is not theirs: we run a cascade of lightweight
encodings before block compression, with FSST for strings and dictionary encoding, so the
bytes reaching zstd are already reduced and the CPU-versus-I/O balance here is not the balance
the paper measured. That difference cuts either way and is exactly why it needs measuring.
What is there today
Verified.
- Default method:
pgcolumnar_compression = COLUMNAR_COMPRESSION_ZSTD (src/columnar_tableam.c:75).
- Default level:
pgcolumnar_compression_level = 3 (:76).
- GUCs:
pgcolumnar.compression (enum none/pglz/lz4/zstd, :2837) and
pgcolumnar.compression_level (1-22, :2847).
- Per-table override already exists:
pgcolumnar.set_options(..., compression, compression_level, ...)
(pgcolumnar--1.0-alpha3.sql:408, :553-566) with reset_options beside it (:586-605).
The mechanism for a different default is therefore already complete. The only thing missing is
the evidence for what the default should be, which is the reverse of how this item is scheduled.
Do it in this order, and phase 1 is not optional
Phase 1 — measure the claim here, before changing anything. For a representative corpus,
compare compression = zstd (level 3) against none, and against lz4, on the same data:
bytes written, and the work done by a scan.
The instrument traps that will otherwise void this:
- Measure the work, not the clock. Use backend instruction counts, not wall time; this box
is not idle and a second agent may be building on it. A ratio taken back to back cancels
common-mode load, an absolute millisecond count does not.
- Assert the scan took the path you think you are measuring, on both arms. A comparison
where one arm silently fell back to a different scan is not a comparison.
- The corpus decides the answer. Incompressible data makes zstd pure cost; highly repetitive
data makes it pure win. Report per corpus shape and never as one number, or the default will be
chosen by whichever fixture was convenient.
- Subtract the cascade. Our lightweight encodings run first. Report the marginal effect of
block compression on top of the cascade, which is the only number that bears on this default.
Phase 2 — rule on the scoping question above, informed by phase 1's numbers.
Phase 3 — change the default, test-first. The arm to write before the change: a table
created with no options records the intended compression in pgcolumnar.stats, and its written
bytes match the intended codec. Red first against today's zstd default.
Phase 4 — the upgrade and documentation consequences, which are the part that bites. A
changed default does not rewrite existing tables, so a database upgraded from alpha3 keeps
zstd-compressed blocks while new tables get something else. Both must read correctly, and an arm
must hold that: write with the old default, change the default, read back. docs/ and the
release notes have to say plainly what changed and what it means for existing data.
State of knowledge
- Verified: no tier concept exists; object storage is not native-table storage; the current
defaults, GUCs and per-table options; the claim's only source is a third-party paper.
- Assumed: that reading 2 is what the roadmap entry meant.
- Unknown: whether block compression is a net cost on fast local storage for pgColumnar.
That is phase 1, and if it turns out to be a net win here, the correct outcome of this issue is
to close it with the measurement recorded and change nothing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
Alpha4's second item, from
design/RELEASE_PLAN_1.0.md:100. Target 2026-09-15.Why it is alpha work. It changes the bytes the writer emits, and the plan's rule is that
anything changing written bytes lands in an alpha even when no signature changes.
Read the first section before scheduling this as "low effort". The plan calls it low effort
and describes it as making a default depend on the storage tier. Measured against the tree, the
premise it rests on does not hold yet, and the finding behind it has never been measured on
pgColumnar.
The blocker: there is no storage tier to key a default off
Verified on
main9628414.grep -rni tier src/*.c src/*.h pgcolumnar--1.0-alpha3.sqlreturns two hits, and neither is the word. Both are the letters
tierinside a longer word:There is no tier concept anywhere in the extension.
Verified: object storage is not a location for native table storage. It is an
import/export/interop surface.
pgcolumnar_objstorehandless3://andgs://URLs forexternal Parquet, Iceberg, and the parallel export sink (
src/columnar_objstore.c,src/columnar_parallel_export.c,test/objstore_sink_write.sh). A native columnar table's ownblocks always live in the PostgreSQL data directory. The
objstore_stash_recoverysuite is abouta test harness moving a module file aside, not about tiered storage.
So "make the default depend on the tier" has no input available. This is a scoping decision
the issue cannot make for itself, and there are two honest readings:
(
fast_local,remote), and let the compression default follow it. This is a newuser-visible option and therefore genuinely alpha surface — larger than "low effort".
here, the change is to the existing global default and per-table options, with block
compression left on for the export and Iceberg paths that genuinely write to object storage.
This is small, and it is probably what the roadmap entry meant.
Reading 2 is my recommendation, because it needs no new concept and matches where our bytes
actually go. It should still be ruled on rather than assumed.
The finding behind this item has not been measured on our engine
Verified.
design/ROADMAP.md:192-198sources the claim to Zeng et al., VLDB 2024(https://www.vldb.org/pvldb/vol17/p148-zeng.pdf). Grepping
design/,docs/andCHANGELOG.mdfor a pgColumnar measurement of compression CPU against I/O saving returns only that roadmap
entry and the release-plan line that restates it.
So we would be changing a default that affects every table on the strength of someone else's
benchmark on someone else's engine. Our encoder is not theirs: we run a cascade of lightweight
encodings before block compression, with FSST for strings and dictionary encoding, so the
bytes reaching zstd are already reduced and the CPU-versus-I/O balance here is not the balance
the paper measured. That difference cuts either way and is exactly why it needs measuring.
What is there today
Verified.
pgcolumnar_compression = COLUMNAR_COMPRESSION_ZSTD(src/columnar_tableam.c:75).pgcolumnar_compression_level = 3(:76).pgcolumnar.compression(enumnone/pglz/lz4/zstd,:2837) andpgcolumnar.compression_level(1-22,:2847).pgcolumnar.set_options(..., compression, compression_level, ...)(
pgcolumnar--1.0-alpha3.sql:408,:553-566) withreset_optionsbeside it (:586-605).The mechanism for a different default is therefore already complete. The only thing missing is
the evidence for what the default should be, which is the reverse of how this item is scheduled.
Do it in this order, and phase 1 is not optional
Phase 1 — measure the claim here, before changing anything. For a representative corpus,
compare
compression = zstd(level 3) againstnone, and againstlz4, on the same data:bytes written, and the work done by a scan.
The instrument traps that will otherwise void this:
is not idle and a second agent may be building on it. A ratio taken back to back cancels
common-mode load, an absolute millisecond count does not.
where one arm silently fell back to a different scan is not a comparison.
data makes it pure win. Report per corpus shape and never as one number, or the default will be
chosen by whichever fixture was convenient.
block compression on top of the cascade, which is the only number that bears on this default.
Phase 2 — rule on the scoping question above, informed by phase 1's numbers.
Phase 3 — change the default, test-first. The arm to write before the change: a table
created with no options records the intended compression in
pgcolumnar.stats, and its writtenbytes match the intended codec. Red first against today's zstd default.
Phase 4 — the upgrade and documentation consequences, which are the part that bites. A
changed default does not rewrite existing tables, so a database upgraded from alpha3 keeps
zstd-compressed blocks while new tables get something else. Both must read correctly, and an arm
must hold that: write with the old default, change the default, read back.
docs/and therelease notes have to say plainly what changed and what it means for existing data.
State of knowledge
defaults, GUCs and per-table options; the claim's only source is a third-party paper.
That is phase 1, and if it turns out to be a net win here, the correct outcome of this issue is
to close it with the measurement recorded and change nothing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK