events_raw (the millpond→hoglake table, partitioned day(timestamp)) is append-only: no UPDATE, no row DELETE. Its only retention operation is "drop everything older than N days". hoglake has no way to say that today. The options are row deletes through deletion vectors (a DV per file for a whole-file delete, then compaction to reclaim), TRUNCATE (the whole table), or snapshot expiry (time travel, which this table does not need).
What is needed
A partition-level drop: end every live data file (and its DV) whose partition values satisfy a predicate, in one snapshot, without touching any object. Two entry points:
- Explicit:
POST /catalogs/{c}/namespaces/{ns}/tables/{t}/partitions/drop with a predicate on the table's partition fields (day(timestamp) < 2026-09-16, or a list of partition tuples), the usual expected_table_uuid / read_snapshot guards, and an idempotency key so a lost response is not a double drop. Returns the snapshot id and the count of files ended.
- Policy: per-table
partition_retention ({ field, keep: 7d }) evaluated by the expiry sweep, so a table drops its own tail without a client.
Mechanism
hog_file_partition_value already carries each file's partition tuple, so the predicate is a metadata query; ending files is FileRepo.endLiveFiles for the matching ids plus the matching hog_delete_file rows, at a freshly allocated snapshot under the catalog commit lock. Row-id ranges are never reused, so nothing else changes.
- Publish
table_deleted_from for the table so guarded writers with an older read_snapshot conflict, exactly as a DV publication does; the changefeed then reports the ended files as deletes, which is what a downstream consumer must apply.
- Objects go through the existing pipeline: once the retention floor passes the drop snapshot, expiry queues the ended files and cleanup deletes them. No new deletion path, no new ledger reason.
- Compaction must not rewrite a partition that policy is about to drop. The planner reads the table's
partition_retention and excludes partitions inside the drop window; rewriting them is pure amplification (MaintenanceSummarySampler should exclude them from debt too).
VerifyService gets one invariant: no live file with a partition value inside the retention window.
Why not DVs
A DV-per-file delete writes N puffin objects, a commit that registers all of them, and then a compaction rewrite that materializes the deletes, for rows nobody will read again. Ending the file rows is one UPDATE.
Retention for append-only tables
For a table like this, snapshot_retention_seconds only needs to cover the longest in-flight read (a scan planned at S still reads files compaction retired after S), and consumer_floor protects changefeed consumers independently. An hour is generous.
Related: #157 (TRUNCATE is the whole-table special case of this), #160 (DV publication, the row-level alternative), #140 (retention as a curve), the millpond events_raw dev instance (charts #16117).
events_raw(the millpond→hoglake table, partitionedday(timestamp)) is append-only: no UPDATE, no row DELETE. Its only retention operation is "drop everything older than N days". hoglake has no way to say that today. The options are row deletes through deletion vectors (a DV per file for a whole-file delete, then compaction to reclaim), TRUNCATE (the whole table), or snapshot expiry (time travel, which this table does not need).What is needed
A partition-level drop: end every live data file (and its DV) whose partition values satisfy a predicate, in one snapshot, without touching any object. Two entry points:
POST /catalogs/{c}/namespaces/{ns}/tables/{t}/partitions/dropwith a predicate on the table's partition fields (day(timestamp) < 2026-09-16, or a list of partition tuples), the usualexpected_table_uuid/read_snapshotguards, and an idempotency key so a lost response is not a double drop. Returns the snapshot id and the count of files ended.partition_retention({ field, keep: 7d }) evaluated by the expiry sweep, so a table drops its own tail without a client.Mechanism
hog_file_partition_valuealready carries each file's partition tuple, so the predicate is a metadata query; ending files isFileRepo.endLiveFilesfor the matching ids plus the matchinghog_delete_filerows, at a freshly allocated snapshot under the catalog commit lock. Row-id ranges are never reused, so nothing else changes.table_deleted_fromfor the table so guarded writers with an olderread_snapshotconflict, exactly as a DV publication does; the changefeed then reports the ended files as deletes, which is what a downstream consumer must apply.partition_retentionand excludes partitions inside the drop window; rewriting them is pure amplification (MaintenanceSummarySamplershould exclude them from debt too).VerifyServicegets one invariant: no live file with a partition value inside the retention window.Why not DVs
A DV-per-file delete writes N puffin objects, a commit that registers all of them, and then a compaction rewrite that materializes the deletes, for rows nobody will read again. Ending the file rows is one UPDATE.
Retention for append-only tables
For a table like this,
snapshot_retention_secondsonly needs to cover the longest in-flight read (a scan planned at S still reads files compaction retired after S), andconsumer_floorprotects changefeed consumers independently. An hour is generous.Related: #157 (TRUNCATE is the whole-table special case of this), #160 (DV publication, the row-level alternative), #140 (retention as a curve), the millpond
events_rawdev instance (charts #16117).