What memory overhead does PromQL query evaluation impose on Prometheus instances?
0 reputation · 11 Aug 2023, 17:46 UTC
0 reputation · 11 Aug 2023, 17:46 UTC
Prometheus query caching (queries.cache.enabled) is documented as a mechanism to reduce memory pressure during repeated query execution, yet the exact memory overhead calculation for PromQL query evaluation remains undocumented. This creates challenges for capacity planning when deploying complex queries that process high-cardinality metrics.
The tsdb storage engine's memory behavior has evolved significantly across versions, with WAL compaction and retention policies (storage.retention.time) being primary controls for preventing memory growth. However, the relationship between query complexity, result set size, and actual Go runtime heap consumption is not precisely specified in official documentation.
Capacity planning for Prometheus servers requires understanding how different query patterns impact memory usage, particularly when evaluating expressions that involve joins, aggregations, or subqueries across multiple time series.
Which specific PromQL constructs contribute most significantly to memory overhead during evaluation, and how can administrators estimate the required memory capacity for a given query workload?
Can the documented query cache mechanism provide reliable bounds for memory consumption prediction, or does it introduce additional uncertainty in capacity planning scenarios?
How does the memory behavior differ between cached and non-cached query execution paths in terms of Go heap allocation patterns?
32275 reputation · 12 Aug 2023, 01:18 UTC
Prometheus query evaluation can consume a significant amount of Go heap, especially for high‑cardinality metrics and long lookback windows. The peak memory footprint is largely driven by the number of matching series, the number of samples per series in the evaluated window, and the cardinality of the output series after aggregations or joins. In practice, a single complex query can spike the heap by several hundred megabytes, even if the steady‑state usage is modest.
group_left / group_right, label_join, label_replace) materialize intermediate series sets in memory.rate(metric[30d])) create a sample matrix that grows linearly with both series count and samples per series.sum, avg, quantile_over_time build a map from label set to accumulator; the map size equals the output series count.While Prometheus does not publish an exact formula, a practical estimate is:
peakHeap ≈ (matchingSeries × samplesPerSeries × 24 bytes) + (outputSeries × mapEntryOverhead) + queryASTOverhead
Typical values: mapEntryOverhead ≈ 48 bytes; queryASTOverhead ≈ 1–2 KB. A safety factor of 2–3× is common for bursty workloads.
| Construct | Memory Pattern | Typical Peak Impact |
|---|---|---|
| High‑cardinality join | Intermediate series set | Series × samples × 24 bytes |
| Subquery (30d rate) | Sample matrix | Series × 30 days × 3 h × 24 bytes |
| Aggregation (by (le, instance)) | Accumulator map | OutputSeries × 48 bytes |
| Range vector selector | Sample slice | Series × samples × 16 bytes |
The query.cache.enabled setting stores only the final result matrix for a query. It reduces CPU and repeated allocation costs for repeated executions, but it does not bound the peak heap during the first evaluation that populates the cache. Cached queries can still trigger large memory spikes when first run.
Additionally, the cache has a per‑entry size limit (default 1 MB) and a total size limit; queries that produce larger results are not cached at all, so cache‑hit rates for heavy queries may be negligible.
go tool pprof -alloc_space http://localhost:9090/debug/pprof/heap
This captures the allocation profile before and after the query.query.log.enable=true
Correlate query_duration_seconds with process_resident_memory_bytes in the same scrape interval.query.cache.enabled=false # run query set
query.cache.enabled=true # run same query set again
Measure peak RSS via cgroup memory.max or ps -o rss.promtool tsdb create-blocks and run the exact query set to validate the estimator.storage.max_local_storage_size or process.max_concurrent_queries).
To refine the capacity plan, please provide the approximate cardinality of the metrics being queried and the typical lookback window for your most expensive queries. This data will tighten the estimator and help decide whether to enable the v3 query engine or adjust GC settings.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.