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.

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.

ModulePurpose
keyOrdered key encoding with LexicographicKey and KeyEncoder.
modelSchema-rich time-series types and location resolution.
shardRange assignment, load scoring, lookup, and splitting.
hintCompact field-hint indexes for query fanout pruning.
distributionBucket layouts, distributions, exemplars, percentiles, and deltas.
aggregateDistributed aggregation traits and basic reducers.
collectBucketed delta aggregation with admission windows.
ingestWrite envelopes, stale-write policy, and range routing.
queryLogical plans, fanout plans, and pushdown fragments.
reliabilityReplica selection and partial-result health metadata.
standingPeriodic standing queries and stable evaluator sharding.
arrowOptional Apache Arrow batch exporters.
compressedOptional Roaring-backed numeric field-hint index for dense set operations.
wireOptional 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