Skip to content

cache_stats

Get comprehensive cache statistics including hits, misses, and hit rate.

Signature

semantic_cache.cache_stats()
RETURNS TABLE(
    total_entries bigint,
    total_hits bigint,
    total_misses bigint,
    hit_rate_percent float4
)

Parameters

None

Returns

Returns a table with a single row containing:

Column Type Description
total_entries bigint Current number of cached entries
total_hits bigint Cumulative cache hits since initialization
total_misses bigint Cumulative cache misses since initialization
hit_rate_percent float4 Hit rate as percentage (0-100)

Description

This function provides a comprehensive view of cache performance by querying both the cache_entries and cache_metadata tables. Statistics are automatically updated with every get_cached_result() call.

Calculation

hit_rate_percent = (total_hits / (total_hits + total_misses)) * 100

If no queries have been executed, hit_rate_percent returns 0.0.

Examples

Basic Usage

SELECT * FROM semantic_cache.cache_stats();

Sample Output:

 total_entries | total_hits | total_misses | hit_rate_percent
---------------+------------+--------------+------------------
          1543 |       8921 |         2103 |            80.93

Monitoring Over Time

-- Create a monitoring table
CREATE TABLE my_app.cache_monitoring (
    timestamp timestamptz DEFAULT NOW(),
    total_entries bigint,
    total_hits bigint,
    total_misses bigint,
    hit_rate_percent float4
);

-- Log current stats
INSERT INTO my_app.cache_monitoring (
    total_entries, total_hits, total_misses, hit_rate_percent
)
SELECT * FROM semantic_cache.cache_stats();

-- View trends
SELECT
    timestamp,
    hit_rate_percent,
    total_entries,
    (total_hits - LAG(total_hits) OVER (ORDER BY timestamp)) as hits_since_last,
    (total_misses - LAG(total_misses) OVER (ORDER BY timestamp)) as misses_since_last
FROM my_app.cache_monitoring
ORDER BY timestamp DESC
LIMIT 24;

Formatted Display

-- Human-readable cache statistics
SELECT
    total_entries as "Cache Entries",
    TO_CHAR(total_hits, '999,999,999') as "Total Hits",
    TO_CHAR(total_misses, '999,999,999') as "Total Misses",
    ROUND(hit_rate_percent, 2) || '%' as "Hit Rate",
    CASE
        WHEN hit_rate_percent >= 80 THEN '✓ Excellent'
        WHEN hit_rate_percent >= 60 THEN '~ Good'
        WHEN hit_rate_percent >= 40 THEN '! Fair'
        ELSE '✗ Poor'
    END as "Performance"
FROM semantic_cache.cache_stats();

Integration with Application Metrics

-- Export for Prometheus/Grafana
CREATE OR REPLACE FUNCTION my_app.cache_metrics()
RETURNS TABLE(
    metric_name text,
    metric_value numeric,
    metric_type text
) AS $$
    SELECT 'cache_entries_total', total_entries::numeric, 'gauge'
    FROM semantic_cache.cache_stats()
    UNION ALL
    SELECT 'cache_hits_total', total_hits::numeric, 'counter'
    FROM semantic_cache.cache_stats()
    UNION ALL
    SELECT 'cache_misses_total', total_misses::numeric, 'counter'
    FROM semantic_cache.cache_stats()
    UNION ALL
    SELECT 'cache_hit_rate', hit_rate_percent::numeric, 'gauge'
    FROM semantic_cache.cache_stats();
$$ LANGUAGE SQL;

Performance Interpretation

Target Hit Rates by Use Case

Use Case Target Hit Rate Typical Rate
LLM/AI Queries 70-85% 75%
Analytics 60-75% 65%
Real-time Data 40-60% 50%
API Results 75-90% 80%

What Different Hit Rates Mean

80%+ Hit Rate 🎯 - Excellent performance - Cache is well-tuned - Significant cost savings - Continue monitoring

-- If hit rate > 80%, you're doing great
SELECT
    CASE
        WHEN hit_rate_percent >= 80 THEN 'Optimal - maintain current settings'
        ELSE 'Needs tuning'
    END as recommendation
FROM semantic_cache.cache_stats();

60-80% Hit Rate ✓ - Good performance - Room for improvement - Consider lowering similarity threshold - Review TTL settings

40-60% Hit Rate ⚠ - Marginal benefit - Review cache strategy - Check similarity threshold (might be too high) - Verify embedding quality

< 40% Hit Rate ❌ - Poor performance - Cache may not be beneficial - Investigate root cause - Consider different caching strategy

Monitoring Patterns

Daily Health Check

-- Daily cache health report
WITH current_stats AS (
    SELECT * FROM semantic_cache.cache_stats()
),
cache_size AS (
    SELECT pg_size_pretty(SUM(result_size_bytes)::BIGINT) as total_size
    FROM semantic_cache.cache_entries
),
expired_count AS (
    SELECT COUNT(*) as expired
    FROM semantic_cache.cache_entries
    WHERE expires_at <= NOW()
)
SELECT
    cs.*,
    cz.total_size,
    ec.expired as expired_entries,
    CASE
        WHEN cs.hit_rate_percent >= 70 THEN 'Healthy'
        WHEN cs.hit_rate_percent >= 50 THEN 'Warning'
        ELSE 'Critical'
    END as health_status
FROM current_stats cs
CROSS JOIN cache_size cz
CROSS JOIN expired_count ec;

Alert on Poor Performance

-- Alert if hit rate drops below threshold
DO $$
DECLARE
    stats RECORD;
BEGIN
    SELECT * INTO stats FROM semantic_cache.cache_stats();

    IF stats.hit_rate_percent < 50 THEN
        RAISE WARNING 'Cache hit rate below 50%%: %',
            stats.hit_rate_percent;
        -- Send alert (implement your alerting logic)
    END IF;
END $$;

Periodic Cleanup Suggestion

-- Suggest cleanup if too many expired entries
WITH stats AS (
    SELECT * FROM semantic_cache.cache_stats()
),
expired AS (
    SELECT COUNT(*) as count
    FROM semantic_cache.cache_entries
    WHERE expires_at <= NOW()
)
SELECT
    s.total_entries,
    e.count as expired_entries,
    ROUND((e.count::numeric / NULLIF(s.total_entries, 0)) * 100, 2) as expired_percent,
    CASE
        WHEN (e.count::numeric / NULLIF(s.total_entries, 0)) > 0.10
        THEN 'Run: SELECT semantic_cache.evict_expired();'
        ELSE 'No action needed'
    END as recommendation
FROM stats s
CROSS JOIN expired e;

Comparing with Application Metrics

Cost Savings Calculation

-- Calculate cost savings from caching
WITH stats AS (
    SELECT * FROM semantic_cache.cache_stats()
)
SELECT
    total_hits,
    total_misses,
    (total_hits + total_misses) as total_queries,
    hit_rate_percent,
    -- Assuming $0.02 per query without cache
    ROUND((total_hits * 0.02)::numeric, 2) as "savings_usd",
    ROUND(((total_hits + total_misses) * 0.02)::numeric, 2) as "cost_without_cache_usd"
FROM stats;

Cache Effectiveness Score

-- Custom effectiveness metric
WITH stats AS (
    SELECT * FROM semantic_cache.cache_stats()
),
cache_info AS (
    SELECT
        COUNT(*) as entries,
        AVG(access_count) as avg_accesses
    FROM semantic_cache.cache_entries
)
SELECT
    s.hit_rate_percent,
    c.avg_accesses,
    -- Effectiveness: hit rate * average accesses per entry
    ROUND((s.hit_rate_percent * c.avg_accesses / 100)::numeric, 2) as effectiveness_score
FROM stats s
CROSS JOIN cache_info c;

Resetting Statistics

Statistics persist across restarts. To reset:

-- Reset hit/miss counters (use with caution!)
UPDATE semantic_cache.cache_metadata
SET total_hits = 0,
    total_misses = 0
WHERE id = 1;

-- Verify reset
SELECT * FROM semantic_cache.cache_stats();

Warning

Resetting statistics loses historical data. Consider logging stats before reset.

For more specific metrics:

-- Just the hit rate percentage
SELECT semantic_cache.cache_hit_rate();

-- Detailed cache health
SELECT * FROM semantic_cache.cache_health;

-- Recent activity
SELECT * FROM semantic_cache.recent_cache_activity;

-- Cost analysis (if using cost tracking)
SELECT * FROM semantic_cache.get_cost_savings(30);  -- Last 30 days

See Also