Delivers the distributed fabric's network layer and HTTP cluster surface: **m8p7: tidal-net crate (gRPC transport)** - GrpcTransport implementing Transport trait via tonic 0.12 - Per-peer circuit breaker (Closed/Open/HalfOpen), mutual TLS via rustls - Boxed error types (clippy-clean), graceful mutex recovery, debug_assert against calling block_on from tokio context - Proto: WalShipping service (ShipSegment, StreamSegments stub, Heartbeat) - 19 tests: contract, mTLS, reconnection, multi-node UAT, benchmarks **m8p8: cluster subcommand + HTTP routes** - ClusterState wrapping SimulatedCluster with region name mapping - Routes: /health, /cluster/status, /cluster/promote, /partition, /heal - Data routes: /items, /embeddings, /signals, /feed, /search (region-aware) - Ranking profiles wired through ClusterConfig to all cluster nodes - Topology YAML config, docker/cluster/Dockerfile (ENTRYPOINT+CMD, non-root) **m8p9: scatter-gather query routing** - Entity-sharded writes via Knuth multiplicative hash - Scatter-gather RETRIEVE and SEARCH with deadline propagation (50ms-5ms) - Partial failure: degraded=true with unavailable_shards metadata - 6 tests: distribution, determinism, multi-shard retrieve, degraded partial results, deadline propagation, scatter-gather search **m8p10: gRPC transport integration tests** - 8 tests over real gRPC: replication convergence, idempotent replay, mixed signals, 3-node fan-out, partition/heal, degraded follower, perf - Documented as tier-2 (in-process+gRPC); tier-3 multi-process pending
107 lines
3.4 KiB
Rust
107 lines
3.4 KiB
Rust
//! Benchmark comparing InProcessTransport vs GrpcTransport throughput.
|
|
//!
|
|
//! Measures segments/sec for both transports to quantify gRPC overhead.
|
|
|
|
use std::collections::HashMap;
|
|
use std::net::SocketAddr;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
|
|
|
|
use tidaldb::replication::WalSegmentId;
|
|
use tidaldb::replication::in_process::InProcessTransportFactory;
|
|
use tidaldb::replication::shard::{RegionId, ShardId};
|
|
use tidaldb::replication::transport::{Transport, WalSegmentPayload};
|
|
|
|
use tidal_net::GrpcTransport;
|
|
use tidal_net::config::GrpcTransportConfig;
|
|
|
|
const SEGMENT_SIZE: usize = 1024; // 1 KB payload per segment
|
|
|
|
fn make_payload(seqno: u64) -> WalSegmentPayload {
|
|
WalSegmentPayload {
|
|
id: WalSegmentId::new(RegionId::SINGLE, ShardId(0), seqno),
|
|
bytes: vec![0xAB; SEGMENT_SIZE],
|
|
event_count: 10,
|
|
}
|
|
}
|
|
|
|
fn free_addr() -> SocketAddr {
|
|
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
|
listener.local_addr().unwrap()
|
|
}
|
|
|
|
fn bench_transports(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("transport_throughput");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
// ── InProcessTransport ──────────────────────────────────────────────
|
|
|
|
{
|
|
let shards = vec![ShardId(0), ShardId(1)];
|
|
let mut transports = InProcessTransportFactory::new(&shards).build();
|
|
let t0 = transports.remove(&ShardId(0)).unwrap();
|
|
let t1 = transports.remove(&ShardId(1)).unwrap();
|
|
|
|
// Drain receiver in background.
|
|
let drain = thread::spawn(move || while t1.recv_segment().is_some() {});
|
|
|
|
let mut seq = 0u64;
|
|
group.bench_function("in_process", |b| {
|
|
b.iter(|| {
|
|
seq += 1;
|
|
t0.send_segment(ShardId(1), make_payload(seq)).unwrap();
|
|
});
|
|
});
|
|
|
|
drop(t0);
|
|
let _ = drain.join();
|
|
}
|
|
|
|
// ── GrpcTransport ───────────────────────────────────────────────────
|
|
|
|
{
|
|
let addr0 = free_addr();
|
|
let addr1 = free_addr();
|
|
|
|
let config0 = GrpcTransportConfig {
|
|
local_shard: ShardId(0),
|
|
listen_addr: addr0,
|
|
peers: HashMap::from([(ShardId(1), addr1)]),
|
|
insecure: true,
|
|
..Default::default()
|
|
};
|
|
let config1 = GrpcTransportConfig {
|
|
local_shard: ShardId(1),
|
|
listen_addr: addr1,
|
|
peers: HashMap::from([(ShardId(0), addr0)]),
|
|
insecure: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let t0 = GrpcTransport::new(config0).expect("grpc transport 0");
|
|
let t1 = GrpcTransport::new(config1).expect("grpc transport 1");
|
|
thread::sleep(Duration::from_millis(200));
|
|
|
|
// Drain receiver in background.
|
|
let drain = thread::spawn(move || while t1.recv_segment().is_some() {});
|
|
|
|
let mut seq = 0u64;
|
|
group.bench_function("grpc_localhost", |b| {
|
|
b.iter(|| {
|
|
seq += 1;
|
|
t0.send_segment(ShardId(1), make_payload(seq)).unwrap();
|
|
});
|
|
});
|
|
|
|
drop(t0);
|
|
let _ = drain.join();
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(benches, bench_transports);
|
|
criterion_main!(benches);
|