Scepter
Scepter is a Rust crate of composable primitives for large-scale time-series routing, indexing, aggregation, and query planning.
It is not a database. It gives database, observability, and stream-processing systems small building blocks that are easy to test in isolation:
- ordered key encoding
- lexicographic range assignment
- field-hint candidate pruning
- distribution-valued metrics
- collection and bucketed aggregation
- ingest routing
- logical query pushdown
- query reliability metadata
- standing-query sharding
The crate has no runtime dependencies and keeps recoverable invalid input in
Result values instead of public API panics.
Links
- Repository: https://github.com/copyleftdev/scepter
- API docs: https://docs.rs/scepter
- Crate: https://crates.io/crates/scepter
Install
Add Scepter to a Rust project:
cargo add scepter
Or edit Cargo.toml:
[dependencies]
scepter = "0.1"
Run the mini engine example from this repository:
cargo run --example mini_engine
Primitives
Scepter is organized by concern.
| Module | Purpose |
|---|---|
key | Ordered key encoding with LexicographicKey and KeyEncoder. |
model | Schema-rich time-series types and location resolution. |
shard | Range assignment, load scoring, lookup, and splitting. |
hint | Compact field-hint indexes for query fanout pruning. |
distribution | Bucket layouts, distributions, exemplars, percentiles, and deltas. |
aggregate | Distributed aggregation traits and basic reducers. |
collect | Bucketed delta aggregation with admission windows. |
ingest | Write envelopes, stale-write policy, and range routing. |
query | Logical plans, fanout plans, and pushdown fragments. |
reliability | Replica selection and partial-result health metadata. |
standing | Periodic standing queries and stable evaluator sharding. |
arrow | Optional Apache Arrow batch exporters. |
compressed | Optional Roaring-backed numeric field-hint index for dense set operations. |
wire | Optional CBOR and Zstd helpers. |
Use the crate root re-exports for common workflows:
use scepter::{FieldHintIndex, FieldPredicate, RangeAssigner};
let mut ranges = RangeAssigner::new();
ranges.assign(b"a".to_vec()..b"m".to_vec(), "leaf-1")?;
let mut index = FieldHintIndex::new();
index.insert_value("ComputeTask", "job", "monarch", "leaf-1");
let candidates = index.candidates(
"ComputeTask",
"job",
&FieldPredicate::Equals("monarch".to_owned()),
);
assert!(candidates.contains("leaf-1"));
Ok::<(), scepter::ShardError>(())
Query Reliability
Distributed queries need two separate reliability decisions:
- which replica should answer each requested range
- whether the combined response is complete or partial
Scepter keeps those decisions explicit.
Replica Resolution
ReplicaResolver groups candidates by target range, removes unavailable
replicas, chooses the strongest primary, and keeps ordered fallbacks.
use scepter::{
ReplicaCandidate, ReplicaQuality, ReplicaResolver, ReplicaState,
};
let resolved = ReplicaResolver::with_max_fallbacks(1).resolve(vec![
ReplicaCandidate::new(
b"a".to_vec()..b"m".to_vec(),
"leaf-a",
ReplicaQuality::new(0, 60, 60, 60, true, ReplicaState::Available),
),
ReplicaCandidate::new(
b"a".to_vec()..b"m".to_vec(),
"leaf-b",
ReplicaQuality::new(0, 60, 55, 60, true, ReplicaState::Recovering),
),
]);
assert_eq!(resolved[0].primary, "leaf-a");
assert_eq!(resolved[0].fallbacks, vec!["leaf-b"]);
Query Health
QueryHealth records child completion and degradation metadata. Issue-only
responses can be complete by count while still partial by quality.
use scepter::{IssueKind, QueryHealth};
let mut health = QueryHealth::with_expected_children(2);
health.record_completed();
health.push_issue("zone-west", IssueKind::PrunedZone, "soft deadline elapsed");
assert!(health.is_partial());
assert_eq!(health.completeness(), 0.5);
Testing
Scepter uses layered testing.
cargo test
Unit tests live beside modules and cover examples at API boundaries.
Property tests live in tests/properties.rs and check invariants over generated
inputs, such as lexicographic ordering, route uniqueness, and bucket behavior.
Mutation-regression tests live in tests/mutation_regression.rs. They capture
edge cases discovered by mutation testing so future changes keep those behaviors
observable.
Run the full mutation suite manually:
cargo mutants --timeout 120 --jobs 2
The generated mutants.out/ directories are local artifacts and are ignored by
git.
Fuzzing uses cargo-fuzz with libFuzzer. Smoke the harness locally:
just fuzz-smoke
Production readiness from a repository checkout requires one billion libFuzzer runs per target:
just fuzz-billion
For targeted campaigns, set FUZZ_TARGETS and FUZZ_RUNS_PER_TARGET before
running scripts/fuzz-campaign.sh.
Local DX
The shortest path is just.
just
Common commands:
just test
just lint
just test-all-features
just book
just book-test
just book-serve
just fuzz-smoke
just fuzz-billion
just verify
just book-serve starts mdBook at http://127.0.0.1:3000.
Without just, use mdBook directly:
mdbook test
mdbook build
mdbook serve --hostname 127.0.0.1 --port 3000