x1117 opened a new issue, #13658:
URL: https://github.com/apache/apisix/issues/13658

   ### Current Behavior
   
   While using APISIX with the Prometheus plugin enabled, we observed that the 
free space of the `prometheus-metrics` shared dict decreases periodically in a 
step-like pattern.
   
   The metric we monitored is:
   
   ```text
   apisix_shared_dict_free_space_bytes{name="prometheus-metrics"}
   ```
   
   The pattern looks like this:
   
   ```text
   free_space stays stable for a while
     ↓
   drops by one step
     ↓
   stays stable again
     ↓
   drops by another step
   ```
   
   In our environment, this happened approximately once per hour. The active 
time series count was not very high, but the shared dict free space kept 
decreasing.
   
   We also dumped the `prometheus-metrics` shared dict and found many expired 
KeyIndex entries still present, for example:
   
   ```text
   __ngx_prom__key_N
   ```
   
   One dump showed:
   
   ```text
   key_count=751496
   free=148631552
   cap=536870912
   phys_used=388239360
   ```
   
   Entry summary:
   
   ```text
   IDX PERM   76
   IDX alive  28
   IDX STALE  747970
   
   VAL PERM   75
   VAL alive  28
   VAL STALE  4124
   VAL GONE   77
   ```
   
   Most stale entries were `__ngx_prom__key_N` index entries.
   
   We reproduced the same type of behavior locally with APISIX `3.17.0`.
   
   During reproduction, `prometheus-metrics` free space dropped multiple times:
   
   ```text
   11:43:26  10395648 -> 10330112  drop 65536
   11:50:08  10330112 -> 10289152  drop 40960
   12:01:27  10289152 -> 8794112   drop 1495040
   12:01:42  8794112  -> 8638464   drop 155648
   12:08:08  8638464  -> 7581696   drop 1056768
   ```
   
   It looks like expired `__ngx_prom__key_N` entries may remain in the 
`prometheus-metrics` shared dict and continue occupying memory, while later 
traffic creates new KeyIndex slots.
   
   ### Expected Behavior
   
   When Prometheus metrics are configured with `expire > 0`, the expired metric 
value entries and their related KeyIndex entries are expected to be reclaimed 
effectively, or at least not keep consuming additional physical memory in the 
`prometheus-metrics` shared dict.
   
   If the number of active time series is bounded, the free space of 
`prometheus-metrics` should eventually become stable instead of continuously 
dropping step by step.
   
   From our observation, the expired entries may not be physically reclaimed 
because OpenResty shared dict expiration is lazy. The passive expiration path 
only checks a small number of entries from the LRU tail, and it can be blocked 
when permanent entries are at the tail. As a result, expired Prometheus entries 
can remain in the shared dict and continue occupying slab memory.
   
   If this is expected behavior of the current implementation, could you please 
suggest the recommended way to avoid continuous `prometheus-metrics` shared 
dict memory consumption by expired entries?
   
   ### Error Logs
   
   There were no fatal APISIX errors during reproduction.
   
   We temporarily added logs around Prometheus KeyIndex operations to 
understand the behavior.
   
   The KeyIndex slot number kept increasing:
   
   ```text
   03:43:17  idx=48..161
   03:49:57  idx=162..275
   04:01:22~04:01:28  idx=276..2555
   04:08:04~04:08:07  idx=2556..4721
   ```
   
   `remove_expired_keys()` also reported worker-local cleanup:
   
   ```text
   remove_expired_keys checked=2280 removed=2280 last=2555
   ```
   
   After that, later traffic created new KeyIndex slots.
   
   ### Steps to Reproduce
   
   1. Run APISIX `3.17.0` via Docker:
   
   ```text
   apache/apisix:3.17.0-debian
   ```
   
   2. Enable the Prometheus plugin and configure metric expiration:
   
   ```yaml
   plugin_attr:
     prometheus:
       metrics:
         http_status:
           expire: 120
         http_latency:
           expire: 120
         bandwidth:
           expire: 120
         upstream_status:
           expire: 120
   ```
   
   3. Create one or more routes with the Prometheus plugin enabled.
   
   4. Send intermittent traffic. For example:
   
   ```text
   active traffic for 60s
   idle for 330s
   active traffic for 60s
   idle for 330s
   active traffic for 60s
   ```
   
   5. Monitor the following metric:
   
   ```text
   apisix_shared_dict_free_space_bytes{name="prometheus-metrics"}
   ```
   
   6. In our local reproduction, to shorten the reproduction time, we 
temporarily changed Prometheus initialization from:
   
   ```lua
   prometheus = base_prometheus.init("prometheus-metrics", metric_prefix)
   ```
   
   to:
   
   ```lua
   prometheus = base_prometheus.init("prometheus-metrics", {
       prefix = metric_prefix,
       remove_expired_keys_interval = 300,
   })
   ```
   
   This made the local cleanup interval shorter than the default `3600s`.
   
   7. Observe that `prometheus-metrics` free space drops in steps. In our 
reproduction, we observed:
   
   ```text
   11:43:26  10395648 -> 10330112  drop 65536
   11:50:08  10330112 -> 10289152  drop 40960
   12:01:27  10289152 -> 8794112   drop 1495040
   12:01:42  8794112  -> 8638464   drop 155648
   12:08:08  8638464  -> 7581696   drop 1056768
   ```
   
   ### Environment
   
   - APISIX version: `3.17.0`
   - Docker image: `apache/apisix:3.17.0-debian`
   - Prometheus plugin: enabled
   - Prometheus dependency in APISIX `3.17.0`:
   
   ```text
   nginx-lua-prometheus-api7 = 0.20250302-1
   ```
   
   - Operating system for local reproduction:
   
   ```text
   Linux 6.6.87.2-microsoft-standard-WSL2
   ```
   
   - etcd version used in local Docker compose:
   
   ```text
   bitnamilegacy/etcd:3.5.11
   ```
   
   - APISIX Dashboard version: not used
   - Plugin runner: not used
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to