7.3 KiB
Placement And Repair Invariants
Use this when: changing anything that resolves an object to a pool, set, or disk, or that admits scanner or heal work; these are the behaviors later scheduler or topology work must preserve.
Source of truth: Sets::get_disks_by_key / get_hashed_set_index in crates/ecstore/src/core/sets.rs; DistributionAlgoVersion in crates/ecstore/src/layout/format.rs; crc_hash / sip_hash in crates/utils/src/hash.rs; ScannerCycleBudget in crates/scanner/src/scanner_budget.rs; crates/scanner/src/scanner_heal_admission_baseline.rs.
Object To Set Hash Rule
Objects reach a set through Sets::get_disks_by_key, which calls get_hashed_set_index on the object key:
DistributionAlgoVersion::V1usescrc_hash(input, set_count).DistributionAlgoVersion::V2andV3usesip_hash(input, set_count, format_id_bytes).- The format ID is part of the V2/V3 distribution seed, so changing the seed, object key, set count, or algorithm changes placement.
Preservation rule: every object read, write, list, heal, repair, and decommission path that resolves a set for an existing object must preserve the same object key and format distribution algorithm.
Pool, Set, And Disk Assignment Boundary
Pool selection is separate from set hashing:
- Existing objects are discovered across pools and resolved to the best current pool candidate before reads or updates continue.
- New object writes select an available pool from current per-pool free-space inputs after suspended or rebalancing pools are skipped.
- Set selection inside a pool uses the object-to-set hash rule above.
- Disk index assignment comes from endpoint and format metadata, not from a scheduler decision.
Boundary rule: schedulers may influence admission, worker concurrency, or buffer sizing, but they must not rewrite pool, set, or disk indexes.
Readiness And Lock Quorum Boundary
Runtime readiness checks storage and lock health independently:
- Storage readiness requires every observed set to meet write quorum based on the set drive count and storage class data/parity shape.
- Lock readiness aggregates per-set lock-client host quorum and fails fast if any set loses quorum.
- Object and bucket mutations acquire namespace locks through the existing storage lock wrappers before changing object or bucket state.
Boundary rule: readiness and lock quorum must stay set-aware. A global healthy disk count or global connected-host count is not sufficient when any individual set is below quorum.
Scanner Budget Preservation
Scanner cycles are bounded by ScannerCycleBudget:
- Runtime budget cancels the child token after the configured duration.
- Object budget cancels after the configured object count.
- Directory budget rejects additional directories and cancels with the directories reason.
- Partial-cycle metrics and checkpoints use the budget reason.
Preservation rule: later scheduler work can change how scan cycles are admitted only if it preserves the budget reason, checkpoint reason, and child-token cancellation behavior.
Heal Admission Preservation
Scanner and background repair work enter the heal manager through explicit admission:
- Scanner object heal requests are low priority and may be accepted, merged, rejected as full, or dropped.
- Required/high-priority heal candidates escalate on non-admission instead of silently disappearing.
- Heal queue admission deduplicates queued and active work unless the request explicitly forces admission.
- Full queues can drop low-priority work or displace lower-priority work for a higher-priority request according to current manager rules.
Preservation rule: repair scheduling changes must keep admission outcomes observable and must not convert rejected or dropped repair work into silent success.
Scanner/heal admission entry points
No cluster-wide coordinator or second generation token exists for scanner/heal admission; each entry point keeps its own guard. crates/scanner/src/scanner_heal_admission_baseline.rs include_str!s the scanner sources and asserts the named guards are still present, so a rename or guard removal fails that test instead of silently leaving this table stale. It also encodes the investigation matrix (scanner read and heal read may overlap; heal write conflicts with scanner reads; data-movement write conflicts with all work; independent sets stay concurrent) without claiming production enforces it.
| Work | Entry point | Current guard | Fallback / namespace semantics |
|---|---|---|---|
| Scanner read/list | nsscanner_disk in crates/scanner/src/scanner_io/io_disk.rs |
Per-disk start_scan() guard; bucket lifecycle/replication/object-lock reads precede scan_data_folder |
Scanner keeps its local disk and durable cursor; no HealManager set-level admission is consulted |
| Scanner metadata read | Object-size and metadata branches in crates/scanner/src/scanner_folder.rs |
Scanner cycle budget and per-disk scan marker | Corrupt metadata records the pending scanner ledger; MRF is a hint, not the durable owner |
| Scanner heal admission | send_required_scanner_heal_request in crates/scanner/src/scanner_folder.rs |
Manager queue dedup and pending ledger (update_pending_scanner_heal_after_admission) |
MRF Enqueued / Coalesced is ledger-only; rejected MRF keeps immediate heal plus ledger |
| Heal auto scan | start_auto_disk_scanner in crates/heal/src/heal/manager/auto_scan.rs |
Queue-first then active-task check; replacement recovery blocklist | Scanning disks remain candidates when degraded quorum needs them; they are not globally excluded |
| Heal object read | heal_object in crates/ecstore/src/set_disk/ops/heal.rs |
Namespace write lock (get_write_lock) unless no_lock; reads file info before commit |
The namespace lock is object-scoped and does not claim scanner cycle ownership |
| Disk selection | get_online_disks_with_healing_and_info in crates/ecstore/src/set_disk/ops/locking.rs |
Healing disks are ordered after new disks; scanning disks may remain candidates | Degraded/quorum fallback is preserved |
| Data movement | wait_for_data_movement_admission in crates/ecstore/src/data_movement/backpressure.rs |
Storage-owned backpressure on foreground pressure; no second coordinator | Any future admission token must be validated at the final metadata/format/delete commit (see unified-object-generation.md) |
Rules: cancellation or a local lease alone is not a fence; if a fixture ever demonstrates a stale destructive write, the fix extends the storage-owned generation/admission primitive and validates the token at the final commit rather than adding a coordinator.
Behavior Change Gates
Any later placement or repair PR must use the following gates:
- Placement gate: prove object-to-set hashing is unchanged for existing object keys and format algorithms.
- Pool gate: prove pool selection does not choose suspended or rebalancing pools unless the existing path already allows it.
- Quorum gate: prove storage readiness and lock readiness remain per-set.
- Scanner gate: prove scan budget reason and checkpoint mapping remain stable.
- Heal gate: prove low-priority scanner heal, forced heal, duplicate merge, and queue-full outcomes remain distinct.
- Rollback gate: if a new scheduler sidecar is disabled, placement and repair must fall back to the current direct ECStore/scanner/heal behavior.