From 27c870ed85deb5b5a1c60e0d1ed831d0b1108d6b Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 4 Nov 2026 21:49:15 +0100 Subject: benchmark members joining --- docs/development.md | 6 +++--- src/simtest/bench.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/docs/development.md b/docs/development.md index db89c04..9107014 100644 --- a/docs/development.md +++ b/docs/development.md @@ -21,9 +21,9 @@ The benchmarks are ignored tests named `bench_*`: the document's (writes and syn messages, `document/bench.rs`), the remakes' replay (`remake/bench.rs`), and what passing changes on costs over the simulated network (`simtest/bench.rs`: messages and bytes per write by kind, propagation delays, for a full mesh, a writer behind NAT, two -transports, partial meshes of 50 and 100 nodes, lossy links, and a member away for a -while; several minutes, or one scenario -alone with `BENCH_SCENARIO`, part of its name: `BENCH_SCENARIO=NAT`; all at once with +transports, partial meshes of 50 and 100 nodes, lossy links, a member away for a +while, and members joining during the writes; several minutes, or one scenario alone +with `BENCH_SCENARIO`, part of its name: `BENCH_SCENARIO=NAT`; all at once with `BENCH_PARALLEL=1`, faster on many cores, though sharing them makes delays come out somewhat higher), and the same estimated on the member list's graph alone for up to 2000 members, with some diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs index 09cd82c..9b18b2c 100644 --- a/src/simtest/bench.rs +++ b/src/simtest/bench.rs @@ -57,6 +57,12 @@ struct Scenario { /// 10 s, not long enough to be held dormant (four keepalives, 20 s), so what a node /// that is gone costs before the others notice. away: Option, + /// Nodes that join during each workload, one at a time, evenly spread (of + /// `nodes`: they start with `nodes` less twice as many), and so what members joining + /// cost while writes go on: member lists differ between nodes until the newcomer's + /// registration reached them, and their trees with them. Their catching up counts + /// in the messages, but not in the delays (`swarm::stats`: not until caught up). + joining: usize, } fn sim_only(_: usize) -> &'static [&'static str] { @@ -107,6 +113,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "writer behind NAT (one link), 13 nodes", @@ -115,6 +122,7 @@ const SCENARIOS: &[Scenario] = &[ net: writer_behind_nat, writer: 1, away: None, + joining: 0, }, Scenario { name: "two transports, 3 bridges, 30 nodes", @@ -123,6 +131,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 5, away: None, + joining: 0, }, Scenario { name: "partial mesh, 50 nodes", @@ -131,6 +140,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, lossy and slow links, 50 nodes", @@ -139,6 +149,7 @@ const SCENARIOS: &[Scenario] = &[ net: lossy, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, 100 nodes", @@ -147,6 +158,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, a member away for 10 s, 50 nodes", @@ -155,6 +167,16 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: Some(3), + joining: 0, + }, + Scenario { + name: "partial mesh, 5 members joining during each workload, 50 nodes", + nodes: 50, + on: sim_only, + net: clean, + writer: 1, + away: None, + joining: 5, }, ]; @@ -327,7 +349,9 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { config.neighbours_k = 12; }; let mut nodes = vec![sim.start_on_with(&ids[0], (scenario.on)(0), &[], Start::Genesis, timing)]; - for (i, id) in ids.iter().enumerate().skip(1) { + // Those there from the start; the others join during the workloads. + let initial = scenario.nodes - 2 * scenario.joining; + for (i, id) in ids.iter().enumerate().take(initial).skip(1) { // The writer behind NAT reaches node 2 alone. let seed = if i == 1 { ids[2].as_str() @@ -339,7 +363,7 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { let all: Vec<&AppState> = nodes.iter().collect(); let all = all.as_slice(); sim.wait_ready(all, SETTLE).await; - let count = scenario.nodes; + let count = initial; sim.wait_until( "every node knows every member", SETTLE, @@ -378,6 +402,7 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { WRITES, 1000 / WRITE_EVERY.as_millis() ); + let mut joined = initial; for (w, (workload, writer)) in [ ("one writer", Some(scenario.writer)), ("writers in turn", None), @@ -386,7 +411,22 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { .enumerate() { let start = Totals::of(&nodes).await; + // Before which writes the members joining in this workload start. + let joins: Vec = (1..=scenario.joining) + .map(|k| WRITES * k / (scenario.joining + 1)) + .collect(); for i in 0..WRITES { + if joins.contains(&i) { + let id = &ids[joined]; + nodes.push(sim.start_on_with( + id, + (scenario.on)(joined), + &[ids[0].as_str()], + Start::Join, + timing, + )); + joined += 1; + } // The middle half, the node that goes away is gone (writing nothing). if let Some(away) = scenario.away { if i == WRITES / 4 { @@ -396,20 +436,23 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { } } let gone = scenario.away.is_some_and(|away| { - (WRITES / 4..WRITES * 3 / 4).contains(&i) && away == i % nodes.len() + (WRITES / 4..WRITES * 3 / 4).contains(&i) && away == i % initial }); if writer.is_none() && gone { tokio::time::sleep(WRITE_EVERY).await; continue; } - let node = &nodes[writer.unwrap_or(i % nodes.len())]; + // In turn, those there from the start: one that just joined may not be + // ready yet. + let node = &nodes[writer.unwrap_or(i % initial)]; // A path of each workload's own: writing what is there already changes // nothing, and nothing would pass. write(node, &format!("bench/{w}/{i}"), json!(i)).await; tokio::time::sleep(WRITE_EVERY).await; } let written = Instant::now(); - sim.wait_converged(all, SETTLE).await; + let all: Vec<&AppState> = nodes.iter().collect(); + sim.wait_converged(&all, SETTLE).await; let converged = written.elapsed(); report( &mut out, -- cgit v1.3.1