Core Question
Core Question
Why do most DNS queries never leave the resolver, and what exactly decides hit versus miss?
Outcome
Outcome
By the end of this session, the learner should be able to:
- explain why the cache is consulted before any network step
- name the key a cache entry is stored under
- explain why the same name with a different type is a miss
- describe what happens to an entry the moment it expires
Read Order
Read Order
- Read
CacheEntry - Read
ResolverCache - Read
entry_is_expired() - Read
lookup() - Read
store() - Run
examples/dns/session_04_walkthrough.py
Read It Like Code
Read It Like Code
CacheEntry(
records,
stored_at,
ttl,
)Fields That Matter
Fields That Matter
| Field | Why it matters |
|---|---|
records | The answer being reused. |
stored_at | When the answer arrived. Expiry is computed from this, not stored. |
ttl | How long the authoritative side allowed this answer to be reused. |
Decision Flow
Decision Flow
key not in cache -> Miss (go to the network) entry past stored_at + ttl -> Expired (delete, then go to the network) otherwise -> Hit (answer locally)
Reading Lens
Reading Lens
The important move in this session is to stop thinking of the cache as an optimization detail and start asking:
- what key was this answer stored under?
- what time did
lookup()run, relative tostored_at + ttl? - did this query touch the network at all?
Toy Model Boundary
Toy Model Boundary
Real caches store negative answers, partial chains, and per-record TTLs, and they cap TTLs with local policy. This lesson keeps one entry per question key so the hit/miss/expired branching stays readable.
Time is an integer passed in as now. Making the clock an argument instead of calling a clock inside is what makes every scenario in the walkthrough reproducible.
Code Landmarks
Code Landmarks
question_key() (from Session 01)
The cache key is the question identity from Session 01, unchanged. Different type, different key, different entry.
entry_is_expired()
One comparison: now >= stored_at + ttl. Expiry is arithmetic, not a background process.
lookup()
The main reading target. Three outcomes, and the Expired branch also deletes — an expired entry is treated as if it were never there.
store()
A network answer becomes a cache entry stamped with arrival time. Nothing else about the resolver changes.
Failure Questions
Failure Questions
Use the source file to answer these:
- Why is
lookup()forWWW.Example.COM.a hit after storingwww.example.com? - Why is
lookup()for the same name withqtype="AAAA"a miss? - At exactly
now == stored_at + ttl, is the entry alive or expired? Which comparison decides? - After an Expired lookup, what does the very next lookup for the same key return, and why?
- Why does
lookup()takenowas a parameter instead of reading a clock?
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/dns/session_04_walkthrough.py
The walkthrough stores one answer and shows hit, miss, and expiry purely by moving now.
Done When
Done When
The learner can say all of the following without looking at notes:
- "The cache is keyed by the full question, so name alone is not enough."
- "Hit, miss, and expired are the only three outcomes, and expired behaves like miss plus a delete."
- "Most queries are answered by arithmetic on
stored_at + ttl, not by the network."
References
References
- RFC 1034 Section 5.3.1
- RFC 1035 Section 7.4
- RFC 2308 Section 5 (caching, including negative caching this toy skips)
Continue