Files
rustfs/docs/superpowers/plans/rebalance-decommission-phase1-safety-plan.md
T
Zhengchao An d1db9a10cd chore(docs): refresh agent docs, guard doc paths, archive plans (#4203)
Agent-instruction and architecture docs had drifted from the code:

- CLAUDE.md: slim to commands + pointers; fix wrong claim that
  `make pre-commit` is the full pre-PR gate (that is `make pre-pr`);
  drop stale pre-#3929 file paths and merged bug narratives
- AGENTS.md: drop dead `rust-refactor-helper` skill rule and the
  hand-maintained (already stale) scoped-AGENTS index; link
  architecture docs from Sources of Truth
- .github/AGENTS.md: replace the outdated copied CI command matrix
  with a pointer to ci.yml
- crates/AGENTS.md: merge duplicated Testing sections
- ARCHITECTURE.md: resolve the utils->config contradiction (edges are
  removed), mark volatile counts as snapshots, fix a bad path
- docs/architecture: add README router; move one-shot plans/trackers
  (rebalance-decommission phases, migration-progress ledger, PR
  template) to docs/superpowers/plans with archive headers; fix stale
  source paths in kept inventories (core/sets.rs, core/pools.rs,
  store/mod.rs, startup_* split from #3671)
- docs/operations/tier-ilm-debugging.md: extracted tier debugging
  playbook with corrected paths
- scripts/check_doc_paths.sh: new guard failing pre-commit/pre-pr when
  instruction/architecture docs reference nonexistent file paths
- .claude/skills: add tier-debug and arch-checks repo skills;
  .gitignore now keeps .claude/skills and docs/operations committable

Verification: ./scripts/check_doc_paths.sh,
./scripts/check_architecture_migration_rules.sh (both pass)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:30:13 +08:00

17 KiB

Archived implementation plan/tracker — moved from docs/architecture/ (2026-07). Kept for history; not maintained. File paths inside may reflect the pre-#3929 module layout (e.g. crates/ecstore/src/rebalance.rs is now crates/ecstore/src/store/rebalance.rs; set_disk.rs is now set_disk/).

Rebalance and Decommission Phase 1 Safety Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Fix the highest-risk rebalance/decommission safety defects so object-version semantics and distributed operation state cannot silently diverge.

Architecture: Phase 1 keeps the existing ECStore/Rebalance/PoolMeta architecture and introduces narrowly scoped behavior changes. The plan favors fail-closed semantics for unsafe migration, peer propagation, and startup conflicts. Each fix block should be implemented as a separate PR unless a shared helper is explicitly called out.

Tech Stack: Rust, Tokio, ECStore, Axum admin handlers, tonic peer RPC, MessagePack metadata, existing RustFS test modules.


Scope

This plan covers:

  • F01: rebalance delete marker and remote tiered version migration semantics.
  • F02: decommission DataMovementOverwriteErr cleanup safety.
  • F03: decommission pool_meta reload as a start barrier.
  • F04: store startup recovery ordering for pool meta and rebalance.
  • F05: rebalance distributed start/stop semantics and stopping visibility.

This plan does not cover multipart streaming, checksum preservation, lifecycle-expired cleanup, overwrite convergence, cleanup warning UX, audit fields, or metadata decoding hardening. Those belong to later phase plans.

Shared Principles

  • Treat unknown target equivalence as unsafe.
  • Do not clean a source entry unless the target state is proven complete or the version is intentionally skipped by a documented policy.
  • Do not report distributed admin success when required peers failed.
  • Do not auto-run rebalance and decommission together after restart.
  • Prefer small local helpers near existing logic over broad refactors.

F01: Rebalance Delete Marker and Remote Tiered Version Safety

Decision Needed

Remote tiered versions need a product decision before coding:

  • Recommended short-term behavior: Match MinIO and skip remote tiered versions during rebalance. This avoids changing remote-tier metadata during a balancing operation and removes the immediate source-cleanup data-loss risk.
  • Later behavior: Implement explicit cross-pool target metadata movement for remote tiered versions only after tests prove remote references, lifecycle state, and cleanup semantics.

Delete markers should not be skipped. They preserve versioned delete semantics and must be copied to the selected target pool before source cleanup.

Files

  • Modify: crates/ecstore/src/rebalance.rs
  • Possibly modify: crates/ecstore/src/data_movement.rs
  • Avoid unless necessary: crates/ecstore/src/set_disk.rs
  • Test: existing rebalance unit tests in crates/ecstore/src/rebalance.rs

Design

  1. Add an ECStore-level helper for rebalance delete marker movement.

    • Input: source pool index, bucket, object name, source FileInfo, version ID.
    • Behavior: choose a target pool using the same placement rules as normal data movement, excluding source/rebalancing/decommissioned pools.
    • Write the delete marker metadata to the target pool with the original version ID, mod time, delete marker flag, and replication state.
    • Return success only when the target write succeeds or an equivalent target delete marker is confirmed.
  2. Change the delete marker branch in migrate_entry_version_with_retry_wait.

    • Do not call SetDisks::delete_object_for_migration for rebalance delete markers.
    • Call the ECStore-level helper via the transfer/backend abstraction or split delete marker handling out of the SetDisks backend path.
    • Do not set moved = true unless the target helper succeeds.
  3. Change remote tiered branch.

    • Short term: return ignored = true, cleanup_ignored = false, moved = false for remote tiered versions so they do not contribute to full source cleanup.
    • If skipping a remote tiered version prevents source cleanup, record a clear status reason so operators understand why the entry remains.
  4. Source cleanup must remain blocked if any version was skipped without cleanup permission.

Implementation Tasks

  • Write a failing test proving a rebalance delete marker currently does not require target-pool metadata before counting complete.
  • Write a failing test proving remote tiered versions do not allow source cleanup under the short-term skip policy.
  • Implement the delete marker target write helper.
  • Replace the rebalance delete marker source-set branch with the target write helper.
  • Replace the remote tiered branch with the skip-without-cleanup policy.
  • Run focused rebalance tests.

Focused Test Commands

cargo test -p rustfs-ecstore rebalance_delete_marker --lib
cargo test -p rustfs-ecstore remote_tier --lib
cargo test -p rustfs-ecstore rebalance_entry --lib

Acceptance Criteria

  • Rebalance does not clean a source entry when a delete marker target write fails.
  • Rebalance preserves versioned delete behavior after source cleanup.
  • Remote tiered versions are either safely skipped without source cleanup or explicitly moved with verified target metadata.
  • Existing non-delete-marker rebalance behavior remains unchanged.

Risks

  • The existing MigrationBackend trait is source-set oriented. If adapting it becomes invasive, prefer an ECStore-specific path in rebalance_entry rather than widening the trait for one branch.
  • Skipping remote tiered versions may leave more source entries behind. That is safer than deleting unverified metadata and can be improved in a later PR.

F02: Decommission DataMovementOverwriteErr Cleanup Safety

Decision

DataMovementOverwriteErr must not be cleanup-safe by default. It can count as complete only after explicit target equivalence verification.

Files

  • Modify: crates/ecstore/src/pools.rs
  • Possibly modify: crates/ecstore/src/data_movement.rs
  • Test: decommission tests in crates/ecstore/src/pools.rs

Design

  1. Update delete marker and remote tiered decommission error handling.

    • Keep object-not-found and version-not-found handling as cleanup-safe only when existing semantics justify it.
    • Remove is_err_data_movement_overwrite(&err) from branches that set cleanup_ignored = true.
  2. Add a narrow equivalence helper only if an existing target version can be inspected cheaply.

    • Compare bucket, object, version ID, delete marker state, ETag where applicable, mod time, and key metadata.
    • If comparison cannot be done reliably in the current code path, treat overwrite as failure for Phase 1.
  3. Preserve source entry on unsafe overwrite.

    • Set failure = true.
    • Include a clear error stage in logs/status.
    • Do not increment the decommissioned count.

Implementation Tasks

  • Write a failing unit test for delete marker decommission where DataMovementOverwriteErr must not count complete.
  • Write a failing unit test for remote tiered decommission where DataMovementOverwriteErr must not count complete.
  • Remove is_err_data_movement_overwrite from cleanup-safe conditions in those branches.
  • Add an explicit helper or comment documenting why overwrite is unsafe without equivalence.
  • Run focused decommission tests.

Focused Test Commands

cargo test -p rustfs-ecstore decommission --lib
cargo test -p rustfs-ecstore DataMovementOverwriteErr --lib

Acceptance Criteria

  • DataMovementOverwriteErr does not set cleanup_ignored = true by default.
  • Source cleanup does not run when overwrite equivalence is unknown.
  • Logs make unsafe overwrite distinguishable from not-found cleanup-safe cases.

Risks

  • Tightening this behavior may cause more decommission retries/failures in clusters that previously masked unsafe state. That is intended; status must make the failure actionable.

F03: Decommission Pool Meta Reload Barrier

Decision

A successful decommission start must mean all required peers have acknowledged the updated pool meta or the API must return an explicit failure/degraded result.

Files

  • Modify: crates/ecstore/src/pools.rs
  • Modify: crates/ecstore/src/notification_sys.rs
  • Modify: rustfs/src/admin/handlers/pools.rs
  • Possibly modify: crates/ecstore/src/rpc/peer_rest_client.rs
  • Possibly modify: rustfs/src/storage/rpc/node_service.rs

Design

  1. Change start_decommission propagation behavior.

    • After pool_meta.save, call reload_pool_meta.
    • If peer reload returns an aggregate error, return that error to the admin handler.
    • Do not silently proceed.
  2. Decide rollback behavior.

    • Preferred minimal Phase 1: fail the API and leave persisted pool_meta in decommission state only if rollback is unsafe or unavailable; status must show reload failure.
    • Stronger option: persist a reverted pool meta before returning failure. This needs careful lock and persistence review and should not be attempted without tests.
  3. Ensure decommission workers do not spawn after reload failure.

    • If the caller starts workers after start_decommission returns, returning Err is sufficient.
    • If any background path can resume from persisted metadata after a failed reload, status must clearly show pending/degraded state.

Implementation Tasks

  • Write a failing test for start_decommission with peer reload failure.
  • Change reload failure from warn to returned error.
  • Update admin handler error mapping so clients do not receive success.
  • Add status/log context for peer reload failure.
  • Run focused pool/decommission tests.

Focused Test Commands

cargo test -p rustfs-ecstore start_decommission --lib
cargo test -p rustfs-ecstore reload_pool_meta --lib
cargo test -p rustfs decommission --lib

Acceptance Criteria

  • Admin start decommission does not return success when peer reload fails.
  • Decommission workers do not start after reload failure through the admin path.
  • Failure output includes enough peer context for operators.

Risks

  • Persisted pool meta may already be updated before reload failure is detected. Avoid adding rollback until the exact persistence safety is reviewed. Fail-closed reporting is the minimum safe fix.

F04: Store Init Recovery Ordering

Decision

Store init must load and install pool meta before deciding whether rebalance can auto-start.

Files

  • Modify: crates/ecstore/src/store/init.rs
  • Possibly modify: crates/ecstore/src/rebalance.rs
  • Possibly modify: crates/ecstore/src/pools.rs

Design

  1. Reorder init sequence.

    • Initialize boot time.
    • Load pool meta.
    • Validate and install pool meta.
    • Resolve resumable decommission pools.
    • Load rebalance meta.
    • Start rebalance only if no decommission is active or resumable.
  2. Define conflict behavior.

    • If active decommission and started rebalance metadata coexist, decommission wins for Phase 1.
    • Rebalance should not start.
    • Record or expose a clear deferred/conflict reason if existing status structures can carry it without broad schema changes.
  3. Preserve existing happy path.

    • Clusters with only rebalance metadata should still auto-start rebalance.
    • Clusters with only decommission metadata should still resume decommission after the existing delay.

Implementation Tasks

  • Write a failing init test for active decommission plus rebalance metadata.
  • Move pool meta load/validate/install before load_rebalance_meta and start_rebalance.
  • Add a helper such as should_auto_start_rebalance_after_init.
  • Ensure decommission resume still uses the installed pool meta.
  • Run focused init tests.

Focused Test Commands

cargo test -p rustfs-ecstore init --lib
cargo test -p rustfs-ecstore rebalance_meta --lib
cargo test -p rustfs-ecstore decommission --lib

Acceptance Criteria

  • Rebalance does not start when active decommission is loaded from pool meta.
  • Rebalance still starts when no decommission is active.
  • Decommission resume behavior is unchanged except that it no longer races with an already-started rebalance.

Risks

  • Init ordering can affect existing startup failure behavior. Keep error handling and stage names explicit so operators can identify which stage failed.

F05: Rebalance Distributed Start/Stop Semantics

Decision

Admin start/stop must not report plain success for partial cluster state. If stop remains asynchronous, status must expose that distinction.

Files

  • Modify: rustfs/src/admin/handlers/rebalance.rs
  • Modify: rustfs/src/storage/rpc/node_service.rs
  • Modify: crates/ecstore/src/notification_sys.rs
  • Modify: crates/ecstore/src/rpc/peer_rest_client.rs
  • Possibly modify: crates/ecstore/src/rebalance.rs

Design

  1. Fix peer RPC stop first.

    • node_service::stop_rebalance must return success=false or gRPC error when store.stop_rebalance().await fails.
    • notification_sys.stop_rebalance must propagate aggregate peer failure instead of warning and returning Ok.
  2. Fix start propagation.

    • Avoid returning success from peer load_rebalance_meta(start=true) before local start validation has completed.
    • If fully synchronous worker startup is too invasive, split semantics:
      • metadata loaded;
      • start accepted;
      • worker running observable through status.
    • Admin start should fail when any required peer fails load/start acceptance.
  3. Define rollback/degraded semantics.

    • Preferred Phase 1: return failure on propagation failure and attempt local stop if local rebalance was already started.
    • If rollback fails, return a degraded error and expose status.
  4. Add status language for stop.

    • If cancellation is requested but workers may still be running, status should not claim fully stopped.
    • Minimal approach: add a stopping or equivalent field if existing response types allow it.

Implementation Tasks

  • Write a failing test for peer stop returning success despite local stop error.
  • Update node_service::stop_rebalance to return failure details.
  • Write a failing test for notification_sys.stop_rebalance aggregate peer failure.
  • Update notification_sys.stop_rebalance to return aggregate error.
  • Write a failing admin start propagation test.
  • Update admin start to fail or rollback on propagation failure.
  • Add status distinction for requested stop versus completed stop if response schema permits.
  • Run focused admin/RPC/rebalance tests.

Focused Test Commands

cargo test -p rustfs-ecstore stop_rebalance --lib
cargo test -p rustfs-ecstore load_rebalance_meta --lib
cargo test -p rustfs rebalance --lib

Acceptance Criteria

  • Peer stop failure is visible to callers.
  • Admin stop fails or reports degraded state on peer failure.
  • Admin start fails or reports degraded state on peer load/start failure.
  • Stop status does not mislead operators while workers are still winding down.

Risks

  • API response schema changes may affect clients. Prefer backward-compatible additions where possible, and keep error responses compatible with existing admin error handling.

Phase 1 Test Matrix

Run these once the individual fix tests pass:

cargo test -p rustfs-ecstore rebalance --lib
cargo test -p rustfs-ecstore decommission --lib
cargo test -p rustfs-ecstore pools --lib
cargo test -p rustfs rebalance --lib
cargo test -p rustfs pools --lib
cargo fmt --all --check

Before opening a PR with code changes:

cargo fmt --all
cargo fmt --all --check
make pre-commit

After build-based verification, clean generated build artifacts to avoid unnecessary disk usage.

Suggested PR Split

  1. PR 1: F02 only. Smallest fail-closed fix.
  2. PR 2: F04 only. Startup ordering and conflict tests.
  3. PR 3: F03 only. Decommission peer reload barrier.
  4. PR 4: F05 stop semantics first, then start semantics if review scope remains manageable.
  5. PR 5: F01 delete marker fix and remote tiered skip policy.

F01 is highest risk, but it may need the most design review. F02 and F04 are good first implementation candidates because they reduce safety risk with narrower code changes.

Open Questions Before Coding

  1. For F01 remote tiered versions, should Phase 1 match MinIO and skip them during rebalance, or should RustFS implement cross-pool remote metadata movement now?
  2. For F03 reload failure, should the implementation attempt to rollback persisted pool meta, or fail closed and expose degraded metadata state?
  3. For F05 start propagation, is a backward-compatible degraded error response acceptable, or must the API stay strictly compatible with current success/error shapes?