quoracle/lib.rs
1//! Quoracle: construct, analyze, and optimize read-write quorum systems.
2//!
3//! A *read-write quorum system* says which sets of nodes may serve a read
4//! and which may serve a write, such that every read set overlaps every
5//! write set. Quoracle lets you:
6//!
7//! - describe quorum systems with an expression algebra
8//! (`a + b` = either, `a * b` = both, [`choose`]/[`majority`] = k of n);
9//! - compute fault tolerance ([`QuorumSystem::resilience`]);
10//! - find the strategy (how often to pick each quorum) that minimizes
11//! load, network traffic, or latency for a given read/write mix, by
12//! linear programming ([`QuorumSystem::strategy`]);
13//! - evaluate any strategy's load, capacity, latency and per-node load;
14//! - search for the best quorum system over a set of nodes ([`search()`]).
15//!
16//! This is a Rust port of the Python [Quoracle] library described in
17//! Whittaker et al., "Read-Write Quorum Systems Made Practical"
18//! (`PaPoC` 2021).
19//!
20//! [Quoracle]: https://github.com/mwhittaker/quoracle
21//!
22//! # Example
23//!
24//! ```
25//! use quoracle::{Distribution, Expr, Node, Objective, QuorumSystem,
26//! StrategyLimits};
27//!
28//! # fn main() -> Result<(), quoracle::Error> {
29//! // A 2x2 grid: read a full row, write one node from each row.
30//! let [a, b, c, d] = ["a", "b", "c", "d"].map(|x| Expr::Node(Node::new(x)));
31//! let qs = QuorumSystem::from_reads(a * b + c * d);
32//! assert_eq!(qs.resilience(), 1);
33//!
34//! // The load-optimal strategy for a 75%-read workload.
35//! let fr = Distribution::fixed(0.75)?;
36//! let strategy = qs.strategy(
37//! Objective::Load, Some(&fr), None, &StrategyLimits::default(), 0,
38//! )?;
39//! let load = strategy.load(Some(&fr), None)?;
40//! assert!((load - 0.5).abs() < 1e-6);
41//! // The system can serve 1 / load = 2x the throughput of a single node.
42//! assert!((strategy.capacity(Some(&fr), None)? - 2.0).abs() < 1e-6);
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! # LP solvers
48//!
49//! Strategy optimization uses [`good_lp`]. The default `microlp` feature is
50//! pure Rust; the `cbc` feature uses COIN-OR CBC (faster on large problems,
51//! needs the system CBC library). Resilience never needs a solver.
52
53#![forbid(unsafe_code)]
54#![warn(missing_docs)]
55#![cfg_attr(docsrs, feature(doc_cfg))]
56
57#[cfg(not(any(feature = "microlp", feature = "cbc")))]
58compile_error!(
59 "quoracle needs an LP solver: enable `microlp` (default) or `cbc`"
60);
61
62pub mod distribution;
63pub mod error;
64pub mod expr;
65pub mod geometry;
66pub mod quorum_system;
67pub mod search;
68
69/// The `hashbrown` version used in this crate's public API (quorums are
70/// `hashbrown::HashSet`s). Use it to build sets that are passed in.
71pub use hashbrown;
72
73pub use distribution::Distribution;
74pub use error::Error;
75pub use expr::{choose, majority, And, Choose, Element, Expr, Node, Or};
76pub use quorum_system::{
77 Objective, Quorum, QuorumSystem, Strategy, StrategyLimits,
78};
79pub use search::{search, SearchConfig, SearchResult};
80
81// Compile and run the code blocks in the README and the user guide.
82#[cfg(doctest)]
83mod doc_tests {
84 #[doc = include_str!("../README.md")]
85 struct Readme;
86 #[doc = include_str!("../docs/src/quick-start.md")]
87 struct QuickStart;
88 #[doc = include_str!("../docs/src/guide.md")]
89 struct Guide;
90}