Plate 41
getaddrinfo DNS Resolve Cost Localhost Lab
Hands-on getaddrinfo cost lab: localhost vs numeric IP vs AI_NUMERICHOST, plus hosts/stub names with real p50/p95 µs. DNS sinkhole labeled honestly here.
Aditya Challa6 min read
On this page
- Intro — what this post promises
- Network honesty (read this first)
- Lab topology
- Lead table — warm loop (p50 / p95)
- Fresh process shots (p50 of 8 runs)
- How to read these numbers
- Pitfalls we hit (or avoided)
- Practical checklist
- Methodology footnote
- Versions / environment
- Dual-stack localhost tax
- Where this sits vs connect() cost
- Ratio sheet (warm p50 ÷ numeric 127.0.0.1)
- Verdict
Intro — what this post promises
Every outbound connect starts with getaddrinfo. Numeric IPs should be near-free; hostnames go through nsswitch/DNS. How many microseconds on this lab box — and what can’t we claim?
This is a hands-on lab with measured numbers:
- Warm-loop p50/p95 for
localhost,127.0.0.1,::1,/etc/hostsexample.com. AI_NUMERICHOSTon IPs (and expected failure on hostnames).- Fresh-process first vs second lookup.
- Honest DNS sinkhole label: many names (even
.invalid) return 198.18.0.1.
Related links:
- http keepalive vs connection close lab
- openssl TLS full vs resume localhost lab
- unix domain socket vs TCP localhost lab
- tcp nodelay vs nagle localhost lab
- Why your average latency graph is lying (p50 / p95 / p99)
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Resolver is a stub/sinkhole (198.18.0.1 for dns.google, cloudflare.com, and *.invalid). This is not public DNS RTT. Affiliates: 0.
Verdict up front: warm numeric IP ~2.2 µs; localhost ~31 µs; hosts/stub names ~150–170 µs. Fresh process first localhost ~1.4 ms, then ~58 µs.
Network honesty (read this first)
| Probe name | getaddrinfo result |
|---|---|
| dns.google | 198.18.0.1 |
| cloudflare.com | 198.18.0.1 |
| never-resolves-shoppercove-lab.invalid | 198.18.0.1 |
| example.com | 198.18.0.1 via /etc/hosts |
| localhost | ::1 + 127.0.0.1 |
So “DNS” arms here measure getaddrinfo + stub nsswitch, not overseas authoritative latency. The ranking (numeric ≪ localhost ≪ name) still teaches the API cost model.
Related links:
Lab topology
Script: lab-evidence/43-getaddrinfo/results/run_lab.py.
Lead table — warm loop (p50 / p95)
| Arm | p50 | p95 |
|---|---|---|
127.0.0.1 | 2.24 µs | 2.70 |
127.0.0.1 + AI_NUMERICHOST | 2.26 µs | 2.66 |
::1 + AI_NUMERICHOST | 1.91 µs | 2.12 |
localhost AF_INET | 20.5 µs | 35.2 |
localhost AF_UNSPEC | 31.5 µs | 48.2 |
example.com (hosts) | 169 µs | 332 |
stub dns.google | 167 µs | 300 |
stub cloudflare.com | 153 µs | 215 |
stub .invalid | 164 µs | 245 |
AI_NUMERICHOST on example.com → gaierror (expected).
Related links:
Fresh process shots (p50 of 8 runs)
| Call | p50 |
|---|---|
| localhost first in new process | 1375 µs |
| localhost second | 58 µs |
127.0.0.1 NUMERICHOST | 10 µs |
Library/nss init dominates the first call. Don’t compare a cold first connect to a warm microbench without labeling.
How to read these numbers
- Prefer numeric IPs or pre-resolved addresses on hot paths (~2 µs here).
localhoststring costs tens of µs warm; milliseconds cold.AI_NUMERICHOSTskips name syntax — use when input must already be an IP.- Stub ~150 µs ≠ “DNS is always fast on the public internet.”
Related links:
- http keepalive vs connection close lab
- openssl TLS full vs resume localhost lab
- tcp nodelay vs nagle localhost lab
Pitfalls we hit (or avoided)
- Publishing stub RTT as Cloudflare latency — sinkhole labeled.
- Ignoring first-call vs warm — fresh process table included.
- Assuming AI_NUMERICHOST speeds hostnames — it errors.
- AF_UNSPEC vs AF_INET — dual lookup costs more for localhost here (31 vs 20 µs).
- Caching myths — glibc/nss may cache; our warm loops are steady-state.
Practical checklist
- Hot path: cache
getaddrinforesults or connect with numeric addresses. - Config validators:
AI_NUMERICHOSTwhen only IPs allowed. - Benchmarks: separate cold process vs warm loop.
- Production DNS: re-measure against real resolvers — not this sinkhole.
- Prefer UDS for local service meshes when names are pure overhead (UDS lab).
Related links:
- json vs orjson vs msgpack localhost lab
- fork COW RSS vs spawn localhost lab
- SQLite WAL vs DELETE journal localhost lab
- sha256 vs blake2 xxhash localhost lab
Methodology footnote
Timed with time.perf_counter around a single getaddrinfo call. Warm arms discard warmup iterations. Fresh-process arms spawn new Python interpreters. Sinkhole detection compares resolved IPs for unrelated names.
Versions / environment
- Python 3.13.5
nameserver 10.0.0.2in resolv.conf; hosts mapsexample.com→ 198.18.0.1- Lab DNS sinkhole to 198.18.0.1
If your sidecar resolves every RPC by hostname, tens to hundreds of µs add up — unless you are on a sinkhole that lies quickly, which is its own incident.
Dual-stack localhost tax
AF_UNSPEC for localhost returned both AAAA (::1) and A (127.0.0.1) and cost ~31 µs p50 versus ~20 µs for AF_INET alone. If your client only speaks IPv4, narrowing the family avoids extra work. If you need Happy Eyeballs across real dual-stack DNS, expect more than this stub — and again, re-bench off the sinkhole.
Where this sits vs connect() cost
A localhost TCP connect on this fleet has been tens of microseconds in earlier labs; a warm numeric getaddrinfo at ~2 µs is negligible beside it, while a cold ~1 ms first resolve is not. Cache at process start when the peer set is fixed.
Service meshes that already dial by IP bypass this tax entirely. Hostname-based configs are for humans; hot data planes should not rediscover the same string every request.
Ratio sheet (warm p50 ÷ numeric 127.0.0.1)
| Arm | ÷ numeric (~2.24 µs) |
|---|---|
| AI_NUMERICHOST IPv4 | ~1.0× |
| localhost AF_INET | ~9× |
| localhost AF_UNSPEC | ~14× |
| hosts/stub names | ~70× |
| fresh process first localhost | ~600× |
That last row is why “resolve once at startup” exists. The middle rows are why connection pools keep addresses warm.
On a real recursive resolver with cache misses, multiply again — this sinkhole cannot show you that; treat stub ~150 µs as a lower bound on the getaddrinfo path with instant answers, not an SLA for the Internet.
When documenting outages, separate “DNS provider slow” from “we call getaddrinfo on every request without caching.”
Verdict
Warm numeric getaddrinfo ~2 µs, localhost ~20–31 µs, hosts/stub names ~150–170 µs on this box. A new process’s first localhost resolve sat near 1.4 ms. Use numeric/cached addresses on hot paths — and do not confuse this sinkhole with the public DNS.
Evidence: lab-evidence/43-getaddrinfo/results/. Affiliates: 0.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. Warm loop: 127.0.0.1 ~2.2 us; AI_NUMERICHOST ~2.3 us; localhost AF_UNSPEC p50 31 us p95 48; localhost AF_INET 20 us. example.com (/etc/hosts) ~169 us. Stub DNS (all names -> 198.18.0.1 including .invalid) ~153-167 us — NOT public DNS RTT. Fresh process localhost first p50 ~1375 us then ~58 us. Affiliates: 0. Evidence: lab-evidence/43-getaddrinfo/.
Related links
Plate 17
platform vs os.uname Inventory: Localhost Lab
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 75
uuid.uuid4 vs uuid.uuid1: Localhost Lab
Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 50
signal vs threading.Event Wakeup: Localhost Lab
Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026