You are reading the docs for rws 18.1 The latest release is 18.8. Read this page for the latest release.

Load Balancing

How rws distributes traffic across backends — round-robin everywhere, plus random, IP-hash, and least-connections in config-driven mode.

Every proxy component in rust-web-server — ReverseProxy, H2ReverseProxy, GrpcProxy, TcpProxy, UdpProxy, WsProxy, and DynamicProxy (config-driven mode) — uses the same underlying selection mechanism: a lock-free atomic round-robin counter.

How round-robin works

Each proxy struct holds an AtomicUsize counter initialised to 0. On every request the counter is incremented with Ordering::Relaxed and the backend index is computed as:

let idx = counter.fetch_add(1, Ordering::Relaxed) % backends.len();

No mutex is acquired; the operation is a single CPU instruction on every major architecture. Under concurrent load requests are distributed evenly across all live backends in cyclic order.

Library API

use rust_web_server::proxy::{LoadBalancing, ReverseProxy};

// Explicit — same as the default.
let proxy = ReverseProxy::new(["http://a:8080", "http://b:8080", "http://c:8080"])
    .strategy(LoadBalancing::RoundRobin);

The .strategy() method exists to allow future strategies to be added without a breaking API change.

Config-driven mode

In config-driven mode, [[upstream]]'s strategy field selects between four strategies:

[[upstream]]
name     = "api"
backends = ["api-1:3000", "api-2:3000", "api-3:3000"]
strategy = "least_connections"   # "round_robin" (default) | "random" | "ip_hash" | "least_connections"
Strategy Behavior
round_robin (default) The same lock-free atomic counter described above.
random Uniform random selection, mixing a nanosecond timestamp with the round-robin counter — no new dependency.
ip_hash Hashes the client IP (DefaultHasher) to pick a backend — the same client IP is consistently routed to the same backend as long as the live backend list doesn't change, giving basic session affinity without a session store.
least_connections Routes to whichever live backend currently has the fewest in-flight requests, tracked via a ConnectionGuard (RAII, decrements on Drop) around each proxied request.

An unrecognized or omitted strategy value falls back to round_robin.

DynamicProxy and live backend lists

When [upstream.health_check] is configured, the health-checker thread maintains a shared Arc<RwLock<Vec<String>>> containing only the currently healthy backends. DynamicProxy (the config-driven proxy adapter) reads from this live list on every request:

All backends:  [api-1, api-2, api-3]
                          ↓  health checker removes api-2
Live backends: [api-1, api-3]
                          ↓  round-robin counter % 2
Request N+0 → api-1
Request N+1 → api-3
Request N+2 → api-1

The live list is protected by RwLock — reads are concurrent; the health-checker thread takes a brief write lock once per probe round to publish the list.

Failover behaviour

ReverseProxy tries backends in order starting from the round-robin cursor position. If a backend fails to connect, the next backend in the list is tried. This continues until either a backend succeeds or all backends have been tried (returning 502).

backends = [A, B, C]
counter = 7 → idx 7 % 3 = 1 → start at B
B fails → try C → C succeeds → return response

Canary / weighted round-robin

CanaryLayer implements weighted round-robin by pre-expanding the rotation: a backend with weight = 3 appears three times in the rotation array. The same lock-free atomic counter selects slots.

// 3 out of 4 slots → stable; 1 out of 4 → canary.
CanaryLayer::new(vec![
    WeightedBackend::new("http://stable:8080", 3),
    WeightedBackend::new("http://canary:8080", 1),
])

See Canary / Traffic Splitting for full details.

Strategy selection is config-driven-proxy only

The library API (ReverseProxy::strategy(LoadBalancing::RoundRobin) above) only has the one variant today. random/ip_hash/least_connections are implemented in DynamicProxy, the adapter backing rws.config.toml's [[upstream]] mode — see Config-driven mode above.

Edit this page on GitHub