The expensive part is often hidden behind an innocent expression
Suppose the panel asks for the rate of a counter at many evaluation timestamps. A Prometheus range query has a start, end, and step. The step controls how frequently the expression is evaluated. It does not automatically mean the engine reads just one raw sample per rendered point.
A range function such as rate(x[5m]) needs samples in its lookback window. Nested subqueries, large label matches, joins and wide aggregations can force substantial intermediate work. Implementations can optimize repeated reads, but a high-level API result size does not bound internal IO, decoding, memory or CPU. Returning only 1,200 points is not an admission policy.
The query might also match more series than the user expects. An expression grouped by service can begin by reading every pod's series, only collapsing identities afterward. If the input labels have high cardinality, the store must search and process that population before the result becomes one service line.
This connects to labels and compression. A compressed time-series block saves disk and read bandwidth, but the query still needs to identify matching series and decode the relevant data. Neither efficient chunks nor small HTTP responses make broad queries free.
Protect the service before optimizing the chart
I would put explicit limits at the read interface: maximum execution time, maximum samples a query may load, concurrency limits, and separate budgets for interactive users versus background jobs. Prometheus exposes settings such as --query.timeout, --query.max-concurrency, and --query.max-samples. Check version-specific behavior. Prometheus command-line options.
A query that exceeds its budget should fail clearly or require an asynchronous job. It should not be allowed to consume the resources needed by incident dashboards and alert evaluation. Ideally, alerting and critical operational reads have reserved or separate compute capacity so that one exploratory dashboard cannot starve emergency detection.
For a multi-tenant cluster, fairness needs more than a global concurrency limit. A large tenant can occupy every available query slot with expensive requests. Allocate read capacity per tenant or workload class and bound queue lengths. Limit actual cost, not just request count. One request can cost thousands of times more than another.
Admission also needs to consider cancellation. Abandoned scans should stop rather than compete with active queries.
Make the cheap query answer the user's actual question
Ask what the engineer was trying to learn. A 30-day CPU capacity trend across all hosts probably does not require 15-second data for every host. A pre-aggregated hourly fleet summary might answer it quickly with a small number of series. A single-host incident at 13:42 yesterday needs a different query plan and finer source resolution.
I would maintain recording rules or a rollup pipeline for common service-level aggregates, with clear labels and retention. The dashboard defaults to summarized data over long ranges. It offers drill-down to finer data over bounded windows, and it warns when requested resolution is unavailable. Do not let the system silently substitute a lossy rollup for an audit query that requires exact points.
For example, 30 days contain 720 hourly buckets, compared with 172,800 raw 15-second samples per host. The numerical reduction is useful only if the hourly aggregate preserves what the panel needs. A monthly p99 cannot be manufactured from averaged hourly p99s, as the previous episode established.
A query cache reduces repeated work, not new broad scans, and needs a freshness policy. Query splitting across time and stores can improve parallelism, yet can also overwhelm downstream workers if concurrency is uncontrolled. Faster execution of one user's query is not success if everyone else's queries become unreliable.
Build an operational budget the team can reason about
Before execution, estimate the number of selected series, time span, step, expression shape, and expected sample volume. The estimate will be imperfect, especially for nested functions. Use it to reject clearly dangerous queries or redirect them to an offline workflow. During execution, measure actual decoded samples, memory, durations, cache hit rates, cancellations and queue wait time.
I would test a worst-case dashboard during a simulated incident while alert queries are running. Acceptance means the exploratory request either completes inside its budget or fails predictably, and critical alerts continue operating. A system that answers everything eventually but blocks incident monitoring is not operationally correct.
The interview takeaway is practical: result cardinality is not query cost. Limit and isolate work, preserve query fairness, use appropriate resolution, and expose why a request was refused. Those controls make historical exploration possible without giving any one browser tab the ability to take down telemetry during the very outage it needs to explain.