In a single FIFO admission queue on an analytical warehouse, why does one long query delay hundreds of short ones?
answer
- arrival order, not cost, decides the wait
- one item at the front holds up the rest
- dashboard latency becomes bimodal
- the cheap queries need their own lane
- classify by role, estimate, or history
basics
~20 sBecause a FIFO queue serves in arrival order: a 20-minute query holding a slot blocks everything behind it, however cheap. This is head-of-line blocking, and the fix is separate admission classes with reserved capacity for short queries.
solid answer
~50 sA first-in-first-out admission queue makes waiting time a function of arrival order, not of cost. When a query that will run for twenty minutes takes the only free slot, every query behind it waits twenty minutes even if it would have finished in two seconds — classic **head-of-line blocking**. Analytical workloads are extremely heavy-tailed: the same warehouse serves millisecond dashboard lookups and hour-long transforms, so a single ordered line is the worst possible structure. The standard fixes are to classify queries into separate lanes with their own reserved slots — a short-query lane that a large query can never occupy — and to route by predicted cost using the optimizer's estimate, the submitting role, or the query's fingerprint from previous runs. Some engines also demote a query mid-flight when it exceeds the runtime its class allows, which repairs a misclassification without killing the query.
code
text · 9 linessingle FIFO queue, 1 slot free:
[T+0 ETL join ~20 min] <- holds the slot
[T+1 dashboard ~2 s ] waits ~20 min
[T+2 dashboard ~2 s ] waits ~20 min
... 200 more short queries, all waiting
two classes with reserved capacity:
batch : 2 slots, large grant, cap 4 h -> ETL join
interactive : 20 slots, small grant, cap 5 m -> dashboards run immediatelygo deeper
Recall that a warehouse queue served strictly in arrival order makes fast queries wait behind slow ones, and that separating them into different lanes is the usual remedy.
Explain head-of-line blocking concretely, why raising the concurrency limit makes it worse rather than better, and how reserved per-class slots and grants change the outcome.
Show how you would classify queries in a real system — role, cost estimate, historical fingerprint — and how you handle the inevitable misclassification with runtime caps and demotion rather than cancellation.
Own the utilisation cost of reservations and the starvation risk of strict priority, and set the policy that decides which workloads are entitled to a protected lane at all.
## The shape of the problem Admission control limits how many queries run at once; the queue holds the rest. If that queue is a single FIFO line, the wait a query experiences depends entirely on **who arrived before it**. That is fine when all work is similar. Analytical workloads are the opposite: runtime distributions are heavy-tailed, spanning milliseconds (a pruned point lookup on a clustered table) to hours (a full rebuild of a fact table). Mixing those in one ordered line means the cheap majority routinely inherits the latency of the expensive minority. This is **head-of-line blocking**: the item at the head of a queue holds up everything behind it regardless of how quickly the followers could be served. It is the same phenomenon as one slow request stalling a pipelined connection, or one slow consumer stalling a shared partition. The user-visible symptom is distinctive and worth recognising: **dashboard latency becomes bimodal**. Most of the day the dashboard answers in two seconds; several times a day it answers in twenty minutes. Nothing about the dashboard query changed — only what was ahead of it. ## Why "just raise the limit" is the wrong fix The instinct is to admit more queries so the short ones slip past. That trades one pathology for another: raising global concurrency shrinks per-query memory grants, which pushes the large queries into spilling, which makes them hold their slots even longer. You have made the head of the line slower while adding contention. Concurrency limits and queue structure are separate knobs and must be tuned separately. ## Lanes, not a line The real fix is to stop having one queue. Split admission into **classes** (queues, resource groups, priority pools — the vocabulary differs by engine) where each class has: - its own concurrency limit and memory grant sized for the class's queries; - a **reserved** share of capacity, so a busy class cannot consume another's; - optionally a maximum runtime, after which the query is aborted or moved. A short-query class with a small grant and high concurrency, reserved capacity, and a hard runtime cap gives interactive users a lane no batch job can ever occupy. A batch class with a large grant and low concurrency gives the transforms the memory they need without spilling. Neither starves the other, because the reservation is enforced at admission. ## Getting queries into the right lane Classification can be done on: - **Identity** — the submitting user, role, service account or application name. Crude but robust, and usually the first thing implemented: BI service account goes to the interactive lane, the orchestrator's service account goes to batch. - **Predicted cost** — the optimizer's estimated bytes to scan or estimated rows. Cheap to obtain (it comes out of planning) but only as good as the statistics; a stale estimate misroutes. - **History** — a normalised query fingerprint plus the runtime of previous executions of that fingerprint. Very accurate for the repeating dashboard and report traffic that dominates warehouses, useless for genuinely novel ad-hoc SQL. Most production setups combine them: route by identity first, override by predicted cost, and correct with history. ## Handling misclassification Every classifier is wrong sometimes, and the dangerous error is a large query admitted into the short lane, where it blocks exactly the traffic the lane exists to protect. Two mechanisms handle it: - **Runtime caps with demotion**: when a query in the interactive class exceeds its allowed runtime, move it into the batch class and let it continue there. The interactive slot is freed; the query still completes, though slower. This is far kinder than killing it. - **Abort rules**: for classes where nothing should ever run long — a public-facing dashboard lane, for instance — cancel outright and surface a clear error, so the author fixes the query rather than retrying it. ## The costs of lanes Reservations mean a lane's capacity can sit idle while another lane queues, so total utilisation falls. Most engines soften this by letting an idle lane's share be borrowed and reclaimed when its own work arrives; where that is not available you are explicitly buying isolation with utilisation, which is usually the right purchase for interactive SLAs. The second cost is **starvation of the long tail**. If the interactive lane always has work and batch is served only from leftovers, the nightly transforms never finish. Guard against it with a floor for the batch class, or with aging — raising a query's effective priority the longer it has waited — so no query can be postponed indefinitely.
- How would you classify an incoming query into the right lane before it runs?Combine signals. Route on identity first — the BI service account or the orchestrator's account is a reliable proxy for workload type. Refine with the optimizer's cost estimate, typically estimated bytes scanned, which is available after planning and costs nothing extra. For repeating traffic, a normalised query fingerprint plus historical runtime is the most accurate signal of all. Then add a runtime cap so a misrouted query is demoted rather than allowed to block its lane.
- What is the risk of giving the interactive class strict priority over batch?Starvation. If interactive work never fully drains, the batch class is served only from leftovers and long transforms may never complete, which shows up as missed data-freshness SLAs rather than slow queries. Prevent it with a guaranteed floor of capacity for batch, or with aging that raises a waiting query's effective priority over time, so nothing can be postponed indefinitely.
A supermarket with one till makes the shopper holding two items wait behind the trolley with two hundred. Express lanes exist because sorting customers by basket size serves everyone faster than serving them strictly in arrival order.
saying these in an interview costs you the question
- Proposes raising the global concurrency limit as the fix
- Thinks priority alone helps a query already holding a slot
- Assumes every query's cost is known accurately before it runs
- Ignores that reserved lanes lower overall utilisation
- Kills misrouted long queries instead of demoting them