test(geode): add LoadBenchmark.fanoutScaling for selective-fanout perf

Verifies the FilterIndex path in LiveEventStore: each event matches
exactly ONE of N subscribers, so without an index the relay walks
all N subs per event (O(N)); with the author-keyed index the lookup
is O(1) and per-event latency stays flat as N grows.

Different from fanoutLatency (broadcast: 1 event reaches every
sub). Here per-event work is lookup-bound rather than
delivery-bound. Configurable via -DfanoutScalingSubs (comma list)
and -DfanoutScalingEvents.

Measured (laptop, JDK 21, full sweep):
  100 subs, 2k events: p50=1.35ms p99=5.71ms
 1000 subs, 2k events: p50=1.05ms p99=3.05ms
 5000 subs, 1k events: p50=1.01ms p99=2.96ms

p50 ~1ms across N — the predicted O(1) scaling. Default events count
adapts downward at high N to stay below WebSocketSessionPump's
8192-frame outbound cap (test-client read side is the bottleneck
above ~5k subs, not the relay).

Plan doc updated with the measured numbers in the "How to verify"
section.
This commit is contained in:
Claude
2026-05-07 23:05:29 +00:00
parent a43783879b
commit 0d0fbf36c9
3 changed files with 189 additions and 4 deletions
+2
View File
@@ -42,6 +42,8 @@ tasks.withType<Test>().configureEach {
// perf.LoadBenchmark tests opt in. Off by default — load tests
// are noisy and slow.
systemProperty("runLoadBenchmark", System.getProperty("runLoadBenchmark") ?: "false")
System.getProperty("fanoutScalingEvents")?.let { systemProperty("fanoutScalingEvents", it) }
System.getProperty("fanoutScalingSubs")?.let { systemProperty("fanoutScalingSubs", it) }
// Show println output from test JVM so the benchmark numbers are
// actually visible without grepping the report XML.
testLogging {