Commit Graph
7 Commits
Author SHA1 Message Date
Tim Olowandtimolow 331c6c3642 shell: volume.check.disk — actionable verdict for diverged vacuumed replicas (#11197)
* shell: volume.check.disk — actionable verdict for diverged vacuumed replicas

When -resurrectMissingNeedles is gated off because both replicas have been
vacuumed (compaction revision > 0) — the normal state of any production
cluster — check.disk previously stopped at 'cannot prove they are missing
writes vs vacuumed deletes' and did nothing, leaving a diverged replica with
no repair path. volume.fix.replication does not catch it either: it only
acts when the replica COUNT is below the expected replication, never when
two replicas are both present but hold different live data.

Classify the divergence instead of dead-ending:

  liveDivergence() counts live (non-deleted) needles present on one replica's
  index but entirely absent from the other, in both directions. Tombstones
  are excluded, so vacuum asymmetry (a compacted replica that dropped deleted
  entries) is not mistaken for divergence.

  reportDivergenceVerdict() turns the count into an operator action:
    - one-sided (one replica has all the live data, the other has no unique
      live needles) -> print the exact safe repair:
        volume.copy -source <complete> -target <lagging> -volumeId <id>
      Re-copying the complete replica is safe precisely because the lagging
      side holds no unique live data; VolumeCopy's verify-before-destroy gate
      independently confirms the source holds the volume before deleting the
      target.
    - two-sided (split-brain, both sides have unique live data) -> warn and
      do NOT emit an auto repair; point to volume.fsck -findMissingChunksInFiler
      to confirm the 'missing' needles are orphans before converging.

Report-only: no data is modified and the resurrection safety gate is
untouched. This is what lets a 13-volume diverged cluster be diagnosed and
repaired in minutes instead of by hand-diffing every index.

Observed motivating case: home SeaweedFS 4.45 cluster, 13 010 cross-rack
volumes diverged after failed replicate writes (ReplicatedWrite MaxAttempts=1
fire-and-forget), 11 one-sided + 2 two-sided, all repaired via volume.copy.

* shell: volume.check.disk — address review nits on divergence verdict

- Use pb.NewServerAddressFromDataNode (dialable ip:port, Address with Id
  fallback) for the advertised volume.copy -source/-target instead of the
  logical node Id, which may not be dialable.
- Make the one-sided verdict tombstone-aware: when the lagging replica has
  been vacuumed, absent live needles may be valid deletions whose tombstones
  were dropped, so a whole-volume re-copy would resurrect them. The command
  is only advertised as safe when the lagging side is proven never-vacuumed
  (compaction revision 0 read under -resurrectMissingNeedles); otherwise a
  caveat is printed pointing at fsck/needle-level repair.
- Fix reversed copy direction when the source replica is the lagging one
  (must copy complete -> lagging in both cases).
- Test: real tombstone (negative size) with the correct 0/0 expectation and
  || assertion; verdict test now covers dialable address, corrected
  direction, and caveat on/off.

* shell: volume.check.disk — per-replica revision knowledge, no copy command for vacuumed lagging side

- Track srcRevKnown/tgtRevKnown separately: in unidirectional mode the
  target revision IS read, so a proven never-vacuumed target no longer
  gets a false resurrection warning (regression: bidi=false, target rev 0).
- A one-sided verdict now only emits the volume.copy command when the
  lagging side is proven never-vacuumed; when it is vacuumed (or unproven)
  the verdict refuses to print the destructive command and points at
  fsck/needle-level repair instead — an appended caveat next to a ready-to-
  paste copy command was still inviting the resurrection.
- deletionCaveat now returns the boolean safety decision.

* shell: volume.check.disk — preserve gRPC port, proven two-sided is not split-brain

- Emit the raw ServerAddress string (host:port.grpcPort) instead of
  String()/ToHttpAddress(), which drops the custom gRPC port and would
  make the suggested volume.copy dial the default port and fail.
- Two-sided divergence with both replicas proven never-vacuumed under
  -resurrectMissingNeedles is mutually missed writes, not split-brain:
  recommend re-running with -apply (in-place resurrection both
  directions) instead of the split-brain no-auto-repair warning.
- Tests: case F (proven two-sided -> -apply, no split-brain warning),
  case G (custom gRPC ports preserved in emitted addresses).

---------

Co-authored-by: timolow <tim@timolow.dev>
2026-09-06 23:25:28 -07:00
Chris Lu 013281b498 shell: volume.check.disk -resurrectMissingNeedles for never-vacuumed replicas (#10316)
An absent needle is normally indistinguishable from a vacuumed delete,
so check.disk skips it and replicas diverged by replication failures
cannot be reunited. But a replica with compaction revision 0 has never
been vacuumed: every delete it processed still holds its tombstone, so
a needle absent there is provably a missing write.

The new flag resurrects absent needles in exactly that case. The
receiving replica's live compaction revision is read after the index
snapshot and must be 0; vacuumed replicas keep the skip, now with the
revision in the message. The decision is passed per direction since
pass 2 runs pairs concurrently. Resurrected needles count toward
-nonRepairThreshold as before.
2026-07-12 00:14:16 -07:00
Chris Lu c2591b4395 fix(replication): verify-before-destroy in VolumeCopy, check.disk, and over-replication trim (#9943)
* volume: verify before destroy in VolumeCopy and replication repair

Four data-safety fixes around copy/repair paths that could destroy or
resurrect data before verifying the source or survivors.

(a) VolumeCopy no longer deletes a pre-existing local replica up front.
The delete is deferred until ReadVolumeFileStatus on the source succeeds,
so a transient source outage (or a retry after one) can no longer wipe a
healthy destination replica. Gated on source readability only; size/count
comparisons are intentionally not used because they invert legitimately
after divergent vacuum/compaction. Mirrored in the Rust volume server.

(b) volume.check.disk no longer resurrects vacuumed-deleted needles. A
key present-and-live on the source but entirely absent on the target is
ambiguous: it may be a genuine missing write, or a needle deleted on the
target and then vacuumed (its index entry and any tombstone are gone). An
individual needle AppendAtNs has no monotonic relation to a vacuum
watermark, so the old cutoff heuristic could not tell them apart. Without
positive proof the absence is a missing write, the safe default is to NOT
push it back. Tradeoff: a real missing write may go unrepaired until a
tombstone-aware path exists, but we never raise back deleted data.

(c) Over-replication trim no longer resurrects needles or removes the
wrong replica. The pre-delete sync now runs read-only (divergence check
only) instead of writing the doomed replica's needles into the survivor.
pickOneReplicaToDelete only ever removes the smallest of multiple healthy
writable replicas; it refuses the trim when doing so would leave only
read-only/integrity-flagged survivors, since file_count>0 alone cannot
prove the survivor's .dat is readable.

(d) Incomplete-volume (.note) cleanup keeps the shared .vif when an .ecx
for the same vid coexists on the disk, so removing an interrupted regular
copy cannot strip a coexisting EC volume's info file. VolumeCopy now
surfaces .note write/remove errors instead of ignoring them. In the Rust
volume server (where a persisting note is actually reachable) the .note
check moves below the empty-stub sweep and EC validation, keeps the .vif
on EC coexistence, and the mount path fails when a .note still persists.

* shell: scope the over-replication writable-survivor guard to the trim path only

The writable-survivor guard (never trim down to a read-only survivor) lived
inside the shared pickOneReplicaToDelete, so it also gated the misplaced-volume
relocation via pickOneMisplacedVolume -- a misplaced read-only volume (e.g. a
full one) would silently stop being rebalanced. Extract pickSmallestReplica
for the relocation path (which deletes-and-recreates and must act on read-only
replicas), and keep the writable-survivor guard only in pickOneReplicaToDelete
used by the over-replication trim.

* seaweed-volume: recompute keep_vif after invalid-EC cleanup in the .note path

keep_vif used the pre-validation ecx_exists snapshot, so when the EC-validation
step above removed the invalid .ecx/shards, the .note cleanup still preserved a
now-orphaned .vif. Re-check .ecx existence at cleanup time, matching the Go
hasEcxFile re-check.

* shell: keep placement when picking an over-replication victim to delete

The trim picked the smallest writable replica without regard to placement, so
it could delete the only replica in a required failure domain (e.g. with "100"
and replicas dc1 + two in dc2, deleting dc1 leaves both survivors in dc2).
Prefer a writable replica whose removal still satisfies placement, falling back
to the smallest writable only when none does.
2026-06-13 20:05:33 -07:00
Lisandro PinandChris Lu ebb06a3908 Mutex command output writes for volume.check.disk. (#7605)
Prevents potential screen garbling when operations are parallelized
.Also simplifies logging by automatically adding newlines on output, if necessary.

Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
2025-12-02 10:14:24 -08:00
Lisandro Pinandchrislu 9744382a18 Rework parameters passing for functions within volume.check.disk. (#7448)
* Rework parameters passing for functions within `volume.check.disk`.

We'll need to rework this logic to account for read-only volumes, and there're already way too many parameters shuffled around.

Grouping these into a single struct simplifies the overall codebase.

* similar fix

* Improved Error Handling in Tests

* propagate the errors

* edge cases

* edge case on modified time

* clean up

---------

Co-authored-by: chrislu <chris.lu@gmail.com>
2025-11-10 16:03:38 -08:00
Lisandro Pin f466ff1412 Nit: use time.Durations instead of constants in seconds. (#7438)
Nit: use `time.Durations` instead of constants in seconds. Makes for slightly more readable code.
2025-11-04 13:02:22 -08:00
Konstantin LebedevandKonstantin Lebedev 2b3e39397e fix: skipping checking active volumes with the same number of files at the moment (#4893)
* fix: skipping checking active volumes with the same number of files at the moment
 https://github.com/seaweedfs/seaweedfs/issues/4140

* refactor with comments
https://github.com/seaweedfs/seaweedfs/issues/4140

* add TestShouldSkipVolume

---------

Co-authored-by: Konstantin Lebedev <9497591+kmlebedev@users.noreply.github.co>
2023-10-09 09:57:26 -07:00