Skip to content

refactor: extract pure flush_one_column() from the flush loop (#445 slice 1) - #589

Merged
jdatcmd merged 1 commit into
commandprompt:mainfrom
ChronicallyJD:feat/445-slice1-flush-one-column
Aug 12, 2026
Merged

refactor: extract pure flush_one_column() from the flush loop (#445 slice 1)#589
jdatcmd merged 1 commit into
commandprompt:mainfrom
ChronicallyJD:feat/445-slice1-flush-one-column

Conversation

@ChronicallyJD

Copy link
Copy Markdown
Collaborator

#445 slice 1: extract a pure flush_one_column() — no behavior change

First slice of the in-COPY parallelism design (#588), per the owner go-ahead on that thread. Pure mechanical refactor; output is byte-identical to the serial path. No workers, no dsm, no GUC yet — this is the foundation the later slices build on, and it settles the whole codec/encoder correctness surface serially.

What changed

pgcolumnar_flush_row_group's per-column loop body is extracted into a standalone

static FlushColumnResult
flush_one_column(Form_pg_attribute att, List *chunkGroups,
                 PgColumnarColumnDef *def, uint64 rowCount, int validityBytes,
                 int encodeEffort, int compressionType, int compressionLevel,
                 uint64 storageId, uint64 groupNumber, int columnIndex);

that reads only its arguments — no writeState reach-through — and returns {chunk bytes, descriptor, block codec, zone-map rows, bloom row}. The function produces its column-chunk bytes ([validity][encoded/compressed]) into its own buffer instead of appending to the shared stripe data; the backend loop assembles the chunks in column order and keeps all I/O and catalog writes exactly where they were. The FSST verdict cache is carried through the def pointer argument.

This is the unit of parallelism the design identifies (a column chunk at flush time, each independent), isolated behind a clean boundary while still running serially in the backend.

Why it's byte-identical

The extraction is line-for-line: the FSST decision, per-vector encode, zone-map/bloom construction, and block codec are unchanged; only writeState->X → parameter and the two appendBinaryStringInfo(data, …) → the local chunk buffer, with the backend appending that chunk to data at the same point. chunkOffset/chunkLength bookkeeping moved to the caller and computes the same values.

Verification (prove-not-trust)

  • Byte-identical vs heap oracle: differential.sh PASSES.
  • Write path across types/codecs: native_writer, native_dml, write_fsst_compressed, write_minmax_fastpath, encode_invariants, encode_effort, corruption, native_reclaim, native_roundtrip all PASS.
  • Two toolchains: forced ASAN+UBSAN (pg18_san) — 0 sanitizer reports on every suite; and assert (pg18a, --enable-cassert) — no TRAP/Assert/crash. -Wshadow=compatible-local clean.

Not in this slice

dsm plumbing (slice 2), the worker pool with serial degradation on slot starvation (slice 3), and the GUC + measurement vs the ~17% ceiling (slice 4). The default flip to parallel-by-default is a later change gated on proving slot-safe degradation, per the plan on #588.

🤖 Generated with Claude Code

https://claude.ai/code/session_017N82wDmsawqSWoWkmxtHmW

…dprompt#445 slice 1)

Slice 1 of the commandprompt#445 in-COPY parallelism design (commandprompt#588): move the per-column
flush body of pgcolumnar_flush_row_group into a standalone
flush_one_column(inputs) -> {chunk, descriptor, codec, zonemap, bloom} that
reads only its arguments, no writeState reach-through. The backend assembles
the column chunks into the stripe in column order and keeps all I/O and
catalog writes. No workers yet; output is byte-identical to the serial path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N82wDmsawqSWoWkmxtHmW

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — byte-identical verified (PG18)

Reviewed as the author of the #588 design this implements, and verified rather than trusted the "byte-identical" claim.

Storage is byte-identical to main, not just read-equivalent. Same 400k-row text-heavy load (FSST-eligible URL + query columns) on this branch and on clean f20c811:

#589 main
pg_total_relation_size 3915776 3915776
content md5 84c76b03… 84c76b03…
encoding descriptors md5 64e2fe13… 64e2fe13…
chunk bytes 3874704 3874704

The descriptor hash matching is the one that matters — it proves the per-column codec/encoder decisions are identical, so the FSST verdict, block codec, and D4 selector all came out the same after the extraction.

Correctness: differential, native_writer, native_zonemap, native_bloom, native_dml, write_fsst_compressed, encode_effort, native_encoding all pass. Warning-clean build.

The extraction is clean: flush_one_column reads only its arguments — no writeState reach-through — and the backend loop keeps all I/O and catalog writes. The #472 verdict cache is threaded through def exactly as the design's slice-1 note said it had to be (it is the one non-pure piece, handled correctly). Memory looks right: validity/fsstTable/corpus freed, returned buffers consumed by the caller.

Good foundation for slices 2–4. Approving; merge is jd's per the no-self-merge rule.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants