mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-23 12:49:04 +00:00
fix(ecstore): migrate tier free versions during decommission
This commit is contained in:
@@ -188,55 +188,54 @@ them.
|
||||
### Decommission Handling
|
||||
|
||||
The exact decommission inventory loader (`load_file_info_versions_exact` via
|
||||
`get_all_file_info_versions`) keeps free-version records inline in `versions`; it
|
||||
never populates `free_versions`, so the source-cleanup preflight comparison of
|
||||
`free_versions` is vacuous for decommission. The migration loop then routes every
|
||||
record through the generic delete-marker handling:
|
||||
`get_all_file_info_versions`) keeps free-version records inline in `versions`.
|
||||
The migration loop handles them before lifecycle expiry and delete-marker
|
||||
shortcuts. It selects a target pool using the free-version-aware lookup, then
|
||||
writes the original free record to every target disk with the normal metadata
|
||||
write quorum. The free-version marker, local version id, transition identity,
|
||||
transition state, and destination id are preserved at the FileInfo/metadata
|
||||
boundary.
|
||||
|
||||
- a record that is the only remaining version without replication is skipped by the
|
||||
empty-delete-marker rule and counted as done;
|
||||
- any other record is copied to the target pool as an ordinary delete marker with the
|
||||
same version id and mod time.
|
||||
|
||||
In both cases the free-version flag and its remote-tier identity are dropped:
|
||||
decommission neither preserves free-version semantics nor performs or reschedules the
|
||||
pending remote-tier delete. Source cleanup then removes the original records together
|
||||
with the source xl.meta.
|
||||
|
||||
Allowed physical-delete timing: the source record may be removed once the migration
|
||||
loop has dispositioned it (copied as a plain marker or skipped as lone), which
|
||||
happens regardless of whether its remote-tier delete was ever performed.
|
||||
The source record is physically removed only after the target write quorum has
|
||||
committed and the source cleanup preflight still matches the exact inventory.
|
||||
If target capacity, metadata validation, lock fencing, or quorum fails, the
|
||||
source record remains and the entry records `state = "free_version_retained"`
|
||||
with reason `tier_free_version_migration_failed`; the worker retries the
|
||||
operation on a later pass. A target record with the same version id is accepted
|
||||
only when its free-version identity matches; a conflicting ordinary version or
|
||||
different free record is an overwrite error. This makes retries idempotent and
|
||||
prevents a free record from replacing a user-visible version.
|
||||
|
||||
### Reference-Audit Result
|
||||
|
||||
No cluster-local consumer resolves a free version after decommission finishes: GET,
|
||||
listing, transition planning, replication, restore, and heal operate either on
|
||||
user-visible versions or while the record still exists. The remote exposure is
|
||||
bounded:
|
||||
After migration, user-facing GET/list/transition/replication/restore paths still
|
||||
exclude the record. Recovery, usage scanning, lifecycle tier cleanup, and heal
|
||||
continue to see it when they request free versions, so an unresolved remote
|
||||
delete remains actionable on the target pool. The committed tier journal remains
|
||||
an independent retry source where one exists; it is not used as a reason to drop
|
||||
the xl.meta record. In particular, `Unknown` transition state records are
|
||||
migrated unchanged rather than discarded: the lifecycle worker retains them if
|
||||
remote identity validation cannot make a delete request.
|
||||
|
||||
- On every user-facing delete path the remote-delete obligation is durably carried
|
||||
by the committed tier-journal entry, which the tier sweeper processes
|
||||
independently of xl.meta; the free-version record is an idempotent second
|
||||
pointer, not the only one. Dropping it during decommission therefore does not
|
||||
orphan the remote object.
|
||||
- Residual exposure: for records whose version state decoded as `Unknown` no
|
||||
journal entry exists, so dropping the unconsumed record loses that cleanup hint
|
||||
and the remote-tier object is orphaned. The same applies to any future internal
|
||||
delete path that removes transitioned versions without a journal entry.
|
||||
Each migrated record emits `state = "free_version_migrated"` with reason
|
||||
`tier_free_version_migrated`. Each failed record emits the retained state and
|
||||
failure reason above. The entry also emits a disposition summary with migrated,
|
||||
retained, and total counts. The final decommission sweep uses the exact loader,
|
||||
counts free records still present, and emits one retained record/reason for each
|
||||
unresolved free version before failing the sweep. This makes both successful
|
||||
migration and retained cleanup obligations visible instead of silently omitting
|
||||
free records.
|
||||
|
||||
Copying a pending record as an ordinary delete marker also adds a user-visible
|
||||
tombstone to the target pool's version history that the source never exposed.
|
||||
|
||||
Because of the residual journal-less case, decommission must account for every
|
||||
free-version record instead of omitting it silently:
|
||||
|
||||
- `decommission_free_versions_skipped` counts the records per decommission entry;
|
||||
- entries with a non-zero count log `state = "free_versions_skipped"` with reason
|
||||
`tier_free_version_not_migrated`.
|
||||
No new S3-visible version or admin response field is needed: free versions remain
|
||||
internal and are never counted as user-visible versions. The structured
|
||||
`decommission_entry` events are the operational status surface for the
|
||||
free-version disposition; the existing decommission item/failed counters still
|
||||
report the enclosing object migration result.
|
||||
|
||||
Regression guard:
|
||||
|
||||
- `decommission_free_version_accounting_reports_skipped_records`
|
||||
- `decommission_free_version_accounting_records_migration_and_retention`
|
||||
- `decommission_tier_free_version_preserves_remote_identity`
|
||||
|
||||
## Regression Guard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user