When hundreds of clients query the same domain at the same instant, BIND does not send hundreds of identical recursive queries upstream. It sends one fetch and attaches the waiting clients to it. Two configuration knobs control how many waiters can attach before BIND starts dropping the overflow: clients-per-query (the soft limit, default 10) and max-clients-per-query (the hard ceiling, default 100).
How it works
BIND deduplicates concurrent identical queries at the fetch level. When the first client queries a name that requires recursion, BIND opens a single fetch to the upstream authoritative server. Subsequent clients querying the same name, type, and class while that fetch is in flight do not trigger additional upstream queries. They attach as waiters to the existing fetch. When the fetch completes, all attached waiters receive the answer.
Without deduplication, a cache miss on a popular name would generate one upstream query per waiting client. A single expiring TTL on a hot domain could produce a burst of hundreds of simultaneous upstream fetches for the same record, consuming recursive-client slots, file descriptors, and upstream bandwidth for redundant work.
The deduplication mechanism has three layers: the initial soft limit, the auto-tuning algorithm that adjusts the effective limit based on observed demand, and the hard ceiling that caps the auto-tuning.
flowchart TD
A[Query for uncached name] --> B{Identical fetch
already in flight?}
B -->|No, first querier| C[Send fetch upstream
Hold 1 recursive-client slot]
B -->|Yes, duplicate| D{Attached waiters
below spillat?}
D -->|Yes| E[Attach to existing fetch]
D -->|No| F[Drop query
Counted in QryDropped]
C --> G[Fetch completes]
G --> H{Succeeded?}
H -->|Yes| I[Answer all waiters
Raise spillat by 5]
H -->|No| J[Fail all waiters
spillat unchanged]The soft limit: clients-per-query (default 10)
When a new query arrives for a name that already has an in-flight fetch, BIND checks how many clients are already attached as waiters. If the waiter count is below the current effective limit, the new client attaches and no upstream query is sent. If the waiter count is at or above the effective limit, the new query is dropped.
The hard ceiling: max-clients-per-query (default 100)
The absolute maximum number of clients that can wait on a single in-flight fetch, regardless of auto-tuning. The auto-tuned limit never exceeds this value.
The auto-tuning algorithm
BIND adjusts the effective limit between clients-per-query and max-clients-per-query based on observed demand. The internal variable tracking this is called spillat (spill threshold).
The algorithm works as follows:
- When a fetch completes successfully and clients were attached as waiters, BIND raises
spillatby 5. The next time a popular name triggers a burst, more clients can attach before being dropped. - After 20 minutes with no drops due to the clients-per-query quota, BIND lowers
spillatby 5, back toward the configured soft limit. spillatnever exceedsmax-clients-per-query.- If the fetch does not complete successfully (upstream authoritative server unreachable or timed out),
spillatis not raised. This prevents BIND from increasing the number of clients waiting on a name that is slow or impossible to resolve, since those waiters would all be held until the fetch times out or fails.
Disabling the limit
Setting either option to 0 removes the corresponding bound. With clients-per-query 0, there is no soft limit. With max-clients-per-query 0, there is no hard ceiling. The only remaining constraint is the global recursive-clients limit (default 1000, soft quota at 90%, meaning 900).
Where drops surface in statistics
Queries dropped because the clients-per-query quota is exceeded are counted in the QryDropped counter in NSStats. QryDropped is a superset that includes drops from multiple fetch-limit mechanisms:
| Drop source | What it limits |
|---|---|
clients-per-query / max-clients-per-query | Waiters per single in-flight fetch |
fetches-per-zone | Simultaneous fetches per domain |
fetches-per-server | Simultaneous fetches per upstream server |
rate-limit (RRL) | Response rate limiting |
RateDropped counts only RRL-related drops. To isolate clients-per-query drops, correlate QryDropped increases with periods when popular names are experiencing cache-miss bursts, and confirm RateDropped is not increasing proportionally.
BIND has a spill logging category that logs queries terminated by fetch-limit quotas. Enabling this logging during investigation can confirm whether drops are specifically from clients-per-query or from another fetch limit.
Where it shows up in production
Thundering herd on popular names
The most common scenario is TTL expiry on a hot domain. When a popular name’s cache entry expires, the next query triggers a fetch. If dozens or hundreds of clients query the same name simultaneously, they all arrive while the fetch is in flight. With the default clients-per-query of 10, once the waiter count reaches the current effective limit, additional clients are dropped if spillat has not yet been raised.
Common triggers:
- CDN edge records with short TTLs queried by many application instances
- Service discovery queries where many containers start simultaneously
- DNS-based load balancer records with low TTLs
Slow upstream amplification
When an upstream authoritative server is slow to respond, each in-flight fetch holds its waiters for the full fetch duration. If the upstream is consistently slow, spillat does not auto-tune upward because the fetch does not complete successfully. The deduplication ceiling stays at the configured soft limit, causing more drops during upstream degradation. This is correct behavior: BIND should not pile up hundreds of clients waiting on an unreachable upstream.
Misconfiguration: clients-per-query greater than max-clients-per-query
Prior to BIND 9.20.8 , if you set clients-per-query higher than max-clients-per-query without also raising the ceiling, BIND accepted the configuration silently. The auto-tuning algorithm could not function because its minimum exceeded its maximum. The soft limit would never adjust upward.
BIND 9.20.8 fixed this via GL #5224: if max-clients-per-query is set lower than clients-per-query, the value is silently adjusted upward to match clients-per-query. If you are running BIND 9.20.7 or earlier and have raised clients-per-query above 100 without also raising max-clients-per-query, your auto-tuning is effectively stuck at the floor.
Tradeoffs
Raising the limits
Increasing clients-per-query and max-clients-per-query allows more clients to benefit from deduplication during popular-name bursts. This reduces upstream query volume and improves response latency for clients that would otherwise be dropped and forced to retry.
The cost is resource consumption. Each client waiting on a fetch holds state in BIND’s memory. Each in-flight fetch holds a slot in the recursive-clients table (default 1000). More waiters per fetch means more state tied to each slot.
If you raise max-clients-per-query significantly, also verify:
- Your
recursive-clientslimit is sufficient for the combined load of unique fetches plus their waiter pools. - Your file descriptor limit has headroom. FD limits are OS-controlled via
ulimitor systemdLimitNOFILE. - The upstream authoritative servers can handle the burst of queries that deduplication was previously suppressing. Raising limits means fewer dropped clients locally but more upstream load when the deduplicated queries finally fire.
Lowering the limits
Lowering clients-per-query below the default makes BIND more aggressive about dropping duplicate queries. This can protect the resolver during upstream degradation by limiting how many clients are held waiting on slow fetches. The tradeoff is more client-visible drops during normal popular-name bursts, increasing perceived latency for clients that must retry.
When to leave the defaults
For most recursive resolvers, the defaults (10 and 100) are reasonable. The auto-tuning algorithm adapts the effective limit based on actual demand, so manual tuning is only needed when:
- You observe sustained
QryDroppedincreases correlated with popular-name bursts. - You have confirmed via the
spilllogging category or process of elimination that drops are from clients-per-query, not RRL or fetches-per-zone. - Your workload has a specific pattern the defaults do not handle well, such as a very high concentration of queries for a small set of names with short TTLs.
Signals to watch
| Signal | Why it matters | Warning sign |
|---|---|---|
QryDropped (NSStats) | Superset counter for all fetch-limit drops including clients-per-query | Sustained increase correlated with popular-name cache-miss bursts |
RateDropped (NSStats) | RRL-specific drops; helps isolate whether QryDropped increase is RRL or clients-per-query | Flat RateDropped while QryDropped rises indicates fetch limits are the cause |
RecursClients (NSStats) | Global recursive client pressure; each fetch holds a slot | Rising toward limit alongside popular-name bursts |
NumFetch (per-view resolver stats) | Per-view active fetch count | High NumFetch with moderate RecursClients suggests many waiters per fetch |
| File descriptor usage | Each waiter holds state; FD headroom needed for upstream sockets | FD usage approaching limit after raising clients-per-query |
| Cache hit ratio | Falling hit ratio increases cache misses, increasing dedup pressure | Declining hit ratio alongside rising QryDropped |
How Netdata helps
- Per-second
QryDroppedandRateDroppedcollection lets you pinpoint the exact moment drops begin and correlate them with cache-miss bursts, TTL expirations, or upstream RTT shifts. Thundering-herd events can be brief enough that minute-level polling misses them. RecursClientsas a live gauge shows whether deduplication drops are happening because the global recursive-client table is also saturated, or whether the issue is isolated to the per-query dedup limit.- Per-view
NumFetchreveals whether deduplication pressure is concentrated in one view, common in split-horizon deployments where internal clients hammer service discovery names. - Cache hit ratio trends show whether declining cache effectiveness is driving more cache misses and therefore more deduplication events.
- Upstream RTT distribution (
QryRTT*buckets) shows whether slow upstream responses are preventingspillatfrom auto-tuning upward, keeping the effective dedup limit pinned at the floor. - File descriptor monitoring confirms whether raising
max-clients-per-queryis safe given current FD headroom on the host.
Related guides
- How BIND actually works in production: a mental model for operators
- BIND monitoring checklist: the signals every production resolver and authoritative server needs
- BIND monitoring maturity model: from survival to expert
- BIND ’no more recursive clients: quota reached’: the recursive-clients circuit breaker
- named not responding on port 53: total outage versus UDP-works-TCP-fails
- BIND NXDOMAIN spike: DGA malware, water torture, and Windows suffix search lists
- BIND RecursClients climbing toward the limit: reading the recursive saturation gauge
- BIND REFUSED responses: ACL denials, recursion policy, and clients that get locked out
- rndc not responding: control-plane failure while queries still work
- BIND SERVFAIL responses: what a DNS SERVFAIL actually means and how to trace the cause






