Lesson 06 / 12
The Domain Name System
The distributed database that translates a name into an address, the resolution chain from root to authoritative server, and time-to-live-based caching.
Contents
The previous lesson established the structure of a name and how authority over it is delegated. It was said that the registry does not hold the name’s address, only the information “ask these servers about this name.”
This lesson covers the machinery that carries out that asking: the domain name
system. The question to answer is how the name example.test is translated into an
address, and why that translation is not redone from scratch on every request.
Why Not a Single File
Keeping a file that maps names to addresses is what was done while the network held few machines. Every machine carried a copy; when a name was added, the file was updated and redistributed to everyone.
This scheme has three distinct limits, and all three are about scale. The file grows in proportion to the number of names. Every change requires every copy to be refreshed, and the frequency of change grows with the number of machines. And a single file means a single authority: every name request has to go through the same point.
The domain name system overcomes all three limits by using a hierarchy. The database is split, authority over each part is delegated to a separate party, and no one holds the whole.
The Roles in the Chain
Four distinct roles exist in resolution.
The stub resolver lives inside the machine making the request. The application’s call to translate a name into an address goes to this component in the operating system. It does not perform resolution itself; it forwards the question to a resolver and waits for the answer.
The recursive resolver is the server that takes on the work. It is usually a server run by the access provider or the organization. It carries out every step needed to find the answer and holds the cache itself.
The root servers sit at the top of the hierarchy. They do not know addresses themselves; they know only which servers each top-level name has been delegated to.
The authoritative servers are the servers that hold a name’s answers as the source
of record. For example.test, the authoritative server is the one the name owner
designated, and it is the same one reported during registration in the previous lesson.
The Resolution Chain
Assuming example.test is not present in any cache at all, the query follows these
steps:
- The application asks the stub resolver in the operating system for the address of
example.test. - The stub resolver forwards the question to the recursive resolver and waits for the answer.
- The recursive resolver asks a root server. The root does not know the address; it
says which servers the authority for the top-level name
testhas been delegated to. - The recursive resolver asks one of those servers. It does not know the address
either; it says which servers the authority for the name
example.testhas been delegated to. - The recursive resolver asks the authoritative server and receives the address.
- The answer returns to the stub resolver, and from there to the application.
The division of labor separates two distinct query forms. The question the stub resolver asks the recursive resolver is recursive: “find this name’s answer and bring it to me.” The questions the recursive resolver asks the root and intermediate servers are iterative: “tell me if you know this, and if you do not, tell me who to ask.”
This distinction is not accidental. If the root and top-level servers had to ask someone else and assemble the answer for every incoming question, their load would be unsustainable. Each intermediate server’s work is a single, fixed-cost step; assembly is concentrated in one recursive resolver.
The fact that the answer at every step takes the form “I do not know the address, ask this one” is a direct reflection of the authority delegation from the previous lesson. The length of the chain is determined not by the number of labels in the name but by how many times authority has been delegated.
The Shape of a Record
Query tools let these answers be seen directly. The command below queries the name that denotes the machine itself; because the answer is produced locally, it is the same everywhere.
dig +noall +answer localhost A
localhost. 0 IN A 127.0.0.1
This single line shows a record’s five fields:
| Field | Value in the example | Meaning |
|---|---|---|
| Name | localhost. |
The name the record belongs to; the trailing dot denotes the root |
| Time to live | 0 |
How many seconds the answer may be kept in cache |
| Class | IN |
The address family; always this value for the internet |
| Type | A |
The record’s type; A carries an IPv4 address |
| Data | 127.0.0.1 |
The record’s content |
When the same command is run for an actual domain name, the format stays the same and the values change. This course gives no such output: the time to live depends on when the query is made, the returned address on where it is made from, and both begin going stale the moment they are written on a page. Run the command yourself and the five fields you see will match the ones above.
The details of record types are the next lesson’s subject.
Caching and Time to Live
If the chain above were run from scratch on every request, opening a single page would require multiple network round trips, and the root servers would see every query made anywhere in the world. What makes the system operable is the storing of answers.
Every record carries a time to live with it: how many seconds the answer can be used without being asked again. The recursive resolver keeps the answer in its cache for this period and answers the same question without running the chain when it recurs.
The party that sets this duration is the authoritative server issuing the answer — in other words, the name owner. This gives the name owner direct control and requires managing a trade-off.
The program below shows this behavior. Because the clock is supplied externally, the output is the same on every run.
class NameCache: """TTL-limited name->address cache. Clock is supplied externally.""" def __init__(self) -> None: self.records: dict[str, tuple[str, int]] = {} self.upstream_query_count = 0 def resolve(self, name: str, now: int, ttl: int, address: str) -> str: record = self.records.get(name) if record is not None and record[1] > now: return f"from cache (remaining TTL: {record[1] - now})" self.upstream_query_count += 1 self.records[name] = (address, now + ttl) return f"upstream query (new TTL: {ttl})" cache = NameCache() for second in [0, 100, 250, 300, 400]: result = cache.resolve("example.test", now=second, ttl=300, address="192.0.2.10") print(f"t={second:>3}s {result}") print(f"total upstream queries: {cache.upstream_query_count}")
t= 0s upstream query (new TTL: 300) t=100s from cache (remaining TTL: 200) t=250s from cache (remaining TTL: 50) t=300s upstream query (new TTL: 300) t=400s from cache (remaining TTL: 200) total upstream queries: 2
Five resolution requests led to only two upstream queries. On an actual recursive resolver, the ratio is far sharper because many users share the same cached record.
The trade-off is this:
- A long time to live reduces upstream queries and speeds up answers. In exchange, when a change is made, the old answer stays in use until the duration expires.
- A short time to live lets a change propagate quickly. In exchange, it raises query load and average response time.
This has a direct counterpart in operating practice: before moving a server, the time to live is lowered; the move is carried out, and once propagation completes the duration is raised back. Skip the advance lowering, and the old address keeps living in caches for as long as the old duration.
This produces a consequence worth keeping in mind throughout: a domain name change does not appear everywhere at once. Different users get different answers for a while, depending on the state of their own caches. This is not a fault; it follows directly from the definition of caching.
Queries with a negative answer are also stored. When a name that does not exist is queried, the answer “no such name exists” is also kept in cache for a while; this is why a newly created name does not appear immediately.
The cache does not live in a single place. The stub resolver, the recursive resolver and the requesting application can each hold a separate cache. Tracing which layer a delay or a stale answer came from therefore requires examining the layers separately.
Summary
- Keeping names in a single file does not scale; the domain name system splits the database by hierarchy and delegates authority over each part.
- The resolution chain starts at the root, descends to the top-level name’s server and from there to the authoritative server; the answer at each step points not to the next address but to the next authority.
- The stub resolver asks the recursive resolver recursively; the recursive resolver asks intermediate servers iteratively. This division of labor keeps the load on root and top-level servers fixed.
- A record consists of name, time to live, class, type and data fields.
- Time to live is set by the name owner and manages the trade-off between query load and how fast a change propagates.
- Caching happens across multiple layers and covers negative answers too; a change does not appear everywhere at the same moment.
Next Step
This lesson showed a record’s five fields and passed over the type field with a single
example, A. Yet a name does not hold only an address underneath it: redirection to
another name, mail routing, verification texts and authority declarations are stored in
the same layout. The next lesson covers these types, which problem each answers, and the
combination rules among them.
To keep your progress and take notes, Log in
My notes
Log in to take notes.