starshard

Starshard

Build crates.io docs.rs License Downloads

English 简体中文

Starshard is a high-performance, lazily sharded concurrent HashMap for Rust.

It is designed for real production workloads where you need:

Status

Current crate version: 2.4.0. Release changes are listed in CHANGELOG.md.

Roadmap capabilities shipped through v2.4.0:

Upgrading from 2.3.x: snapshot versions now identify committed data, and invalid replication topologies are rejected. Review the 2.4 upgrade notes before using advanced APIs. Performance improvements are supported by operation/allocation counts; workload-specific throughput and p99 still require measurement.

Installation

[dependencies]
starshard = { version = "2.4.0", features = ["async", "rayon", "serde", "lifecycle", "advanced"] }
# minimal:
# starshard = "2.4.0"

5-Minute Path

  1. Start with default sync map: ShardedHashMap::new(64).
  2. If you are in Tokio runtime, switch to AsyncShardedHashMap.
  3. If snapshots are frequent, try SnapshotMode::Cached first, then SnapshotMode::Cow.
  4. If shard count is user-driven or external-input-driven, use strict constructors (try_with_*).

Migration guide:

Feature Flags

Feature What you get Typical use
async AsyncShardedHashMap (Tokio RwLock) async services and workers
rayon large sync snapshots; parallel IterBuilder filtering with lifecycle large scans and expensive predicates
serde sync serialize/deserialize + async serializable snapshot helper persistence/export
lifecycle per_shard_load, memory_stats, drain, lifecycle structs observability and maintenance
advanced transaction/CAS/replication/diagnostic APIs advanced concurrency and control planes

Quick Start (Sync)

use starshard::ShardedHashMap;

let m: ShardedHashMap<String, i32> = ShardedHashMap::new(64);
m.insert("k1".into(), 10);
assert_eq!(m.get_borrowed("k1"), Some(10));
assert_eq!(m.len(), 1);

Quick Start (Async)

#[cfg(feature = "async")]
#[tokio::main]
async fn main() {
    use starshard::AsyncShardedHashMap;

    let m: AsyncShardedHashMap<String, i32> = AsyncShardedHashMap::new(64);
    m.insert("k1".into(), 10).await;
    assert_eq!(m.get_borrowed("k1").await, Some(10));
}

Common Operations (Cheat Sheet)

Goal Sync API Async API
insert/update insert(k, v) insert(k, v).await
read get(&k) get(&k).await
borrowed-key read get_borrowed(q) get_borrowed(q).await
delete remove(&k) remove(&k).await
entry insert/update entry(k).or_insert_with(f) entry(k).await.or_insert_with(f).await
get or insert get_or_insert_with(k, f) get_or_insert_with(k, f).await
batch insert batch_insert(items) batch_insert(items).await
batch read batch_get(&keys) batch_get(&keys).await
conditional update compute_if_present(&k, f) compute_if_present(&k, f).await
conditional insert compute_if_absent(k, f) compute_if_absent(k, f).await
metrics/introspection shard_stats() / memory_stats() shard_stats().await / memory_stats().await

For borrowed keys, use get_borrowed, contains_borrowed, and remove_borrowed (for example, an &str query against String keys). Existing get, contains, and remove retain their &K signatures. Reads still return cloned values.

Entry-style insert/update:

use starshard::ShardedHashMap;

let map: ShardedHashMap<String, usize> = ShardedHashMap::new(16);
let value = map
    .entry("read-version".to_string())
    .and_modify(|count| *count += 1)
    .or_insert_with(|| 1);
assert_eq!(value, 1);

Atomic get-or-create helper:

use starshard::ShardedHashMap;

let map: ShardedHashMap<String, Vec<u64>> = ShardedHashMap::new(16);
let lane = map.get_or_insert_with("read-version".to_string(), Vec::new);
assert!(lane.is_empty());

Constructor Strategy

Choose by strictness and control level:

Adaptive Rebalance (v2.2.1)

Stop-the-world rebalance

use starshard::{RebalanceOptions, ShardedHashMap};

let m: ShardedHashMap<String, i32> = ShardedHashMap::new(8);
let report = m.rebalance_to(32, RebalanceOptions::default()).unwrap();
assert_eq!(report.from_shards, 8);
assert_eq!(report.to_shards, 32);

Online incremental rebalance

use starshard::ShardedHashMap;

let m: ShardedHashMap<String, i32> = ShardedHashMap::new(8);
m.start_rebalance_online(32).unwrap();

while m.rebalance_status().state == "migrating" {
    m.advance_rebalance(2);
}

assert_eq!(m.rebalance_status().state, "idle");

Semantics:

Snapshot Modes (v2.2.1)

SnapshotMode lets you pick snapshot behavior per workload:

Valid Cached/Cow hits validate the data epoch under a short cache lock, without acquiring routing. Missing or stale snapshots use a separate builder gate and ordered shard read locks. This coalesces concurrent rebuilds while allowing ordinary reads; writers and topology changes wait. Cached reads can finish during topology-only migration; they are not lock-free. Async cache reads preserve Tokio’s cooperative task budget.

iter() returns owned entries and therefore still clones entries from a shared cache, after releasing routing. Replaced cached data is also destroyed outside routing. Use shared_snapshot() to obtain an Arc<Vec<(K, V)>> and read repeated snapshots without copying their entries. Existing snapshot handles remain immutable after later writes. With advanced, cow_snapshot() also exposes a shared view and its data version.

use starshard::{ShardedHashMap, SnapshotMode};

let clone_map: ShardedHashMap<String, i32> =
    ShardedHashMap::with_snapshot_mode(64, SnapshotMode::Clone);
let cached_map: ShardedHashMap<String, i32> =
    ShardedHashMap::with_snapshot_mode(64, SnapshotMode::Cached);
let cow_map: ShardedHashMap<String, i32> =
    ShardedHashMap::with_snapshot_mode(64, SnapshotMode::Cow);

Mode selection guide

Workload profile Recommended mode
high write + low snapshot frequency Clone
medium write + medium snapshot frequency Cached
low write + high snapshot frequency Cow or Cached

Consistency Model

Performance Notes

Serde Semantics

Examples and Benchmarks

Examples:

Benchmark entry:

The benchmark suite pre-generates keys and reuses contention workers. Timed contention batches include barrier synchronization, amortized over 1000 operations per worker. It samples 1/2/4/8/16 threads, a 90% hot-key distribution, 16-byte/4-KiB values, 10%/50% writes, and all snapshot modes without running the full Cartesian product. Owned iteration and shared snapshot handles are measured separately.

cargo bench --bench bench_main -- concurrent_mixed
cargo bench --bench bench_main -- snapshot_modes
cargo bench --bench bench_main -- shared_snapshot
cargo bench --bench bench_main -- concurrent_read_paths
cargo bench --bench bench_main -- concurrent_cached_snapshot
cargo bench --all-features --bench bench_main -- snapshot_rebuild
cargo bench --all-features --bench bench_main -- batch_mutations

Criterion reports batch timing and throughput here. These benchmarks do not measure individual-operation p99 latency, allocations, peak memory, or migration pause bounds; those require separate workload instrumentation.

Validation

Before release or upgrade verification:

cargo fmt --all
cargo test --all-features
cargo check --all-features

Current Limits

License

Dual license:

You may choose either license.

Disclaimer