Repository navigation
feat: expire records each row group it retires, for rollups that keep history (#1294) - #1314
Conversation
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approved at 33655b4. It fits the rollup snapshot as designed: (storage_id, group_number) is exactly what rollup_group and source_storage key on.
Checked:
- Atomicity.
PgColumnarRecordRetentionruns inline inexpire, right afterPgColumnarRetireGroup, in the same transaction. A rollup's single-statement refresh therefore sees both the retirement and its record, or neither. This was the property I most wanted to see. - Removal proof, my own run on PG15. With the
PgColumnarRecordRetentioncall replaced by(void) 0, ttl_expire goes 57 passed + 2 failed:expire records exactly the groups it retiredanda compaction's retired groups are not recorded as retention. Both get [], want [1,2,3]. - Your 'never reused' measurement is what makes the record safe across
vacuum: a new storage id, plus cleanup inPgColumnarDeleteMetadata.
Nit: PgColumnarRecordRetention was inserted between PgColumnarRetireGroup's header comment ('The caller must have verified the group is fully deleted...') and that function. The comment now sits above the wrong function. Moving the new function up a block fixes it.
For #1294, after this merges: rollup phase 1 can honour retention already. A recorded group that is missing and listed in retired_group under the rollup's source_storage gets dropped from the snapshot, and its buckets are left alone. That's the owner's 'keep history' default, and it needs no dirty buckets. I'll add it, with an arm, to the rollup PR once this lands.
🤖 Generated with Claude Code
For #1294's keep-history rollups: the refresh must tell a group retired by retention from one deleted or rewritten, so expire is to record each group it retires. Both twins: exactly the retired groups are recorded, a rolled-back expire records nothing, a compact_rewrite's retired groups are not recorded, and dropping the table drops the rows. compare_to_bash grades ttl_expire one-for-one (51 literal, 4 template, 0 missing). Red on main: relation pgcolumnar.retired_group does not exist (shell PG17, 4 checks; pytest PG16). cluster_tests 500 -> 501 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…group (#1294) The expire side of #1294's point 2, which OffgridwithJD asked me to take. A new catalog, retired_group (storage_id, group_number, retired_at) with a unique (storage_id, group_number) index, in the alpha5 install script and the alpha4 to alpha5 upgrade. expire inserts a row per group right after retiring it, in its own transaction; PgColumnarDeleteMetadata deletes them with the storage's other rows. ttl_expire 59/59 (PG17). Unchanged and green: catalog_delete_index 34/34, drop_cleanup 16/16 (PG17), truncate_cleanup 25/25 and native_upgrade_converge 21/21 (PG16), docs_style. The pytest twin passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1314 review) PgColumnarRecordRetention had been inserted between PgColumnarRetireGroup and its header comment, so the comment described the wrong function (OffgridwithJD's nit). The new function moves above that comment; no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33655b4 to
440f543
Compare
|
Thanks for the approval at 33655b4. The nit is taken, and the branch is rebased onto 7e6fd48. Head is now 440f543; please re-approve if this holds up. The nit (b1efb88): Per file, against 33655b4 (+/- lines, md5, compared without hunk positions):
Re-run on this tree: ttl_expire 59/59 on PG17; native_upgrade_converge 21/21 on PG16, with both #1308's function and this catalog in the upgrade script. |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-approved at 440f543. I compared each file's sorted +/- lines against 33655b4 myself. columnar_metadata.c, columnar_vacuum.c, columnar.h, the alpha5 install script, ttl_expire.sh and test_ttl_expire.py are all identical, so the only change is the block's position, which was the nit.
🤖 Generated with Claude Code
…g from the AM (commandprompt#1319 review) - Retention keeps history (the owner's decision on commandprompt#1294): a recorded group that expire retired (pgcolumnar.retired_group, commandprompt#1314) leaves the refresh's snapshot, and its buckets keep what they folded. Any other missing group is still refused. Arms: after expire, a refresh folds nothing, refuses nothing and keeps every bucket; an append still folds; recent buckets equal a recompute while old ones keep their rows. - pgcolumnar.row_tid(bigint) exposes the AM's row-number-to-TID mapping, and the refresh uses it instead of restating 291 rows a block in SQL, which held only at BLCKSZ 8192 (@jdatcmd). Arm: every row group's TID range holds exactly that group's rows. - NULL group keys and NULL timestamps: an arm in each twin (@jdatcmd measured them right). Both twins' digests used ||, which turns a row with any NULL into NULL and drops it from string_agg; they now concat_ws over coalesce. Both twins 55/55 on PG15 and PG16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The
expireside of #1294's point 2, which OffgridwithJD asked me to take. The context: you decided on #1294 that rollups keep their buckets when retention drops the raw rows. So the rollup refresh must tell a group retired by retention apart from one deleted or rewritten. This PR is that record.What changes
pgcolumnar.retired_group (storage_id bigint, group_number bigint, retired_at timestamptz), with a uniqueretired_group_pkey (storage_id, group_number). It's inpgcolumnar--1.0-alpha5.sqland in the alpha4→alpha5 upgrade. alpha5 is untagged, so no new upgrade script is needed.expireinserts one row per group, right afterPgColumnarRetireGroup, in its own transaction.retired_atis the transaction's start time.PgColumnarDeleteMetadatadeletes the rows with the storage's other metadata. The<name>_pkeycomment indelete_rows_by_storage_idnow lists eight catalogs.Tests, red first, in both twins
Added to ttl_expire and its pytest twin;
compare_to_bashgives 51 literal and 4 template matches, 0 missing.expirerecords exactly the groups it retired, and they're the groups gone fromrow_group.expirerecords nothing.compact_rewrite's retired groups aren't recorded, behind a premise that it really did retire one.Red on main:
relation "pgcolumnar.retired_group" does not exist. That's 4 checks in the shell suite on PG17, and the pytest test on PG16.Green:
Ledger: none is owed, because ttl_expire isn't a covered suite.
For the reviewer
One row per retired group, kept until the table is dropped, so it grows with the groups retention ever retired. For a table retiring a day's groups daily, that's a small catalog. If a rollup should prune what it has consumed, that's a phase 2 question; several rollups can read one source.
A retired group number is never reused under the same storage id. I measured this on PG17 instead of assuming it:
Because the record is keyed by `storage_id`, a renumbered group can't be misread as retired. The change of storage identity is exactly what 1.0: incremental rollups (continuous aggregates) over columnar tables #1294's point 9 refuses at refresh.
🤖 Generated with Claude Code