Quoracle
Quoracle is a Rust library for designing and analyzing read-write quorum systems: the rules that decide which replicas a read must contact, which replicas a write must contact, and how often to use each choice.
Give it a quorum system and a workload (the fraction of operations that are reads) and it tells you:
- fault tolerance: how many nodes can fail while reads and writes still work;
- load and capacity: how busy the busiest node is, and so how much throughput the whole system can sustain;
- network cost and latency: expected nodes contacted, and expected time to assemble a quorum;
- the optimal strategy: the probability of choosing each quorum that
minimizes load, network cost, or latency, optionally subject to limits on
the others and to
f-failure resilience; - the best quorum system for a set of nodes, found by search.
It is a Rust port of the Python Quoracle library from Read-Write Quorum Systems Made Practical (Whittaker, Charapko, Hellerstein, Howard, Stoica, PaPoC 2021, paper). The algorithms and results match the Python version; its test suite is ported as parity tests.
When to use Quoracle
Use it when you are choosing or tuning a replication scheme and want numbers instead of rules of thumb:
- You run Paxos, Raft-style, Dynamo-style (
R + W > N) or flexible-quorum replication and want to know whether majorities are actually the best choice for your read/write mix. Often they aren't. With 9 nodes, a 3×3 grid sustains 1.67× the throughput of a majority quorum at 50% reads, and read-one/write-all sustains 2.8× at 90% reads. - Your nodes are heterogeneous: some have more capacity or lower latency, and you want quorums that use them well.
- You need to verify a design: that every read quorum intersects every write quorum, and how many failures it tolerates.
- You need a runtime quorum picker: compute a strategy once, then
sample
get_read_quorum()/get_write_quorum()per request.
Quoracle is a planning and analysis library. It does not do networking, failure detection, or replication. Plug its output into your own protocol. It also doesn't model ordered schemes like chain replication.
Scale: quorums are enumerated explicitly. That suits replica groups of
up to about 15 nodes. Measured on one core: the optimal strategy for a
majority of 9 nodes takes about 1 ms, 13 nodes about 70 ms, and 15 nodes
(6,435 quorums) about 1 s. Resilience is closed-form and takes microseconds.
search is exhaustive and practical to about 6 nodes; give it a timeout for
more.
Installation
[]
= "2"
The default microlp feature is a pure-Rust LP solver, with no system
dependencies. You can use COIN-OR CBC instead if you already depend on it.
It needs the CBC C library, and in our measurements it is no faster at these
problem sizes:
= { = "2", = false, = ["cbc"] }
See SOLVERS.md. The minimum supported Rust version is 1.88.
Quick start
use ;
Integrating Quoracle
1. Model your nodes. Node identifiers can be any Ord + Hash + Display
type (strings, integers, your own IDs). Add per-node capacity (requests per
second) and latency if your nodes differ:
use Node;
use Duration;
#
2. Describe the quorum system. a + b means "either", a * b means
"both", choose(k, ...) / majority(...) mean "any k". Build from reads
(writes become the dual, the smallest write rule that is safe), from writes,
or from both with QuorumSystem::new, which checks that they intersect.
3. Describe the workload. Distribution::fixed(0.9) is 90% reads. If
the mix changes over time, use Distribution::weighted(&[(0.9, 3.0), (0.5, 1.0)]) to optimize for a weighted mix of read fractions.
4. Optimize. strategy(objective, read_fraction, write_fraction, &limits, f) minimizes Load, Network, or Latency. Use StrategyLimits
to bound the other metrics, and f > 0 to only use quorums that still work
after f of their nodes fail (useful to avoid retries).
5. Use the result. Either sample quorums at runtime
(get_read_quorum), or read the probabilities (sigma_r() / sigma_w())
and feed them into your own router. Per-node metrics (node_load,
node_utilization, node_throughput) help with capacity planning.
Search: if you don't know which quorum system to use,
search(&nodes, &SearchConfig { .. }) tries all of them and returns the best
one with its strategy.
Errors: every fallible call returns quoracle::Error:
NoStrategyFound when limits can't be met, NonOverlappingQuorums for
unsafe read/write pairs, and Invalid* for bad arguments. Nothing in the
public API panics on bad input.
Threads: all types are Send + Sync. With the cbc feature, solves are
serialized through a global lock, because CBC is not thread-safe.
Quorums are hashbrown::HashSets; the matching version is re-exported as
quoracle::hashbrown.
Examples
Documentation
- API reference: https://docs.rs/quoracle
- User guide: https://gburd.github.io/rs-quoracle/
- CHANGELOG.md, including the 1.x → 2.0 migration guide
Development
Everything is pinned by the Nix flake (nix develop gives the toolchain,
CBC, cargo-llvm-cov, and mdbook; nix flake check builds and tests both
solver backends), or use any Rust ≥ 1.88:
CI runs on both Codeberg (the primary repository) and GitHub (a mirror). Both test the two solver backends and the MSRV, and enforce ≥ 85% line, function, and branch coverage.
License
Licensed under either of Apache-2.0 or MIT at your option. Based on the MIT-licensed Python Quoracle by Michael Whittaker; see NOTICE.