Skip to content

Commit d69800d

Browse files
committed
feat(discovery): enhance MOLS traffic distribution with P2C pressure optimization and stickiness
1 parent 532c071 commit d69800d

5 files changed

Lines changed: 159 additions & 13 deletions

File tree

portal/discovery/mols.go

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const (
3535
molsFallbackRTTThreshold = 2 * time.Second
3636
molsMinActiveNodes = 2
3737
defaultMaxActiveRelays = 3
38+
molsP2CPressureDelta = 0.3
3839
)
3940

4041
func molsScore(i, j, m1, m2, order int) int {
@@ -105,7 +106,8 @@ func molsCongestionScore(i, j, m1, m2, order int) int {
105106
return (order*order + 1) - molsScore(i, (order-1)-j, m1, m2, order)
106107
}
107108

108-
// hashToGridIndex maps an identity string to a stable FNV-1a hash. Callers
109+
// hashToGridIndex maps an identity string to a stable FNV-1a hash with a 2nd-stage
110+
// bit-mixing cascade (avalanche diffusion to eliminate clustering). Callers
109111
// fold it into the current grid order with % order; the folded index is not
110112
// stable across orders, so it is recomputed whenever the pool size changes.
111113
func hashToGridIndex(s string) uint32 {
@@ -114,6 +116,11 @@ func hashToGridIndex(s string) uint32 {
114116
h ^= uint32(s[i])
115117
h *= 16777619
116118
}
119+
h ^= h >> 16
120+
h *= 0x85ebca6b
121+
h ^= h >> 13
122+
h *= 0xc2b2ae35
123+
h ^= h >> 16
117124
return h
118125
}
119126

@@ -286,17 +293,33 @@ func RankRelayPool(autoPool []RelayState, localAddress string) []string {
286293
return 0
287294
})
288295

289-
tierOut := make([]string, 0, len(candidates))
296+
var nonSaturated []molsCandidate
297+
var saturated []molsCandidate
290298
for _, candidate := range candidates {
291-
if !candidate.state.IsSaturated {
292-
tierOut = append(tierOut, candidate.state.Descriptor.APIHTTPSAddr)
299+
if candidate.state.IsSaturated {
300+
saturated = append(saturated, candidate)
301+
} else {
302+
nonSaturated = append(nonSaturated, candidate)
293303
}
294304
}
295-
for _, candidate := range candidates {
296-
if candidate.state.IsSaturated {
297-
tierOut = append(tierOut, candidate.state.Descriptor.APIHTTPSAddr)
305+
306+
// P2C pressure optimization: If candidate 0 has significantly higher
307+
// pressure than candidate 1, swap them to relieve node pressure without herd effect.
308+
if len(nonSaturated) >= 2 {
309+
p0 := nonSaturated[0].state.Pressure()
310+
p1 := nonSaturated[1].state.Pressure()
311+
if p0-p1 > molsP2CPressureDelta {
312+
nonSaturated[0], nonSaturated[1] = nonSaturated[1], nonSaturated[0]
298313
}
299314
}
315+
316+
tierOut := make([]string, 0, len(candidates))
317+
for _, candidate := range nonSaturated {
318+
tierOut = append(tierOut, candidate.state.Descriptor.APIHTTPSAddr)
319+
}
320+
for _, candidate := range saturated {
321+
tierOut = append(tierOut, candidate.state.Descriptor.APIHTTPSAddr)
322+
}
300323
return tierOut
301324
}
302325

@@ -327,8 +350,41 @@ func SelectPriority(states []RelayState, routeState RouteState) []string {
327350
if maxActive <= 0 {
328351
maxActive = defaultMaxActiveRelays
329352
}
330-
if len(auto) > maxActive {
331-
auto = auto[:maxActive]
332-
}
353+
auto = applyActiveStickiness(auto, routeState.ActiveRelayURLs, maxActive)
333354
return append(explicit, auto...)
334355
}
356+
357+
// applyActiveStickiness preserves currently active relays that remain in the
358+
// ranked eligible pool to avoid connection churn.
359+
func applyActiveStickiness(ranked []string, activeRelayURLs []string, maxActive int) []string {
360+
if len(activeRelayURLs) == 0 || len(ranked) <= maxActive {
361+
if len(ranked) > maxActive {
362+
return ranked[:maxActive]
363+
}
364+
return ranked
365+
}
366+
activeSet := make(map[string]struct{}, len(activeRelayURLs))
367+
for _, u := range activeRelayURLs {
368+
activeSet[u] = struct{}{}
369+
}
370+
selected := make([]string, 0, maxActive)
371+
for _, u := range ranked {
372+
if _, isActive := activeSet[u]; isActive {
373+
selected = append(selected, u)
374+
if len(selected) == maxActive {
375+
break
376+
}
377+
}
378+
}
379+
if len(selected) < maxActive {
380+
for _, u := range ranked {
381+
if !slices.Contains(selected, u) {
382+
selected = append(selected, u)
383+
if len(selected) == maxActive {
384+
break
385+
}
386+
}
387+
}
388+
}
389+
return selected
390+
}

portal/discovery/mols_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package discovery
22

33
import (
44
"fmt"
5+
"slices"
56
"testing"
67
"time"
78

@@ -102,6 +103,77 @@ func TestMOLSSelectPriorityKeepsUnobservedAutoSeed(t *testing.T) {
102103
}
103104
}
104105

106+
func TestHashToGridIndexDistribution(t *testing.T) {
107+
const buckets = 7
108+
counts := make(map[int]int, buckets)
109+
for i := 0; i < 1000; i++ {
110+
addr := fmt.Sprintf("192.168.1.%d:8080", i)
111+
idx := int(hashToGridIndex(addr) % buckets)
112+
counts[idx]++
113+
}
114+
// Every bucket must receive at least some items without starving
115+
for b := 0; b < buckets; b++ {
116+
if counts[b] == 0 {
117+
t.Errorf("bucket %d received 0 items", b)
118+
}
119+
}
120+
}
121+
122+
func TestMOLSP2CPressurePromotion(t *testing.T) {
123+
relayA := confirmedRelayState(t, "https://relay-a.example")
124+
relayB := confirmedRelayState(t, "https://relay-b.example")
125+
126+
// relayA: High load momentum and tail inflation (P90=100ms, P50=10ms)
127+
relayA.LoadFactor = 0.75
128+
relayA.EWMALoad = 0.75
129+
relayA.LoadDelta = 0.2
130+
for i := 0; i < 90; i++ {
131+
relayA.RTTTracker.Add(10 * time.Millisecond)
132+
}
133+
for i := 0; i < 10; i++ {
134+
relayA.RTTTracker.Add(100 * time.Millisecond)
135+
}
136+
137+
// relayB: Low load and uniform RTT (P90=20ms, P50=20ms)
138+
relayB.LoadFactor = 0.1
139+
relayB.EWMALoad = 0.1
140+
for i := 0; i < 100; i++ {
141+
relayB.RTTTracker.Add(20 * time.Millisecond)
142+
}
143+
144+
if relayA.Pressure() <= relayB.Pressure()+molsP2CPressureDelta {
145+
t.Fatalf("relayA pressure (%.2f) should exceed relayB pressure (%.2f) + delta (%.2f)",
146+
relayA.Pressure(), relayB.Pressure(), molsP2CPressureDelta)
147+
}
148+
}
149+
150+
func TestMOLSSelectPriorityActiveStickiness(t *testing.T) {
151+
relays := make([]RelayState, 10)
152+
for i := range relays {
153+
relays[i] = confirmedRelayState(t, fmt.Sprintf("https://relay-stick-%d.example", i))
154+
}
155+
156+
// First selection without active relays
157+
firstPick := SelectPriority(relays, RouteState{MaxActiveRelays: 2})
158+
if len(firstPick) != 2 {
159+
t.Fatalf("len(firstPick) = %d, want 2", len(firstPick))
160+
}
161+
162+
// Suppose relay 9 was currently connected and is healthy
163+
activeRelay := "https://relay-stick-9.example"
164+
secondPick := SelectPriority(relays, RouteState{
165+
ActiveRelayURLs: []string{activeRelay},
166+
MaxActiveRelays: 2,
167+
})
168+
169+
if len(secondPick) != 2 {
170+
t.Fatalf("len(secondPick) = %d, want 2", len(secondPick))
171+
}
172+
if !slices.Contains(secondPick, activeRelay) {
173+
t.Fatalf("secondPick %v should contain activeRelay %q due to stickiness", secondPick, activeRelay)
174+
}
175+
}
176+
105177
func BenchmarkMOLSRankRelayPool(b *testing.B) {
106178
localAddr := "test-client-address"
107179
relays := make([]RelayState, 100)

portal/discovery/relayset.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,7 @@ func (s *RelaySet) PlanRoutes(explicitPath []string, routeState RouteState) ([]R
491491
if maxActive <= 0 {
492492
maxActive = defaultMaxActiveRelays
493493
}
494-
if len(ranked) > maxActive {
495-
ranked = ranked[:maxActive]
496-
}
494+
ranked = applyActiveStickiness(ranked, routeState.ActiveRelayURLs, maxActive)
497495
routes := make([]Route, 0, len(ranked)+len(routeState.ExplicitRelayURLs))
498496
for _, relayURL := range routeState.ExplicitRelayURLs {
499497
eligible := true

portal/discovery/relaystate.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,22 @@ func (state *RelayState) EvaluateSaturation() {
200200
state.IsSaturated = saturated == 1
201201
}
202202

203+
// Pressure computes the normalized pressure index using tail latency ratio
204+
// (P90/P50 inflation) and load momentum (EWMALoad + beta * LoadDelta).
205+
func (state RelayState) Pressure() float64 {
206+
p50 := float64(state.RTTTracker.Get(0.50))
207+
p90 := float64(state.RTTTracker.Get(0.90))
208+
209+
var tailInflation float64
210+
if p50 > 0 && p90 > p50 {
211+
tailInflation = (p90 - p50) / p50
212+
}
213+
214+
const beta = 0.5
215+
loadMomentum := state.EWMALoad + (beta * state.LoadDelta)
216+
return tailInflation + loadMomentum
217+
}
218+
203219
func (state *RelayState) UpdateEWMARTT(newRTT time.Duration) {
204220
const alpha = 0.3
205221
state.RTTDelta = absDuration(newRTT - state.DiscoveryRTT)
@@ -252,6 +268,9 @@ func (state RelayState) eligibleForMultiHop(routeState RouteState, now time.Time
252268

253269
type RouteState struct {
254270
ExplicitRelayURLs []string
271+
// ActiveRelayURLs holds currently active connected relay URLs to enable
272+
// connection-level stickiness and prevent listener churn during ranking updates.
273+
ActiveRelayURLs []string
255274
// MaxActiveRelays caps auto-selected listener entries. Zero or negative
256275
// values use the selection default of 3. Multi-hop paths may use further
257276
// eligible relays as non-entry hops.

sdk/expose.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ func (e *Exposure) reconcileRelayListeners(failOnError bool) error {
706706
cfg := e.Config()
707707
routes, err := e.relaySet.PlanRoutes(append([]string(nil), cfg.MultiHop...), discovery.RouteState{
708708
ExplicitRelayURLs: append([]string(nil), cfg.RelayURLs...),
709+
ActiveRelayURLs: e.ActiveRelayURLs(),
709710
MaxActiveRelays: cfg.MaxActiveRelays,
710711
MultiHopDepth: cfg.MultiHopDepth,
711712
RequireUDP: cfg.UDPEnabled,

0 commit comments

Comments
 (0)