A high performance concurrent caching library for Rust
[!NOTE] If you have any questions about Moka's APIs or internal design, you can ask the AI chatbot at DeepWiki in a natural language: https://deepwiki.com/moka-rs/moka
[!NOTE]
v0.12.0had major breaking changes on the API and internal behavior. Please read the MIGRATION-GUIDE.md for the details.
Moka is a fast, concurrent cache library for Rust. Moka is inspired by the Caffeine library for Java.
Moka provides cache implementations on top of hash maps. They support full concurrency of retrievals and a high expected concurrency for updates.
All caches perform a best-effort bounding of a hash map using an entry replacement algorithm to determine which entries to evict when the capacity is exceeded.
Moka provides a rich and flexible feature set while maintaining high hit ratio and a high level of concurrency for concurrent access.
No cache implementation is perfect for every use cases. Moka is a complex software and can be overkill for your use case. Sometimes simpler caches like Mini Moka or Quick Cache might be a better fit.
The following table shows the trade-offs between the different cache implementations:
| Feature | Moka v0.12 | Mini Moka v0.10 | Quick Cache v0.6 |
|---|---|---|---|
| Thread-safe, sync cache | ✅ | ✅ | ✅ |
| Thread-safe, async cache | ✅ | ❌ | ✅ |
| Non-concurrent cache | ❌ | ✅ | ✅ |
| Bounded by the maximum number of entries | ✅ | ✅ | ✅ |
| Bounded by the total weighted size of entries | ✅ | ✅ | ✅ |
| Near optimal hit ratio | ✅ TinyLFU | ✅ TinyLFU | ✅ S3-FIFO |
Per-key, atomic insertion. (e.g. get_with method) |
✅ | ❌ | ✅ |
| Cache-level expiration policies (time-to-live and time-to-idle) | ✅ | ✅ | ❌ |
| Per-entry variable expiration | ✅ | ❌ | ❌ |
| Eviction listener | ✅ | ❌ | ✅ (via lifecycle hook) |
| Lock-free, concurrent iterator | ✅ | ❌ | ❌ |
| Lock-per-shard, concurrent iterator | ❌ | ✅ | ✅ |
| Performance, etc. | Moka v0.12 | Mini Moka v0.10 | Quick Cache v0.6 |
|---|---|---|---|
| Small overhead compared to a concurrent hash table | ❌ | ❌ | ✅ |
| Does not use background threads | ❌ → ✅ Removed from v0.12 | ✅ | ✅ |
| Small dependency tree | ❌ | ✅ | ✅ |
Moka is powering production services as well as embedded Linux devices like home routers. Here are some highlights:
[!NOTE]
v0.12.0had major breaking changes on the API and internal behavior. Please read the MIGRATION-GUIDE.md for the details.
getMoka should work on most 64-bit and 32-bit platforms if Rust std library is
available with threading support. However, WebAssembly (Wasm) and WASI targets are
not supported.
The following platforms are tested on CI:
The following platforms are not tested on CI but should work:
The following platforms are not supported:
nostd environment (platforms without std library) are not supported.To add Moka to your dependencies, run cargo add as the followings:
# To use the synchronous cache:
cargo add moka --features sync
# To use the asynchronous cache:
cargo add moka --features futureIf you want to use the cache under an async runtime such as tokio or async-std, you should specify the future feature. Otherwise, specify the sync feature.
The thread-safe, synchronous caches are defined in the sync module.
Cache entries are manually added using insert or get_with method, and
are stored in the cache until either evicted or manually invalidated.
Here's an example of reading and updating a cache by using multiple threads:
…You can try the synchronous example by cloning the repository and running the following cargo instruction:
$ cargo run --example sync_exampleIf you want to atomically initialize and insert a value when the key is not present,
you might want to check the document for other insertion methods
get_with and try_get_with.
The asynchronous (futures aware) cache is defined in the future module.
It works with asynchronous runtime such as Tokio,
async-std or actix-rt.
To use the asynchronous cache, enable a crate feature called "future".
Cache entries are manually added using an insert method, and are stored in the cache until either evicted or manually invalidated:
async fn or async block), use insert or invalidate
method for updating the cache and await them.blocking method to access blocking version of
insert or invalidate methods.Here is a similar program to the previous example, but using asynchronous cache with Tokio runtime:
…You can try the asynchronous example by cloning the repository and running the following cargo instruction:
$ cargo run --example async_example --features futureIf you want to atomically initialize and insert a value when the key is not present,
you might want to check the document for other insertion methods
get_with and try_get_with.
getFor the concurrent caches (sync and future caches), the return type of get
method is Option<V> instead of Option<&V>, where V is the value type. Every
time get is called for an existing key, it creates a clone of the stored value V
and returns it. This is because the Cache allows concurrent updates from threads so
a value stored in the cache can be dropped or replaced at any time by any other
thread. get cannot return a reference &V as it is impossible to guarantee the
value outlives the reference.
If you want to store values that will be expensive to clone, wrap them by
std::sync::Arc before storing in a cache. Arc is a thread-safe
reference-counted pointer and its clone() method is cheap.
use std::sync::Arc;
let key = ...
let large_value = vec![0u8; 2 * 1024 * 1024]; // 2 MiB
// When insert, wrap the large_value by Arc.
cache.insert(key.clone(), Arc::new(large_value));
// get() will call Arc::clone() on the stored value, which is cheap.
cache.get(&key);If different cache entries have different "weights" — e.g. each entry has
different memory footprints — you can specify a weigher closure at the cache
creation time. The closure should return a weighted size (relative size) of an entry
in u32, and the cache will evict entries when the total weighted size exceeds its
max_capacity.
use moka::sync::Cache;
fn main() {
let cache = Cache::builder()
// A weigher closure takes &K and &V and returns a u32 representing the
// relative size of the entry. Here, we use tNo open issues yet, or sync has not completed.