Mini Moka

repository·v0.11.x·Indexed 41 days ago

https://github.com/moka-rs/mini-moka

A fast, concurrent, in-memory cache library for Rust and a lightweight edition of Moka. It provides high-concurrency cache implementations inspired by Java's Caffeine, featuring both thread-safe (sync::Cache) and single-threaded (unsync::Cache) versions. Supports bounding by entry count or weighted size, expiration policies including Time to Live (TTL) and Time to Idle (TTI), and LFU/LRU eviction algorithms. Version 0.11.0.

Tokens
3.7K
Snippets
12
Records
16
Agent score
51%

What's inside Mini Moka

  1. Avoid expensive clones in `get` using `Arc`

    v0.11.x

    Because mini_moka::sync::Cache::get returns a clone of the value (Option<V>) rather than a reference (Option<&V>), retrieving large or expensive-to-clone values can impact performance.

    To mitigate this, wrap your values in std::sync::Arc. Cloning an Arc is a cheap operation that only increments a reference count, rather than copying the underlying data.

    use std::sync::Arc;
    use mini_moka::sync::Cache;
    
    let cache: Cache<usize, Arc<Vec<u8>>> = Cache::new(100);
    let large_value = vec![0u8; 2 * 1024 * 1024]; // 2 MiB
    
    // Wrap in Arc before inserting
    cache.insert(1, Arc::new(large_value));
    
    // get() will call Arc::clone(), which is very fast
    let retrieved = cache.get(&1);
  2. Use Mini Moka for concurrent or single-threaded caching

    v0.11.x

    Mini Moka provides two primary cache implementations depending on your concurrency requirements:

    1. Thread-safe, highly concurrent cache: Use the sync::Cache module when you need to share the cache across multiple threads. This requires the sync feature to be enabled.
    2. Non-thread-safe, single-threaded cache: Use the unsync::Cache module for applications running in a single thread where thread-safety is not required.

    Both implementations support bounding (by entry count or weighted size), expiration policies (TTL/TTI), and eviction algorithms inspired by Caffeine (LFU admission and LRU eviction).

    // Example of selecting the appropriate module:
    // For multi-threaded use (requires 'sync' feature):
    // use mini_moka::sync::Cache;
    
    // For single-threaded use:
    use mini_moka::unsync::Cache;
  3. Configure cache expiration and eviction policies

    v0.11.x

    Mini Moka uses the Policy abstraction to manage how entries are admitted, evicted, and expired.

    Key capabilities include:

    • Bounding: Limit the cache by the maximum number of entries or by the total weighted size of entries (Size-aware eviction).
    • Expiration:
      • Time to Live (TTL): Evict entries after a certain duration since creation.
      • Time to Idle (TTI): Evict entries after a certain duration since their last access.
    • Eviction Algorithms: Uses a combination of Least Frequently Used (LFU) for admission control and Least Recently Used (LRU) for eviction.
  4. Configure a cache using CacheBuilder

    v0.11.x

    Use CacheBuilder to configure and instantiate a Cache. You can set capacity limits, expiration policies (TTL and TTI), and custom weighting for entries.

    Expiration Policies

    • Time to Live (TTL): An entry expires after a fixed duration since it was first inserted.
    • Time to Idle (TTI): An entry expires after a duration of inactivity (no get or insert calls) since its last access.

    Important Constraints

    • Panic on long durations: Calling .build() or .build_with_hasher() will panic if time_to_live or time_to_idle is set to a duration longer than 1000 years. This prevents overflow during expiration calculations.
    • Capacity: You can specify max_capacity (total size) and initial_capacity (starting number of entries).
    use mini_moka::sync::Cache;
    use std::time::Duration;
    
    let cache = Cache::builder()
        // Max 10,000 entries
        .max_capacity(10_000)
        // Time to live (TTL): 30 minutes
        .time_to_live(Duration::from_secs(30 * 60))
        // Time to idle (TTI):  5 minutes
        .time_to_idle(Duration::from_secs( 5 * 60))
        // Create the cache.
        .build();
    
    // This entry will expire after 5 minutes (TTI) if there is no get().
    cache.insert(0, "zero");
    
    // This get() will extend the entry life for another 5 minutes.
    cache.get(&0);
    
    // Even though we keep calling get(), the entry will expire
    // after 30 minutes (TTL) from the insert().
  5. Use the synchronous thread-safe Cache

    v0.11.x

    The mini_moka::sync::Cache provides a thread-safe, highly concurrent in-memory cache.

    Key behaviors:

    • Concurrency: Supports full concurrency for retrievals and high concurrency for updates. It can be shared across threads by calling .clone(), which is a cheap operation.
    • Manual Management: Entries are added via .insert(key, value) and remain until evicted (based on capacity/policies) or manually removed via .invalidate(&key).
    • Retrieval: The .get(&key) method returns Option<V>. Note that it returns a clone of the value, not a reference, to ensure memory safety during concurrent updates.
    use mini_moka::sync::Cache;
    
    // Create a cache that can store up to 10,000 entries.
    let cache = Cache::new(10_000);
    
    // Insert a value
    cache.insert(1, "value".to_string());
    
    // Get a value (returns a clone)
    let val = cache.get(&1);
    
    // Manually invalidate
    cache.invalidate(&1);
  6. Configure Expiration Policies (TTL and TTI)

    v0.11.x

    Mini Moka supports two types of expiration policies via the CacheBuilder:

    1. Time to Live (TTL): An entry expires after a fixed duration has passed since it was inserted.
    2. Time to Idle (TTI): An entry expires after a fixed duration has passed since it was last accessed (via get) or inserted.

    Note: The builder will panic if you attempt to set a duration longer than 1000 years to prevent overflow errors during expiration calculations.

    use mini_moka::sync::Cache;
    use std::time::Duration;
    
    let cache = Cache::builder()
        // TTL: Expires 30 minutes after insertion
        .time_to_live(Duration::from_secs(30 * 60))
        // TTI: Expires 5 minutes after last access/insertion
        .time_to_idle(Duration::from_secs(5 * 60))
        .build();
    
    cache.insert(0, "zero");
    // This get() extends the entry's life for another 5 minutes (TTI)
    cache.get(&0);
  7. Configure Size Aware Eviction

    v0.11.x

    By default, caches are bounded by the maximum number of entries. If your entries have varying memory footprints, you can use a weigher to bound the cache by total weighted size instead.

    Use Cache::builder() to configure a weigher closure. The closure receives the key and value and must return a u32 representing the relative size. The cache will then evict entries when the sum of these weights exceeds max_capacity.

    use mini_moka::sync::Cache;
    
    let cache = Cache::builder()
        // The weigher returns the byte length of the String as the weight
        .weigher(|_key, value: &String| -> u32 {
            value.len().try_into().unwrap_or(u32::MAX)
        })
        // The cache will hold up to 32MiB of total weight
        .max_capacity(32 * 1024 * 1024)
        .build();
    
    cache.insert(0, "zero".to_string());
  8. Use ConcurrentCacheExt to sync the cache during testing

    v0.11.x

    The ConcurrentCacheExt trait provides a sync() method for concurrent cache implementations. This method performs any pending maintenance operations (such as eviction or expiration) that the cache might have deferred. It is primarily intended for use in testing environments to ensure the cache state is up-to-date before assertions.

    use mini_moka::ConcurrentCacheExt;
    
    // Assuming `cache` is an instance of a concurrent cache
    cache.sync();
  9. Build a cache with a custom hasher

    v0.11.x

    If you need to use a specific hashing algorithm (instead of the default RandomState), use the build_with_hasher method on the CacheBuilder.

    // Assuming S implements BuildHasher + Clone + Send + Sync + 'static
    let cache = Cache::builder()
        .max_capacity(100)
        .build_with_hasher(my_custom_hasher);
  10. Use the non-thread-safe Cache implementation

    v0.11.x

    The unsync module provides a non-thread-safe, single-threaded cache implementation built upon std::collections::HashMap. This is suitable for scenarios where you do not need to share the cache across multiple threads and want to avoid the overhead of synchronization primitives like Mutex or RwLock.

    use mini_moka::unsync::{Cache, CacheBuilder};
    
    // Example of building and using an unsync cache
    let cache = CacheBuilder::new()
        .build();
    
    cache.insert("key", "value");
    let val = cache.get("key");
  11. Configure an unsync Cache using CacheBuilder

    v0.11.x

    Use CacheBuilder to configure and instantiate a non-thread-safe Cache. You can set capacity limits, expiration policies (TTL and TTI), and custom weight calculation.

    Configuration Options

    • max_capacity(u64): Sets the maximum capacity of the cache. When using a weigher, this capacity refers to the total weight rather than the number of entries.
    • initial_capacity(usize): Sets the initial number of entries in the cache.
    • weigher(FnMut(&K, &V) -> u32): Sets a closure to calculate the relative size (weight) of an entry. This is used for size-aware eviction.
    • time_to_live(Duration): Sets the Time To Live (TTL). An entry expires after this duration has passed since it was last inserted.
    • time_to_idle(Duration): Sets the Time To Idle (TTI). An entry expires after this duration has passed since it was last accessed via get or insert.

    Important Notes

    • Panic Condition: Calling .build() or .build_with_hasher() will panic if time_to_live or time_to_idle is configured to be longer than 1000 years. This prevents overflow during expiration calculations.
    • Default Hasher: Using .build() uses std::collections::hash_map::RandomState as the hasher.
    use mini_moka::unsync::Cache;
    use std::time::Duration;
    
    let mut cache = Cache::builder()
        // Max 10,000 elements
        .max_capacity(10_000)
        // Time to live (TTL): 30 minutes
        .time_to_live(Duration::from_secs(30 * 60))
        // Time to idle (TTI):  5 minutes
        .time_to_idle(Duration::from_secs( 5 * 60))
        // Create the cache.
        .build();
    
    // This entry will expire after 5 minutes (TTI) if there is no get().
    cache.insert(0, "zero");
    
    // This get() will extend the entry life for another 5 minutes.
    cache.get(&0);
    
    // Even though we keep calling get(), the entry will expire
    // after 30 minutes (TTL) from the insert().