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:
| Field | Meaning |
|---|---|
hits | Cumulative count of successful cache lookups (monotonic) |
miss | Cumulative count of failed lookups (key not found) |
full | Cumulative 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
| Cause | What it looks like | First thing to check |
|---|---|---|
| Cache undersized for working set | items at or near max_items; full non-zero and rising; hit ratio low even after restart | Compare items to max_items and check the full counter trend |
| TTL expiry too aggressive | Hit ratio drops after restart and never recovers; full is zero; items well below max_items | Check expires values in cache_set calls or routing config |
| Blocksize too small for values | items stays at 0 or very low despite insert attempts; full may be zero; no error logged | Verify 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 time | Confirm 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 full | Check 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
Confirm the cache is enabled. Check your uWSGI config for
cache2directives. If the application uses Redis or Memcached internally, the uWSGI cache metrics are irrelevant. Verify by checking whethercaches[]appears in the stats output.Check the
fullcounter trend. Take two readings a few minutes apart. Iffullis increasing, inserts are actively failing. If it is zero, the cache has room but misses are still rising (pointing to TTL or churn).Check
itemsversusmax_items. Ifitemsis at or nearmax_items, the cache is full. If true,itemsat 999 withmax_itemsof 1000 means the cache is at capacity.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.
Check for silent insert failures. If
itemsstays near zero despite the application attempting inserts,blocksizemay be too small for the values being stored. Compare your actual response body sizes against the configured blocksize.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 = truein your config.Check the uWSGI version if using
purge_lru. Ifpurge_lru=1is set but eviction does not seem to work, upgrade. Also verify that combiningpurge_lru=1withbitmap=1on large caches does not trigger known segfaults in your version.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
caches[].full (rate) | Each increment is a failed insert; that key will miss on next lookup | Any non-zero rate |
caches[].miss (rate) | Rising miss rate means more requests bypass the cache and hit the backend | Rate increasing while traffic is stable |
caches[].items vs max_items | Shows how close the cache is to capacity | items within 1 of max_items |
Hit ratio hits / (hits + miss) | Primary cache efficiency metric | Sustained drop below baseline |
Worker avg_rt | Cache misses show up as slower responses; correlation confirms cache impact | avg_rt rising in step with miss rate |
| uWSGI version | Known bugs affect purge_lru and bitmap mode | Running 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.
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
itemsto 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
fullcounter 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
fullcounter 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. itemsrelative tomax_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.
Related guides
- uWSGI all workers busy: reading the busy ratio before the queue fills
- uWSGI avg_rt is not a real average: why the latency number lies
- uWSGI chain reload: cycling workers one at a time for zero-downtime deploys
- uWSGI cheaper subsystem: dynamic worker scaling and the false ‘missing workers’ alert
- uWSGI connection refused: clients turned away when the backlog overflows
- uWSGI file descriptor limits: raising ulimit -n and systemd LimitNOFILE
- uWSGI in gevent/async mode: why worker busy ratio stops meaning anything
- uWSGI threaded mode and the GIL: why more threads don’t add CPU parallelism
- uWSGI reload thundering herd: capacity drops to zero during a slow restart
- uWSGI harakiri death spiral: workers killed and respawned while throughput collapses
- uWSGI harakiri not configured: stuck workers with no timeout and no recovery
- uWSGI harakiri timeout: setting it against request duration and nginx timeouts






