ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 41

  1. Blog

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 Challa·30 September 2026·6 min read

Summary
On this page
  1. Intro — what this post promises
  2. Network honesty (read this first)
  3. Lab topology
  4. Lead table — warm loop (p50 / p95)
  5. Fresh process shots (p50 of 8 runs)
  6. How to read these numbers
  7. Pitfalls we hit (or avoided)
  8. Practical checklist
  9. Methodology footnote
  10. Versions / environment
  11. Dual-stack localhost tax
  12. Where this sits vs connect() cost
  13. Ratio sheet (warm p50 ÷ numeric 127.0.0.1)
  14. 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:

  1. Warm-loop p50/p95 for localhost, 127.0.0.1, ::1, /etc/hosts example.com.
  2. AI_NUMERICHOST on IPs (and expected failure on hostnames).
  3. Fresh-process first vs second lookup.
  4. 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 namegetaddrinfo result
dns.google198.18.0.1
cloudflare.com198.18.0.1
never-resolves-shoppercove-lab.invalid198.18.0.1
example.com198.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:

  • unix domain socket vs TCP localhost lab

Lab topology

socket.getaddrinfo(host, port, type=SOCK_STREAM, flags=..., family=...)
Warm loops: 500–1000 calls after warmup
Fresh process: 8× python -c one-shots for first/second localhost + numeric

Script: lab-evidence/43-getaddrinfo/results/run_lab.py.


Lead table — warm loop (p50 / p95)

Armp50p95
127.0.0.12.24 µs2.70
127.0.0.1 + AI_NUMERICHOST2.26 µs2.66
::1 + AI_NUMERICHOST1.91 µs2.12
localhost AF_INET20.5 µs35.2
localhost AF_UNSPEC31.5 µs48.2
example.com (hosts)169 µs332
stub dns.google167 µs300
stub cloudflare.com153 µs215
stub .invalid164 µs245

AI_NUMERICHOST on example.com → gaierror (expected).

Related links:

  • Why your average latency graph is lying (p50 / p95 / p99)

Fresh process shots (p50 of 8 runs)

Callp50
localhost first in new process1375 µs
localhost second58 µs
127.0.0.1 NUMERICHOST10 µ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).
  • localhost string costs tens of µs warm; milliseconds cold.
  • AI_NUMERICHOST skips 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)

  1. Publishing stub RTT as Cloudflare latency — sinkhole labeled.
  2. Ignoring first-call vs warm — fresh process table included.
  3. Assuming AI_NUMERICHOST speeds hostnames — it errors.
  4. AF_UNSPEC vs AF_INET — dual lookup costs more for localhost here (31 vs 20 µs).
  5. Caching myths — glibc/nss may cache; our warm loops are steady-state.

Practical checklist

  • Hot path: cache getaddrinfo results or connect with numeric addresses.
  • Config validators: AI_NUMERICHOST when 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.2 in resolv.conf; hosts maps example.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.

getaddrinfoai_numerichostdns resolve latencylocalhost lookupnsswitchlocalhost labsrepython socket

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/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

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

On this page

  1. Intro — what this post promises
  2. Network honesty (read this first)
  3. Lab topology
  4. Lead table — warm loop (p50 / p95)
  5. Fresh process shots (p50 of 8 runs)
  6. How to read these numbers
  7. Pitfalls we hit (or avoided)
  8. Practical checklist
  9. Methodology footnote
  10. Versions / environment
  11. Dual-stack localhost tax
  12. Where this sits vs connect() cost
  13. Ratio sheet (warm p50 ÷ numeric 127.0.0.1)
  14. Verdict
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove