accelerator 0.1.0

MVP multi-level cache runtime with singleflight load de-duplication
Documentation
# accelerator - Multi-Level Cache Runtime for Rust

[English]README.md | [็ฎ€ไฝ“ไธญๆ–‡]README.zh-CN.md

Accelerator is a pluggable, async-first cache runtime for high-concurrency Rust services.
It provides a unified API over local cache (L1) and remote cache (L2), with miss load
(source-of-truth load), batch loading, invalidation broadcast, and built-in observability.

## ๐Ÿš€ Features

- ๐Ÿงญ Multi-level modes: `Local`, `Remote`, `Both`
- ๐Ÿงฑ Default backends: `moka` (L1) + `redis` (L2)
- ๐Ÿ”Œ Pluggable backends via traits:
  - `LocalBackend<V>`
  - `RemoteBackend<V>`
  - `InvalidationSubscriber`
- ๐Ÿฑ Unified runtime APIs:
  - `get`, `mget`, `set`, `mset`, `del`, `mdel`, `warmup`
- ๐Ÿ“ฅ Loader contracts:
  - single-key: `Loader<K, V>`
  - batch: `MLoader<K, V>`
- ๐Ÿ›ก๏ธ Miss handling:
  - single-key miss can use singleflight dedup (`penetration_protect`)
  - batch miss (`mget`) uses `MLoader::mload` directly
- ๐Ÿ”„ Resilience and stability:
  - negative cache (`cache_null_value`, `null_ttl`)
  - TTL jitter (`ttl_jitter_ratio`)
  - refresh-ahead (`refresh_ahead`)
  - stale fallback (`stale_on_error`)
- ๐Ÿ“ก Cross-instance local cache consistency:
  - Redis Pub/Sub invalidation broadcast
- ๐Ÿ‘€ Observability:
  - runtime counters (`metrics_snapshot`)
  - diagnostic state (`diagnostic_snapshot`)
  - OTel-friendly metric points (`otel_metric_points`)
  - tracing spans on core paths
- ๐Ÿช„ Procedural macros:
  - `cacheable`, `cacheable_batch`, `cache_put`, `cache_evict`, `cache_evict_batch`

## ๐Ÿ“‹ Table of Contents

- [๐Ÿ“ฆ Installation]#installation
- [๐Ÿค  Quick Start]#quick-start
- [๐Ÿฑ API Overview]#api-overview
- [๐Ÿงฉ Macro Usage]#macro-usage
- [๐Ÿ—๏ธ Backend Extension]#backend-extension
- [๐Ÿช„ Examples]#examples
- [๐ŸŽ๏ธ Benchmark and Regression Gate]#benchmark-and-regression-gate
- [๐Ÿงช Integration Tests]#integration-tests
- [๐Ÿงฐ Local Full Stack]#local-full-stack
- [๐Ÿ“š Documentation]#documentation

## ๐Ÿ“ฆ Installation

Use as a local workspace crate:

```toml
[dependencies]
accelerator = { path = "/path/to/accelerator" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

If you publish/use from registry, replace with the registry version.

## ๐Ÿค  Quick Start

### Local-only cache (L1 = moka)

```rust
use std::time::Duration;

use accelerator::builder::LevelCacheBuilder;
use accelerator::config::CacheMode;
use accelerator::{ReadOptions, local};

#[tokio::main]
async fn main() -> accelerator::CacheResult<()> {
    let local_backend = local::moka::<String>().max_capacity(100_000).build()?;

    let cache = LevelCacheBuilder::<u64, String>::new()
        .area("user")
        .mode(CacheMode::Local)
        .local(local_backend)
        .local_ttl(Duration::from_secs(60))
        .null_ttl(Duration::from_secs(10))
        .loader_fn(|id: u64| async move { Ok(Some(format!("user-{id}"))) })
        .build()?;

    let v = cache.get(&42, &ReadOptions::default()).await?;
    assert_eq!(v, Some("user-42".to_string()));
    Ok(())
}
```

### Two-level cache (L1 + L2)

```rust
use std::time::Duration;

use accelerator::builder::LevelCacheBuilder;
use accelerator::config::CacheMode;
use accelerator::{local, remote};

let local_backend = local::moka::<String>().max_capacity(100_000).build()?;
let remote_backend = remote::redis::<String>()
    .url("redis://127.0.0.1:6379")
    .key_prefix("demo")
    .build()?;

let cache = LevelCacheBuilder::<u64, String>::new()
    .area("user")
    .mode(CacheMode::Both)
    .local(local_backend)
    .remote(remote_backend)
    .local_ttl(Duration::from_secs(60))
    .remote_ttl(Duration::from_secs(300))
    .broadcast_invalidation(true)
    .build()?;
```

## ๐Ÿฑ API Overview

Core runtime:

- `LevelCache<K, V, LD, LB, RB>`
- `ReadOptions { allow_stale, disable_load }`
- `CacheMode::{Local, Remote, Both}`

Main methods:

- Read: `get`, `mget`
- Write: `set`, `mset`
- Invalidate: `del`, `mdel`
- Warmup: `warmup`

Diagnostics and metrics:

- `metrics_snapshot() -> CacheMetricsSnapshot`
- `diagnostic_snapshot() -> CacheDiagnosticSnapshot`
- `otel_metric_points() -> Vec<OtelMetricPoint>`

Loader traits:

- `Loader<K, V>::load(&K) -> Future<CacheResult<Option<V>>>`
- `MLoader<K, V>::mload(&[K]) -> Future<CacheResult<HashMap<K, Option<V>>>>`

## ๐Ÿงฉ Macro Usage

Import macros from:

```rust
use accelerator::macros::{cache_evict, cache_evict_batch, cache_put, cacheable, cacheable_batch};
```

Macro behavior and constraints:

- `#[cacheable(...)]`: cache-first read, miss executes function body, then `set` write-back.
- `#[cacheable_batch(...)]`: `mget` first, loads misses only, then `mset` write-back.
- `#[cache_put(...)]`: executes function first, then `set` to cache on success.
- `#[cache_evict(...)]` / `#[cache_evict_batch(...)]`: invalidates after success by default (`before = false`).
- Macros only support `async fn` methods on `impl` blocks (`&self` / `&mut self` receiver).
- `on_cache_error` supports `"ignore"` (default) or `"propagate"`.

Minimal single-key example:

```rust
use accelerator::macros::{cache_evict, cache_put, cacheable};

impl UserService {
    #[cacheable(cache = self.cache, key = user_id, cache_none = true, on_cache_error = "ignore")]
    async fn get_user(&self, user_id: u64) -> accelerator::CacheResult<Option<User>> {
        self.repo.find_by_id(user_id).await
    }

    #[cache_put(cache = self.cache, key = user.id, value = Some(user.clone()))]
    async fn save_user(&self, user: User) -> accelerator::CacheResult<()> {
        self.repo.upsert(user.clone()).await
    }

    #[cache_evict(cache = self.cache, key = user_id, before = false)]
    async fn delete_user(&self, user_id: u64) -> accelerator::CacheResult<()> {
        self.repo.delete(user_id).await
    }
}
```

Minimal batch example:

```rust
use accelerator::macros::{cache_evict_batch, cacheable_batch};
use std::collections::HashMap;

impl UserService {
    #[cacheable_batch(cache = self.cache, keys = user_ids)]
    async fn batch_get(&self, user_ids: Vec<u64>) -> accelerator::CacheResult<HashMap<u64, Option<User>>> {
        self.repo.batch_find(&user_ids).await
    }

    #[cache_evict_batch(cache = self.cache, keys = user_ids, before = false)]
    async fn batch_delete(&self, user_ids: Vec<u64>) -> accelerator::CacheResult<()> {
        self.repo.batch_delete(&user_ids).await
    }
}
```

Runnable references:

- `examples/macro_best_practice.rs`
- `examples/macro_batch_best_practice.rs`

## ๐Ÿ—๏ธ Backend Extension

To replace default backends:

1. Implement `LocalBackend<V>` for your local cache.
2. Implement `RemoteBackend<V>` and `InvalidationSubscriber` for your remote cache.
3. Plug them into `LevelCacheBuilder::local(...)` and `LevelCacheBuilder::remote(...)`.

The runtime uses static dispatch (generics), not runtime `dyn` objects.

## ๐Ÿช„ Examples

See `examples/`:

- `fixed_backend_best_practice.rs` (moka + redis)
- `macro_best_practice.rs` (macro-based single-key flow)
- `macro_batch_best_practice.rs` (macro-based batch flow)
- `clickstack_otlp.rs` (optional OTLP bootstrap, feature `otlp`)

Run:

```bash
cargo run --example fixed_backend_best_practice
```

If Redis is unavailable at `ACCELERATOR_REDIS_URL` (default `redis://127.0.0.1:6379`),
the example exits gracefully.

## ๐ŸŽ๏ธ Benchmark and Regression Gate

One-click script:

```bash
./scripts/bench.sh
./scripts/bench.sh bench-local --runs 3 --sample-size 60
./scripts/bench.sh bench-redis --runs 3 --sample-size 60 --redis-url redis://127.0.0.1:6379
./scripts/bench.sh regression --threshold 0.15
```

Raw commands:

```bash
cargo bench --bench cache_path_bench -- --sample-size=60
ACCELERATOR_BENCH_REDIS_URL=redis://127.0.0.1:0 cargo bench --bench cache_path_bench -- --sample-size=60
cargo run --bin export_bench_baseline --
cargo run --bin check_bench_regression -- --threshold 0.15
```

Detailed playbook: `docs/performance-engineering-playbook.md`

## ๐Ÿงช Integration Tests

Redis integration tests are in `tests/redis_integration.rs`.

- They run with `cargo test`.
- If Redis is unavailable, tests skip gracefully where designed.
- Override endpoint with `ACCELERATOR_TEST_REDIS_URL`.

## ๐Ÿงฐ Local Full Stack

Start local stack:

```bash
cd scripts
docker compose up -d
```

Run end-to-end tests:

```bash
cargo test --test redis_integration
cargo test --test stack_integration
```

`stack_integration` uses real `sqlx + Postgres` loader flow.

ClickStack UI: `http://127.0.0.1:8080`  
OTLP ingest ports: `4317` and `4318`

## ๐Ÿ“š Documentation

English is the default documentation language. Chinese versions are maintained under `docs/zh/`.

| Topic | English | ไธญๆ–‡๏ผˆ็ฎ€ไฝ“๏ผ‰ |
| --- | --- | --- |
| README | [`README.md`]README.md | [`README.zh-CN.md`]README.zh-CN.md |
| Terminology Baseline | [`docs/terminology.md`]docs/terminology.md | [`docs/zh/terminology.zh-CN.md`]docs/zh/terminology.zh-CN.md |
| Capability Model | [`docs/multi-level-cache-capability-model.md`]docs/multi-level-cache-capability-model.md | [`docs/zh/multi-level-cache-capability-model.zh-CN.md`]docs/zh/multi-level-cache-capability-model.zh-CN.md |
| Performance Playbook | [`docs/performance-engineering-playbook.md`]docs/performance-engineering-playbook.md | [`docs/zh/performance-engineering-playbook.zh-CN.md`]docs/zh/performance-engineering-playbook.zh-CN.md |
| Cache Ops Runbook | [`docs/cache-ops-runbook.md`]docs/cache-ops-runbook.md | [`docs/zh/cache-ops-runbook.zh-CN.md`]docs/zh/cache-ops-runbook.zh-CN.md |
| Local Stack Guide | [`docs/local-stack-integration.md`]docs/local-stack-integration.md | [`docs/zh/local-stack-integration.zh-CN.md`]docs/zh/local-stack-integration.zh-CN.md |
| Code Flattening Guideline | [`docs/code-flattening-guideline.md`]docs/code-flattening-guideline.md | [`docs/zh/code-flattening-guideline.zh-CN.md`]docs/zh/code-flattening-guideline.zh-CN.md |