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
Owner ruling (chat, 2026-10-01 ~19:08 UTC): "yes randomize it". The root servers, and any server list the iterative resolver walks, are tried in a random order on each resolution rather than always starting at a.root-servers.net; it still falls through to the next on failure. Assigned to clawbot for the dnswatcher manager. Iron rule for the test: no mocked DNS. Test the ordering itself (for example with a seeded random source), and keep any resolution test on live DNS.
model: opus-5-5
Owner ruling (chat, 2026-10-01 ~19:08 UTC): "yes randomize it". The root servers, and any server list the iterative resolver walks, are tried in a random order on each resolution rather than always starting at `a.root-servers.net`; it still falls through to the next on failure. Assigned to clawbot for the dnswatcher manager. Iron rule for the test: no mocked DNS. Test the ordering itself (for example with a seeded random source), and keep any resolution test on live DNS.
model: opus-5-5
sneak
was unassigned by clawbot2026-10-01 21:08:36 +02:00
clawbot
self-assigned this 2026-10-01 21:08:36 +02:00
Done in #199: every list of servers the resolver walks, the root servers and each zone's nameservers alike, is now tried in a random order each time, with failover unchanged.
Model: opus-5-5
Done in https://git.eeqj.de/sneak/dnswatcher/pulls/199: every list of servers the resolver walks, the root servers and each zone's nameservers alike, is now tried in a random order each time, with failover unchanged.
Model: opus-5-5
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.clawbot referenced this issue2026-09-21 09:44:48 +02:00
Owner ruling (chat, 2026-10-01 ~19:08 UTC): "yes randomize it". The root servers, and any server list the iterative resolver walks, are tried in a random order on each resolution rather than always starting at
a.root-servers.net; it still falls through to the next on failure. Assigned to clawbot for the dnswatcher manager. Iron rule for the test: no mocked DNS. Test the ordering itself (for example with a seeded random source), and keep any resolution test on live DNS.model: opus-5-5
Done in #199: every list of servers the resolver walks, the root servers and each zone's nameservers alike, is now tried in a random order each time, with failover unchanged.
Model: opus-5-5