Skip to content

Commit a7592b6

Browse files
authored
Blog: Query Rejection (#6944)
Signed-off-by: Erlan Zholdubai uulu <[email protected]>
1 parent 4f4de93 commit a7592b6

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
date: 2025-08-04
3+
title: "Block the Blast: How Query Rejection Protects Your Cortex Cluster"
4+
linkTitle: Query Rejection in Cortex
5+
tags: [ "blog", "cortex", "query", "rejection" ]
6+
categories: [ "blog" ]
7+
projects: [ "cortex" ]
8+
description: >
9+
Query rejection gives Cortex operators a last-resort safety net against disruptive queries that bypass resource limits. Learn how it works, how to configure it, and best practices to protect your multi-tenant cluster.
10+
author: Erlan Zholdubai uulu ([@erlan-z](https://github.com/erlan-z))
11+
---
12+
13+
# Introduction
14+
15+
We had events where a set of seemingly **harmless-looking** dashboard queries kept slipping just under our limits yet repeatedly **OOM-killing the querier pods**. Our safeguard mechanisms weren’t enough, and the only hope was that the tenant would either stop those queries or that we’d have to throttle all traffic from that tenant. Usually it wasn’t all traffic causing trouble—it was a small set of queries coming from a specific dashboard or some query with specific characteristics. We wished there was a way to manually specify query characteristics and reject them without throttling everything. **This inspired us to build query rejection**, a last-resort safety net for operators running multi-tenant Cortex clusters.
16+
17+
## Why Limits Aren’t Enough
18+
19+
Cortex already includes resource limiting, throttling and other resource safeguards, but these protections can’t cover every edge case. Some limits are enforced too late in the query lifecycle to matter; others are broad and can’t target specific bad actors. As a result, a single heavy query can bypass all service limits and still:
20+
21+
- Cause OOM kills that disrupt other tenants.
22+
- Degrade availability or spike latency for everyone.
23+
- Require manual operator intervention to restore normal service.
24+
25+
We needed a more precise tool—one that lets operators proactively block specific query patterns without harming legitimate traffic.
26+
27+
## What Is Query Rejection?
28+
29+
Think of query rejection as an “emergency stop” in a factory. It sits in front of the query engine and checks each request against a set of operator-defined rules. If a request matches criteria, it’s rejected immediately. This allows you to target the handful of queries that cause trouble without imposing a blanket slowdown on everyone else.
30+
31+
**Key features:**
32+
33+
- **Per-tenant control:** It's defined in the tenant limit configuration, which only targets queries from specific tenant. 
34+
- **Precise matching:** You can specify different query attributes to narrow down to specific queries. All fields within a rule set must match (AND logic). If needed, you can define multiple independent rule sets to target different types of queries.
35+
- **Pre-processing enforcement:** Query rejection is applied before the query is executed, allowing known-bad patterns to be blocked before consuming any resources.
36+
37+
## Matching Criteria
38+
39+
Heavy queries often share identifiable traits. Query rejection lets you match on a variety of attributes and reject only those requests. You can combine as many of these as needed:
40+
41+
- **API type:** `query`, `query_range`, `series`, etc.
42+
- **Query string (regex):** Match by pattern, e.g., any query containing “ALERT”.
43+
- **Time range:** Match queries whose range falls between a configured **min** and **max**.
44+
- **Time window:** Match queries based on how far their time window is from now by specifying relative **min** and **max** boundaries. This is often used to distinguish queries that hit hot storage versus cold storage.
45+
- **Step value (resolution):** Block extremely fine resolutions (e.g., steps under 5s).
46+
- **Headers:** Match by User-Agent, Grafana dashboard UID (`X-Dashboard-Uid`) or panel ID (`X-Panel-Id`).
47+
48+
By combining these fields, you can zero in on the exact query patterns causing problems without over-blocking.
49+
50+
## Configuring Query Rejection
51+
52+
You define query rejection rules per tenant in a runtime config file. Each rule specifies a set of attributes that must all match for the query to be rejected. The configuration supports multiple such rule sets.
53+
54+
Here’s an example configuration:
55+
56+
```yaml
57+
# runtime_config.yaml
58+
overrides:
59+
<tenant_id>:
60+
query_rejection:
61+
enabled: true
62+
query_attributes:
63+
- api_type: query_range
64+
regex: .*ALERT.*
65+
query_step_limit:
66+
min: 6s
67+
max: 20s
68+
dashboard_uid: "dash123"
69+
```
70+
71+
**What this does:**
72+
73+
- `enabled` This allows you to temporarily turn off query rejection without removing the configuration. It can help verify whether previously blocked queries are still causing issues.
74+
- `query_attributes` is a list of rejection rules. A query will only be rejected if it matches all attributes within one rejection rule.
75+
76+
In the example above, the single rejection rule requires the following:
77+
78+
- API type must be `query_range`.
79+
- The query string must contain the word `ALERT`.
80+
- The step must be between 6 and 20 seconds.
81+
- The request must come from a dashboard with UID `dash123`.
82+
83+
If all of these conditions match, the request is rejected with a `422` response. If even one condition doesn’t match, the query is allowed to run. You can define additional rejection rules in the list to target different patterns.
84+
85+
## Practical Example
86+
87+
Imagine a dashboard panel that repeatedly hits your cluster with a query like this:
88+
89+
```bash
90+
curl \
91+
'http://localhost:8005/prometheus/api/v1/query?query=customALERTquery&start=1718383304&end=1718386904&step=7s' \
92+
-H "User-Agent: other" \
93+
-H "X-Dashboard-Uid: dash123"
94+
```
95+
96+
Because this request matches all the configured attributes, it will be blocked. But if even one field is different—such as a longer step duration or a different dashboard UID—the query will go through.
97+
98+
## Best Practices and Cautions
99+
100+
- **Start with narrow rules.** Use the most specific fields first—like panel ID or dashboard UID—to reduce risk of over-blocking.
101+
102+
- **Monitor rejections.** Use the `cortex_query_frontend_rejected_queries_total` metric to track rejected queries, and check logs for detailed query information.
103+
104+
- **Communicate with tenants.** Let affected tenants know if their queries are being blocked, and help them adjust their dashboards accordingly.
105+
106+
## Conclusion
107+
108+
When traditional safeguards fall short, query rejection gives operators precise control to block only what’s harmful—without slowing down everything else.
109+
110+
If you operate a shared Cortex environment, consider learning how to use query rejection effectively. It might just save you from the next incident—by preventing OOM kills, degraded performance, or disruption to other tenants.
111+

0 commit comments

Comments
 (0)