Files
seaweedfs/sw-block/design/v2-algorithm-overview.md
T
pingqiuandClaude Opus 4.6 46ef79ce35 fix: stable ServerID in assignments, fail-closed on missing identity, wire into ProcessAssignments
Finding 1: Identity no longer address-derived
- ReplicaAddr.ServerID field added (stable server identity from registry)
- BlockVolumeAssignment.ReplicaServerID field added (scalar RF=2 path)
- ControlBridge uses ServerID, NOT address, for ReplicaID
- Missing ServerID → replica skipped (fail closed), logged

Finding 2: Wired into real ProcessAssignments
- BlockService.v2Bridge field initialized in StartBlockService
- ProcessAssignments converts each assignment via v2Bridge.ConvertAssignment
  BEFORE existing V1 processing (parallel, not replacing yet)
- Logged at glog V(1)

Finding 3: Fail-closed on missing identity
- Empty ServerID in ReplicaAddrs → replica skipped with log
- Empty ReplicaServerID in scalar path → no replica created
- Test: MissingServerID_FailsClosed verifies both paths

7 tests: StableServerID, AddressChange_IdentityPreserved,
MultiReplica_StableServerIDs, MissingServerID_FailsClosed,
EpochFencing_IntegratedPath, RebuildAssignment, ReplicaAssignment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:46:17 -07:00

687 lines
21 KiB
Markdown

# V2 Algorithm Overview
Date: 2026-03-27
Status: strategic design overview
Audience: CEO / owner / technical leadership
## Purpose
This document explains the current V2 direction for `sw-block`:
- what V2 is trying to solve
- why V1 and V1.5 are not enough as the long-term architecture
- why a WAL-based design is still worth pursuing
- how V2 compares with major market and paper directions
- how simulation and the real test runner systematically build confidence
This is not a phase report and not a production-commitment document.
It is the high-level technical rationale for the V2 line.
## Relationship To Other Documents
| Document | Role |
|----------|------|
| `v1-v15-v2-comparison.md` | Detailed comparison of the three lines |
| `v2-acceptance-criteria.md` | Protocol validation bar |
| `v2_scenarios.md` | Scenario backlog and simulator mapping |
| `v2-open-questions.md` | Remaining algorithmic questions |
| `protocol-development-process.md` | Method for protocol work |
| `learn/projects/sw-block/algorithm_overview.md` | Current V1/V1.5 system review |
| `learn/projects/sw-block/design/algorithm_survey.md` | Paper and vendor survey |
| `learn/projects/sw-block/test/README.md` | Real test runner overview |
| `learn/projects/sw-block/test/test-platform-review.md` | Test platform maturity and standalone direction |
## 1. Executive Summary
The current judgment is:
- `V1` proved that the basic WAL-based replicated block model can work.
- `V1.5` materially improved real recovery behavior and now has stronger operational evidence on real hardware.
- `V2` exists because the next correctness problems should not be solved by incremental local fixes. They should be made explicit in the protocol itself.
The central V2 idea is simple:
- short-gap recovery should be explicit
- stale authority should be explicitly fenced
- catch-up vs rebuild should be an explicit decision
- recovery ownership should be a protocol object, not an implementation accident
`V2` is not yet a production engine. But it is already the stronger architectural direction.
The correct strategic posture today is:
- continue `V1.5` as the production line
- continue `V2` as the long-term architecture line
- continue WAL investigation because we now have a serious validation framework
- if prototype evidence later shows a structural flaw, evolve to `V2.5` before heavy implementation
## 2. The Real Problem V2 Tries To Solve
At the frontend, a block service looks simple:
- `write`
- `flush` / `sync`
- failover
- recovery
But the real difficulty is not the frontend verb set. The real difficulty is the asynchronous distributed boundary between:
- local WAL append on the primary
- durable progress on replicas
- client-visible commit / sync truth
- failover and promotion safety
- recovery after lag, restart, endpoint change, or timeout
This is the root reason V2 exists.
The project has already learned that correctness problems in block storage do not usually come from the happy path. They come from:
- a replica going briefly down and coming back
- a replica coming back on a new address
- a delayed stale barrier or stale reconnect result
- a lagging node that is almost, but not quite, recoverable
- a failover decision made on insufficient lineage information
V2 is the attempt to make those cases first-class protocol behavior instead of post-hoc patching.
## 3. Why V1 And V1.5 Are Not Enough
This overview does not need a long retelling of `V1` and `V1.5`.
What matters is their architectural limit.
### What `V1` got right
`V1` proved the basic shape:
- ordered WAL
- primary-replica replication
- extent-backed storage
- epoch and lease as the first fencing model
### Why `V1` is not enough
Its main shortcomings were:
- short-gap recovery was too weak and too implicit
- lagging replicas too easily fell into rebuild or long degraded states
- changed-address restart was fragile
- stale authority and stale results were not modeled as first-class protocol objects
- the system did not cleanly separate:
- current WAL head
- committed prefix
- recoverable retained range
- stale or divergent replica tail
### Why `V1.5` is still not enough
`V1.5` fixed several real operational problems:
- retained-WAL catch-up
- same-address reconnect
- `sync_all` correctness on real tests
- rebuild fallback after unrecoverable gap
- control-plane refresh after changed-address restart
Those fixes matter, and they are why `V1.5` is the stronger production line today.
But `V1.5` is still not the long-term architecture because its recovery model remains too incremental:
- reconnect logic is still layered onto an older shipper model
- recovery ownership was discovered as a bug class before it became a protocol object
- catch-up vs rebuild became clearer, but still not clean enough as a top-level protocol contract
- the system still looks too much like "repair V1" rather than "define the next replication model"
### What `V2` changes
`V2` is not trying to invent a completely different storage model.
It is trying to make the critical parts explicit:
- recovery ownership
- lineage-safe recovery boundary
- catch-up vs rebuild classification
- per-replica sender authority
- stale-result rejection
- explicit recovery orchestration
So the correct comparison is still:
- `V1.5` is stronger operationally today
- `V2` is stronger architecturally today
That is not a contradiction. It is the right split between a current production line and the next architecture line.
```mermaid
flowchart TD
V1[V1]
V15[V1_5]
V2[V2]
realFailures[RealFailures]
realTests[RealHardwareEvidence]
simAndProto[SimulationAndPrototype]
V1 --> V15
V15 --> V2
realFailures --> V15
realFailures --> V2
V15 --> realTests
V2 --> simAndProto
```
## 4. How V2 Solves WAL And Extent Synchronization
The core V2 question is not simply "do we keep WAL?"
The real question is:
**how do WAL and extent stay synchronized across primary and replica while preserving both stability and performance?**
This is the center of the V2 design.
### 4.1 The basic separation of roles
V2 treats the storage path as two different but coordinated layers:
- **WAL** is the ordered truth for recent history
- **extent** is the stable materialized image
WAL is used for:
- strict write ordering
- local crash recovery
- short-gap replica catch-up
- durable progress accounting through `LSN`
Extent is used for:
- stable read image
- long-lived storage
- checkpoint and base-image creation
- long-gap recovery only through a real checkpoint/snapshot base, not through guessing from the current live extent
This separation is the first stability rule:
- do not ask current extent to behave like historical state
- do not ask WAL to be the only long-range recovery mechanism forever
### 4.2 Primary-replica synchronization model
The intended V2 steady-state model is:
1. primary allocates monotonic `LSN`
2. primary appends ordered WAL locally
3. primary enqueues the record to per-replica sender loops
4. replicas receive in order and advance explicit progress
5. barrier/sync uses **durable replica progress**, not optimistic send progress
6. flusher later materializes WAL-backed dirty state into extent
The local WAL-to-extent lifecycle can be understood as:
```mermaid
stateDiagram-v2
[*] --> WalAppended
WalAppended --> SenderQueued
SenderQueued --> ReplicaReceived
ReplicaReceived --> ReplicaDurable
ReplicaDurable --> SyncEligible
SyncEligible --> ExtentMaterialized
ExtentMaterialized --> CheckpointAdvanced
note right of WalAppended
Ordered local WAL exists
and defines the write LSN
end note
note right of ReplicaDurable
Replica durable progress
is now explicit
end note
note right of ExtentMaterialized
Flusher moves stable data
from WAL-backed dirty state
into extent
end note
```
The critical synchronization rule is:
- **client-visible sync truth must follow durable replica progress**
- not local send progress
- not local WAL head
- not "replica probably received it"
This is why V2 uses a lineage-safe recovery target such as `CommittedLSN` instead of a looser notion like "current primary head."
### 4.2.1 Sync mode and result model
V2 also makes the sync-result logic more explicit.
- `best_effort` should succeed after the primary has reached its local durability point, even if replicas are degraded.
- `sync_all` should succeed only when all required replicas are durable through the target boundary.
- `sync_quorum` should succeed only when a true durable quorum exists through the target boundary.
This decision path can be presented as:
```mermaid
flowchart TD
writeReq[WriteAndSyncRequest]
localDurable[PrimaryLocalDurable]
barrierEval[EvaluateReplicaDurableProgress]
bestEffortAck[best_effortAck]
syncAllAck[sync_allAck]
syncQuorumAck[sync_quorumAck]
rejectOrBlock[RejectOrBlock]
writeReq --> localDurable
localDurable --> bestEffortAck
localDurable --> barrierEval
barrierEval -->|"allRequiredReplicasDurable"| syncAllAck
barrierEval -->|"durableQuorumExists"| syncQuorumAck
barrierEval -->|"notEnoughDurableReplicas"| rejectOrBlock
```
The key point is that sync success is no longer inferred from send progress or socket health.
It is derived from explicit durable progress at the right safety boundary.
### 4.3 Why this should be stable
This model is designed to be stable because the dangerous ambiguities are separated:
- **write ordering** is carried by WAL and `LSN`
- **durability truth** is carried by barrier / flushed progress
- **recovery ownership** is carried by sender + recovery attempt identity
- **catch-up vs rebuild** is an explicit classification, not an accidental timeout side effect
- **promotion safety** depends on committed prefix and lineage, not on whichever node looks newest
In other words, V2 stability comes from reducing hidden coupling.
The design tries to remove cases where one piece of state silently stands in for another.
### 4.4 Why this can still be high-performance
The performance argument is not that V2 is magically faster in all cases.
The argument is narrower and more realistic:
- keep the primary write path simple:
- ordered local WAL append
- enqueue to per-replica sender loops
- no heavy inline recovery logic in foreground writes
- keep most complexity off the healthy hot path:
- sender ownership
- reconnect classification
- catch-up / rebuild decisions
- timeout and stale-result fencing
live mostly in recovery/control paths
- use WAL for what it is good at:
- recent ordered delta
- short-gap replay
- stop using WAL as the answer to every lag problem:
- long-gap recovery should move toward checkpoint/snapshot base plus tail replay
So the V2 performance thesis is:
- **healthy steady-state should remain close to V1.5**
- **degraded/recovery behavior should become much cleaner**
- **short-gap recovery should be cheaper than rebuild**
- **long-gap recovery should stop forcing an unbounded WAL-retention tax**
That is a much stronger and more believable claim than saying "V2 will just be faster."
### 4.5 Why WAL is still worth choosing
The reason to keep the WAL-based direction is that it gives the best foundation for this exact synchronization problem:
- explicit order
- explicit history
- explicit committed prefix
- explicit short-gap replay
- explicit failover reasoning
WAL is risky only if the design blurs:
- local write acceptance
- replica durable progress
- committed boundary
- recoverable retained history
V2 exists precisely to stop blurring those things.
So the current project position is:
- WAL is not automatically safe
- but WAL is still the most promising base for this block service
- because the project now has enough real evidence, simulator coverage, and prototype work to investigate it rigorously
## 5. Comparison With Market And Papers
The current V2 direction is not chosen because other vendors are wrong. It is chosen because other directions solve different problems and carry different costs.
### Ceph / RBD style systems
Ceph-style block systems avoid this exact per-volume replicated WAL shape. They gain:
- deep integration with object-backed distributed storage
- mature placement and recovery machinery
- strong cluster-scale distribution logic
But they pay elsewhere:
- more system layers
- more object-store and peering complexity
- a heavier operational and conceptual model
This is not a free simplification. It is a different complexity trade.
For `sw-block`, the design choice is to keep a narrower software block service with more explicit per-volume replication semantics instead of inheriting the full distributed object-backed block complexity stack.
### PolarFS / ParallelRaft style work
These systems explore more aggressive ordering and apply strategies:
- out-of-order or conflict-aware work
- deeper parallelism
- more sophisticated log handling
They are valuable references, especially for:
- LBA conflict reasoning
- recovery and replay cost thinking
- future flusher parallelization ideas
But they also introduce a much heavier correctness surface.
The project does not currently want to buy that complexity before fully proving the simpler strict-order path.
### AWS chain replication / EBS-style lessons
Chain replication and related work are attractive because they address real bandwidth and recovery concerns:
- Primary NIC pressure
- forwarding topology
- cleaner scaling for RF=3
This is one of the more plausible borrowable directions later.
But it changes:
- latency profile
- failure handling
- barrier semantics
- operational topology
So it belongs to a later architecture stage, not to the current V2 core proof.
### The actual strategic choice
The project is deliberately choosing:
- a narrower software-first block design
- explicit per-volume correctness
- strict reasoning before performance heroics
- validation before feature expansion
That is not conservatism for its own sake. It is how to build a block product that can later be trusted.
## 6. Why This Direction Fits SeaweedFS And Future Standalone sw-block
`sw-block` started inside SeaweedFS, but V2 is already being shaped as the next standalone block service line.
That means the architecture should preserve two things at once:
### What should remain compatible
- placement and topology concepts where they remain useful
- explainable control-plane contracts
- operational continuity with the SeaweedFS ecosystem
### What should become more block-specific
- replication correctness
- recovery ownership
- recoverability classification
- block-specific test and evidence story
So the current direction is:
- use SeaweedFS as the practical ecosystem and experience base
- but shape V2 as a true block-service architecture, not as a minor sub-feature of `weed/`
This is why the V2 line belongs under `sw-block/` rather than as a direct patch path inside the existing production tree.
## 7. The Systematic Validation Method
The second major reason the current direction is rational is the validation method.
The project is no longer relying on:
- implement first
- discover behavior later
- patch after failure
Instead, the intended ladder is:
- contract and invariants
- scenario backlog
- simulator
- timer/race simulator
- standalone prototype
- real engine test runner
```mermaid
flowchart TD
contract[ContractAndInvariants]
scenarios[ScenarioBacklog]
distsim[distsim]
eventsim[eventsim]
prototype[enginev2Prototype]
runner[RealTestRunner]
confidence[SystemAndProductConfidence]
contract --> scenarios
scenarios --> distsim
scenarios --> eventsim
distsim --> prototype
eventsim --> prototype
prototype --> runner
runner --> confidence
```
This is the right shape for a risky block-storage algorithm:
- simulation for protocol truth
- prototype for executable truth
- real runner for product/system truth
## 8. What The Simulation System Proves
The simulation system exists to answer:
- what should happen
- what must never happen
- which V1/V1.5 shapes fail
- why the V2 shape is better
### `distsim`
`distsim` is the main protocol simulator.
It is used for:
- protocol correctness
- state transitions
- stale authority fencing
- promotion and lineage safety
- catch-up vs rebuild
- changed-address restart
- candidate safety
- reference-state checking
### `eventsim`
`eventsim` is the timing/race layer.
It is used for:
- barrier timeout behavior
- catch-up timeout behavior
- reservation timeout behavior
- same-tick and delayed event ordering
- stale timeout effects
### What the simulator is good at
It is especially strong for proving:
- stale traffic rejection
- explicit recovery boundaries
- timeout/race semantics
- failover correctness at committed prefix
- why old authority must not mutate current lineage
### What the simulator does not prove
It does not prove:
- real TCP behavior
- real OS scheduling behavior
- disk timing
- real `WALShipper` integration
- real frontend behavior under iSCSI or NVMe
So the simulator is not the whole truth.
It is the algorithm/protocol truth layer.
## 9. What The Real Test Runner Proves
The real test runner under `learn/projects/sw-block/test/` is the system and product validation layer.
It is not merely QA support. It is a core part of whether the design can be trusted.
### What it covers
The runner and surrounding test system already span:
- unit tests
- component tests
- integration tests
- distributed scenarios
- real hardware workflows
The environment already includes:
- real nodes
- real block targets
- real fault injection
- benchmark and result capture
- run bundles and scenario traceability
### Why it matters
The runner is what tells us whether:
- the implemented engine behaves like the design says
- the product works under real restart/failover/rejoin conditions
- the operator workflows are credible
- benchmark claims are real rather than accidental
This is why the runner is best thought of as:
- implementation truth
- system truth
- product truth
not just test automation.
## 10. How Simulation And Test Runner Progress Systematically
The intended feedback loop is:
1. V1/V1.5 real failures happen
2. those failures are turned into design requirements
3. scenarios are distilled for simulator use
4. the simulator closes protocol ambiguity
5. the standalone prototype closes execution ambiguity
6. the real test runner validates system behavior on real environments
7. new failures or mismatches feed back into design again
This gives the project two different but complementary truths:
- `simulation -> algorithm / protocol correctness`
- `test runner -> implementation / system / product correctness`
That separation is healthy.
It prevents two common mistakes:
- trusting design without real behavior
- trusting green system tests without understanding the protocol deeply enough
## 11. Current Status And Honest Limits
### What is already strong
- `V1.5` has materially better recovery behavior than `V1` and stronger operational evidence
- `V2` has stronger architectural structure than `V1.5`
- the simulator has serious acceptance coverage
- the prototype line has already started closing ownership and orchestration risk
- the real test runner is large enough to support serious system validation
### What is not yet done
- `V2` is not a production engine
- prototype work is still in early-to-mid stages
- historical-data / recovery-boundary prototype work is not complete
- steady-state performance of `V2` is not yet proven
- real hardware validation of `V2` does not yet exist
So the correct statement is not:
- "V2 is already better in production"
The correct statement is:
- "V2 is the better long-term architecture, but not yet the stronger deployed engine"
## 12. Why The Current Direction Is Rational
The current direction is rational because it keeps the right split:
- `V1.5` continues as the production line today
- `V2` continues as the next architecture line
This lets the project:
- keep shipping and hardening what already works
- explore the better architecture without destabilizing the current engine
- use simulation, prototype work, and the real runner to decide whether V2 should become the next real engine
The final strategic rule should remain:
- continue WAL investigation because the project now has a credible validation framework
- continue V2 because the architectural evidence is strong
- if prototype evidence later reveals a structural flaw, redesign to `V2.5` before heavy implementation
That is the disciplined path for a block-storage algorithm.
## Bottom Line
If choosing based on current production proof:
- use `V1.5`
If choosing based on long-term protocol quality:
- choose `V2`
If choosing based on whether WAL should still be investigated:
- yes, because the project now has the right validation stack to investigate it responsibly
That is the current strategic answer.