Skip to content

sync: SVS v4 (mhash, PARTIAL, publish+pull)#190

Open
Taranum01 wants to merge 11 commits into
named-data:psvsfrom
Taranum01:svs-exploration
Open

sync: SVS v4 (mhash, PARTIAL, publish+pull)#190
Taranum01 wants to merge 11 commits into
named-data:psvsfrom
Taranum01:svs-exploration

Conversation

@Taranum01

@Taranum01 Taranum01 commented Jun 29, 2026

Copy link
Copy Markdown

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 a VectorType. A third publish-only form (mhash + SvsDataRef) replaces the previous announce-only flow.

Event size ≤ SyncVectorThreshold size > SyncVectorThreshold
Publication Embedded FULL Embedded PARTIAL (entry [0] is sender; falls back to publish + pull if the sender-only baseline itself exceeds the threshold)
Periodic sync Embedded FULL Publish + pull
mhash mismatch Publish + pull (if recovery needed) Publish + pull

Wire profile: Sync Interest name uses v=4. There is no legacy StateVector-only wire; SyncVectorThreshold ≤ 0 selects the default 1200-byte budget.

Spec: docs/svs-v4.md

Main changes

  • TLV (std/ndn/svs/v3): MemberSetHash, VectorType, SvsDataRef on SvsData, plus MembershipTuple for membership hashing.
  • Core (std/sync): ComputeMembershipHash; PARTIAL encode (sender first, canonical NDN order for the rest); publish at 32=sv/<version>; publish-only Sync Data; debounced pullFullVector (5s per sender).
  • Tests (std/sync/svs_test.go): membership-hash stability and order independence; FULL/PARTIAL round-trip; publish-only decoding; handleMhashMismatch; pull-debounce.
  • Reliability (std/object): metadata fetch retries 3 → 5 and segment fetcher maxRetries 3 → 5, to keep ndnd cat working on the 53-node sprint e2e topology.

Comments addressed

  • All 22 review comments from Adam (a-thieme) and all 16 from Tianyuan (tianyuan129) addressed.
  • The Copilot review comments (legacy-mode reconciliation, partialCandidateNames sort stability) are addressed in the commits.
  • Commit history is squashed to 4 commits; agent trailers stripped.

Test plan

  • go test ./std/sync/... — passes locally
  • GitHub e2e (make e2e, sprint topology) — passing locally with the retry bumps

@Taranum01
Taranum01 changed the base branch from main to psvs June 29, 2026 17:07
Taranum01 pushed a commit to Taranum01/ndnd that referenced this pull request Jun 29, 2026
Publish the revision spec alongside the std/sync implementation in PR named-data#190.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread e2e/topo.min.conf Outdated
@@ -0,0 +1,16 @@
# Small topology for local e2e (6 nodes, minimal link delays).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not introduce new topologies in e2e tests?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 79b79eetopo.min.conf, make e2e-local, and the docker script were local Mac dev helpers only. CI continues to use topo.sprint.conf.

Comment thread scripts/e2e-local-docker.sh Outdated
@@ -0,0 +1,21 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we need this extra things?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 79b79ee for the same reason — not needed in the upstream repo.

Comment thread std/engine/face/multicast_face.go Outdated
@@ -0,0 +1,82 @@
package face

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am completely lost, we are revising the SVS protocol. Why invent the multicast face here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread std/sync/svs.go Outdated
}
svsData = buildAnnounceSvsData(stateSnap, ref)
} else {
svsData = buildSvsDataForSend(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use arg struct to avoid long parameter list.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 79b79eebuildSvsDataForSend now takes a svsSendInput struct.

Comment thread std/sync/svs.go Outdated
mtimeSnap,
)
}
logSyncSend(s, reason, svsData)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a leftover from your debugging?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — removed in 79b79ee. Deleted svs_debug.go and the logSyncSend / logSyncRecv calls on the send/recv path.

Comment thread std/sync/svs_announce.go Outdated
}
return exceedsSyncThreshold(threshold, inlineFullSize(state))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry those helper functions are not helping with readability and mainainability.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread std/sync/svs_debug.go Outdated
@@ -0,0 +1,89 @@
package sync

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see it is necessary to have this file -- if this is only for debugging.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed svs_debug.go entirely in 79b79ee.

Comment thread std/sync/svs_mesh_test.go Outdated
@@ -0,0 +1,157 @@
package sync

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you describe what this test is about?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the necessity of this since we already have the e2e tests in CI

@Taranum01

Copy link
Copy Markdown
Author

Thanks for the review, @tianyuan129 — addressed in 79b79ee:

  • Removed local-only e2e topology / docker runner (topo.min.conf, e2e-local, e2e-local-docker.sh)
  • Removed production multicast_face; mesh integration tests use a test-only hub in svs_mesh_test.go
  • Removed svs_debug.go and debug send/recv logging
  • buildSvsDataForSend now takes a svsSendInput struct
  • Merged svs_announce.go into svs_pull.go + svs_encode.go

Also updated the PR description: SyncVectorThreshold=0 uses legacy StateVector-only wire (no mhash), which preserves DV prefix-table behavior and is what fixed e2e.

go test ./std/sync/... passes locally; CI should re-run on the new commit.

@Taranum01

Copy link
Copy Markdown
Author

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.

@Taranum01

Copy link
Copy Markdown
Author

@tianyuan129 Pushed ffcc890 — fixes the test/lint failure (goimports column alignment in std/sync/svs_mesh_test.go). Could you re-run CI / re-review when convenient? No logic changes; only the field alignment in meshMulticastFace was touched.

Comment thread std/examples/svs/large-sync/main.go Outdated
@@ -0,0 +1,89 @@
package main

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we did not change the Sync API, I do not see the necessity of having a separate example.

Comment thread std/sync/svs.go Outdated

// SyncVectorThreshold is the max inline SvsData size (bytes).
// 0 = legacy mode (StateVector-only wire, no mhash or announce+pull recovery).
SyncVectorThreshold int

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to be backward compatiable.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Y not X"

Comment thread std/sync/svs.go Outdated

recvSv: make(chan svSyncRecvSvArgs, 128),

fullVectorPrefix: deriveFullVectorPrefix(opts.SyncDataName),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread std/sync/svs_encode.go Outdated
Comment on lines +33 to +68
// 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))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that we must have those tiny helper functions?

I don't know....I prefer inline them for readability.

Comment thread std/sync/svs_mesh_test.go Outdated
@@ -0,0 +1,157 @@
package sync

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the necessity of this since we already have the e2e tests in CI

Comment thread std/sync/svs_mhash.go Outdated
Comment on lines +10 to +14
// membershipTuple is one (Name, BootstrapTime) pair in the sync group.
type membershipTuple struct {
name enc.Name
boot uint64
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread std/sync/svs_pull_test.go Outdated
@Taranum01

Copy link
Copy Markdown
Author

All review comments addressed in the latest push — please take another look when you have a moment.

@Taranum01

Copy link
Copy Markdown
Author

Tested locally: test_001.scenario_ndnd_fw now passes 3/3 on the sprint topology with 91f49f8. Three small changes — debounced pullFullVector (5s per sender), NoMetadata+TryStore on SvsALO's consumeObject, and segment fetcher maxRetries: 3 → 5 (there was already a // TODO: make it configurable).

Comment thread std/object/client_consume.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 SvsData TLV to carry MemberSetHash (mhash), VectorType, and SvsDataRef, 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 mhash mismatch 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.

Comment thread std/sync/svs.go Outdated
Comment thread std/sync/svs_encode.go Outdated

@a-thieme a-thieme left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look at everything except for the unit tests

note: I accidentally submitted as a change request as opposed to comment

Comment thread docs/svs-v3-revision.md Outdated
Comment thread docs/svs-v3-revision.md Outdated

### 1.2 Large groups

When the encoded State Vector exceeds **`SyncVectorThreshold`** (configurable application limit), nodes use three dissemination modes:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can leave it coded as a constant in the ndnd svs library. I don't think it should be application-defined

Comment thread docs/svs-v3-revision.md Outdated
| `VectorType` | `0xCD` | `0` = FULL, `1` = PARTIAL |
| `StateVector` | `0xC9` | See Section 3.2 |

#### 3.1.2 Announce-only form

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Announce" should probably be changed to something about publishing the full vector because "announce" is usually used for prefix announcement in routing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest updating the go identifiers as well to reduce maintainer confusion

Comment thread docs/svs-v3-revision.md Outdated
Comment thread docs/svs-v3-revision.md Outdated
Comment thread std/sync/svs_encode.go
Comment thread std/sync/svs_membership_hash.go
Comment thread std/sync/svs_pull.go
Comment on lines +73 to +79
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread std/sync/svs_pull.go
Comment thread std/sync/svs_pull.go
Taranum Wasu added 3 commits July 21, 2026 12:45
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.
@Taranum01 Taranum01 changed the title sync: SVS v3 large-group revision (mhash, PARTIAL, announce+pull) sync: SVS v4 (mhash, PARTIAL, publish+pull) Jul 21, 2026
@Taranum01
Taranum01 force-pushed the svs-exploration branch 2 times, most recently from 5ee8ec8 to 4ec986e Compare July 21, 2026 22:09
@a-thieme
a-thieme requested a review from Copilot July 21, 2026 23:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • partialCandidateNames tries to sort remaining by canonical name and then by recency, but slices.SortFunc is 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))

Comment thread std/sync/svs_pull.go
Comment on lines +227 to +231
localTuples, remoteTuples := membershipTupleCount(s.state), membershipTupleCount(recvSv)
if localTuples > remoteTuples && membershipContains(s.state, recvSv) {
go s.sendRecoveryAnnounce()
return
}
Comment thread std/sync/svs.go Outdated
Taranum Wasu added 2 commits July 22, 2026 15:10
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.
@Taranum01
Taranum01 force-pushed the svs-exploration branch 5 times, most recently from 1b54a20 to 5d8b1be Compare July 22, 2026 13:24
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.
@Taranum01

Copy link
Copy Markdown
Author

@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?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Comment thread std/sync/svs.go
Comment on lines +621 to +625
// 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
Comment thread std/sync/svs_pull.go Outdated
Comment on lines +183 to +191
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")
}
}
Comment thread std/sync/svs_pull.go
Comment thread std/sync/svs.go
Comment on lines +760 to 770
// 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 a-thieme left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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

Comment thread docs/svs-v4.md Outdated
Comment on lines +31 to +32
**MemberSetHash (`mhash`)** is always carried inside `SvsData`. It is a
**membership hash**, not a hash of the full State Vector.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for clarification, just say what mhash is

Comment thread docs/svs-v4.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/svs-v4.md Outdated
`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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless ndnlpv2 was mentioned in previous specs, I don't think it's necessary here

Comment thread docs/svs-v4.md
Comment on lines +137 to +138
| `VectorType` | `0xCD` | `0` = FULL, `1` = PARTIAL |
| `StateVector` | `0xC9` | See §3.2 |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/svs-v4.md Outdated
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is Python strawman?

Comment thread docs/svs-v4.md Outdated
Comment on lines +285 to +287
> **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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this as a github issue after PR is merged instead of as part of the spec

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for an issue, also no no need to mention in spec.

Comment thread docs/svs-v4.md
> 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/svs-v4.md Outdated
Comment on lines +412 to +420
### 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify that this implies A is partial. This rule exemption is because we don't know what names were omitted from A

Comment thread std/sync/svs.go Outdated

// SyncVectorThreshold is the max inline SvsData size (bytes).
// 0 = legacy mode (StateVector-only wire, no mhash or announce+pull recovery).
SyncVectorThreshold int

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Y not X"

Comment thread std/sync/svs_encode.go Outdated
Comment on lines +226 to +230
//
// [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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarification was only needed for me during review, not in code

@Pesa

Pesa commented Jul 23, 2026

Copy link
Copy Markdown
Member

"Y not X" or "not X, Y"

LLMs love spewing this crap.

Taranum Wasu added 3 commits July 24, 2026 14:27
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.
@Taranum01

Copy link
Copy Markdown
Author

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.

Taranum Wasu added 2 commits July 24, 2026 15:08
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants