Cached vs Non-Cached DNS: A Lazy Experiment That Got Interesting

I was bored and just wanted to code something to spark the life, of course.
And, to fight it, my brain thought, "how fast really is my DNS provider?"
No architecture diagram. No benchmarking framework. No “this is how DNS testing should be done.” In fact, I didn’t even check how DNS benchmarking is usually done in the real world.
So, I casually started coding and end up with two scripts, and honestly, the results were way more interesting than I expected.
Before you even read further, I’d actually recommend:
👉 Go through the scripts
👉 Run them yourself
👉 Then come back
Repo: https://github.com/FlareXes/fastest-dns
Because, the results depend a lot on your network, your routing, your ISP… everything.
The Idea
The idea was simple:
Take a few popular public DNS resolvers:
Cloudflare (1.1.1.1)
Google (8.8.8.8)
Quad9 (9.9.9.9)
Test them in two scenarios:
Cached DNS
Non-cached DNS
And measure:
Average latency
Median latency
P95 latency
No fancy tooling. Just Python + dig.
🛑 Check Before You Proceed (So, Don’t Skip)
Before you get excited and hit run, a few things that can completely ruin your results:
First - Make Sure Nothing Is Overriding Your DNS
If you’re using tools like: VPNs, Custom DNS clients like Portmaster, or OS-level DNS overrides.
Then there’s a good chance your DNS queries are being intercepted or rerouted.
Even if your script says:
dig @1.1.1.1 example.com
Your system might still ignore that and use something else. If that happens, your results are basically fake. You’ll think you’re benchmarking Cloudflare but you’re not.
Second - Don’t Abuse Public DNS Servers (Seriously)
Don’t go wild with queries. These are public DNS services. Don’t hammer them with insane request rates.
The script already behaves nicely:
time.sleep(random.uniform(0.5, 1.0))
Keep it reasonable. Respect the service.
Third - You Can Tweak Everything
This isn’t a rigid benchmark. It’s more like a playground. You can change pretty much everything:
SERVERS = {
"Cloudflare": "1.1.1.1",
"Google": "8.8.8.8",
"Quad9": "9.9.9.9"
}
RUNS = 20
Want to:
Add your ISP’s DNS? Go ahead.
Increase runs to 100? Sure.
Change delays between queries? Do it.
Swap domains? Totally fine.
This is meant to be explored, not followed blindly.
Simple Script — Talking to DNS
Both scripts revolve around one simple idea, run dig, extract the query time.
Here’s the core piece:
def query_dns(server, domain):
result = subprocess.run(
["dig", f"@{server}", domain],
capture_output=True,
text=True
)
match = re.search(r"Query time: (\d+)", result.stdout)
return int(match.group(1)) if match else None
Second, I tracked Metrics that actually matters
Average (Looks Useful, Lies Sometimes)
Add everything and divide by number of runsBut here’s the problem:
If one or two queries spike hard (which happens in DNS), they can pull the average up significantly. So you might think: “This DNS is slow”.When actually: 90% of queries were fast and 2 were just unlucky.
Median (What You Actually Experience Most of the Time)
It ignores extreme spikes. So if you want to know, what users usually experience how stable the resolver is.P95 (Worst Case)
It tells you: “What do the slowest 5% of queries look like?”
Phase One — Cached DNS
I started with cached DNS.
The idea here is simple: query the same domain repeatedly and let the resolver cache do its job.
Here’s the key part of the script :
# Warm-up queries
for _ in range(3):
query_dns(ip, DOMAIN)
Why Warm-Up Matters
This primes the cache.
Without it:
First few queries would be slower (cold start)
Results would be inconsistent
After warm-up, every query hits cached data.
Second Phase — Non-Cached DNS
You cannot force public DNS resolvers to skip cache. So, I forced DNS resolver to miss the cache by generates a new long domain every time.
def random_domain():
name = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
return f"{name}.com"
These domains are almost guaranteed to not exist
no cached answer exists
The resolver has to do full DNS resolution
It’s a bit hacky but it works surprisingly well.
Now the resolver has to actually do its job:
Ask root servers
Ask
.comTLD serversAsk authoritative servers
Each step adds: Network hops, Latency, Variability.
One thing I almost ignored, I added random delays between queries to:
prevent artificial bursts
avoid hammering servers
time.sleep(random.uniform(0.5, 1.0))
Final Thoughts
I don't any for this one, thanks for the reading.





