<p align="center">
<h1 align="center">loadwise</h1>
<p align="center">
A protocol-agnostic load balancing strategy engine for Rust.
<br />
Pick the right node — nothing more, nothing less.
</p>
<p align="center">
<a href="https://crates.io/crates/loadwise"><img src="https://img.shields.io/crates/v/loadwise.svg" alt="crates.io" /></a>
<a href="https://docs.rs/loadwise"><img src="https://img.shields.io/docsrs/loadwise" alt="docs.rs" /></a>
<a href="https://github.com/arcboxlabs/loadwise/actions"><img src="https://img.shields.io/github/actions/workflow/status/arcboxlabs/loadwise/ci.yml?branch=main" alt="CI" /></a>
<a href="LICENSE-MIT"><img src="https://img.shields.io/crates/l/loadwise" alt="License" /></a>
</p>
</p>
---
loadwise answers one question: *"given these nodes, which one should handle the next request?"*
It ships **7 strategies**, a **health state machine**, **composable filters**, and a **pluggable state store** — all in a synchronous, zero-allocation hot path. No async runtime, no tracing crate, no metrics dependency.
```rust
use loadwise::{Strategy, SelectionContext};
use loadwise::strategy::RoundRobin;
let strategy = RoundRobin::new();
let backends = ["10.0.0.1:8080", "10.0.0.2:8080", "10.0.0.3:8080"];
let ctx = SelectionContext::default();
let idx = strategy.select(&backends, &ctx).unwrap();
println!("route to {}", backends[idx]);
```
## Why loadwise?
Most load balancers live inside a proxy. loadwise gives you the **algorithm without the infrastructure** — embed it in any Rust application, from an API gateway to an LLM router to a game server matchmaker.
- **Zero-cost abstraction** — `Strategy::select` returns an `Option<usize>`, not a reference. No allocation on the hot path.
- **Protocol-agnostic** — works with HTTP, gRPC, WebSocket, database connections, or anything you can put in a slice.
- **Three opt-in traits** — `Node` (identity), `Weighted` (weight), `LoadMetric` (load score). Implement only what your strategy needs.
- **No opinion on I/O** — no async runtime, no HTTP client, no health-check poller. You report events; loadwise manages state.
## Strategies
| **`RoundRobin`** | — | Atomic sequential cycling |
| **`WeightedRoundRobin`** | `Weighted + Node` | Nginx smooth weighted round-robin |
| **`Random`** | — | Uniform random |
| **`WeightedRandom`** | `Weighted` | Proportional random by weight |
| **`LeastLoad`** | `LoadMetric` | Pick the node with lowest `load_score()` |
| **`PowerOfTwoChoices`** | `LoadMetric` | Randomly sample 2, pick the less loaded — O(1) |
| **`ConsistentHash`** | `Node` | Virtual-node hash ring with caching |
Compose strategies with **`WithFallback`** (generic, zero-cost) or **`FallbackChain`** (dynamic dispatch):
```rust
use loadwise::strategy::{LeastLoad, RoundRobin, WithFallback};
// Try least-load first; if no candidates remain after filtering, fall back to round-robin.
let strategy = WithFallback::new(LeastLoad, RoundRobin::new());
```
## Health Tracking
A four-state machine with configurable thresholds:
```text
┌─────────┐ failures ≥ ⌈t/2⌉ ┌──────────┐ failures ≥ t ┌───────────┐
│ Healthy │ ─────────────────→ │ Degraded │ ────────────────→ │ Unhealthy │
└─────────┘ └──────────┘ └───────────┘
↑ │ │
│ recovery_successes met │ 1 success │ 1 success
│ ↓ ↓
│ ┌─────────┐ ┌────────────┐
└──────────────────────────│ Healthy │←── recoveries ────│ Recovering │
└─────────┘ └────────────┘
```
```rust
use loadwise::{HealthTracker, HealthStatus, ConsecutiveFailurePolicy};
let tracker = HealthTracker::new(ConsecutiveFailurePolicy::default());
tracker.report_failure(&"node-1");
tracker.report_failure(&"node-1");
tracker.report_failure(&"node-1");
assert_eq!(tracker.status(&"node-1"), HealthStatus::Unhealthy);
tracker.report_success(&"node-1");
assert_eq!(tracker.status(&"node-1"), HealthStatus::Recovering);
```
## Filtering
Narrow candidates before selection — zero-allocation bitmask filters:
```rust
use loadwise::filter::{HealthFilter, AllOf, FnFilter, Filter};
// Only route to healthy + recovering nodes
let filter = HealthFilter::available();
// Combine filters with AND logic
let combined = AllOf::new(vec![
Box::new(HealthFilter::healthy_only()),
Box::new(FnFilter(|node: &MyNode| node.region == "us-east")),
]);
```
## Observability
Bring your own metrics stack — loadwise provides a hook, not a dependency:
```rust
use loadwise::metrics::MetricsCollector;
struct PrometheusCollector;
impl MetricsCollector for PrometheusCollector {
fn on_selection(&self, node_id: &dyn std::fmt::Debug, strategy: &str) {
// prometheus::counter!("lb_selections", "strategy" => strategy).inc();
}
fn on_health_change(&self, node_id: &dyn std::fmt::Debug,
from: loadwise::HealthStatus, to: loadwise::HealthStatus) {
// record transition
}
}
```
## Crate Structure
```text
loadwise/
├── loadwise-core Core traits, 7 strategies, health, filters, state store
├── loadwise Facade — re-exports core with optional feature flags
├── loadwise-store-redis Redis-backed StateStore (HASH-based, JSON-serialised)
└── loadwise-tower Tower Layer / Service adapter (planned)
```
| [`loadwise`](crates/loadwise) | Facade — re-exports core, optional `tower` / `redis` features |
| [`loadwise-core`](crates/loadwise-core) | All logic: traits, strategies, health, filters, state store |
| [`loadwise-store-redis`](crates/loadwise-store-redis) | Redis-backed `StateStore` via single HASH |
| [`loadwise-tower`](crates/loadwise-tower) | Tower `Layer` / `Service` adapter *(planned)* |
## Installation
```toml
[dependencies]
loadwise = "0.1"
```
**MSRV:** 1.85 (Rust edition 2024)
## Design Principles
1. **`select()` is synchronous** — returns an index into the candidate slice. No futures, no allocation.
2. **Interior mutability** — strategies use `AtomicUsize` or `Mutex` internally; callers hold `&self`.
3. **Fingerprint-based caching** — stateful strategies (hash ring, WRR weights) detect candidate-set changes automatically via a hash of node IDs.
4. **Pluggable everything** — `StateStore` for persistence, `HealthPolicy` for state transitions, `MetricsCollector` for observability. Implement the trait, bring your own backend.
## Contributing
Contributions are welcome! Please make sure `cargo test` passes and new public items have `///` doc comments with examples.
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option.