ClickHouse / ClickHouse/ClickHouse
Quota KEYED BY normalized_query_hash: MAX errors limit never enforced
- Dominant language
- C++
- Stars
- 49.9k
- Forks
- 9k
- Avg merge
- 21h 32m
- Merged PRs (30d)
- 515
Description
### Summary
When a quota is configured with `KEYED BY normalized_query_hash`, the `MAX errors` limit is silently never enforced — queries keep erroring indefinitely without ever receiving `QUOTA_EXCEEDED`.
This was introduced by PR #99586 ("Quota by normalized query hash").
### Root Cause
PR #99586 added per-hash interval checks via `usedForNormalizedQuery(hash, ERRORS, 0, true)` in `executeQuery.cpp`. However, the two places that **increment** the error counter were not updated:
- `logExceptionBeforeStart` (line ~858): `quota->used(QuotaType::ERRORS, 1, false)` — routes to **shared** session intervals
- Exception callback (line ~1965): `my_quota->used(QuotaType::ERRORS, 1, false)` — routes to **shared** session intervals
Both increment the shared session intervals, but `usedForNormalizedQuery` reads from the **per-hash** intervals. The per-hash ERRORS counter is therefore always 0, so `MAX errors` is never reached.
### Reproduction
```sql
-- Setup
CREATE USER u_errbug IDENTIFIED WITH no_password;
GRANT SELECT ON *.* TO u_errbug;
CREATE QUOTA q_errbug
KEYED BY normalized_query_hash
FOR INTERVAL 1 hour MAX errors = 2
TO u_errbug;
-- Run 5+ erroring queries as u_errbug (must use FROM to avoid system.one bypass)
-- Expected: QUOTA_EXCEEDED after 2 errors
-- Actual: all queries return FUNCTION_THROW_IF_VALUE_IS_NON_ZERO, quota never exceeded
SELECT throwIf(1, 'err') FROM numbers(1); -- query 1
SELECT throwIf(1, 'err') FROM numbers(1); -- query 2
SELECT throwIf(1, 'err') FROM numbers(1); -- query 3
SELECT throwIf(1, 'err') FROM numbers(1); -- query 4
SELECT throwIf(1, 'err') FROM numbers(1); -- query 5
```
### Expected Behavior
After 2 erroring queries with the same normalized hash, subsequent queries with that hash should receive `QUOTA_EXCEEDED`.
Contributor guide
Assessment
This issue has not been assessed yet.