Raised as a question rather than filed as a defect, because it may be a deliberate simplicity choice. @sneak — a one-word answer settles it.
Observation
internal/resolver/iterative.go — rootServerList() returns the thirteen roots in a fixed order beginning 198.41.0.4 (a.root-servers.net), and queryServers walks that slice strictly in order, returning on the first success:
Because the happy path succeeds on the first entry, every iterative resolution dnswatcher performs begins by querying a.root-servers.net, and the other twelve roots are contacted only when it fails. The remaining twelve are, in practice, a failover list rather than a pool.
Two consequences:
Load concentration. Real resolvers randomise or RTT-rank root selection precisely so load spreads across the anycast constellation. With many targets and a short DNSWATCHER_DNS_INTERVAL, this points all root traffic at one operator.
Correlated failure. If a.root-servers.net is slow or unreachable from the host, every single resolution pays that timeout before failing over, rather than one-in-thirteen of them.
This surfaced while investigating the test flakiness in #93: with ~35 parallel tests on a 48-core machine, every test fired its first query at the same IP within milliseconds of the others, which fits the observed "a different subset fails each run" signature. That issue was scoped test-side and fixed test-side, so this production behaviour was deliberately left untouched.
Note queryServers is shared — it is also used for the per-domain nameserver lists, not only the roots — so any change affects both call sites.
The question
Is the fixed order intentional (deterministic, simple, easy to reason about) or incidental? If incidental, the fix is small: shuffle the list per resolution, or track per-server RTT and prefer the fastest.
If you want it changed, say so and this becomes a normal work unit with the definition of done below. If it is deliberate, say so and I will close this.
Definition of done, if you want it fixed
Root server selection is randomised per resolution (or RTT-ranked), so no single root receives all first-contact traffic.
Failover behaviour is unchanged: all thirteen are still tried before the resolution fails.
The same treatment is applied deliberately, or deliberately not applied, to the per-domain nameserver call site — and the choice stated.
Tests against live DNS, per repo policy. No mocking.
make check green.
Raised as a question rather than filed as a defect, because it may be a deliberate simplicity choice. @sneak — a one-word answer settles it.
## Observation
`internal/resolver/iterative.go` — `rootServerList()` returns the thirteen roots in a fixed order beginning `198.41.0.4` (`a.root-servers.net`), and `queryServers` walks that slice strictly in order, returning on the first success:
```go
for _, ip := range servers {
resp, err := r.queryDNS(ctx, ip, name, qtype)
if err == nil {
return resp, nil
}
lastErr = err
}
```
Because the happy path succeeds on the first entry, **every iterative resolution dnswatcher performs begins by querying `a.root-servers.net`**, and the other twelve roots are contacted only when it fails. The remaining twelve are, in practice, a failover list rather than a pool.
Two consequences:
1. **Load concentration.** Real resolvers randomise or RTT-rank root selection precisely so load spreads across the anycast constellation. With many targets and a short `DNSWATCHER_DNS_INTERVAL`, this points all root traffic at one operator.
2. **Correlated failure.** If `a.root-servers.net` is slow or unreachable from the host, every single resolution pays that timeout before failing over, rather than one-in-thirteen of them.
This surfaced while investigating the test flakiness in https://git.eeqj.de/sneak/dnswatcher/issues/93: with ~35 parallel tests on a 48-core machine, every test fired its first query at the same IP within milliseconds of the others, which fits the observed "a different subset fails each run" signature. That issue was scoped test-side and fixed test-side, so this production behaviour was deliberately left untouched.
Note `queryServers` is shared — it is also used for the per-domain nameserver lists, not only the roots — so any change affects both call sites.
## The question
Is the fixed order intentional (deterministic, simple, easy to reason about) or incidental? If incidental, the fix is small: shuffle the list per resolution, or track per-server RTT and prefer the fastest.
If you want it changed, say so and this becomes a normal work unit with the definition of done below. If it is deliberate, say so and I will close this.
## Definition of done, if you want it fixed
1. Root server selection is randomised per resolution (or RTT-ranked), so no single root receives all first-contact traffic.
2. Failover behaviour is unchanged: all thirteen are still tried before the resolution fails.
3. The same treatment is applied deliberately, or deliberately not applied, to the per-domain nameserver call site — and the choice stated.
4. Tests against live DNS, per repo policy. No mocking.
5. `make check` green.
sneak
was assigned by clawbot2026-08-10 15:17:07 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Raised as a question rather than filed as a defect, because it may be a deliberate simplicity choice. @sneak — a one-word answer settles it.
Observation
internal/resolver/iterative.go—rootServerList()returns the thirteen roots in a fixed order beginning198.41.0.4(a.root-servers.net), andqueryServerswalks that slice strictly in order, returning on the first success:Because the happy path succeeds on the first entry, every iterative resolution dnswatcher performs begins by querying
a.root-servers.net, and the other twelve roots are contacted only when it fails. The remaining twelve are, in practice, a failover list rather than a pool.Two consequences:
DNSWATCHER_DNS_INTERVAL, this points all root traffic at one operator.a.root-servers.netis slow or unreachable from the host, every single resolution pays that timeout before failing over, rather than one-in-thirteen of them.This surfaced while investigating the test flakiness in #93: with ~35 parallel tests on a 48-core machine, every test fired its first query at the same IP within milliseconds of the others, which fits the observed "a different subset fails each run" signature. That issue was scoped test-side and fixed test-side, so this production behaviour was deliberately left untouched.
Note
queryServersis shared — it is also used for the per-domain nameserver lists, not only the roots — so any change affects both call sites.The question
Is the fixed order intentional (deterministic, simple, easy to reason about) or incidental? If incidental, the fix is small: shuffle the list per resolution, or track per-server RTT and prefer the fastest.
If you want it changed, say so and this becomes a normal work unit with the definition of done below. If it is deliberate, say so and I will close this.
Definition of done, if you want it fixed
make checkgreen.