Skip to content

Reconsider default block compression: the tier it is supposed to key off does not exist, and the finding is unmeasured here (1.0-alpha4) #890

Description

@jdatcmd

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:

  1. 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".
  2. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions