Response times are creeping upward on cached endpoints. Application logs show nothing. Worker busy ratio is normal. The cause is likely silent: uWSGI cache misses are increasing, and each miss forces the worker to compute or fetch the response instead of serving from shared memory.

This article covers uWSGI’s built-in cache subsystem (cache2), not external caches. If caches[] is absent from the stats server output, the uWSGI cache is not enabled and these diagnostics do not apply.

The stats server exposes cache metrics in the caches[] array. Two symptoms dominate: a rising miss rate with steady traffic (cache churn or insufficient capacity), and a non-zero full counter (insert operations failing because the cache has no free slots). Both degrade response time, but they have different root causes and fixes.

What it means

The uWSGI cache is an in-process key-value store backed by shared memory mapped across all workers. It is configured via cache2 in your uWSGI config. Each cache has a fixed number of slots (items) and a fixed block size (blocksize). When the cache is full, new insert attempts fail silently or with a warning, depending on configuration.

Three counters tell you almost everything:

FieldMeaning
hitsCumulative count of successful cache lookups (monotonic)
missCumulative count of failed lookups (key not found)
fullCumulative count of insert operations that failed because the cache had no free slot

The hit ratio is hits / (hits + miss). A drop with stable traffic means the cache is too small for the working set, or items are expiring before re-request.

The full counter is the more direct signal. Any non-zero value means the cache rejected an insert. Each rejected insert is a future cache miss for that key.

flowchart TD
    A[Cache miss rate rising] --> B{full counter greater than 0?}
    B -->|Yes| C[Cache capacity exhausted]
    B -->|No| D{items near max_items?}
    D -->|Yes| E[Undersized cache or churn]
    D -->|No| F{blocksize too small?}
    F -->|Possibly| G[Silent insert failures]
    F -->|No| H[TTL expiry too aggressive]
    C --> I[Increase items or enable purge_lru]
    E --> I
    G --> J[Increase blocksize]
    H --> K[Review expires values]

Common causes

CauseWhat it looks likeFirst thing to check
Cache undersized for working setitems at or near max_items; full non-zero and rising; hit ratio low even after restartCompare items to max_items and check the full counter trend
TTL expiry too aggressiveHit ratio drops after restart and never recovers; full is zero; items well below max_itemsCheck expires values in cache_set calls or routing config
Blocksize too small for valuesitems stays at 0 or very low despite insert attempts; full may be zero; no error loggedVerify blocksize exceeds your largest cached value
Master process not running (no sweeper)TTL-based expiry never fires; cache fills with expired entries never cleaned; full rises over timeConfirm master = true in config
purge_lru initialization bug (pre-2.0.24)purge_lru=1 set but eviction does not work; cache fills and stays fullCheck uWSGI version; upgrade to 2.0.24+ if using purge_lru

Quick checks

# Dump all cache stats from the stats server
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.caches[]'

# Focused view of key cache metrics
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.caches[] | {name, items, max_items, hits, miss, full}'

# Calculate current hit ratio
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.caches[] | .hits as $h | .miss as $m | {name, hit_ratio: ($h / ($h + $m))}'

# Check whether items is near capacity
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.caches[] | {name, items, max_items, utilization: (.items / .max_items)}'

# Look for the DANGER full-cache warning in uWSGI logs
journalctl -u uwsgi --no-pager | grep "DANGER.*cache.*FULL"

# Check uWSGI version for known cache bugs
uwsgi --version

# Verify master process is running (required for cache sweeper)
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.pid'

Adjust the stats socket address (127.0.0.1:9191) to match your deployment. For UNIX socket stats, use uwsgi --connect-and-read /path/to/stats.sock.

How to diagnose it

  1. Confirm the cache is enabled. Check your uWSGI config for cache2 directives. If the application uses Redis or Memcached internally, the uWSGI cache metrics are irrelevant. Verify by checking whether caches[] appears in the stats output.

  2. Check the full counter trend. Take two readings a few minutes apart. If full is increasing, inserts are actively failing. If it is zero, the cache has room but misses are still rising (pointing to TTL or churn).

  3. Check items versus max_items. If items is at or near max_items, the cache is full. If true, items at 999 with max_items of 1000 means the cache is at capacity.

  4. Check hit ratio over time. If hit ratio is high immediately after a restart but degrades over hours, TTL expiry or eviction is too aggressive relative to the access pattern. If hit ratio is low from the start, the cache is undersized or the working set does not fit.

  5. Check for silent insert failures. If items stays near zero despite the application attempting inserts, blocksize may be too small for the values being stored. Compare your actual response body sizes against the configured blocksize.

  6. Check whether the master process is running. The cache sweeper thread, responsible for TTL-based expiration, only runs when the master process is enabled. Without it, expired items are never cleaned up and the cache fills permanently with stale entries. Confirm master = true in your config.

  7. Check the uWSGI version if using purge_lru. If purge_lru=1 is set but eviction does not seem to work, upgrade. Also verify that combining purge_lru=1 with bitmap=1 on large caches does not trigger known segfaults in your version.

Metrics and signals to monitor

SignalWhy it mattersWarning sign
caches[].full (rate)Each increment is a failed insert; that key will miss on next lookupAny non-zero rate
caches[].miss (rate)Rising miss rate means more requests bypass the cache and hit the backendRate increasing while traffic is stable
caches[].items vs max_itemsShows how close the cache is to capacityitems within 1 of max_items
Hit ratio hits / (hits + miss)Primary cache efficiency metricSustained drop below baseline
Worker avg_rtCache misses show up as slower responses; correlation confirms cache impactavg_rt rising in step with miss rate
uWSGI versionKnown bugs affect purge_lru and bitmap modeRunning old 2.0.x with purge_lru enabled

Fixes

Cache is undersized

Increase the items value in your cache2 configuration. Each item consumes blocksize bytes of shared memory, so the total footprint is items * blocksize. Verify the host has enough RAM for the larger allocation.

If you cannot increase items due to memory constraints, enable LRU eviction with purge_lru=1. This evicts the least recently accessed item when the cache is full, making room for new inserts.

The draft states that when `purge_lru=1` is active, the `expires` argument on `cache_set` calls is ignored and eviction is purely access-based. If you need TTL-based expiry alongside LRU eviction, verify this behavior against your uWSGI version before relying on it.

Blocksize is too small

Increase blocksize to accommodate your largest cached value. For example, with blocksize=65536, the largest storable item may be 65535 bytes. If your cached responses are larger, inserts fail silently with no error.

If values vary widely in size and most are small, consider bitmap mode (bitmap=1). Do not combine bitmap=1 with purge_lru=1 on large caches unless you have verified your uWSGI version is free of known segfaults.

TTL expiry too aggressive

Review the expires values passed to cache_set or configured in routing rules. If items expire before the next request for the same key, the cache provides no benefit. Increase expires to match or exceed the typical inter-request interval for each cached resource.

If you are using purge_lru=1, verify whether TTL is still honored (see the caveat above). If TTL-based expiry is required and purge_lru does override it, size the cache to hold the full working set instead.

Master process not running

Enable master = true in your uWSGI config. Without the master process, the cache sweeper thread never starts, and TTL-based expiration does not work. Expired items accumulate until the cache is full of stale data with no mechanism to reclaim slots.

Suppressing full-cache log warnings

If you have consciously decided to let the cache run full (for example, with purge_lru handling eviction), the repeated *** DANGER cache "<name>" is FULL !!! *** log lines on every insert can flood your logs. Use ignore_full to suppress these warnings only when you have an eviction strategy in place. Otherwise it hides a real capacity problem.

Prevention

  • Size the cache to the working set, not to a round number. Measure how many unique keys the application references within the TTL window. Set items to at least 120% of that count to allow for growth.
  • Match blocksize to actual value sizes. Audit your largest cached responses and set blocksize with headroom. Silent insert failures from blocksize mismatch are the hardest to diagnose because they produce no error.
  • Monitor the full counter as a rate, not as an absolute. Any non-zero rate means inserts are being rejected. Alert on it.
  • Track hit ratio over time, not just point-in-time. A slowly declining hit ratio over days or weeks indicates the working set is growing past cache capacity.
  • Verify the master process is enabled if you rely on TTL expiry. Without the sweeper thread, the cache fills with expired entries and never recovers.
  • Keep uWSGI current. The cache subsystem has received fixes in recent 2.0.x releases. Verify which bugs affect your version before relying on purge_lru or bitmap mode.

How Netdata helps

Netdata collects uWSGI cache metrics from the stats server at per-second resolution. Correlate these signals during investigation:

  • Cache hit and miss rates over time. Reveals whether a declining hit ratio is gradual (working set growth) or sudden (code change or traffic shift).
  • The full counter rate. The most direct signal of capacity exhaustion. Per-second collection catches burst insert failures that coarser polling intervals miss.
  • Miss rate vs. worker avg_rt. Confirms cache misses are the cause of response time degradation, not a downstream dependency.
  • items relative to max_items. Shows proximity to capacity before inserts start failing.
  • Anomaly detection on hit ratio. Surfaces slow working-set drift over weeks that static thresholds would miss.