Moka Cache Hit Rate Statistics: Measuring Cache Performance in Real Time

Moka does not expose a built-in hit-rate percentage directly, but provides debug counters and TinyLFU frequency sketch data that you can combine with manual instrumentation to calculate precise cache hit rate statistics.

Moka is a high-performance concurrent cache library for Rust (moka-rs/moka) that uses the TinyLFU admission policy to maintain near-optimal hit ratios. While the library tracks every cache access internally for its admission decisions, retrieving Moka cache hit rate statistics requires enabling the unstable-debug-counters feature and implementing your own hit/miss counters around lookup operations.

How Moka Tracks All Accesses Internally

Moka’s TinyLFU (Tiny Least Frequently Used) admission policy relies on a probabilistic Count-Min Sketch to estimate key popularity. According to the source code in [src/policy.rs](https://github.com/moka-rs/moka/blob/main/src/policy.rs#L76-L78), this design intentionally tracks all accesses—not just keys currently stored in the cache.

The FrequencySketch struct in src/common/frequency_sketch.rs implements this mechanism. Every cache lookup (hit or miss) invokes FrequencySketch::increment, which updates a compact 4-bit counter table. The sketch periodically halves these counters to age out old traffic patterns, ensuring the popularity estimate reflects recent access patterns.

However, the sketch records frequency, not outcome. It cannot distinguish between a hit and a miss because its purpose is admission control (deciding whether to cache a new key), not performance metrics.

Accessing Debug Statistics via the API

To inspect the internal state supporting hit rate analysis, enable the unstable-debug-counters feature in your Cargo.toml:

[dependencies]
moka = { version = "0.12", features = ["future", "unstable-debug-counters"] }

With this feature enabled, the async cache exposes the debug_stats() method, implemented in [src/future/base_cache.rs](https://github.com/moka-rs/moka/blob/main/src/future/base_cache.rs#L139-L141) and publicly accessible via [src/future/cache.rs](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs#L766-L767). This method returns a CacheDebugStats struct defined in [src/common/concurrent/debug_counters.rs](https://github.com/moka-rs/moka/blob/main/src/common/concurrent/debug_counters.rs#L119-L141):

use moka::future::Cache;

#[tokio::main]
async fn main() {
    let cache = Cache::builder()
        .max_capacity(100)
        .build();

    // Perform some cache operations
    cache.insert("key1", "value1").await;
    let _ = cache.get(&"key1").await; // hit
    let _ = cache.get(&"key2").await; // miss

    // Retrieve diagnostic snapshot
    let stats = cache.debug_stats().await;
    println!("Entries: {}", stats.entry_count);
    println!("Weighted size: {}", stats.weighted_size);
    println!("Frequency sketch size (bytes): {}", stats.freq_sketch_size);
    println!("HashMap capacity: {}", stats.hashmap_capacity);
}

These metrics help you understand cache occupancy and the memory overhead of the frequency sketch, but they do not provide the hit count directly.

Calculating Hit Rate Manually

Since the internal FrequencySketch does not differentiate hits from misses, you must instrument your cache lookups to compute a traditional hit rate (hits / (hits + misses)). Wrap the get or try_get_with methods with atomic counters:

use moka::future::Cache;
use std::sync::atomic::{AtomicU64, Ordering};

static HITS: AtomicU64 = AtomicU64::new(0);
static MISSES: AtomicU64 = AtomicU64::new(0);

async fn get_with_metrics<K, V>(cache: &Cache<K, V>, key: &K) -> Option<V>
where
    K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
    V: Clone + Send + Sync + 'static,
{
    match cache.get(key).await {
        Some(value) => {
            HITS.fetch_add(1, Ordering::Relaxed);
            Some(value)
        }
        None => {
            MISSES.fetch_add(1, Ordering::Relaxed);
            None
        }
    }
}

#[tokio::main]
async fn main() {
    let cache = Cache::builder().max_capacity(10).build();
    
    cache.insert(1, "one").await;
    get_with_metrics(&cache, &1).await; // hit
    get_with_metrics(&cache, &2).await; // miss
    
    let hits = HITS.load(Ordering::Relaxed);
    let misses = MISSES.load(Ordering::Relaxed);
    let hit_rate = hits as f64 / (hits + misses).max(1) as f64;
    
    println!("Hit rate: {:.2}% ({}/{})", hit_rate * 100.0, hits, hits + misses);
}

This pattern gives you precise Moka cache hit rate statistics while the internal TinyLFU sketch ensures the cache maintains optimal admission policies based on the same access patterns you are measuring.

Understanding the Frequency Sketch Structure

The FrequencySketch in src/common/frequency_sketch.rs uses a fixed-size table of 4-bit counters (16 possible values per slot) to estimate popularity. When you call debug_stats(), the freq_sketch_size field reveals the memory footprint of this structure.

For advanced debugging, you can inspect the sketch capacity:

use moka::common::frequency_sketch::FrequencySketch;

fn inspect_sketch(sketch: &FrequencySketch) {
    // Returns the number of counter slots in the table
    println!("Sketch table length: {}", sketch.table_len());
}

The sketch automatically handles hash collisions via the Count-Min algorithm and performs periodic aging (halving all counters) to decay old statistics, as implemented in the reset and increment methods between lines 70-124.

Summary

  • Moka does not provide a native hit-rate percentage metric; you must instrument lookups manually using atomic counters or similar metrics libraries.
  • Enable the unstable-debug-counters feature to access Cache::debug_stats(), which returns entry counts, weighted size, and frequency sketch memory usage from src/common/concurrent/debug_counters.rs.
  • The TinyLFU policy tracks all accesses (hits and misses) via the FrequencySketch in src/common/frequency_sketch.rs to optimize admission decisions, but this data represents popularity, not hit rate.
  • Combine manual hit/miss counters with the debug_stats() metadata to calculate accurate hit rates while monitoring the cache’s internal health.

Frequently Asked Questions

Does Moka provide a built-in hit rate metric?

No. According to the source code in src/common/concurrent/debug_counters.rs, Moka exposes CacheDebugStats containing entry counts and sketch sizes, but not hit/miss counters. You must wrap Cache::get calls with your own instrumentation to calculate hit rates.

What is the TinyLFU frequency sketch?

The FrequencySketch is a Count-Min Sketch implementation that uses 4-bit counters to probabilistically estimate how frequently keys are accessed. Located in src/common/frequency_sketch.rs, it powers the TinyLFU admission policy by tracking all cache lookups to determine which items deserve cache residency.

How do I enable debug statistics in Moka?

Add the unstable-debug-counters feature to your Cargo.toml dependency on Moka, then call cache.debug_stats().await on async cache instances. This returns a snapshot of internal metrics including entry_count, weighted_size, and freq_sketch_size.

Why does Moka track missed keys in its statistics?

The TinyLFU admission policy requires a global view of key popularity to decide whether to admit new entries. As documented in src/policy.rs, tracking all accesses—including misses—allows the cache to recognize "one-hit wonders" (keys accessed only once) and prevent them from evicting frequently used items, thus maintaining the near-optimal hit ratio claimed in the project README.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →