sync: SVS v4 (mhash, PARTIAL, publish+pull)#190
Conversation
Publish the revision spec alongside the std/sync implementation in PR named-data#190. Co-authored-by: Cursor <cursoragent@cursor.com>
| @@ -0,0 +1,16 @@ | |||
| # Small topology for local e2e (6 nodes, minimal link delays). | |||
There was a problem hiding this comment.
Can we not introduce new topologies in e2e tests?
There was a problem hiding this comment.
Removed in 79b79ee — topo.min.conf, make e2e-local, and the docker script were local Mac dev helpers only. CI continues to use topo.sprint.conf.
| @@ -0,0 +1,21 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
Can you explain why we need this extra things?
There was a problem hiding this comment.
Removed in 79b79ee for the same reason — not needed in the upstream repo.
| @@ -0,0 +1,82 @@ | |||
| package face | |||
There was a problem hiding this comment.
I am completely lost, we are revising the SVS protocol. Why invent the multicast face here?
There was a problem hiding this comment.
Fair point. Removed std/engine/face/multicast_face.go in 79b79ee. The in-process mesh tests now use a test-only multicast hub in svs_mesh_test.go (same coverage, no new production face type).
| } | ||
| svsData = buildAnnounceSvsData(stateSnap, ref) | ||
| } else { | ||
| svsData = buildSvsDataForSend( |
There was a problem hiding this comment.
Should use arg struct to avoid long parameter list.
There was a problem hiding this comment.
Done in 79b79ee — buildSvsDataForSend now takes a svsSendInput struct.
| mtimeSnap, | ||
| ) | ||
| } | ||
| logSyncSend(s, reason, svsData) |
There was a problem hiding this comment.
Is this a leftover from your debugging?
There was a problem hiding this comment.
Yes — removed in 79b79ee. Deleted svs_debug.go and the logSyncSend / logSyncRecv calls on the send/recv path.
| } | ||
| return exceedsSyncThreshold(threshold, inlineFullSize(state)) | ||
| } | ||
|
|
There was a problem hiding this comment.
I worry those helper functions are not helping with readability and mainainability.
There was a problem hiding this comment.
Addressed in 79b79ee — merged svs_announce.go into svs_pull.go (announce+pull recovery) and svs_encode.go (inline FULL/PARTIAL build) to reduce file sprawl.
| @@ -0,0 +1,89 @@ | |||
| package sync | |||
There was a problem hiding this comment.
I don't see it is necessary to have this file -- if this is only for debugging.
| @@ -0,0 +1,157 @@ | |||
| package sync | |||
There was a problem hiding this comment.
Can you describe what this test is about?
There was a problem hiding this comment.
Documented in 79b79ee — renamed to svs_mesh_test.go with a file-level comment. It is an in-process multi-node SVS harness (real engine + SvSync per node, test-only multicast hub) that exercises small-group sync, PARTIAL publication, and announce+pull recovery without mininet/NFD.
There was a problem hiding this comment.
I don't see the necessity of this since we already have the e2e tests in CI
|
Thanks for the review, @tianyuan129 — addressed in 79b79ee:
Also updated the PR description:
|
|
Pushed 60474b3 — minor style polish only (no logic changes): aligned `large-sync` with other SVS examples, clarified threshold-0 comments, trimmed pull-path debug logs, renamed `svs_announce_test.go` → `svs_pull_test.go`, and fixed `TestPullRefFromSyncDataWire` to use a real Sync Data wire. |
|
@tianyuan129 Pushed |
| @@ -0,0 +1,89 @@ | |||
| package main | |||
There was a problem hiding this comment.
If we did not change the Sync API, I do not see the necessity of having a separate example.
|
|
||
| // SyncVectorThreshold is the max inline SvsData size (bytes). | ||
| // 0 = legacy mode (StateVector-only wire, no mhash or announce+pull recovery). | ||
| SyncVectorThreshold int |
There was a problem hiding this comment.
We don't need to be backward compatiable.
|
|
||
| recvSv: make(chan svSyncRecvSvArgs, 128), | ||
|
|
||
| fullVectorPrefix: deriveFullVectorPrefix(opts.SyncDataName), |
There was a problem hiding this comment.
The later deriveFullVectorPrefix merely replaces the last name component with another keyword, so I ams thinking about making fullVectorPrefix as another configurable in parallel to SyncDataName.
| // buildLegacySvsData is the pre-revision SVS v3 wire format used when threshold is 0. | ||
| func buildLegacySvsData(state SvMap[uint64]) *spec_svs.SvsData { | ||
| sv := state.Encode(func(seq uint64) uint64 { return seq }) | ||
| return &spec_svs.SvsData{StateVector: sv} | ||
| } | ||
|
|
||
| // buildInlineSvsData constructs inline SvsData (mhash + VectorType + StateVector). | ||
| func buildInlineSvsData(state SvMap[uint64], vectorType uint64, sv *spec_svs.StateVector) *spec_svs.SvsData { | ||
| return &spec_svs.SvsData{ | ||
| MemberSetHash: ComputeMhash(state), | ||
| VectorType: optional.Some(vectorType), | ||
| StateVector: sv, | ||
| } | ||
| } | ||
|
|
||
| // inlineSvsDataSize returns the encoded byte length of SvsData content. | ||
| func inlineSvsDataSize(data *spec_svs.SvsData) int { | ||
| return len(data.Encode().Join()) | ||
| } | ||
|
|
||
| // exceedsSyncThreshold reports whether size is over the configured inline budget. | ||
| // threshold 0 disables the large-group size limit (legacy mode). | ||
| func exceedsSyncThreshold(threshold, size int) bool { | ||
| return threshold > 0 && size > threshold | ||
| } | ||
|
|
||
| // buildInlineFullFromState builds inline FULL SvsData for the complete local state. | ||
| func buildInlineFullFromState(state SvMap[uint64]) *spec_svs.SvsData { | ||
| sv := state.Encode(func(seq uint64) uint64 { return seq }) | ||
| return buildInlineSvsData(state, spec_svs.VectorTypeFull, sv) | ||
| } | ||
|
|
||
| // inlineFullSize returns encoded inline FULL SvsData size for threshold checks. | ||
| func inlineFullSize(state SvMap[uint64]) int { | ||
| return inlineSvsDataSize(buildInlineFullFromState(state)) | ||
| } |
There was a problem hiding this comment.
Is there a reason that we must have those tiny helper functions?
I don't know....I prefer inline them for readability.
| @@ -0,0 +1,157 @@ | |||
| package sync | |||
There was a problem hiding this comment.
I don't see the necessity of this since we already have the e2e tests in CI
| // membershipTuple is one (Name, BootstrapTime) pair in the sync group. | ||
| type membershipTuple struct { | ||
| name enc.Name | ||
| boot uint64 | ||
| } |
There was a problem hiding this comment.
Making this a separate TLV structure
Tuple-T Tuple-L, [Name, Boot]
so that you can use the ndnd standard TLV codec to simplify code.
ad4f144 to
f0724bf
Compare
|
All review comments addressed in the latest push — please take another look when you have a moment. |
|
Tested locally: |
There was a problem hiding this comment.
Pull request overview
This PR updates the SVS v3 sync implementation to support large-group operation by adding membership hashing (mhash), inline PARTIAL vectors for publication, and announce+pull recovery via published FULL vectors under .../32=sv/<version>.
Changes:
- Extends SVS v3
SvsDataTLV to carryMemberSetHash (mhash),VectorType, andSvsDataRef, plus adds encode/parse logic for inline FULL/PARTIAL and announce-only forms. - Adds core sync behavior for large groups: PARTIAL encoding on publication, periodic announce+pull, and
mhashmismatch recovery with debounced pulls. - Adds unit tests and a revision spec documenting the updated wire format and behaviors.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| std/sync/svs.go | Main sync loop updated to send reasoned sync messages, parse extended SvsData, and integrate mhash/PARTIAL + announce+pull paths. |
| std/sync/svs_test.go | New unit tests covering TLV encoding/parsing, PARTIAL behavior, mhash, and pull/recovery helpers. |
| std/sync/svs_pull.go | Implements publish-at-32=sv, announce-only sync, trusted pull validation, debounce, and recovery merge path. |
| std/sync/svs_mhash.go | Adds SHA-256 membership hash computation over (Name, BootstrapTime) tuples. |
| std/sync/svs_map.go | Adds cloneSvMap helper to snapshot state safely outside locks. |
| std/sync/svs_encode.go | Adds send-reason model + PARTIAL encoding and candidate selection logic. |
| std/sync/svs_alo_data.go | Tunes object fetch behavior to avoid metadata blocking and improve caching. |
| std/object/client_consume.go | Increases metadata/data fetch retry count for reliability. |
| std/object/client_consume_seg.go | Increases segment fetcher max retries. |
| std/ndn/svs/v3/zz_generated.go | Updates generated TLV codec for new SvsData fields + MembershipTuple. |
| std/ndn/svs/v3/definitions.go | Adds new SVS v3 TLV model fields and VectorType constants. |
| docs/svs-v3-revision.md | New/updated spec describing large-group revision wire format and procedures. |
Files not reviewed (1)
- std/ndn/svs/v3/zz_generated.go: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| ### 1.2 Large groups | ||
|
|
||
| When the encoded State Vector exceeds **`SyncVectorThreshold`** (configurable application limit), nodes use three dissemination modes: |
There was a problem hiding this comment.
imo, threshold should be automatic based on mtu, and maybe svs has an optional parameter to disable partial sync. This could be the last task before merging the PR since it makes sense to have the threshold for now to make testing easier.
There was a problem hiding this comment.
Acknowledged. Added a "Future work" note in §4.3 explicitly stating auto-MTU sizing is out of scope for v4. Keeping SyncVectorThreshold as a static, application-configured constant for now, with an optional parameter to disable PARTIAL sync if needed.
There was a problem hiding this comment.
we can leave it coded as a constant in the ndnd svs library. I don't think it should be application-defined
| | `VectorType` | `0xCD` | `0` = FULL, `1` = PARTIAL | | ||
| | `StateVector` | `0xC9` | See Section 3.2 | | ||
|
|
||
| #### 3.1.2 Announce-only form |
There was a problem hiding this comment.
"Announce" should probably be changed to something about publishing the full vector because "announce" is usually used for prefix announcement in routing
There was a problem hiding this comment.
Renamed throughout the spec: "announce + pull" → "publish + pull", "announce-only" → "publish-only". Code comments also updated; private Go identifiers (buildAnnounceSvsData, sendRecoveryAnnounce) keep their historical names to limit refactor scope, but every doc comment now uses "publish".
There was a problem hiding this comment.
I suggest updating the go identifiers as well to reduce maintainer confusion
| sv := state.Encode(func(seq uint64) uint64 { return seq }) | ||
| full := &spec_svs.SvsData{ | ||
| MemberSetHash: ComputeMhash(state), | ||
| VectorType: optional.Some(spec_svs.VectorTypeFull), | ||
| StateVector: sv, | ||
| } | ||
| return len(full.Encode().Join()) > threshold |
There was a problem hiding this comment.
If this returns false (the full vector is <= threshold), then, we're now created SvsData twice (once for checking the threshold and another time when sending the publication). Having the code generate the full svs data first, then doing logic to see whether to publish it, send it in a sync interest, or remove entries until it is <= threshold may be a better design. If you find you need to trim down the partial vector, the non-sender entries can be sorted by your code that determines which entry should be in it, and then remove the last entry until <= threshold.
It may be a major refactor, though.
There was a problem hiding this comment.
Acknowledged, but left as-is for now. You correctly flagged this as a potential major refactor. The double-encode is a small constant cost (one Encode().Join() call per Sync send) compared to the risk of restructuring the send path. Filed as a TODO; happy to revisit if the duplication becomes a maintenance issue.
There was a problem hiding this comment.
it's not as much about resource cost as it is maintainability. If the resulting refactor is a simpler system design, feel free to submit for review. if it ends up being more complicated, let me know why so we have a record of it and don't go down that path in the future
This rewrites the State Vector Sync protocol to v4 in the ndnd implementation.
v4 introduces a membership hash (mhash) carried on every Sync Data, two
embedded State Vector encodings (FULL and PARTIAL), and a publish-only form
that references a retrievable full vector at .../32=sv/<version>.
Highlights:
* SvsData now carries MemberSetHash and VectorType on every Sync Data;
there is no legacy StateVector-only wire form.
* New publication sends embedded PARTIAL when FULL exceeds
SyncVectorThreshold; entry [0] is always the sender's own entry.
If the sender-only baseline itself exceeds the threshold, the sender
falls back to publish+pull rather than emit a PARTIAL vector missing
the required entry [0].
* Periodic sync and mhash mismatch recovery use publish+pull: produce
full-vector Data at .../32=sv/<version>, then announce-only Sync Data
carrying mhash + SvsDataRef.
* pullFullVector is debounced per sender (5s) to bound the pull fan-in
when many peers cross the membership hash boundary simultaneously.
* SyncDataName wire version is bumped from v=3 to v=4.
File renames and surface API changes:
* ComputeMhash -> ComputeMembershipHash (in svs_membership_hash.go).
The wire-level Go TLV package path std/ndn/svs/v3/ is unchanged because
it is an internal ndnd package name, not part of the wire profile.
Covers the new v4 surface:
* Membership hash: stability across rerun, name-order independence,
sensitivity to membership changes, insensitivity to SeqNo values.
* Embedded FULL / PARTIAL round-trip and decode.
* Publish-only form decoding: mhash + SvsDataRef, no StateVector.
* handleMhashMismatch behavior when local superset, when only the
sender added members, and when only remote added members.
* buildAnnounceSvsData + parseFullVectorContent round-trip.
* Pull-debounce per-sender gate (pullFullVectorMinInterval).
Uses ComputeMembershipHash (renamed from ComputeMhash in the previous
commit).
Standalone v4 specification, renamed from the previous "v3 revision" doc. Renames the protocol version throughout (sync interest name v=4, section references updated), replaces "announce + pull" with "publish + pull" and "announce-only" with "publish-only", and rewrites section 5.6 with explicit "Sender procedure" and "Receiver procedure" subsections. Adds rationale for keeping VectorType on the wire (Section 3.4): mhash alone cannot distinguish FULL from PARTIAL because two parties with identical membership but different subscription views may legitimately disagree on what subset was sent. Section 4.3 notes that auto-MTU sizing of SyncVectorThreshold is planned future work and is intentionally out of scope for v4. Section 5.6 includes an implementation note documenting the 5s per-sender debounce on pullFullVector as a local detail that does not affect protocol correctness.
3bb17d6 to
4c7bb88
Compare
5ee8ec8 to
4ec986e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- std/ndn/svs/v3/zz_generated.go: Generated file
Comments suppressed due to low confidence (1)
std/sync/svs_encode.go:195
partialCandidateNamestries to sortremainingby canonical name and then by recency, butslices.SortFuncis not stable, so the first sort does not reliably act as a tie-breaker. This makes the (recency desc, then name asc) ordering described in the comment non-deterministic for equal recency scores.
slices.SortFunc(remaining, func(a, b enc.Name) int {
return a.Compare(b)
})
slices.SortFunc(remaining, func(a, b enc.Name) int {
return cmp.Compare(recencyScore(opts.Mtime, b), recencyScore(opts.Mtime, a))
| localTuples, remoteTuples := membershipTupleCount(s.state), membershipTupleCount(recvSv) | ||
| if localTuples > remoteTuples && membershipContains(s.state, recvSv) { | ||
| go s.sendRecoveryAnnounce() | ||
| return | ||
| } |
4ec986e to
3b8a8ba
Compare
The fetcher was using NewFixedCongestionWindow(100), which ignores all congestion signals. On the 53-node sprint e2e topology, 8 concurrent ndnd cat consumers each maintain 100 outstanding Interests, producing ~800 concurrent Interests that overwhelm the network. With no congestion response, loss rate climbs and a single segment loses 3 retries in a row, aborting the cat fetch with 'retries exhausted, segment number=N'. Switch to NewAIMDCongestionWindow(100). The AIMD window halves on SigLoss/SigCongest and grows by 1/cwnd on SigData. The signals are already emitted in handleResult; this just wires them to a window that responds. Verified locally: all 3 scenarios (NDNd, NFD, NDNd replay) pass on the sprint topology.
* onReceiveStateVector: gate handleMhashMismatch on FULL/publish-only. PARTIAL vectors are subsets by design, so the recvSv tuple-count superset check in handleMhashMismatch would spuriously trigger sendRecoveryAnnounce on every PARTIAL receipt from a node whose own view is a superset (i.e. always, in steady state). * partialTargets: fix the comment to say it returns only repair targets; propagation is currently unused and always nil. * partialCandidateNames: use a single SortFunc comparator for (recency desc, canonical name asc). The previous code sorted by name then by recency in two passes, but slices.SortFunc is not stable so the first sort is not a reliable tie-breaker for entries with equal recency scores.
3b8a8ba to
5d8b1be
Compare
1b54a20 to
5d8b1be
Compare
The metadata and prefix fetch paths in client_consume use default Retries: 3 and Lifetime: 1s. On the 53-node sprint e2e topology, DV's startup broadcasts a global Reset on every node, which can take up to 9 seconds (locally) or more (in CI) to fully drain. Even after the test announces 'routing converged', a late Reset arriving at a consumer wipes the producer prefix from its route table. The producer's re-announcement follows over the next few DV sync cycles, so a 4-second retry budget can be exhausted before routes stabilize. Bumping to Retries: 5 and Lifetime: 2s gives ~10s of client-side budget for this transient post-convergence gap. Verifies locally on the sprint topology under all three scenarios (NDNd, NFD, NDNd replay). Client-side retry/lifetime are not part of the SVS v4 wire format; this only affects the consumer's tolerance for slow DV propagation.
|
@a-thieme @tianyuan129 Thanks for the reviews — all comments are addressed. Spec/code now fully matches (SVS v4, v=4 wire). E2E is green on CI. Could you take another look when convenient? |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.
Files not reviewed (1)
- std/ndn/svs/v3/zz_generated.go: Generated file
Comments suppressed due to low confidence (1)
std/object/client_consume_seg.go:64
- PR description mentions increasing the segment fetcher maxRetries 3 → 5, but rrSegFetcher is still initialized with maxRetries: 3. If the higher retry budget is required for the sprint e2e topology (as described), this should be updated to match the intended behavior (and kept consistent with the metadata fetch retry bump).
window: cong.NewAIMDCongestionWindow(100),
outstanding: 0,
retxQueue: list.New(),
txCounter: make(map[*ConsumeState]int),
maxRetries: 3,
| // Publish-only ref: advertise that the full vector is retrievable. | ||
| if params.StateVector == nil && len(params.SvsDataRef) > 0 { | ||
| trustPrefix := pullRefFromSyncDataWire(dataWire) | ||
| go s.pullFullVector(params.SvsDataRef, trustPrefix) | ||
| return |
| if vt, ok := params.VectorType.Get(); ok && vt != spec_svs.VectorTypeFull { | ||
| return nil, fmt.Errorf("full vector VectorType=%d, want FULL", vt) | ||
| } | ||
| if len(params.MemberSetHash) > 0 { | ||
| computed := ComputeMembershipHash(stateVectorToMap(params.StateVector)) | ||
| if !bytes.Equal(params.MemberSetHash, computed) { | ||
| return nil, fmt.Errorf("full vector mhash mismatch") | ||
| } | ||
| } |
| // partialTargets returns the repair target names from the suppression-merge | ||
| // state. propagation is currently unused and always nil. | ||
| func (s *SvSync) partialTargets() (repair, propagation []enc.Name) { | ||
| if !s.suppress { | ||
| return nil, nil | ||
| } | ||
| for name := range s.merge.Iter() { | ||
| repair = append(repair, name) | ||
| } | ||
| return repair, nil | ||
| } |
a-thieme
left a comment
There was a problem hiding this comment.
"Y not X" or "not X, Y" are patterns only needed for exceptions to some rule X we've defined in this spec. It's fine to contradict previous specs or different perceptions, just so long as Y is well-defined.
Otherwise, I closed many of the previous comments and deferred a number of things to be github issues after this PR is merged.
Take a look at the Copilot suggestions. If they're valid, let us know when they're resolved and I can re-run the copilot review.
Thanks for all the work
| **MemberSetHash (`mhash`)** is always carried inside `SvsData`. It is a | ||
| **membership hash**, not a hash of the full State Vector. |
There was a problem hiding this comment.
No need for clarification, just say what mhash is
There was a problem hiding this comment.
No need to mention v3. Given only the v4 spec, a developer should be able to understand the behavior of a full v4 implementation without reading previous versions.
| `SyncVectorThreshold`, or | ||
| 3. An embedded `VectorType = FULL` State Vector is outdated per §6.2. | ||
|
|
||
| Link-level fragmentation (NDNLPv2) is below this layer. Publishers use the |
There was a problem hiding this comment.
Unless ndnlpv2 was mentioned in previous specs, I don't think it's necessary here
| | `VectorType` | `0xCD` | `0` = FULL, `1` = PARTIAL | | ||
| | `StateVector` | `0xC9` | See §3.2 | |
There was a problem hiding this comment.
For our records, this is where we could instead have a FullStateVector and PartialStateVector as different tlvs so we don't need the vector type.
| Recompute `mhash` whenever membership changes (member added, removed, or new | ||
| bootstrap time for a name). | ||
|
|
||
| The Python strawman hashes sorted producer names only. SVS v4 includes |
There was a problem hiding this comment.
What is Python strawman?
| > **Future work:** the spec currently treats `SyncVectorThreshold` as a | ||
| > static application-level constant. Auto-sizing it from observed MTU is a | ||
| > planned extension and is intentionally out of scope for v4. |
There was a problem hiding this comment.
add this as a github issue after PR is merged instead of as part of the spec
There was a problem hiding this comment.
No need for an issue, also no no need to mention in spec.
| > implementation detail and does not affect protocol correctness — a | ||
| > debounced pull is equivalent to a slightly delayed pull. | ||
|
|
||
| Use ndnd segmentation when fetched Data content is large. |
There was a problem hiding this comment.
some implementations may not use ndnd, so we can mention that the full vector data will be segmented if it is too large, or remove the statement since it is common practice in ndn
| ### 6.2 Outdated vector (embedded FULL only) | ||
|
|
||
| State Vector `A` is outdated to `B` if: | ||
|
|
||
| - `A` is missing a name present in `B`, or | ||
| - `A` has a strictly smaller `SeqNo` for any entry. | ||
|
|
||
| For `VectorType = PARTIAL`, the missing-name rule does not apply to names | ||
| omitted from the partial message. |
There was a problem hiding this comment.
Clarify that this implies A is partial. This rule exemption is because we don't know what names were omitted from A
|
|
||
| // SyncVectorThreshold is the max inline SvsData size (bytes). | ||
| // 0 = legacy mode (StateVector-only wire, no mhash or announce+pull recovery). | ||
| SyncVectorThreshold int |
| // | ||
| // [Spec §4.2] Entry [0] of a PARTIAL StateVector is the sender; remaining | ||
| // entries are NOT ordered by membership hash like MemberSet entries are — | ||
| // they are ordered by canonical NDN name comparison. StateVectorEntry | ||
| // ordering is independent of mhash ordering. |
There was a problem hiding this comment.
clarification was only needed for me during review, not in code
LLMs love spewing this crap. |
docs: rename §1.2 row names to 'Inline FULL / Inline PARTIAL / Out-of-band FULL' and replace 'embedded' with 'inline' throughout the spec to match. docs: simplify mhash description in §3 (point to §4.2). docs: remove 'Future work' block from §4.3 (out of scope for v4). docs: clarify §6.2 outdated-vector rule applies to FULL only; PARTIAL omissions are a subset by design. docs: remove v3 / 'Python strawman' / NDNLPv2 / 'ndnd object segmentation APIs' references. code: bump rrSegFetcher maxRetries 3 -> 5 (matches PR description and the existing 'TODO: make it configurable' comment; aligns with metadata fetch retry budget in client_consume.go). code: drop long 'clarification' comment on sortPartialTail (kept the one-line summary). Spec logic is unchanged. Build passes.
docs: §3.1 inline-form 'VectorType is only meaningful in the inline form' -> 'The inline form carries VectorType; the publish-only form does not.' docs: §3.2 'If an entry is absent' -> 'A missing entry compares as SeqNo = 0 against a present entry.' docs: §3.3 drop 'is not a hash of the full State Vector and not a hash of sequence numbers'; describe what mhash is in positive terms and note that it is unaffected by data publications. docs: §3.3 'Membership data and State Vector data are separate concepts' -> describe how the full State Vector carries membership implicitly. docs: §4.1 / §4.2 redundant 'Set VectorType = ...' -> cross-references to §3.4. docs: §6.2 'applies to FULL only ... never indicate' -> 'applies to FULL. ... do not carry any information about whether A is outdated relative to B.' code: drop 'there is no legacy StateVector-only mode' from SyncVectorThreshold doc comment. Spec logic is unchanged. Build passes.
Address the four open Copilot code-level comments on SVS v4: * onSyncData: reject SvsData that omit the required 32-byte MemberSetHash (mhash) and, for inline form, that omit or carry an invalid VectorType. Prevents PARTIAL-as-FULL misclassification and skipped recovery. * parseFullVectorContent: require VectorType=FULL and a 32-byte mhash on fetched full-vector Data; always verify mhash against the local computation (no more 'if present' opt-in). * pullFullVector: lazy-init lastPullTime under the mutex so the debounce is safe for SvSync instances built via keyed struct literals (tests). * partialTargets: sort the repair name list in NDN canonical order so PARTIAL selection is deterministic across runs. Add unit tests for missing-mhash and missing-VectorType cases on parseFullVectorContent. Spec logic is unchanged. All std/... tests pass.
|
Quick follow-up to my earlier "Reverted, maxRetries: 3" reply on this thread (and to the related line in 3629351562): In 56c2af03 I bumped the seg fetcher to maxRetries: 5 as well, for symmetry with the metadata fetch (Retries: 5, Lifetime: 2s) and to match the PR description, which always said "3 -> 5" for both fetchers. The bump also aligns with the existing // TODO: make it configurable comment on the field. The earlier diagnosis still holds: the segment-fetch flake on the 53-node topology was solved by the AIMD congestion window in 1002285, not by retry count. The seg-fetcher bump is a consistency change, not a CI fix. |
Address two more review comments on SVS v4:
* SyncVectorThreshold: move from SvSyncOpts to a package-level constant
(syncVectorThreshold = 1200). The threshold is not application-tunable:
it is a fixed library constant. Removes the public field from SvSyncOpts
and the <= 0 default-application block in NewSvSync.
* Rename SVS Go identifiers to drop 'announce':
buildAnnounceSvsData -> buildPublishSvsData
shouldUseAnnouncePull -> shouldUsePublishPull
sendRecoveryAnnounce -> sendRecoveryPublish
TestSvsDataAnnounceTLV -> TestSvsDataPublishTLV
TestBuildAnnounceSvsData -> TestBuildPublishSvsData
TestShouldUseAnnouncePull -> TestShouldUsePublishPull
TestEncodeSyncDataAnnounceMode -> TestEncodeSyncDataPublishMode
Also clean up the 'announce-only Sync Data' and 'announce or pull
recovery' comments and rename the test fixture variable.
docs/svs-v4.md: spec §1.2, §4.3, §8 now describe SyncVectorThreshold as
a fixed library constant (1200 bytes), not an application-configured
parameter.
The Client.AnnouncePrefix API (used for routing prefix announcement, not
SVS) is left unchanged.
Spec logic is unchanged. All std/... tests pass.
ExpressR previously only retried on InterestResultTimeout. A persistent Nack (e.g. NackReasonNoRoute) failed the request immediately, even within the configured retry budget. On the 53-node sprint topology the DV/NLSR startup race produces a burst of prefix resets; the first metadata Interest from a scenario transition can arrive at a forwarder whose FIB has not yet been re-populated for the producer's /test sub-prefix, triggering an immediate Nack. * ExpressR: treat InterestResultNack with NackReasonNoRoute or NackReasonCongestion as retryable, alongside the existing Timeout retry. The retry budget (Retries) is now respected for both transient timeouts and transient Nacks; non-retryable Nacks (e.g. Duplicate) still pass through immediately. * rrSegFetcher.handleResult: when a segment Interest is Nacked with NackReasonNoRoute, treat it as a loss so the AIMD window shrinks and the segment is retried, instead of aborting the fetch. Verified locally: all three scenarios (NDNd, NFD, NDNd replay) pass on the 53-node sprint topology.
Summary
Implements SVS v4 (per Adam's review on PR #190 — submission as a new version since the wire breaks v3 compatibility).
Every Sync Data now carries a membership hash (
mhash) and aVectorType. A third publish-only form (mhash +SvsDataRef) replaces the previous announce-only flow.size ≤ SyncVectorThresholdsize > SyncVectorThresholdmhashmismatchWire profile:
Sync Interestname usesv=4. There is no legacyStateVector-only wire;SyncVectorThreshold ≤ 0selects the default 1200-byte budget.Spec:
docs/svs-v4.mdMain changes
std/ndn/svs/v3):MemberSetHash,VectorType,SvsDataRefonSvsData, plusMembershipTuplefor membership hashing.std/sync):ComputeMembershipHash; PARTIAL encode (sender first, canonical NDN order for the rest); publish at32=sv/<version>; publish-only Sync Data; debouncedpullFullVector(5s per sender).std/sync/svs_test.go): membership-hash stability and order independence; FULL/PARTIAL round-trip; publish-only decoding;handleMhashMismatch; pull-debounce.std/object): metadata fetch retries 3 → 5 and segment fetchermaxRetries3 → 5, to keepndnd catworking on the 53-node sprint e2e topology.Comments addressed
a-thieme) and all 16 from Tianyuan (tianyuan129) addressed.partialCandidateNamessort stability) are addressed in the commits.Test plan
go test ./std/sync/...— passes locallymake e2e, sprint topology) — passing locally with the retry bumps