Repository navigation
Scrape duration using prometheus is increasing continuously until restart #13586
Description
Activity
PrometheusExporterImpl calls deployment-global
recalculateCapacity()once per zone, causing O(zones) redundant full-infrastructure capacity recomputes on every scrapeSummary
PrometheusExporterImpl.updateMetrics()invokesalertManager.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
/metricsscrape duration scale withzones × (hosts + pools + pods)instead ofhosts + pools + pods, and it runs synchronously on the scrape-serving thread.Affected code
plugins/integrations/prometheus/.../PrometheusExporterImpl.java,updateMetrics():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: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_selectdelta for a single/metricsscrape: ~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 therecalculateCapacity()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_timeoutof 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:
- Remove the call from the exporter. Let the metrics read whatever the scheduled
recalculateCapacity()background pass last wrote. Capacity gauges tolerate being up tocapacity.check.periodstale, and the scrape becomes a pure read. - If a fresh recompute at scrape time is considered necessary, hoist it out of the loop so it runs at most once per scrape:
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()iterateshostDao.listAll()for every zone andcontinues on hosts not in the currentdcId, 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
- Observed
thanks, guys, an AI generated analysis:
Root cause (code inspection, 4.22.0.0 tag):
PrometheusExporterImpl.updateMetrics()callsalertManager.recalculateCapacity()once per datacenter inside its scrape loop:AlertManagerImpl.recalculateCapacity()already loops over every zone, host, and storage pool internally:So with Z zones, a single
/metricsscrape triggers Z redundant full-environment recalculations (O(Z²) work). Each call also spins up a brand-newExecutors.newFixedThreadPool(...)per host and per storage pool (recalculateHostCapacities()/recalculateStorageCapacities(), lines ~296-368), then tears it down — so the thread-pool churn scales with scrape frequency, not with the intended once-per-capacity.check.periodschedule fromAlertManagerImpl's ownCapacityCheckertimer (start(), line ~264).On top of that,
PrometheusExporterServerImpl.start()creates the JDKHttpServerwithout a custom executor (nosetExecutor(...)call), so scrape requests are served through the server's single default dispatch path — a slow scrape blocks/queues the next one rather than running concurrently.Put together: every Prometheus scrape (typically every 15-60s) forces a full, uncoordinated, thread-pool-churning capacity recalculation across the whole environment, running concurrently with — and uncoordinated against — the existing timer-driven recalculation. That matches "duration climbs slowly over 7-10 days, restart clears it" well: no single scrape is catastrophic, but the accumulated thread churn and request queuing degrade the JVM over time until a restart resets it.
I checked open issues/PRs for
recalculateCapacity/prometheusand didn't find this tracked elsewhere, so filing this as new.Proposed fix (PR incoming): stop calling
recalculateCapacity()from the scrape path — the exporter should read the capacity data the existing timer already maintains, not force a synchronous recompute per request — and haveAlertManagerImplreuse one long-lived executor for capacity calculation instead of creating/tearing one down per call.Can you confirm on your end? If you're able to test a build with
alertManager.recalculateCapacity()commented out ofupdateMetrics(), that would confirm this is the whole story before we merge. In the meantime, reducing your Prometheus scrape interval (and/or the periodic restart you're already doing) is the only workaround.- linked a pull request that will close this issueoptimise prometheus scraping workflow #13650
on Jul 20, 2026
problem
We are using Prometheus to scrape metrics from the management server.
We see a issue that the scrape_duration_seconds metric for the cloudstack job is increasing in slow pace and after ~7-10 days the scrape duration is longer then the configured interval and we get no more metrics.
First time we saw this just doing a time curl towards the mangement server it took 40-50s for metrics to come back.
versions
CloudStack 4.22.0.0
The steps to reproduce the bug
What to do about it?