Motivation
Storage placement treats every node as equally durable. Each owner derives DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER = 160 virtual positions (crates/core/src/dht/virtual_node.rs) and owner selection is nearest-clockwise position among the observed owner set. A tab that opened ten seconds ago and a seed that has run for months therefore hold the same share of data and generate the same hand-off and repair traffic when they leave. Under browser churn the repair load is proportional to the churn rate of the least stable nodes.
Ring position must be uniform for routing, but storage share need not be. The relevant results:
- Godfrey, Shenker & Stoica, "Minimizing Churn in Distributed Systems" (SIGCOMM 2006): choosing which nodes carry responsibility by longest observed uptime materially lowers effective churn across five real traces, while any "preference list" chosen by another metric (ring position is one) raises it.
- Maymounkov & Mazières, "Kademlia" (IPTPS 2002), Fig. 1, and Stutzbach & Rejaie, "Understanding Churn in Peer-to-Peer Networks" (IMC 2006), §4.5: remaining lifetime grows with observed uptime, so uptime is the right weight.
- Two-tier / hierarchical DHTs (Garcés-Erice et al., "Hierarchical Peer-to-peer Systems", Euro-Par 2003; "Evaluation of hierarchical DHTs to mitigate churn effects in mobile networks", Computer Communications 2016) move responsibility to a stable tier and report large reductions in maintenance traffic under mobile-grade churn. libp2p's Kademlia "client mode" is the same idea in production: restricted or intermittent nodes route but do not serve.
- Dynamo (DeCandia et al., SOSP 2007) weights virtual nodes per host by capacity; the same mechanism weights by stability here.
Proposal
Keep a single routing ring and vary only the number of virtual storage positions a node claims:
- Storage class. A node advertises
dht_virtual_nodes ∈ [0, 256] in its connect offer (the field already exists and is rejected on mismatch today; it becomes a per-node value instead of a network constant). Browser tabs default to 0; native daemons keep 160; an operator may raise a seed to 256.
- Uptime ramp. A node that starts at
0 may raise its class after it has been up for a threshold (e.g. one hour), re-advertised through the next stabilize round; claiming more is self-penalising (more data, more repair), so no proof is needed. Sybil concentration is explicitly a non-goal of the project.
- Owner selection unchanged: nearest clockwise virtual position among the observed owner set, now with heterogeneous counts.
find_storage_owner already handles an arbitrary observed set.
- Inbox holders follow the same rule.
RelayMessage currently resolves by ring geometry alone (find_successor), which is what puts inbox carriers on tabs; resolve the holder through the virtual-owner map as data does, so held messages land on class > 0 nodes. (This composes with inbox replication.)
- A class-0 node still owns its own DID position for addressing, routing and its own inbox address; it just holds no one else's data.
Acceptance
- A five-node simulated ring with one class-160 node and four class-0 tabs places every data replica and inbox carrier on the class-160 node; tab churn generates no storage repair traffic.
- Raising a node's class after the uptime threshold triggers exactly one additive republish toward it, bounded by the existing one-delivery-per-pass throttle.
- Mixed classes converge to the same owner map on every node (existing view-relative
REPLICATED law extended to weighted positions).
docs/src/advanced-topic/config.yaml.md documents the class and the browser default.
Related
Inbox replication, adaptive successor list, churn simulator. This deliberately stops short of a two-tier routing overlay (browser as client of a stable node): that changes addressing semantics and is out of scope before 1.0.
Issue family (browser-churn stability, 2026-09-14)
#773 churn simulator · #774 adaptive successor list and stabilization period · #775 age-ranked peer cache and re-join · #776 RTT-derived timeouts, ICE restart, lookup retry · #777 inbox replication and Leave · #778 stability-weighted storage · #779 browser lifecycle events. Finger convergence is #768 / #770; native seed redial is #763.
Motivation
Storage placement treats every node as equally durable. Each owner derives
DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER = 160virtual positions (crates/core/src/dht/virtual_node.rs) and owner selection is nearest-clockwise position among the observed owner set. A tab that opened ten seconds ago and a seed that has run for months therefore hold the same share of data and generate the same hand-off and repair traffic when they leave. Under browser churn the repair load is proportional to the churn rate of the least stable nodes.Ring position must be uniform for routing, but storage share need not be. The relevant results:
Proposal
Keep a single routing ring and vary only the number of virtual storage positions a node claims:
dht_virtual_nodes ∈ [0, 256]in its connect offer (the field already exists and is rejected on mismatch today; it becomes a per-node value instead of a network constant). Browser tabs default to0; native daemons keep 160; an operator may raise a seed to 256.0may raise its class after it has been up for a threshold (e.g. one hour), re-advertised through the next stabilize round; claiming more is self-penalising (more data, more repair), so no proof is needed. Sybil concentration is explicitly a non-goal of the project.find_storage_owneralready handles an arbitrary observed set.RelayMessagecurrently resolves by ring geometry alone (find_successor), which is what puts inbox carriers on tabs; resolve the holder through the virtual-owner map as data does, so held messages land on class > 0 nodes. (This composes with inbox replication.)Acceptance
REPLICATEDlaw extended to weighted positions).docs/src/advanced-topic/config.yaml.mddocuments the class and the browser default.Related
Inbox replication, adaptive successor list, churn simulator. This deliberately stops short of a two-tier routing overlay (browser as client of a stable node): that changes addressing semantics and is out of scope before 1.0.
Issue family (browser-churn stability, 2026-09-14)
#773 churn simulator · #774 adaptive successor list and stabilization period · #775 age-ranked peer cache and re-join · #776 RTT-derived timeouts, ICE restart, lookup retry · #777 inbox replication and Leave · #778 stability-weighted storage · #779 browser lifecycle events. Finger convergence is #768 / #770; native seed redial is #763.