You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Recovered from an unvalidated stash before dropping it, so the idea and its measurement are not lost. Nothing here is gated, and the numbers below pre-date two mitigations that have since landed. Treat it as a lead to re-measure, not a finding.
The shape
pgcolumnar_flush_row_group decides FSST keep/drop once per row group:
PgColumnarFsstBuildChunkTable (src/columnar_write_state.c:1094) builds the symbol table
PgColumnarFsstHelpsCompressed (src/columnar_write_state.c:1115) decides whether to keep it
The decision cannot use a sample. The comment at the call site records why: on the sampled prefix FSST can look 12% worse while over the whole column it is 23% better, an inversion no margin on a sample would make safe. So asking the question costs a full FSST encode of the entire corpus, per row group.
For a column whose data does not change character, that recomputes the same answer for every row group of the load.
The measurement, as recorded at the time
Profiled on a 20,000,000-row numeric load, against a tree based on 0b6f24c (2026-07-29):
symbol
share of backend
encode_fsst_shared
34.8%
ColumnarFsstBuildChunkTable
17.7%
The verdict was "FSST does not help" every time, and the stored bytes were byte-identical either way. More than half the backend went to re-deriving a constant.
Re-measure before acting. That profile pre-dates both of these:
PgColumnarFsstDictWins (Bulk load is 4.9x slower than heap, and import inherits it #155), a cheap distinct probe that skips the build when the dictionary wins outright. It does not touch the repeated whole-corpus decision, and it does not help the case here, where the dictionary does not win but FSST still loses consistently.
pgcolumnar_fsst_min_gain_percent, which changed the threshold but not how often it is evaluated.
Whatever remains after those two is the real size of this.
The proposal
Cache the verdict on PgColumnarColumnDef and reuse it for a bounded number of row groups:
#defineCOLUMNAR_FSST_UNKNOWN 0
#defineCOLUMNAR_FSST_HELPS 1
#defineCOLUMNAR_FSST_HURTS 2
#defineCOLUMNAR_FSST_REUSE 16
int8fsstVerdict; /* COLUMNAR_FSST_* */intfsstVerdictAge; /* row groups since the verdict was taken */
A HURTS verdict skips the build and the decision together. A HELPS verdict keeps the table without paying the whole-corpus encode again. The age bound is what keeps it honest: a column whose data changes character mid-load is still noticed, within COLUMNAR_FSST_REUSE row groups.
What has to be proven before this can land
The saving is real only if the stored bytes do not change. That is the whole risk: a stale verdict silently degrades compression, and nothing in the current suites would notice.
A differential arm asserting byte-identical storage with the cache on and off, on a column where the verdict is stable.
A fixture whose data changes character mid-load, asserting the verdict is re-taken within the bound and the storage matches the uncached result. Without this the age bound is untested and COLUMNAR_FSST_REUSE is a number nobody checked.
The load-time win measured in-suite, not from a standalone probe.
Provenance
The stash also carried a COLUMNAR_FSST_MIN_GAIN_SHIFT margin, which is superseded: pgcolumnar_fsst_min_gain_percent does that job configurably. Only the caching is unlanded.
Original diff, 99 insertions across src/columnar_encoding.c and src/columnar_write_state.c, base 0b6f24c. It will not apply cleanly (the surrounding code has moved, and the symbols were renamed to the PgColumnar prefix by #389); it is a description of the approach, not a patch to apply.
Recovered from an unvalidated stash before dropping it, so the idea and its measurement are not lost. Nothing here is gated, and the numbers below pre-date two mitigations that have since landed. Treat it as a lead to re-measure, not a finding.
The shape
pgcolumnar_flush_row_groupdecides FSST keep/drop once per row group:PgColumnarFsstBuildChunkTable(src/columnar_write_state.c:1094) builds the symbol tablePgColumnarFsstHelpsCompressed(src/columnar_write_state.c:1115) decides whether to keep itThe decision cannot use a sample. The comment at the call site records why: on the sampled prefix FSST can look 12% worse while over the whole column it is 23% better, an inversion no margin on a sample would make safe. So asking the question costs a full FSST encode of the entire corpus, per row group.
For a column whose data does not change character, that recomputes the same answer for every row group of the load.
The measurement, as recorded at the time
Profiled on a 20,000,000-row numeric load, against a tree based on
0b6f24c(2026-07-29):encode_fsst_sharedColumnarFsstBuildChunkTableThe verdict was "FSST does not help" every time, and the stored bytes were byte-identical either way. More than half the backend went to re-deriving a constant.
Re-measure before acting. That profile pre-dates both of these:
PgColumnarFsstDictWins(Bulk load is 4.9x slower than heap, and import inherits it #155), a cheap distinct probe that skips the build when the dictionary wins outright. It does not touch the repeated whole-corpus decision, and it does not help the case here, where the dictionary does not win but FSST still loses consistently.pgcolumnar_fsst_min_gain_percent, which changed the threshold but not how often it is evaluated.Whatever remains after those two is the real size of this.
The proposal
Cache the verdict on
PgColumnarColumnDefand reuse it for a bounded number of row groups:A
HURTSverdict skips the build and the decision together. AHELPSverdict keeps the table without paying the whole-corpus encode again. The age bound is what keeps it honest: a column whose data changes character mid-load is still noticed, withinCOLUMNAR_FSST_REUSErow groups.What has to be proven before this can land
The saving is real only if the stored bytes do not change. That is the whole risk: a stale verdict silently degrades compression, and nothing in the current suites would notice.
COLUMNAR_FSST_REUSEis a number nobody checked.Provenance
The stash also carried a
COLUMNAR_FSST_MIN_GAIN_SHIFTmargin, which is superseded:pgcolumnar_fsst_min_gain_percentdoes that job configurably. Only the caching is unlanded.Original diff, 99 insertions across
src/columnar_encoding.candsrc/columnar_write_state.c, base0b6f24c. It will not apply cleanly (the surrounding code has moved, and the symbols were renamed to thePgColumnarprefix by #389); it is a description of the approach, not a patch to apply.