zhuxiangyi opened a new pull request, #965:
URL: https://github.com/apache/paimon-rust/pull/965
### Purpose
Linked issue: #964 (part 1 of 4)
Tables written through paimon-rust (Rust API, pypaimon native, C FFI) never
expire snapshots, so
their snapshot, manifest, and data files only ever grow. This PR adds
snapshot expiration to the
core crate and exposes it as `CALL sys.expire_snapshots`, following Java's
`ExpireSnapshotsImpl`
and the Flink/Spark `expire_snapshots` procedure.
### Brief change log
- `Table::new_expire_snapshots()` / `ExpireSnapshots`
(`table/expire_snapshots.rs`):
- Chooses snapshots to expire with Java's rules:
- never more than `snapshot.num-retained.max` snapshots are kept, and
never fewer than
`snapshot.num-retained.min`;
- a snapshot expires only once the snapshot after it is older than the
cut-off
(`snapshot.time-retained`, or an explicit `older_than`);
- snapshots that a consumer (`consumer/consumer-*`) still reads from are
kept;
- one run expires at most `snapshot.expire.limit` snapshots.
Each rule can be overridden per call.
- Expiring `[earliest, end)` deletes, in Java's order:
1. data files deleted by the delta manifests of `(earliest, end]` that
the closest earlier
tag does not still read;
2. changelog files of `[earliest, end)`;
3. manifest lists, manifests and their extra files, index manifests,
index files, statistics
files, and row-id reassign plans that neither `end` nor a tag in the
range references;
4. the snapshot files, and finally the EARLIEST hint.
- `table/snapshot_deletion.rs` (Java `FileDeletionBase` /
`SnapshotDeletion`) holds the planning
and deletion. Every read failure makes the plan delete less, never more:
- an unreadable delta manifest skips that snapshot's data files;
- a tag that cannot be read skips the files it might protect;
- a skipping set that cannot be built skips manifest deletion;
- if the end snapshot disappears concurrently, the run stops before
deleting any manifest.
- Unlike Java, the check "a file the same delta re-adds (a level upgrade) is
not deleted" does not
depend on the DELETE entry coming before the ADD entry.
- New `CoreOptions`: `snapshot.num-retained.min` / `.max`,
`snapshot.time-retained`,
`snapshot.expire.limit`, with Java's defaults. Invalid values fail instead
of silently falling
back, since this path deletes files.
- `SnapshotManager::try_get_snapshot` and `earliest_hint_exists`.
- DataFusion `CALL sys.expire_snapshots(table, retain_max, retain_min,
older_than, max_deletes,
options)` returns `deleted_snapshots_count`, like Java. `older_than`
accepts epoch milliseconds
or a `yyyy-MM-dd HH:mm:ss[.SSS]` timestamp in the local time zone (Java's
format). `options`
are dynamic table options for the call.
- Uses `table.snapshot_manager()`, so REST-catalog tables resolve their
latest snapshot through the
catalog, as in Java.
Scope notes:
- Like Java's procedure, which builds its `ExpireConfig` without changelog
options, changelog
files expire with their snapshots. `changelog.num-retained.*` /
`changelog.time-retained`
(changelog decoupling) need a changelog manager and are left for later.
- `snapshot.clean-empty-directories` is not implemented yet.
### Tests
- `table::expire_snapshots::tests` (14 tests). The central check is
`assert_files_match_references`: after a run, the data, manifest, and
index files on disk must
be exactly the files some remaining snapshot or tag references. Live data
files come from the
scan planner, not from the deletion code. The scenarios:
- an empty table;
- an append-only table, where every data file is kept;
- overwrite, where the replaced files are deleted;
- a partitioned overwrite, which checks partition path resolution;
- a tag keeps its data files and manifests and stays readable through
`scan.tag-name`;
- a consumer keeps the snapshot it reads, which stays readable through
`scan.snapshot-id`;
- each retention rule on its own: `num-retained.max`, `older_than`
(successor rule),
`max_deletes`, `retain_min`;
- invalid retention arguments and options are rejected;
- a level upgrade (DELETE and ADD of the same file in one delta) keeps the
file;
- rewritten files are deleted;
- a dropped global index's files are deleted while an index still in use
is kept;
- a missing end snapshot keeps all manifests;
- the EARLIEST hint is written even when nothing expires;
- `previous_tag` and `find_skipping_tags`.
- The tests catch the key mistakes: disabling tag protection or upgrade
protection each makes a
test fail.
- `CoreOptions` defaults, overrides, and invalid values.
- DataFusion `tests/procedures.rs`:
- an end-to-end `CALL sys.expire_snapshots` test covering `retain_max`,
`older_than`,
`max_deletes`, and `options`, checked against `$snapshots` and the table
data;
- argument validation: an unknown argument, a non-integer, `retain_max`
below `retain_min`, and
a bad timestamp.
- A unit test for `older_than` parsing.
- `cargo test -p paimon --all-targets --features fulltext,vortex` and `cargo
clippy --all-targets
--workspace --features fulltext,vortex -- -D warnings` pass.
### API and Format
- New public API: `Table::new_expire_snapshots()` / `ExpireSnapshots`; new
`CoreOptions`
accessors; new `sys.expire_snapshots` procedure.
- No storage format change.
### Documentation
`docs/src/sql.md` documents `expire_snapshots`: its arguments, the table
options behind them, and
what the procedure keeps.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]