loadwise 0.1.0

A protocol-agnostic load balancing strategy engine for Rust
Documentation
<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

| Strategy | Requires | Algorithm |
|---|---|---|
| **`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)
```

| Crate | Description |
|---|---|
| [`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.