# Moka Snapshot Restore Feature: Architecture and Implementation Guide

>  Explore the Moka snapshot restore feature architecture and implementation for robust cache persistence. Learn how to overcome complex challenges like timer wheel consistency and shard hashing.

- Repository: [moka-rs/moka](https://github.com/moka-rs/moka)
- Tags: architecture
- Published: 2026-03-07

---

**The Moka cache library does not currently provide a built-in snapshot/restore capability, as implementing full cache persistence requires solving complex challenges around timer wheel consistency, non-deterministic shard hashing, and serialization bounds that are not yet present in the v0.12.14 codebase.**

The `moka-rs/moka` repository provides high-performance concurrent caching for Rust applications through both synchronous (`moka::sync::Cache`) and asynchronous (`moka::future::Cache`) APIs. While users can obtain per-entry views via the `Entry<K, V>` type, a comprehensive Moka snapshot restore feature capable of persisting entire cache state across process restarts remains listed as future work in the project roadmap.

## Understanding Moka's Internal Cache Structure

Moka stores cached data in a **sharded hash map** (`cht::SegmentedHashMap`), where each shard contains `ValueEntry` objects wrapped in lightweight `MiniArc` reference counters. According to the source in [`src/common/concurrent/entry_info.rs`](https://github.com/moka-rs/moka/blob/main/src/common/concurrent/entry_info.rs), each `ValueEntry` maintains not only the value itself but also expiration metadata including TTL, idle timeout, and access counters.

The public-facing snapshot view is the `Entry<K, V>` type defined in [`src/common/entry.rs`](https://github.com/moka-rs/moka/blob/main/src/common/entry.rs). This struct provides a lightweight clone of a single key-value pair along with two boolean flags: `is_fresh` indicating a newly computed value, and `is_old_value_replaced` signaling an overwrite. While `Entry` serves as an effective snapshot of individual cache items, it contains no internal pointers to the cache's underlying storage, making it suitable for single-key observations but insufficient for bulk persistence.

## Why a Full Snapshot/Restore Feature is Non-Trivial

Implementing a complete Moka snapshot restore feature requires addressing several architectural complexities that extend beyond simple serialization:

**Timer Wheel Consistency.** The expiration system relies on a timer wheel implementation in [`src/common/timer_wheel.rs`](https://github.com/moka-rs/moka/blob/main/src/common/timer_wheel.rs) (see line 228) that maintains rotating buckets of entries scheduled for expiration. Capturing a consistent snapshot requires freezing this wheel to prevent "time-of-check / time-of-use" race conditions, where an entry might expire between the snapshot initiation and completion.

**Non-Deterministic Sharding.** The `cht::SegmentedHashMap` uses per-process hash seeds that make shard placement non-deterministic. Restoring a snapshot on a different process would require either recomputing hashes for each key or storing original shard indices, complicating cross-platform persistence.

**Serialization Constraints.** Current Moka implementations do not expose `Serialize` or `DeserializeOwned` trait bounds on cache keys (`K`) or values (`V`). Adding snapshot support would necessitate either:
- Requiring `K: Serialize + DeserializeOwned` and `V: Serialize + DeserializeOwned` bounds
- Implementing a custom serde-like trait system
- Handling the conversion of `Instant` types to serializable `u64` timestamps representing remaining TTL

## Proposed API Design for Snapshot/Restore

Based on the architecture analysis, a future Moka snapshot restore feature would likely expose functions similar to this sketch:

```rust
use moka::sync::Cache;
use serde::{Serialize, Deserialize};
use std::io::{Write, Read};

/// Serializes the entire cache state to a writer.
pub fn snapshot<W: Write, K, V>(
    cache: &Cache<K, V>, 
    writer: W
) -> Result<(), std::io::Error>
where
    K: Serialize + Eq + std::hash::Hash,
    V: Serialize,
{
    // 1. Freeze timer wheel at current epoch
    // 2. Iterate all shards in SegmentedHashMap
    // 3. Serialize entries with remaining TTL metadata
    unimplemented!()
}

/// Restores a cache from a serialized snapshot.
pub fn restore<R: Read, K, V>(
    reader: R
) -> Result<Cache<K, V>, std::io::Error>
where
    K: Deserialize<'static> + Eq + std::hash::Hash,
    V: Deserialize<'static>,
{
    // 1. Deserialize CacheSnapshot structure
    // 2. Rebuild Cache with original capacity
    // 3. Insert entries using Cache::insert_with_expiry
    // 4. Reinitialize timer wheel with stored epoch
    unimplemented!()
}

```

The actual implementation would reside in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) for the synchronous variant and [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) for the async version, wrapping the existing `entry`, `insert`, and iterator methods that currently operate around line 800 of the synchronous implementation.

## Implementation Considerations and Challenges

**Consistent State Capture.** Lock-free reads across the sharded map are efficient, but obtaining a coherent view requires either pausing the timer wheel or capturing a single epoch and filtering entries that expire during serialization. The existing code in [`timer_wheel.rs`](https://github.com/moka-rs/moka/blob/main/timer_wheel.rs) already snapshots single expiration states, which could be repurposed for bulk operations.

**Feature Flag Protection.** Due to the dependency on `serde` and the binary size implications, snapshot functionality would likely be gated behind an optional `snapshot` feature flag in [`Cargo.toml`](https://github.com/moka-rs/moka/blob/main/Cargo.toml), keeping the core library lightweight for users who do not require persistence.

**Metadata Preservation.** Beyond simple key-value pairs, a complete snapshot must preserve:
- Per-entry weights (for weighted caches)
- Remaining TTL and idle timeouts
- Access counters (for LRU/LFU policies)
- Timer wheel epoch state

## Current Status and Workarounds

As documented in [`README.md`](https://github.com/moka-rs/moka/blob/main/README.md) at line 585 and tracked in issue #314, the Moka snapshot restore feature remains on the project roadmap without a concrete implementation in the current release. Users requiring persistence today must manually iterate over cache entries and rebuild the cache on restart, though this approach cannot perfectly reconstruct internal state like hit rates or precise expiration timings.

```rust
use moka::sync::Cache;
use std::collections::HashMap;

// Manual workaround for cache persistence
let cache: Cache<String, u64> = Cache::new(10_000);

// Simulating snapshot
let mut snapshot_data = HashMap::new();
for (key, entry) in cache.iter() {
    if let Some(value) = entry.value() {
        snapshot_data.insert(key.clone(), *value);
    }
}

// Simulating restore
let restored: Cache<String, u64> = Cache::new(10_000);
for (key, value) in snapshot_data {
    restored.insert(key, value).await;
}

```

## Summary

- Moka uses a sharded `SegmentedHashMap` storing `ValueEntry` objects wrapped in `MiniArc`, with per-entry snapshots available via the `Entry<K, V>` type in [`src/common/entry.rs`](https://github.com/moka-rs/moka/blob/main/src/common/entry.rs).
- A full cache snapshot requires freezing the timer wheel ([`src/common/timer_wheel.rs`](https://github.com/moka-rs/moka/blob/main/src/common/timer_wheel.rs) line 228) to prevent expiration races during serialization.
- Non-deterministic hash seeds in the shard layout complicate cross-process restoration, requiring either hash recomputation or index storage.
- Implementation would require `serde` trait bounds on keys and values, likely behind an optional Cargo feature flag.
- The feature is acknowledged in the README (line 585) and tracked in issue #314, but no public API exists in v0.12.14.

## Frequently Asked Questions

### Does Moka currently support snapshot and restore functionality?

No, as of version 0.12.14, Moka does not provide built-in snapshot or restore capabilities. The repository lists this as a future enhancement, and users must implement manual serialization by iterating over `Cache::iter()` and rebuilding the cache on startup, though this loses precise expiration metadata and internal statistics.

### Why can't Moka simply serialize its internal hash map?

The `cht::SegmentedHashMap` uses non-deterministic hash seeds that vary per process, making raw shard indices non-portable. Additionally, the timer wheel in [`src/common/timer_wheel.rs`](https://github.com/moka-rs/moka/blob/main/src/common/timer_wheel.rs) maintains expiration state through rotating buckets that must be captured atomically to prevent entries from expiring mid-serialization, requiring careful coordination with the expiration system.

### What serialization traits would Moka require for snapshot support?

A snapshot feature would require `K: Serialize + DeserializeOwned` and `V: Serialize + DeserializeOwned` bounds (or equivalent custom traits), plus the ability to convert `Instant` deadlines to serializable `u64` timestamps representing remaining TTL. This would likely be gated behind an optional `snapshot` feature in [`Cargo.toml`](https://github.com/moka-rs/moka/blob/main/Cargo.toml) to avoid forcing serde dependencies on all users.

### How does the `Entry<K, V>` type relate to cache snapshots?

The `Entry<K, V>` type defined in [`src/common/entry.rs`](https://github.com/moka-rs/moka/blob/main/src/common/entry.rs) provides a single-entry snapshot containing the value and metadata flags like `is_fresh` and `is_old_value_replaced`. While useful for observing individual cache operations, it lacks the expiration timing and weight data necessary for reconstructing a complete cache state, making it insufficient for full persistence without additional API extensions.