jansyren commented on issue #13586:
URL: https://github.com/apache/cloudstack/issues/13586#issuecomment-4958065705

   # PrometheusExporterImpl calls deployment-global `recalculateCapacity()` 
once per zone, causing O(zones) redundant full-infrastructure capacity 
recomputes on every scrape
   
   ### Summary
   
   `PrometheusExporterImpl.updateMetrics()` invokes 
`alertManager.recalculateCapacity()` **inside** the per-datacenter loop. 
`AlertManagerImpl.recalculateCapacity()` is deployment-global — it takes no 
zone/scope argument and recomputes capacity for *all* hosts, storage pools, 
pods and datacenters every time it runs. Calling it once per zone therefore 
repeats the entire global recompute N times per scrape, where N = number of 
zones.
   
   On a multi-zone deployment this makes the `/metrics` scrape duration scale 
with `zones × (hosts + pools + pods)` instead of `hosts + pools + pods`, and it 
runs synchronously on the scrape-serving thread.
   
   ### Affected code
   
   **`plugins/integrations/prometheus/.../PrometheusExporterImpl.java`**, 
`updateMetrics()`:
   
   ```java
   for (final DataCenterVO dc : dcDao.listAll()) {
       final String zoneName = dc.getName();
       final String zoneUuid = dc.getUuid();
       alertManager.recalculateCapacity();        // <-- deployment-global, 
called once PER ZONE
       addHostMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid);
       addVMMetrics(...);
       ...
   }
   ```
   
   **`server/.../AlertManagerImpl.java`**, `recalculateCapacity()` — takes no 
zone parameter and iterates the entire deployment:
   
   ```java
   public void recalculateCapacity() {
       ...
       recalculateHostCapacities();      // hostDao.listIdsByType(Routing) -> 
ALL routing hosts
       recalculateStorageCapacities();   // _storagePoolDao.listAllIds()   -> 
ALL storage pools
       List<DataCenterVO> datacenters = _dcDao.listAll();
       for (DataCenterVO datacenter : datacenters) { ... }   // ALL datacenters
       List<HostPodVO> pods = _podDao.listAll();
       for (HostPodVO pod : pods) { ... }                    // ALL pods
   }
   ```
   
   Because the method is global, the per-zone invocation in the exporter 
recomputes the same all-zones capacity set once for every zone.
   
   ### Impact / measurements
   
   Environment: CloudStack 4.22.0.0, 7 zones, 30 routing hosts.
   
   - Observed `Com_select` delta for a single `/metrics` scrape: **~20,839 
queries**.
   - Scrape duration climbs from **~0.9 s to ~4.0 s over ~3 days** of uptime, 
resetting on management-server restart.
   - Thread dumps during a scrape show the serving thread predominantly in 
`PrometheusExporterImpl.updateMetrics()` at the `recalculateCapacity()` call 
site and in the downstream host-capacity recompute.
   - JVM heap is flat (no leak); MariaDB executes the individual capacity query 
in <1 ms. The cost is the sheer number of redundant recomputes per scrape, not 
slow individual queries or GC.
   
   With a default Prometheus `scrape_timeout` of 10s, a deployment on this 
trajectory eventually crosses the timeout and drops scrapes.
   
   ### Suggested fix
   
   `recalculateCapacity()` is deployment-global and is **already run on a 
timer** by the background capacity checker (`capacity.check.period`, default 
300000 ms). The exporter does not need to force a synchronous recompute at all, 
and certainly not once per zone.
   
   Options, in order of preference:
   
   1. **Remove the call from the exporter.** Let the metrics read whatever the 
scheduled `recalculateCapacity()` background pass last wrote. Capacity gauges 
tolerate being up to `capacity.check.period` stale, and the scrape becomes a 
pure read.
   2. **If a fresh recompute at scrape time is considered necessary, hoist it 
out of the loop** so it runs at most once per scrape:
   
   ```java
   alertManager.recalculateCapacity();                 // once, before the loop
   for (final DataCenterVO dc : dcDao.listAll()) {
       addHostMetrics(...);
       ...
   }
   ```
   
   Either change reduces the recompute work per scrape by a factor of N(zones) 
with no loss of correctness (single-zone deployments are unaffected).
   
   ### Secondary observations (not the primary defect)
   
   - `addHostMetrics()` iterates `hostDao.listAll()` for every zone and 
`continue`s on hosts not in the current `dcId`, so all hosts are scanned once 
per zone. Filtering by datacenter at the DAO level 
(`hostDao.listByDataCenterId(dcId)` or equivalent) would avoid the repeated 
full scan.
   - Within the surviving per-host body, several single-row DAO lookups are 
issued per host (`findByHostId`, `getHostTags`, `getHostStatistics`, 
`findByHostIdType` ×3, `listByHostId`). These could be batched, though they are 
a minor cost next to the per-zone global recompute.
   
   ### Environment
   
   - CloudStack: 4.22.0.0
   - Hypervisor mgmt: KVM
   - DB: MariaDB
   - Zones: 7, Routing hosts: 30
   - JDK: OpenJDK 17
   
   
   Added some troubleshooting I have done regarding this with Claude Opus, at 
least it didn't destroy anything and it seems to have found some good pointers. 
I hope this will help since I am not a JAVA programmer I have to pass the torch 
to the experts.
   
   Best of luck and thanks for looking into this
   
   Jan


-- 
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