Moka Snapshot Restore Feature: Architecture and Implementation Guide
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, 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. 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 (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 + DeserializeOwnedandV: Serialize + DeserializeOwnedbounds - Implementing a custom serde-like trait system
- Handling the conversion of
Instanttypes to serializableu64timestamps 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:
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 for the synchronous variant and 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 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, 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 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.
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
SegmentedHashMapstoringValueEntryobjects wrapped inMiniArc, with per-entry snapshots available via theEntry<K, V>type insrc/common/entry.rs. - A full cache snapshot requires freezing the timer wheel (
src/common/timer_wheel.rsline 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
serdetrait 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 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →