Part VIII

Distributed File Systems

How can programs on many computers open, read and write files kept on other machines as if those files were on their own disk? This Part builds the answer in layers: an ordinary UNIX file system, the requirements a distributed one must meet, an abstract file service architecture, and then the real thing, Sun NFS, with AFS as its rival. File service design is examined every year, from drawing the architecture to the NFS cache check.

  • You willdraw the file service architecture and explain why its flat file service can be stateless and idempotent.
  • You willtrace an NFS mount, lookup and read through the VFS, v-nodes and file handles.
  • You willrun the NFS client cache check (Tc, Tm, t) on a timeline, and tell write-through from commit.
  • You willcompare NFS with AFS, and answer the Tutorial 8 questions.
8.1

Why a Distributed File System? exam

Imagine a lab of fifty computers. A simulation job on one machine produces results that a plotting job on another machine needs, and every student wants their home directory to follow them to whichever computer they sit at. If each computer only had its own disk, files would have to be copied around by hand, and nobody would know which copy is the latest. A distributed file system solves this by letting every computer use files that live elsewhere through the ordinary file calls it already knows.

Definition · distributed file system (DFS)

A distributed file system lets programs on different computers store and access remote files exactly as they do local files, through the normal file-system interface (open, read, write and so on). Its performance and reliability should be comparable to those of local disks (CDK §12.1).

Three reasons make a DFS worth building. Each one is something a single local disk cannot give you.

Reason 1

Sharing

Jobs on several machines can read one shared dataset and write their results into a shared area, with no copying.
Reason 2

Location independence

Users don't need to remember whether server A, B or C holds a document. They just use its name.
Reason 3

Easier management

Dedicated storage servers simplify capacity planning, access control, backup and archiving. Other services (name, authentication, print, web) can keep their own data on the file service too.
NETWORK CLIENT COMPUTER 1 job 1: read /shared/dataset CLIENT COMPUTER 2 job 2: read /shared/dataset CLIENT COMPUTER 3 job 3: write /shared/results FILE SERVICE one shared file tree /shared/dataset /shared/results server A server B server C which server holds a file is hidden; storage is managed and backed up in one place
The three reasons in one picture. Blue boxes are client programs and red is the file service; this colouring holds for the whole Part. Jobs 1 and 2 share one dataset (sharing), nobody names server A, B or C (location independence), and all the disks sit behind one service that can be managed centrally.

The catch is that a familiar interface does not make the network disappear. Three problems come with it. Network delays make a remote read slower than a local one. Partial failures happen, where one machine or link fails while the others keep running, so a client may be left waiting on a server that has crashed. And concurrent updates happen, where two clients change the same file at the same time. These three problems drive the requirements in 8.3.

Keep the scope in mind too. The DFS in this Part is a basic one: it only emulates a local file system for remote clients. It keeps no persistent replicas of files and gives no streaming guarantees (for video, say).

Storage systems and their properties exam

A DFS is one kind of storage system among many. CDK Fig. 12.1 compares them on four properties, and the comparison shows what is special about a DFS. The four columns ask simple questions:

  • Sharing: can processes on different computers use the same data?
  • Persistence: does the data survive after the program (or the machine) stops?
  • Distributed cache/replicas: are copies of the data kept at more than one place? A cache is a nearby copy kept for speed; a replica is a full extra copy.
  • Consistency maintenance: once the data has been updated, does some mechanism keep the copies in agreement?

In the consistency column, 1 means strict one-copy consistency (everyone sees exactly what they would see if only one copy existed), ✓ means slightly weaker guarantees, and 2 means considerably weaker guarantees.

CDK Fig. 12.1 · storage systems compared
Storage systemSharingPersistenceDistributed cache/replicasConsistency maintenanceExample
Main memory✗✗✗1RAM
File system✗✓✗1UNIX file system
Distributed file system✓✓✓✓Sun NFS
Web✓✓✓✗Web server
Distributed shared memory✓✗✓✓Ivy (Part VII)
Remote objects (RMI/ORB)✓✗✗1CORBA
Persistent object store✓✓✗1CORBA Persistent State Service
Peer-to-peer storage✓✓✓2OceanStore

Notice that the DFS row is ticked in every column: it is shared, persistent, cached at many places, and still kept (nearly) consistent. That combination is what makes it hard to build.

The consistency column needs a little unpacking. Where there is only one copy, as in RAM or a local file system, consistency is strict for free, because there is nothing to disagree with. DFSs such as NFS and AFS cache parts of files at the clients, so there are many copies, and they use mechanisms that approximate one-copy consistency. The Web also caches pages, in browsers and in proxies, but it never tells those caches when a page changes, so a client must check explicitly. That is fine for browsing, but useless for cooperative applications that need to see each other's updates. Remote objects, finally, are shared but neither persistent nor replicated; with a single copy, their consistency is strict.

ONE COPY ONLY RAM, a local file system the singlecopy process 1 process 2 strict one-copy consistency (1) DFS: CACHES + CHECKS NFS, AFS master copyat the server cache at A cache at B check approximates one-copy (✓) WEB: CACHES NEVER TOLD browser and proxy caches page at theweb server browser cache proxy cache no consistency maintenance (✗)
The consistency column, drawn. Left: one copy, nothing to disagree. Middle: a DFS keeps copies at clients but checks them against the server (teal arrows), so it comes close to one copy. Right: the Web's caches are never told about updates (red crosses), so a client must re-check by itself.
Check yourselfWhy does the Web have no consistency maintenance, while a DFS does? (Self-test Q11)

Web caches, in browsers and proxies, are never notified of updates, so a client must re-check explicitly whenever it cares. A DFS, by contrast, approximates one-copy semantics with a mechanism: NFS validates cached blocks with timestamps (8.9), and AFS uses callbacks from the server (8.11).

Check yourselfWhich storage system in CDK Fig. 12.1 is shared, persistent and replicated, but gives only considerably weaker consistency (a "2")?
  1. Distributed shared memory
  2. Peer-to-peer storage, e.g. OceanStore
  3. Remote objects, e.g. CORBA
  4. A distributed file system, e.g. Sun NFS

B. Peer-to-peer storage is the only "2". DSM (A) is not persistent and scores ✓; remote objects (C) are not replicated and score 1; the DFS (D) scores ✓ in every column.

Check yourselfRemote objects are shared by many clients. Why do they nevertheless get strict consistency (1)?

Because they are neither persistent nor replicated: there is only a single copy of each object, at its server. With one copy, every client sees the same state, so consistency is strict without any extra mechanism.

How this Part is examined

The 2016 final asked for the NFS client caching policy (Q8(a), 5 marks, worked in 8.9). The official 2026 practice exam asks you to draw the file service architecture (Q6, 5 marks, 8.4) and has two multiple-choice items on soft mounts and write-through (8.8, 8.9). The Week 12 study points list the DFS requirements, the file service architecture, NFS cache validation, mounts and server write caching. AFS and the later developments (8.11) are the lowest priority: they are outside both the slides and the reading list, and are here so you can answer "compare NFS and AFS".

In short
  • A DFS lets programs use remote files exactly like local files, with comparable performance and reliability.
  • It is worth it for sharing, location independence and easier management, but the network brings delays, partial failures and concurrent updates.
  • Among storage systems, only the DFS is shared, persistent, cached at many places and kept close to one-copy consistent.
8.2

File System Basics

Before distributing a file system, we need the vocabulary of an ordinary one: what a file is, how names work, and which calls programs make. The details matter, because the most important design decision in a DFS turns out to be which parts of the UNIX interface to keep and which to change.

Files, attributes and directories

A file has two parts. Its data is a sequence of bytes (data items) that can be read and written at any position. Its attributes describe the file and are kept in an attribute record. Some fields of the record are maintained by the system and cannot be updated by users: the file length, the timestamps and the reference count. Others can be updated by the owner: the owner itself, the file type and the access control list, for example the UNIX permissions rw-rw-r-- (owner and group may read and write, everyone else may only read).

DATA: A SEQUENCE OF BYTES 0 1 2 3 4 5 6 … r e s u l t s … read or write starting at any position The file's name is not stored here: names live in directories. Attribute record SET BY THE SYSTEM UPDATABLE BY THE OWNER File length Creation timestamp Read timestamp Write timestamp Attribute timestamp Reference count Owner File type Access control list e.g. rw-rw-r--
A file = data + attributes. The bytes on the left can be accessed at any position. The attribute record on the right splits into fields only the system may change (left column) and fields the owner may change (right column). Keep the reference count in mind: it reappears in the directory service.

A directory is a special file that maps text names to internal file identifiers. Because a directory can contain other directories, they form a hierarchy (a tree). A pathname such as /home/alice/notes.txt is a multi-part name that traces a route through that hierarchy, starting from the root or from the current directory. One file can have several names: the link call adds another, and the file's reference count records how many names it has. Names exist for users; internal identifiers exist so that the system can locate the object. Finally, metadata means everything stored to manage files: attributes, directories and any other persistent management data.

/ home etc alice bob notes.txt shared.txt name 1 name 2 file 42 · reference count = 2 one set of data and attributes a pathname is a route from the root: /home/alice/notes.txt link() added a second name for the same file, so its count is 2 unlink() removes one name; at count 0 the file is deleted
Names are for people; the file is one object. The highlighted path spells /home/alice/notes.txt. A second name in bob's directory leads to the same file, which is why the file carries a reference count rather than a single name.

File system modules

CDK Fig. 12.2 splits a file system into six modules stacked in layers. Each module depends only on the modules below it, so each can be understood (and replaced) on its own. A DFS needs every one of them, plus two more things a local system never needed: client–server communication and distributed naming and location of files.

each depends only on those below Directory module File module Access control module File access module Block module Device module relates file names to file IDs relates file IDs to particular files checks permission for the operation reads or writes file data or attributes accesses and allocates disk blocks disk I/O and buffering A DFS needs all six + client–server communication + distributed naming and location
Read top to bottom: a call such as "read bytes from /home/alice/notes.txt" is turned into a file ID (directory), then a file (file), checked (access control), carried out (file access), mapped onto disk blocks (block) and finally performed on the disk (device).

The UNIX file operations exam

A file descriptor (filedes) is a small integer that the kernel hands back when a process opens a file; the process then uses it, rather than the name, in every later call. The read-write pointer is the current position in the open file, which the kernel keeps for you. The standard UNIX interface is this:

CallWhat it does
filedes = open(name, mode)
filedes = creat(name, mode)
Opens an existing file, or creates a new one. Both return a file descriptor. The mode is read, write or both.
status = close(filedes)Closes the open file.
count = read(filedes, buffer, n)
count = write(filedes, buffer, n)
Transfer n bytes between the file and the buffer. They return the count actually transferred and advance the read-write pointer.
pos = lseek(filedes, offset, whence)Moves the read-write pointer, relatively or absolutely (whence says which).
status = unlink(name)Removes the name; deletes the file if it has no other names.
status = link(name1, name2)Adds the new name name2 for file name1.
status = stat(name, buffer)Gets the file attributes into the buffer.

UNIX is stateful, and why that matters

The interface above hides a lot of state, meaning information the kernel remembers between calls. For each process the kernel keeps an open-file list holding a read-write pointer for each open file. And access rights are checked once, at open; the result is remembered until close. Watch the hidden state at work in the copy program from the slides: it never mentions a position, yet every read continues where the last one stopped.

/* copy file1 to file2: a sketch of the slide's program */
fd1 = open("file1", READ);
fd2 = open("file2", WRITE);
while ((n = read(fd1, buf, 1024)) > 0)
    write(fd2, buf, n);
close(fd1);
close(fd2);
The hidden read-write pointer, one call at a time
USER PROCESS · COPY PROGRAM fd1 = open("file1", READ); fd2 = open("file2", WRITE); while ((n = read(fd1,buf,1024)) > 0) write(fd2, buf, n); close(fd1); close(fd2); read returned n = 1024 read returned n = 1024 read returned n = 452 read returned n = 0: loop ends No byte position appears anywhere in this program: the kernel supplies it. KERNEL · OPEN-FILE TABLE OF THIS PROCESS fd file read-write pointer access checked fd1 file1 once, at open fd2 file2 once, at open 0 1024 2048 2500 (end) 0 1024 2048 2500 both entries removed by close file1 (2500 bytes), pointer of fd1 0 1024 2048 2500 file2 (being written), pointer of fd2 0 2500
  1. The program is about to start. The kernel's open-file table for this process is empty. File1 holds 2500 bytes; file2 does not exist yet as far as this process is concerned.
  2. open("file1", READ): the kernel checks access rights now, once, creates entry fd1 with read-write pointer 0, and returns fd1.
  3. open("file2", WRITE): another check, another entry, fd2, pointer 0.
  4. First loop: read(fd1, buf, 1024) returns 1024 and moves fd1's pointer to 1024; write(fd2, buf, 1024) moves fd2's pointer to 1024. The program never said "1024".
  5. Second loop: both pointers advance to 2048, again silently.
  6. Third loop: only 452 bytes are left, so read returns 452 and the pointers reach 2500, the end.
  7. The next read returns 0 (end of file), so the loop ends.
  8. close(fd1), close(fd2) remove both entries. Notice the cost if this table lived on a remote server: the server would have to remember a pointer and an access decision for every open file of every client.

That last point is the whole lesson. If a DFS server copied this model, it would need per-client state: an open-file table with pointers and remembered access checks for every client. As 8.4 shows, the file service architecture deliberately avoids this.

Check yourselfA process calls read(fd, buf, 100) twice in a row on the same open file. Do the two calls return the same bytes? Why?

No (unless the file is shorter than 100 bytes and both hit the end). Each read advances the hidden read-write pointer by the count transferred, so the second call returns the next 100 bytes. The call does not name a position, so repeating it does something different. This is exactly why UNIX read is not idempotent (8.4).

Check yourselfWhen does UNIX check whether a process may read a file: on every read, or once? And what is the reference count for?

Once, at open; the result is remembered until close. The reference count records how many names (links) the file has; unlink deletes the file only when its last name is removed.

In short
  • A file is data (bytes) plus an attribute record; a directory maps names to file IDs; a file may have several names, counted by its reference count.
  • A file system is six layered modules; a DFS adds client–server communication and distributed naming and location.
  • UNIX is stateful: the kernel keeps a read-write pointer per open file and checks access once, at open. A server copying this would need per-client state.
8.3

DFS Requirements exam

What exactly must a DFS promise so that remote files really do feel local, despite delays, failures and other users? CDK lists eight requirements. The first, transparency, comes in five forms, and those five are a favourite short-answer question (Tutorial 8 asked for them).

1 · Transparency (five forms) Accesssame operations for local and remote files Locationnames don't reveal where a file is stored Mobilityfiles move; clients and their tables unchanged Performancesatisfactory while load varies within a range Scalinggrows incrementally: users, files, network 2 · Concurrent file updates one client's changes don't disturb others; usually file or record locking 3 · File replication load sharing + fault tolerance; most systems only cache at clients 4 · Hardware and OS heterogeneity clients and servers on any OS and hardware (openness) 5 · Fault tolerance survive client, server, network failures; at-most-once, or idempotent operations 6 · Consistency ideally one-copy update semantics; caching makes it approximate 7 · Security access control lists, authenticated requests, signed (and encrypted) messages 8 · Efficiency at least a local file system's power, generality and speed; easy to administer The file service is usually the most heavily loaded service in an intranet: balance, don't maximise
Eight requirements, five of them inside the first. Learn the transparency list on the left word for word; the other seven are best remembered as the problems from 8.1 (failures, concurrent updates, copies) plus openness, security and speed.

1 · Transparency

Transparency means hiding something from the user or programmer, here the fact that files are remote. Because the file service is usually the most heavily loaded service in an intranet, transparency has to be balanced against complexity and performance: each form costs something to provide. The five forms are these.

  • Access transparency: there is one set of operations for local and remote files, so programs written for local files work unchanged on remote ones.
  • Location transparency: there is a uniform name space. A file's name doesn't reveal where it is stored, and files can be relocated without changing their pathnames.
  • Mobility transparency: files or whole volumes can move between servers without changing client programs or the client administration tables (such as a client's list of what is mounted where).
  • Performance transparency: performance stays satisfactory while the load varies within a specified range.
  • Scaling transparency: the service can grow incrementally, with more users, more files and a bigger network.
ACCESS open read write same calls local file on this disk remote file on a server programs need no change LOCATION /shared/report.txt which one? server A server B the name reveals no location; relocated file keeps its path MOBILITY server B server C volume moves client programs: unchanged client admin tables: unchanged stronger than location: nobody has to update anything
Three transparencies that are easy to confuse. Access is about the operations (same calls), location about the name (it says nothing about where), and mobility about what happens when files move (nothing on the client needs changing, not even its tables).

2 to 8 · The other requirements

2 · Concurrent file updates. One client's changes to a file must not interfere with other clients using the same file. Full concurrency control (the kind databases use) is costly, so most file services settle for UNIX-style advisory or mandatory file or record locking: a client locks a file, or a region of it, while it updates it. (An advisory lock only works if every program checks it; a mandatory lock is enforced by the system.)

3 · File replication. Keeping several copies of a file gives load sharing (requests spread over servers, which helps scalability) and fault tolerance (if one copy is lost, another remains). The price is that updates to the copies must be coordinated. Few systems replicate fully, but most cache files or blocks at clients, which is a limited form of replication.

4 · Hardware and OS heterogeneity. The service interfaces are defined so that clients and servers can run on different operating systems and hardware. This is a matter of openness: anyone can implement either side.

5 · Fault tolerance. The service must keep working through client, server and network failures without corrupting files. Two RPC styles from Part VI make this possible. With at-most-once semantics, a request is carried out once or not at all, never twice. With at-least-once semantics, the client simply resends until it gets a reply, so a request may run more than once; that is only safe if the operations are idempotent, meaning that doing one twice has the same effect as doing it once. Stateless servers help too, because a server that remembers nothing about its clients can restart after a crash with no recovery. Tolerating disconnection, though, needs replication.

6 · Consistency. UNIX gives one-copy update semantics: every process sees the contents it would see if only a single copy of the file existed, so a read after a write always sees that write. Caching or replication delays the spread of updates, so a DFS may deviate from this. Whatever model it promises, both cached data and cached attributes (length, timestamps) must follow it.

writer file reader x = 1 write x = 2 x = 2 read → 2
One copy (UNIX). There is only the file itself, so any read after the write sees 2.
client A server client B x = 1 cache: x = 1 write x = 2 x = 2 read → 1 (from cache)
A DFS with caching. B reads its own cached copy and sees the stale 1. This is the deviation from one-copy semantics that NFS limits in 8.9.

7 · Security. Like any file system, a DFS needs access control lists. Over a network it needs more: authentication of requests, so that the server checks the real identity of the user behind each request, and protection of messages by digital signatures and optional encryption (Part X).

8 · Efficiency. A DFS should offer at least the power, generality and performance of a local file system, and it should be convenient to administer.

Check yourself · Tutorial 8, Q1 · official answerName and explain the transparencies a DFS should address.

The tutors' official answer (examinable):

  • Access: client programs don't know whether a file is local or remote.
  • Location: client programs don't know where a file is stored, and files can be relocated without changing their pathnames.
  • Mobility: neither client programs nor the system administration tables on client nodes need to change when files move.
  • Performance: performance stays acceptable while the load varies within a specified range.
  • Scaling: the service can be expanded without loss of performance.
Check yourself · Ed practice quiz 1A file is moved from server A to server B overnight, and clients use the same pathname with no configuration changes. Which transparency is this?
  1. Access transparency
  2. Location transparency
  3. Performance transparency
  4. Scaling transparency

B, location transparency, the best fit among the options: the pathname stays the same after relocation. The fact that no change to client administration tables is needed is also a mark of mobility transparency, but that option is not offered.

Check yourself · Ed practice quiz 2open() and read() work identically on local and remote files. Which transparency is this?

Access transparency: one set of operations for local and remote files.

Check yourself · Ed practice quiz 3A home directory moves to another server, and client mount tables and administration settings need no change. Which transparency is this?

Mobility transparency: the move needs no change to client programs or to client administration tables. That second part is what separates it from location transparency.

Check yourselfRequirement 5 offers two routes to fault tolerance. What are they, and what extra property makes the second one safe?

At-most-once semantics (the server makes sure a request is never carried out twice), or at-least-once semantics (the client keeps resending) combined with idempotent operations, so that a duplicate execution does no harm. A stateless server helps either way, because it restarts with nothing to recover.

In short
  • Eight requirements: transparency, concurrent updates, replication, heterogeneity, fault tolerance, consistency, security, efficiency.
  • Transparency has five forms: access (same calls), location (name hides where), mobility (moves need no client changes, not even tables), performance, scaling.
  • Fault tolerance comes from at-most-once or at-least-once + idempotent operations, helped by stateless servers; consistency ideally means one-copy update semantics, for data and attributes.
8.4

The File Service Architecture exam

How should a file service be split up so that its server is simple, survives crashes, and still lets programs use the familiar UNIX calls? CDK's answer is an abstract model, the file service architecture, that underlies both NFS and AFS. It divides the work among three components, which separates concerns and, crucially, allows a stateless server. The official 2026 practice exam asks you to draw it.

CLIENT COMPUTER Application program Application program UNIX-style calls Client module • one familiar (UNIX-like) API • resolves pathnames by iterative Lookup • keeps file descriptors and offsets • knows where the servers are • caches recently used blocks NETWORK SERVER COMPUTER Directory service maps text names to UFIDs; creates and updates directories Lookup AddName UnName GetNames stores directories as files Flat file service operates on file contents and attributes, by UFID only Read Write Create Delete GetAttributes SetAttributes allocates UFIDs; knows nothing about names RPC: name→UFID RPC: by UFID
The diagram to reproduce in the exam. Blue is the client side, violet the directory service, red the flat file service. Label each service with its operations, and do not forget the arrow from the directory service down to the flat file service: directories are themselves files.

The three components

Flat file service performs operations on file contents and attributes, and it identifies files only by a UFID. It allocates a fresh UFID on each Create, and it knows nothing about names. "Flat" means exactly that: no names, no directories, no hierarchy, just numbered files.

Directory service maps text names to UFIDs, and creates and updates directories. It is itself a client of the flat file service, because the directory files are stored there like any other file.

Client module runs on each client computer and presents one familiar API to applications, for example by emulating the UNIX calls, so applications see an ordinary file system. Behind that API it resolves pathnames by iterative Lookup calls (one name component at a time, 8.5), keeps file descriptors and read-write offsets locally, knows the network locations of the servers, and caches recently used blocks.

Definition · UFID (unique file identifier)

A UFID is a long sequence of bits that is unique among all files in the distributed system, not just on one server. It is generated by Create, and every flat file operation names its file by UFID.

The flat file service interface

Six operations, every one naming its file by UFID. Notice that Read and Write carry an explicit position i; there is no open, no close and no file descriptor.

OperationEffect
Read(UFID, i, n) → DataReads up to n items starting at position i.
Write(UFID, i, Data)Writes Data starting at position i, extending the file if needed.
Create() → UFIDCreates an empty file and returns a new UFID.
Delete(UFID)Removes the file.
GetAttributes(UFID) → AttrReturns the file's attributes.
SetAttributes(UFID, Attr)Sets the attributes (only the owner-updatable ones). Normally restricted to the directory service.

Differences from UNIX, made for fault tolerance exam

The flat file interface departs from UNIX in four ways, all of them for the sake of fault tolerance:

UNIXFlat file service
open and close, with a server-side open-file tableNo open/close at all: clients use the UFID directly
A hidden read-write pointerAn explicit position in every Read and Write; the client keeps the offset
read and write are not idempotent: a repeat moves the pointerAll operations except Create are idempotent
StatefulStateless server
client knows only fd 3 server fd 3 → file f pointer 1024 read(fd 3, n) n bytes state for every client a repeated read returns the NEXT n bytes
UNIX style: the server remembers. The position lives in a table at the server, one entry per open file per client.
client module offset 1024 flat file server no open-file table, nothing about this client Read(f,1024,n) Data stateless a repeated Read returns the SAME bytes
Flat file style: the request says it all. The client module keeps the offset and sends it every time, so the server needs no memory of the client.

Idempotent operations and at-least-once RPC

An operation is idempotent if performing it twice (or many times) has the same effect as performing it once. Idempotent operations make at-least-once RPC safe: if a request times out, the client just sends it again, without needing to know whether the first copy was executed. The slide example is Write(f, 100, "ABC"): doing it twice leaves bytes 100–102 holding "ABC", exactly as doing it once does (assuming no other writes come in between). A blind retry of Create, by contrast, makes an extra file.

Retrying after a timeout: Write is safe, Create is not
client module flat file server 1 · Write(f, 100, "ABC") 2 · reply lost timeout: resend the same request 3 · Write(f, 100, "ABC") again 4 · reply: done 5 · Create() reply: g lost timeout: resend Create() 6 · Create() again reply: h SERVER'S STORED FILES file f, bytes 100–102: - - - A B C A B Cstill the same: idempotent files that exist: f f, g f, g, hg is an extra, orphan file:Create is NOT idempotent
  1. The client module and the flat file server. On the right is what the server stores: file f, whose bytes 100–102 are about to be written.
  2. Write(f, 100, "ABC") reaches the server, which writes "ABC" at position 100.
  3. The reply is lost in the network. The client cannot tell whether the request or the reply was lost.
  4. The client's timeout expires and it simply resends the identical request (at-least-once semantics). The server writes "ABC" at position 100 again.
  5. The reply arrives. Bytes 100–102 hold "ABC", exactly as if the write had happened once: Write is idempotent, so the retry did no harm.
  6. Now the client sends Create(). The server creates file g and replies with its UFID, but this reply is lost too.
  7. The client retries, and the server, which keeps no record of earlier requests, creates another file, h. File g now exists with no name and no client that knows its UFID: Create is not idempotent, the one exception in the interface.

Stateless servers

Stateless means the server keeps no per-client state between requests: no open-file table, no offsets, no remembered access checks. Every request carries everything needed to carry it out. As a result, a server that crashes can restart with nothing to rebuild, and clients just carry on sending requests. Stateless does not mean the server stores nothing: file contents, directories and attributes are of course stored persistently on its disks.

client still holds fd 3 stateful server crashed, restarted open-file table lost in the crash read(fd 3, n) error: fd 3? state must be rebuilt, or clients fail
Stateful: a crash loses client state. After a restart the server no longer knows what fd 3 or its pointer meant.
client module offset 1024 flat file server crashed, restarted no per-client state to lose nothing to rebuild Read(f,1024,n) Data clients simply carry on
Stateless: nothing to lose. Each request carries the UFID and the position, so a freshly restarted server can serve it straight away.

Access control without server state

Statelessness makes access control harder. In UNIX the check happens once, at open, and the result is remembered; a stateless server cannot remember it. Meanwhile the server's RPC interface is an open entry point that anyone on the network can call, and identities can be forged. That leaves two stateless options:

  1. Capability. Access is checked once, when a name is mapped to a UFID, and the server returns a capability: an unforgeable token that says "the holder may do these operations on this file". The client presents the capability with each later request.
  2. Identity on every request. The user's identity travels with every request, and the server checks the access rights on every operation. This is the more common option, and it is the one used by NFS and AFS.
client server 1 · map name, user check once UFID + capability 2 · Read(capability, i, n) Data
Option 1, capability. Checked once, at name mapping; the token proves the right afterwards.
client server Read(UFID, i, n), user check Data Write(UFID, i, d), user check again done used by NFS and AFS
Option 2, identity on every request. More checking, but nothing to remember between requests.
Neither option stops a liar

Both schemes trust the identity (or token) that arrives in the request. Neither prevents a forged identity; that needs authentication, such as digital signatures or Kerberos (Part X). NFS's version of this problem, and its fix, are in 8.7.

Worked exam question

Sample exam 2026 Q6 · 5 marks · worked answer, not official

Draw a diagram of a model File Service Architecture (FSA). Explain the role of the flat file service and the client module in this architecture.

Staff released no solutions to the practice exam; the answer below is the study guide's worked answer.

Model answer

Diagram. Draw the figure at the top of this section. On the left, a client computer with several application programs calling a client module. On the right, a server computer holding the directory service and the flat file service. An arrow from the client module to each service, labelled with that service's RPC operations: Lookup, AddName, UnName, GetNames for the directory service (names to UFIDs), and Read, Write, Create, Delete, GetAttributes, SetAttributes for the flat file service (works by UFID). An arrow from the directory service to the flat file service, because directories are themselves stored as files.

Flat file service. It performs the operations on the contents and attributes of files, identifying files only by UFIDs, which are unique among all files in the distributed system and are allocated by Create. Its interface differs from UNIX to support fault tolerance: no open or close, an explicit position in every Read and Write, and every operation except Create is idempotent. The server can therefore be stateless: it keeps no per-client state, a client can simply repeat a request after a timeout (at-least-once semantics), and a crashed server restarts with nothing to rebuild.

Client module. One runs on each client computer. It integrates and extends the operations of the flat file service and the directory service under a single application programming interface, for example emulating the UNIX file operations, so applications see a familiar file system. To do so it resolves pathnames by calling Lookup one component at a time, keeps the open-file descriptors and read-write positions locally, knows the network locations of the flat file server and directory server, and caches recently used file blocks to improve performance.

Check yourself · Self-test Q2Why does the flat file service have no open/close? Which of its operations are idempotent?

It needs no open or close because UFIDs are used directly and every Read and Write carries an explicit position, so there is no server-side pointer or open-file table. The server is therefore stateless, and a crash and restart need no recovery.

Read, Write, Delete, GetAttributes and SetAttributes are idempotent, so at-least-once retries are safe: Write(f, 100, "ABC") done twice has the same effect as done once. Create is not: a retry makes an extra file. UNIX read and write are not idempotent either, because a repeat advances the hidden pointer.

Check yourself · Self-test Q3How is access control enforced by a stateless server?

A stateless server cannot store access-check results, so it has two options. It can issue a capability when a name is mapped to a UFID, which the client presents with later requests; or the user ID is sent with every request and checked every time, which is what NFS and AFS do. Neither stops forged IDs on its own; that requires authentication, such as Kerberos.

Check yourselfWhich statement about a "stateless" file server is correct?
  1. It stores no data on disk, only in memory.
  2. It keeps no per-client state, such as open files, offsets or access checks, between requests.
  3. It never caches anything.
  4. Its operations are all idempotent, including Create.

B. Stateless refers only to per-client session state. The server certainly stores files persistently (A is wrong), may cache disk blocks for its own speed (C), and Create is the one non-idempotent flat file operation (D).

Check yourselfA client sends Delete(f), the reply is lost, and it retries. What happens, and is Delete idempotent?

The file is already gone after the first execution; the retry leaves it gone. The effect on the stored files is the same as doing it once, so the guide counts Delete among the idempotent operations. (The retry may report differently; NFS's version of this, remove, is handled with a duplicate-request cache in 8.7.)

In short
  • Three components: the flat file service (contents and attributes, by UFID), the directory service (names to UFIDs, itself a client of the flat file service), and the client module (UNIX-like API, iterative lookup, offsets, caching).
  • No open/close, explicit positions, all operations idempotent except Create, so the server is stateless and at-least-once retries are safe.
  • Without server state, access is checked by capability or (as in NFS and AFS) by identity on every request; forged identities need authentication.
8.5

The Directory Service and Pathnames

The flat file service only understands UFIDs, but people use names like /home/tawfiq/results.csv. Something has to turn one into the other. That is the job of the directory service, working together with the client module, which walks a pathname one name at a time.

The directory service interface

The directory service has four operations. Each takes a directory, Dir, as its first argument; Dir is itself a UFID, because a directory is just a file.

OperationEffect
Lookup(Dir, Name) → UFIDTranslates one name in directory Dir. Throws NotFound if it is absent.
AddName(Dir, Name, UFID)Adds an entry. Throws NameDuplicate if the name is already there. Increments the file's reference count.
UnName(Dir, Name)Removes an entry and decrements the count; the file is deleted when the count reaches zero.
GetNames(Dir, Pattern) → NamesReturns the names that match a regular expression, in the style of shell wildcards (e.g. *.csv).

Compare these with UNIX: AddName plays the part of link, and UnName the part of unlink, including deleting the file when its last name goes.

Directory service a client of the flat file service Read Write Flat file service UFID H type = directory Name UFID alice A tawfiq T the bytes of a directory file UFID F type = file date,temp 1 Nov,21 … bytes of results.csv
A directory is just a file with a known format. The flat file service stores both kinds of file the same way; only the type attribute tells them apart. The directory service reads and writes directory files through ordinary flat file operations, which is why it counts as a client of the flat file service.

Resolving a pathname, step by step

Because Lookup translates only one name, a full pathname is resolved one component at a time, starting from the root directory's well-known UFID, R (every client module knows it in advance). To resolve /home/tawfiq/results.csv and read its first 4096 items, the client module makes four RPCs.

Iterative lookup of /home/tawfiq/results.csv
PATH BEING RESOLVED / → UFID R home → UFID H tawfiq → UFID T results.csv → F client module directory service flat file service 1 · Lookup(R, "home") H 2 · Lookup(H, "tawfiq") T 3 · Lookup(T,"results.csv") F 4 · Read(F, 0, 4096) Data
  1. The client module knows only the root's well-known UFID, R. The rest of the path is still unresolved (faded).
  2. Lookup(R, "home"): the directory service reads directory R (through the flat file service) and finds "home", whose UFID is H.
  3. Lookup(H, "tawfiq") uses the UFID the previous step returned, and yields T.
  4. Lookup(T, "results.csv") yields F, the UFID of the file itself. Every component except the last had to be a directory, which the directory service can check from each object's type attribute.
  5. Now the client module talks to the flat file service directly: Read(F, 0, 4096) returns up to 4096 items from position 0. Three lookups plus one read: one RPC per path component.

The Java sketch below shows the same loop. It is short, but every line matters.

import java.io.IOException;

interface DirectoryService {
    long lookup(long dirUfid, String name) throws IOException; // one-step RPC
}

class ClientModule {
    static final long ROOT_UFID = 1L;            // well-known UFID of "/"
    private final DirectoryService ds;
    ClientModule(DirectoryService ds) { this.ds = ds; }

    // Resolve a pathname by iterative single-component lookups
    long pathLookup(String path) throws IOException {
        long current = ROOT_UFID;
        for (String part : path.split("/")) {
            if (part.isEmpty()) continue;        // skip leading "/" and "//"
            current = ds.lookup(current, part);  // next step uses previous UFID
        }
        return current;
    }
}
  • interface DirectoryService describes the remote directory service as the client sees it. Its lookup is the one-step RPC: one directory UFID and one name in, one UFID out. It throws IOException because any remote call can fail.
  • ROOT_UFID = 1L is the root's well-known UFID: hard-wired, so resolution has somewhere to start. (UFIDs are modelled as long here; real ones are longer.)
  • current = ROOT_UFID starts the walk at /.
  • path.split("/") breaks /home/tawfiq/results.csv into "", "home", "tawfiq", "results.csv". The leading slash produces an empty first piece, and a doubled slash would produce another, so if (part.isEmpty()) continue; skips them.
  • current = ds.lookup(current, part) is the heart of the method: each RPC is made in the directory found by the previous one, exactly as in the stepper.
  • The returned current is the file's UFID, ready for Read or Write on the flat file service.

The hierarchic file system

The directories form a tree whose root sits at a well-known UFID. A file gets multiple names through AddName, with the reference count tracking how many it has. A type attribute marks each object as either a file or a directory, which lets resolution check that every component of a pathname except the last is a directory: you cannot Lookup inside a plain file.

File groups

One server cannot hold every file in a large organisation, so the service must spread files over many servers, and clients must know where to send each request. The tool for this is the file group: a collection of files located on one server, much like a UNIX filesystem (volume) that can be mounted. Groups can move between servers, but a file never changes group.

Each UFID contains a group ID, so the client module can tell from the UFID alone which group a file is in, and route the request to the server holding that group. Group IDs must be globally unique. One way to achieve that is to combine the 32-bit IP address of the host that created the group with a 16-bit date. But that IP address must not be used to locate the group, because the group may since have moved. Instead, the service keeps a separate map from groups to servers.

A UFID group ID: globally unique IP address of creator 32 bits date 16 bits rest of the UFID identifies the file within its group not used to locate the group server 1 group G (old) server 2 group G group moves files never change group; groups can change server group → server map group server G server 2 updated when a group moves
Unique is not the same as locating. The IP address in the group ID only guarantees uniqueness (no two hosts can mint the same ID). To find the group, the client module consults the group-to-server map, which is what changes when the group moves.
Check yourselfHow many directory service calls does the client module make to resolve /a/b/c/d.txt, starting from the root's UFID? What does each use as its first argument?

Four Lookup calls, one per component: Lookup(R,"a"), then Lookup(A,"b"), Lookup(B,"c") and Lookup(C,"d.txt"). Each uses the UFID returned by the previous call as its directory argument; the first uses the root's well-known UFID.

Check yourselfA file has names in two directories. Someone calls UnName on one of them. Is the file deleted?

No. UnName removes that entry and decrements the reference count from 2 to 1. The file is deleted only when the count reaches zero, i.e. when its last name is removed.

Check yourselfA group ID contains the IP address of the host that created the group. Why not just send requests for the group to that address?
  1. IP addresses are too short to be unique.
  2. The group may have moved to another server since it was created.
  3. The date field would be ignored.
  4. Clients are not allowed to see the UFID.

B. Groups can move between servers, so the creator's address only serves to make the ID unique. Location comes from the separate group-to-server map.

In short
  • Directory service: Lookup (one name), AddName (count +1), UnName (count −1, delete at 0), GetNames (pattern match). Directories are files, so it is a client of the flat file service.
  • Pathnames are resolved iteratively by the client module, one Lookup per component, from the root's well-known UFID.
  • Files are spread over servers in file groups; the group ID inside each UFID is unique, and a separate map says which server holds the group now.
8.6

Sun NFS: Architecture exam

The abstract model shows the shape of a good file service; Sun NFS (Network File System) is the real system built on that shape. The problem it had to solve is practical: make remote files work with unchanged UNIX programs, on many kinds of computer, from a server that survives crashes. Its answer has three key parts: a kernel layer called the VFS, opaque file handles, and a stateless server.

NFS appeared in 1985 and was the first file service designed as a product. Its interfaces are public, version 3 is an Internet standard (RFC 1813), and it runs on almost every operating system. The NFS protocol is a set of RPCs for operating on a remote file store. The roles are symmetric: any host can be a server, exporting its own files, and a client, mounting other hosts' files, at the same time. NFS follows the abstract model of 8.4 closely; CDK describes the UNIX implementation of version 3.

NETWORK CLIENT COMPUTER Application program Application program UNIX KERNEL UNIX system calls Virtual file system (VFS) local local remote UNIX file system Other file system NFS client in the kernel SERVER COMPUTER Mount service user level (see 8.8) UNIX KERNEL Virtual file system UNIX file system NFS server in the kernel NFS protocol · Sun RPC over UDP or TCP
Follow a remote read. An unchanged program makes an ordinary system call; the VFS sees the file is remote and hands it to the NFS client, which sends an RPC to the NFS server, which uses the server's own VFS and UNIX file system. Both NFS modules live in the kernel; only the mount service (gold) runs at user level.

The NFS client and server modules both sit in the kernel. They communicate by Sun RPC, which was built for NFS, over either UDP or TCP. A port mapper on each host lets clients bind to RPC services by name: a client asks it which port, say, the NFS service is listening on. The RPC interface is open: any process can send requests, and valid requests with valid credentials are executed. Signed credentials and encryption are optional extras.

The VFS and v-nodes

The virtual file system (VFS) is a kernel layer that integrates local and remote file systems into one name space. It is how NFS achieves access transparency: every file call goes through the VFS, which routes it to the right module (the UNIX file system, the NFS client, or another file system) and translates between NFS file identifiers and local ones.

The VFS keeps two kinds of record. There is one VFS structure per mounted file system, linking that file system to the local directory it is mounted on. And there is one v-node per open file (a "virtual i-node"), flagged as either local, in which case it holds a reference to the file's i-node (the UNIX on-disk record of a file), or remote, in which case it holds the file's NFS file handle.

KERNEL · VIRTUAL FILE SYSTEM ONE VFS STRUCTURE PER MOUNTED FILE SYSTEM mounted at / local UNIX file system mounted at /usr/students remote: Server 1, /export/people The VFS knows which subtree is local and which belongs to which server. ONE V-NODE PER OPEN FILE v-node · local → i-node v-node · remote → NFS file handle UNIX file system NFS client local remote
Two tables, two jobs. VFS structures (left) record what is mounted where; v-nodes (right) record, for each open file, whether it is local or remote and how to reach it. The flag on the v-node is what routes each call.

File handles

The file handle is NFS's UFID: the identifier the server uses for a file. It is opaque to clients, meaning clients never look inside it; they just hand it back with each request. In UNIX it is built from three fields.

opaque to clients: they store it and hand it back, never look inside filesystem identifier unique per file system (the file group) i-node number which file within that file system i-node generation number incremented whenever the i-node number is reused an old handle a client still holds (fs 3, i-node 1834, gen 7) obtained before the file was deleted server: i-node 1834 today reused for a new file, gen 8 7 ≠ 8, so the old handle is refused read Without the generation number, the stale handle would silently reach someone else's new file.
Why three fields? The filesystem identifier picks the file group (NFS uses the mountable file system as its group), the i-node number picks the file, and the generation number makes sure an old handle cannot reach a new file that happens to reuse the same i-node. (The numbers 3, 1834, 7 and 8 are made up for the example.)

Where do handles come from? A client gets its first handle when it mounts a file system (8.8). After that, lookup, create and mkdir return further handles, and every other operation takes a handle as an argument.

Why the NFS client lives in the kernel

The NFS client module could have been a user-level library linked into programs. It is in the kernel instead, for three reasons:

Reason 1

No recompiling

Programs use ordinary system calls without recompilation or reloading; the remote-file support is underneath them.
Reason 2

One shared cache

One module with one shared cache serves all the processes on the machine, instead of every program caching separately.
Reason 3

Protected key

The key used to authenticate user IDs stays in the kernel, so a user-level program cannot get it and impersonate another user.

The client caches remote blocks in the same buffer cache as local disk I/O. But many hosts may cache the same file, and that raises a consistency problem, which 8.9 deals with.

Check yourself · Self-test Q4Explain the VFS and v-nodes. What is in an NFS file handle?

The VFS integrates local and remote file systems, routes each call to the right module, and in doing so gives access transparency. It keeps one VFS structure per mount and one v-node per open file; a local v-node refers to an i-node, while a remote one holds a file handle.

An NFS file handle consists of the filesystem identifier, the i-node number and the i-node generation number. It is opaque to clients, and the generation number stops reused i-nodes from being reached by stale handles.

Check yourselfThe VFS lets unchanged programs open remote files with the usual system calls. Which transparency does that provide?
  1. Location transparency
  2. Access transparency
  3. Mobility transparency
  4. Replication transparency

B, access transparency: the same operations work on local and remote files. Location transparency (A) is a separate matter that depends on how clients mount file systems (8.10), a classic near-miss.

Check yourselfGive two operations that hand the client a new file handle, and say where the very first handle comes from.

lookup, create and mkdir all return handles. The very first handle comes from mounting: the mount protocol returns the handle of the mounted directory.

In short
  • NFS client and server are kernel modules talking Sun RPC over UDP or TCP; the RPC interface is open, and any host can be client and server.
  • The VFS gives access transparency; it keeps one VFS structure per mount and one v-node per open file (local → i-node, remote → file handle).
  • A file handle = filesystem ID + i-node number + generation number; it is opaque, and the generation number stops stale handles reaching new files.
8.7

NFS Operations and Security

What can an NFS client actually ask the server to do, and how does a server that remembers nothing decide whether to allow it? This section lists the server interface, shows where NFS bends the "everything is idempotent" rule, and follows the identity check from plain UIDs to Kerberos.

The server interface (version 3, simplified)

Unlike the abstract model, NFS combines directory and file operations in one service. For example, a single create(dirfh, name, attr) → newfh, attr both creates a file and enters its name in a directory (dirfh is the directory's file handle). Most operations correspond roughly to one in the abstract model:

NFS operationRoughly like (abstract model)Notes
lookup(dirfh, name) → fh, attrLookupLooks up one pathname component.
create(dirfh, name, attr) → newfh, attrCreate + AddNameCreates the file and names it in one call.
remove(dirfh, name)UnName
getattr(fh) → attrGetAttributes
setattr(fh, attr)SetAttributesSetting the size to 0 truncates the file.
read(fh, offset, count) → attr, dataReadAlso returns the latest attributes.
write(fh, offset, count, data) → attrWriteExplicit offset, like the model.
rename, link, symlink, readlink, mkdir, rmdir(UNIX counterparts)Behave like the UNIX calls of the same names.
readdir(dirfh, cookie, count)(no direct counterpart given)Reads a directory in a representation-independent way; the cookie marks where the next batch starts.
statfs(fh)(no direct counterpart given)File system status, such as block size and free space.

Idempotency, and where it breaks

As in the abstract model, explicit offsets keep read and write idempotent, so the client can simply retry them after a timeout. The slides add a nuance, though: some operations, such as remove and rename, are not naturally idempotent. A retried remove whose first attempt succeeded would report "no such file", so the client would be told that an operation failed when it actually worked. Servers therefore keep a duplicate-request cache: a record of recent requests and the replies sent, so a retransmitted request gets the original reply instead of being executed again.

client server remove(d, "old.txt") deleted timeout remove(d, "old.txt") not found error: no such file told it failed, though it succeeded
Without the cache. The retry is executed again, and a second remove of the same name cannot succeed.
client server remove(d, "old.txt") deleted timeout remove(d, "old.txt") seen it: replay ok (original reply) duplicate-request cache answers the retry
With a duplicate-request cache. The server recognises the retransmission and replays the reply it sent the first time.

Access control and authentication

Because the server is stateless, it cannot remember an access decision from an earlier call. So it checks the user's identity against the file's permissions on every request, the "identity on every request" option from 8.4. To make this possible, Sun RPC sends the user's UNIX UID (user ID) and GID (group ID) with each call.

The loophole is that nothing proves those numbers are true. Any process that can send RPCs can put whatever UID it likes in a request, and the server cannot tell the difference.

Alice's client really Alice attacker's process not Alice NFS server checks UID/GID against the file's permissions read(fh), UID alice read(fh), UID alice forged: just a number in the message fix: DES-encrypted credentials, later Kerberos
Two identical requests. The server sees the same bytes from the honest client (blue) and the attacker (red), so a plain UID is no proof of identity. Authentication must be added on top.

The fixes were DES-encrypted credentials and, later, Kerberos (Part X), a system in which a trusted server issues users cryptographic tickets that prove who they are. But Kerberized NFS does not send a full Kerberos ticket with every request: doing so for every stateless request was too costly. It uses a hybrid scheme instead.

Kerberized NFS: authenticate once at mount, then match on every request
client 10.0.0.5 host 10.0.0.9 NFS server 1 · mount + full Kerberos authentication file handle mount record UID: alice addr: 10.0.0.5 2 · read(fh), UID alice matches: accept data 3 · read(fh), UID alice wrong address: reject rejected Cheap and safe only if one user at a time logs in to each client: the check is on UID + client address.
  1. Alice works at the client with address 10.0.0.5. Another host, 10.0.0.9, is on the same network. The NFS server is stateless for file operations.
  2. Mount time. When Alice's home and root file systems are mounted, the mount server receives full Kerberos authentication data, so the server knows for certain who is behind this client. It replies with the file handle.
  3. The server stores the user's ID and the client's address with the mount record: UID alice, address 10.0.0.5. This is the only extra state.
  4. A later request from 10.0.0.5 carrying UID alice matches the record, so it is accepted. No ticket travels with it, so the check is cheap.
  5. A request claiming UID alice from 10.0.0.9 does not match the record's address, so it is rejected. Forging the UID alone is no longer enough.
  6. The catch: the check is UID + address, so it is cheap and safe only if one user at a time logs in to each client. A second user on 10.0.0.5 would be sending from the matching address.
Check yourselfNFS read and write are idempotent. Why does an NFS server still need a duplicate-request cache?

Because some operations, such as remove and rename, are not naturally idempotent. A retried remove whose first attempt succeeded would fail with "no such file". The duplicate-request cache lets the server recognise the retransmission and return the original reply.

Check yourselfHow does a stateless NFS server enforce access control, and what is the loophole?

Sun RPC carries the user's UID and GID with every call, and the server checks them against the file's permissions on every request. The loophole: any process can forge the UID in a request. The fixes are DES-encrypted credentials and, later, Kerberos.

Check yourselfKerberized NFS does not attach a Kerberos ticket to each request. What does it do instead, and under what assumption is that safe?

Full Kerberos authentication happens once, at mount time. The server records the user's ID and the client's address with the mount, and accepts later requests only if their UID and sender address match. Per-request tickets were too costly for stateless requests. It is safe if only one user at a time logs in to each client.

In short
  • NFS merges directory and file operations (e.g. create = Create + AddName); read also returns attributes; explicit offsets keep read and write idempotent.
  • remove and rename are not naturally idempotent, so servers keep a duplicate-request cache.
  • Identity (UID, GID) is checked on every request but can be forged; Kerberized NFS authenticates at mount and then matches UID + client address.
8.8

Mounting and Pathname Translation exam

Before a client can use any remote file, it needs a starting point: the file handle of some remote directory, glued into its own directory tree. That is what mounting does. Once a remote directory is mounted, the client still has to turn a long pathname into a file handle, and NFS makes it do that one component at a time. This section covers both.

The mount service

Each server runs a user-level mount service. Its configuration file, /etc/exports, lists the file systems the server is willing to export and which hosts may mount each one. The slide example gives every host on the 192.168.1.x subnet read-write access to /store:

/store   192.168.1.0/255.255.255.0(rw)

On the client, a modified mount command names three things: the remote host, the remote directory (which can be any subtree of an exported file system) and the local mount point, the local directory where the remote one should appear. The mount protocol, an RPC to the mount service, returns the remote directory's file handle, and this is passed to the VFS and the NFS client. From then on, programs open files under the mount point without knowing they are remote.

SERVER 1 / export people jon bob exports /export/people CLIENT / vmunix usr students x staff jon bob jim… /usr/students/jon is really Server 1's /export/people/jon SERVER 2 / nfs users jim ann exports /nfs/users remote mount remote mount
Two servers grafted into one client tree (CDK Fig. 12.10). Server 1's /export/people is mounted at the client's /usr/students, and Server 2's /nfs/users at /usr/staff. The dashed grey nodes are what the client sees under its mount points; they really live on the servers. A program can open /usr/students/jon without knowing it is remote.

Hard mounts and soft mounts exam

A remote file system can be mounted in one of two ways. They differ only in what happens when the server is unavailable. Hard mounts favour transparency, while soft mounts let programs handle the failure themselves.

Hard mountSoft mount
Server unavailableThe process blocks; the client retries until successAn error is returned after a few retries
PlusTransparent: processes simply continue after the server restartsRobust programs can detect and handle the failure
MinusPrograms can't recover gracefully from long outagesMany utilities don't check errors and misbehave
UseMost installationsRare
process NFS client server down blocked read request retry retry … restarted retry data data
Hard mount: wait forever. The client retries until the server is back; the process (gold bar) is blocked the whole time, then carries on as if nothing happened.
process NFS client server down read request retry retry gives up after a few retries error program decides what to do
Soft mount: give up and report. After a few retries the client returns an error, which a robust program can handle (and a careless one ignores).
Check yourself · Sample exam 2026 Q1(9) · worked answer, not officialMultiple choice: applications should receive an error after a few retries when the NFS server is unreachable, rather than wait. How should the remote file system be mounted?

Soft-mounted (option (a) on the paper). With a hard mount, the process blocks while the client retries until the server is reachable again, which is exactly the "wait" the question rules out.

The automounter

Mounting everything at boot is wasteful, and it fails if a server happens to be down. The automounter mounts a remote directory on demand, when an empty mount point is first referenced. It holds a table of mount points, each listing one or more servers.

AUTOMOUNTER TABLE /usr/lib → A, B, C identical read-only copies on all three servers program open("/usr/lib/…") automounter on the client server A server B (first) server C mounts /usr/lib from B unmounts after several minutes without use 1 2 3 4
Mount on first use, from whoever answers first. Because the three servers hold identical copies, the client gets the same files whichever one wins.
  1. A program references the empty mount point /usr/lib for the first time.
  2. The automounter looks up the mount point in its table and probes all the listed servers.
  3. Server B is the first to respond.
  4. The automounter mounts from B. After several minutes without use, it unmounts the directory again.

If the servers listed for a mount point hold identical copies (for example of /usr/lib), this gives read-only replication, plus some fault tolerance (a dead server simply doesn't answer) and some load balancing (a busy server answers late).

Pathname translation: one lookup per component

Why can't the client just send the whole path /usr/students/jon/notes.txt to a server and get back a handle? Because the path may cross a client mount point (or follow a symbolic link) into another server, and only the client knows its own mounts. The server has never heard of the client's /usr.

THE CLIENT'S PATH / usr students jon notes.txt mount point resolved by the client (own disk + mount table) must be looked up on Server 1 Server 1 has never heard of the client's /usr/students (a symlink could also lead elsewhere) So the client walks the path itself, sending one lookup per component.
Only the client can see the seam. The boundary between "local" and "on Server 1" lies in the client's mount table, so the server cannot be handed the whole path.

The client therefore translates the path iteratively, sending one lookup per component, each using the handle returned by the previous one. Caching lookup results hides most of the cost. When nothing is cached, though, /usr/users/jim/code/xyz.c needs 5 lookups. Here is the iteration for a file under the mounted directory, followed by a read.

NFS: open and read /usr/students/jon/notes.txt
user process NFS client + VFS NFS server mount earlier returned fh0 for /usr/students open("/usr/students/jon/notes.txt") lookup(fh0, "jon") fh1, attr lookup(fh1, "notes.txt") fh2, attr file descriptor (its v-node holds fh2) read 8192 bytes read(fh2, offset 0, count 8192) attr, data
  1. The client mounted Server 1's directory at /usr/students earlier, so it already holds that directory's handle, fh0. The user process knows nothing about NFS.
  2. The process calls an ordinary open. The VFS walks the path locally: / and usr are on the local disk, and students is the mount point, which maps to fh0.
  3. The NFS client sends lookup(fh0, "jon") for the first remote component and gets back the handle fh1 plus attributes.
  4. Next component: lookup(fh1, "notes.txt"), using the handle just returned, gives fh2. Two remote components, two lookups.
  5. The client returns an ordinary file descriptor to the process. Behind it, the file's v-node holds fh2; the read-write offset is kept at the client.
  6. The process reads 8192 bytes. The client turns that into read(fh2, offset 0, count 8192): handle and explicit offset, so the request is idempotent.
  7. The server replies with the attributes and the data. Every reply piggy-backs fresh attributes, which the client cache uses in 8.9.
Check yourself · Self-test Q8Why is NFS pathname translation iterative at the client? How many lookups does /usr/users/jim/code/xyz.c need?

The path may cross client mount points or symbolic links onto other servers, and only the client knows its mounts, so the server cannot translate it in one go. The client sends one lookup per component, each using the previous handle, which for this path means 5 lookups (usr, users, jim, code, xyz.c) when nothing is cached. Caching lookup results limits the cost, but lookup is still about 50% of server calls.

Check yourself · Self-test Q5Hard mounts vs soft mounts?

With a hard mount, the process blocks and the client retries forever. This is transparent, since processes carry on after the server restarts, but applications hang during outages. It is the common choice.

With a soft mount, an error is returned after a few retries. Applications can then recover, but many don't check errors.

Check yourselfThe automounter lists three servers holding identical copies of /usr/lib. What does that give, and what does it not give?

It gives read-only replication, with some fault tolerance and some load balancing, because the client mounts from whichever server answers first. It does not give replication of updates: nothing keeps writable copies consistent.

In short
  • The server's mount service exports what /etc/exports allows; the mount protocol returns the remote directory's file handle, which is grafted onto a local mount point.
  • Hard mount: block and retry until success (transparent, common). Soft mount: error after a few retries (robust programs can cope, rare). The automounter mounts on demand from the first server to answer.
  • Pathnames are translated iteratively, one lookup per component, because only the client knows its mount points.
8.9

Caching in NFS exam

Without caching, every read and write would cross the network, and NFS would be far slower than a local disk. With caching, there are copies of the same data in many places, and two different problems appear. At the server, the question is persistence: when the server says "written", is the data safe on disk if the server then crashes? At the clients, the question is consistency: does a client's cached copy still match what other clients have written? Keep the two apart; mixing them up is a classic way to lose marks.

Server caching

UNIX background. NFS server caching builds on ordinary UNIX caching. The kernel keeps a buffer cache of disk pages in memory. Read-ahead fetches the pages after those just read, betting that the program will want them next. Delayed write writes a modified page to disk only when its buffer is evicted (thrown out of the cache to make room). And sync flushes the whole cache to disk every 30 s. All of this works safely because there is only one cache and nothing bypasses it.

process KERNEL BUFFER CACHE page 7 page 8 page 9 page 2 just read read ahead dirty read-ahead: fetch the pages after those just read delayed write: a dirty page goes to disk only when its buffer is evicted sync: flush the whole cache every 30 s evict, sync disk Safe because there is only one cache and nothing bypasses it.
Ordinary UNIX caching. Dashed pages were fetched early by read-ahead; the gold page has been modified in memory but not yet written to disk. On one machine nobody can see the disk "behind" the cache, so the delay is harmless.

On an NFS server, caching reads causes no problems. Caching writes needs care, because clients must be sure their data is persistent if the server crashes. Why does that matter so much? Because of independent failures: the client keeps running after the server crashes, and it may act on writes it believes are safe (deleting its own copy, say, or telling the user "saved"). There are two policies. Write-through gives the guarantee on every reply but is slow; commit, added in NFS version 3, keeps the benefit of the cache but delays the guarantee.

Write-throughCommit (v3)
HowWritten to the cache and to disk before replyingKept in the cache; written to disk when the client sends a commit request, and the commit reply comes once the data is on disk
GuaranteePersistent as soon as the write reply arrivesPersistent after the commit reply. Clients commit on close of a file opened for writing
CostHeavy disk I/O and slow writes; poor when many writes hit the same dataFull cache benefit, but extra messages, and uncommitted data can be lost
client server disk write #1 to disk ok: on disk write #2 to disk ok: on disk safe, but every write waits for the disk
Write-through. The reply itself is the guarantee: once the client hears "ok", the data survives a server crash.
client server disk write #1 ok (cached) write #2 ok (cached) uncommitted: lost if the server crashes commit (on close) flush committed: on disk fast writes, but extra messages and a risk window
Commit. Writes are answered from the cache; only the commit reply guarantees persistence. The gold window is the price.
Exam MCQ trap · write-through vs commit vs delayed write

Read the question for when the data must be safe. "Safe as soon as the server replies to the write" means write-through. "Safe once the client has committed (e.g. on close)" means commit. A plain delayed write, as in the UNIX buffer cache, gives no guarantee at all about when the data reaches disk.

Check yourself · Sample exam 2026 Q1(10) · worked answer, not officialMultiple choice: once the NFS server has replied to a write, the data must survive a server crash, even if the client never sends a commit request. Which server caching policy is this?

Write-through (option (d) on the paper), which writes the data to disk before replying. Under the commit policy the data is safe only after a commit request, and a delayed write gives no guarantee at all.

Check yourself · Self-test Q7Compare write-through and commit server caching.

With write-through, the data is on disk before the server replies. This is safe, but slow, with heavy disk I/O. With commit, the data stays cached until the client commits it, which it does on close. This is fast, but needs extra messages, and uncommitted data can be lost. Either way, the policy is needed because client and server fail independently.

Client caching exam

The NFS client caches the results of read, write, getattr, lookup and readdir. That creates the consistency problem from 8.3: when one client writes a file, other clients' cached copies are not updated. And reads are affected too: if another client writes between two of your reads, your second read may return stale data from your cache. NFS has no callbacks (the server never tells clients that something changed), so clients poll the server to check their cached data.

To make polling cheap, each cache entry carries two timestamps:

  • Tc is the time when the entry was last validated (checked against the server) by this client.
  • Tm is the time when the block was last modified at the server. The client holds the value it saw, Tmclient; the server holds the current value, Tmserver.
CLIENT CACHE ENTRY (ONE PER CACHED BLOCK) cached block the data itself Tc when last validated Tm_client server's mod. time, as cached SERVER (PER FILE) Tm_server for data blocks Tm_server for attributes compared via getattr t = freshness interval: how long an entry may be trusted without asking the server
The three parameters of the cache check. Tc and Tmclient live in each client cache entry; Tmserver lives at the server (separately for data and attributes); t is a setting. Notice that Tm values are always server times, so the comparison does not depend on the client's clock.

An entry is valid at time T if:

The NFS validity condition
(T − Tc < t) ∨ (Tmclient = Tmserver)

where t is the freshness interval and ∨ means "or". In words: the entry is valid if it was validated less than t ago, or if the client's Tm still matches the server's (the block hasn't changed at the server since it was cached).

Validation happens on every use of an entry, in a fixed order. The cheap test comes first:

  1. The client tests T − Tc < t locally, with no network I/O. If it is true, the client uses the entry.
  2. Otherwise, it sends a getattr to fetch Tmserver. (The server keeps one Tm for a file's data blocks and another for its attributes.)
  3. If Tmclient = Tmserver, the entry is valid, and the client sets Tc = T (the entry has just been re-validated).
  4. If they differ, the client invalidates the entry and re-fetches the data from the server.
use a cached entry at time T T − Tc < t ? checked first: local, no network I/O yes use the entry no getattr → Tm_server one round trip to the server Tm_client = Tm_server ? yes valid: set Tc = T no invalidate; re-fetch the data
Cheap test first, expensive test only if needed. Most accesses stop at the first diamond and never touch the network. Only when the entry is older than t does the client pay for a getattr.

A worked example (from the slides, t = 10 s)

Client A has cached a value, and client B then updates it at the server. The moment to watch is A's read at 6 s.

TimeEventResult
0 sA validates and caches value 10Tc = 0, Tm = m0
4 sB's update to 20 reaches the serverTmserver = m1
6 sA reads; 6 − 0 < 10A returns the stale value 10 without asking the server
11 sA reads; 11 − 0 ≥ 10getattr shows m0 ≠ m1, so A fetches 20
The freshness check on a timeline (t = 10 s, then t = 5 s)
client A server client B 0 4 5 6 10 11 time (s) value 10 · Tm = m0 value 10 · Tm = m0 cache: 10 · Tc = 0 · Tm = m0 freshness window: T − Tc < t = 10 s B writes 20 value 20 · Tm = m1 6 − 0 = 6 < 10: use cache → 10 (stale!) 11 − 0 = 11 ≥ 10: ask the server getattr → Tm = m1 m0 ≠ m1: invalidate read → value 20 now Tc = 11, Tm = m1 With t = 5 s instead: at 6 s, 6 − 0 ≥ 5, so A sends getattr and returns the correct value 20.
  1. Three timelines: client A, the server and client B. Time runs right, in seconds. The server holds value 10, last modified at server time m0. The freshness interval is t = 10 s.
  2. 0 s: A validates and caches value 10, so Tc = 0 and Tmclient = m0. For the next 10 s (shaded), A will trust this entry without asking.
  3. 4 s: B's update to 20 reaches the server, whose modification time becomes m1. Nobody tells A: NFS has no callbacks.
  4. 6 s: A reads. T − Tc = 6 − 0 = 6, which is less than 10, so the cheap local test passes and A returns its cached 10, which is stale. No message is sent.
  5. 11 s: A reads again. 11 − 0 = 11 ≥ 10, so the window has expired and A sends getattr. The server reports Tmserver = m1, which differs from A's m0, so A invalidates the entry.
  6. A re-fetches the data and gets 20; its entry now has Tc = 11 and Tm = m1. Staleness lasted from 4 s to 11 s, never longer than t.
  7. Now try t = 5 s (outlined window). At 6 s, 6 − 0 ≥ 5, so A would already have sent a getattr, seen m1 ≠ m0, and returned the correct 20. Smaller t, fresher data, but more getattr calls from every client.

So staleness is bounded by t, and choosing t trades consistency against traffic. A small t gives near one-copy semantics, at the cost of more getattr calls. Solaris adapts t per file between 3 and 30 s, depending on how often the file is updated, and uses 30–60 s for directories, which carry a lower risk of concurrent update.

NFS cuts the validation traffic in three more ways:

  • A new Tmserver is applied to all cached blocks of the file, not only the block being checked, so one getattr re-validates (or invalidates) the whole file.
  • Attributes are piggy-backed on every operation's reply (recall that read returns attributes), so every reply refreshes Tm for free.
  • As just described, t adapts per file.

Writes and bio-daemons

Writes work differently. A modified page is marked dirty and flushed to the server asynchronously when the file is closed or at a client sync. This mimics local write-back; it is not the same thing as persistence at the server, which is the server policy's job (write-through or commit, above).

Bio-daemons (block I/O daemons) are background processes that keep the client from blocking on the network. They do read-ahead, fetching the next block after each read, and delayed write, sending a block once it fills. They help performance but are not required for correctness.

process write(), read() CLIENT CACHE (KERNEL) blk 1 blk 2 blk 3 blk 4 dirty read ahead bio-daemons read-ahead: fetch the next block delayed write: send a full block dirty pages flushed on close or sync NFS server write-through or commit next block async The process does not wait for the network on these steps.
Client writes are write-back, not write-through. The process's write lands in the client cache (gold, dirty) and is sent to the server later, on close or sync; bio-daemons do the network work in the background.
The upshot: NFS is not one-copy

Updates are delayed in the writer's cache, and readers can be stale for up to t. That is why NFS is not suitable for close coordination between processes on different hosts (for example, two programs passing messages through a shared file).

And do not confuse the two mechanisms: server write-through and commit are about persistence (does the data survive a server crash?), whereas client validation is about consistency between clients (do I see your write?).

Past exam question

2016 final · Q8(a) · 5 marks

Describe the caching policy used by NFS at the client. What parameters are used? What checks are done, in what order and why?

Model answer
  • What is cached. The client caches the results of read, write, getattr, lookup and readdir. There are no server callbacks, so the client polls the server to validate cached entries before using them.
  • Parameters. Three are kept for each cache entry. Tc is the time the entry was last validated by the client. Tm is the time the block was last modified at the server: the client holds Tm_client (the cached value) and the server holds Tm_server (the current value, one for data blocks and one for attributes). t is the freshness interval, which Solaris sets adaptively per file at 3–30 s, depending on update frequency, and at 30–60 s for directories.
  • Validity. An entry is valid at time T if (T − Tc < t) ∨ (Tm_client = Tm_server).
  • Checks, in order. On every access to a cached entry, first T − Tc < t is computed locally with no network I/O; this is cheap, so it is done first, and if true the entry is used immediately. Only if it fails does the client send getattr to obtain Tm_server and compare it with Tm_client. If equal, the entry is valid and Tc is set to T; if not, the entry is invalidated and fresh data is requested from the server.
  • Why this order. It avoids a server round trip on most accesses. Traffic is reduced further by piggy-backing attributes on every reply and by applying a new Tm_server to all blocks of the file.
  • Trade-off. A small t gives near one-copy consistency but more getattr load.
  • Writes. Modified pages are marked dirty and flushed to the server asynchronously on close or sync, and bio-daemons do read-ahead and delayed write so that processes don't block. The server then makes the data persistent through write-through or commit.
Check yourselft = 8 s. Client A validated a cached block at Tc = 20 s. Client B's update reaches the server at 23 s. A reads at 26 s and again at 29 s. What does each read return, and does it contact the server?

At 26 s: 26 − 20 = 6 < 8, so the local test passes. A uses its cache with no network I/O and returns the old (stale) value.

At 29 s: 29 − 20 = 9 ≥ 8, so A sends getattr. Tmserver changed at 23 s, so it no longer equals Tmclient; A invalidates the entry, fetches B's new value, and sets Tc = 29.

Check yourselfWhy does the NFS client test T − Tc < t before comparing Tmclient with Tmserver?
  1. Because the Tm comparison is less accurate.
  2. Because the first test is local and free, while the second needs a getattr round trip.
  3. Because the server requires it.
  4. Because Tc is always larger than Tm.

B. The time test needs no network I/O, so doing it first avoids a server round trip on most accesses. Only when it fails is the expensive getattr made.

Check yourselfAn administrator halves t on every client. What gets better, and what gets worse?

Better: consistency. Readers can now be stale for at most half as long, closer to one-copy semantics. Worse: load. Entries expire twice as often, so every client sends more getattr calls, and the server handles more traffic.

In short
  • Server: reads are cached freely; writes use write-through (on disk before the reply, safe but slow) or commit (cached until the client commits on close, fast but uncommitted data can be lost). This is about persistence.
  • Client: polling with Tc, Tm and t. Valid if (T − Tc < t) ∨ (Tmclient = Tmserver); the local time test comes first, getattr only if it fails. Staleness is bounded by t.
  • Writes are flushed asynchronously on close or sync, helped by bio-daemons; NFS is not one-copy and not for close coordination.
8.10

NFS Evaluated exam

How well does NFS actually meet the eight requirements of 8.3? This section first looks at performance, where measurements found two surprises, and then scores NFS requirement by requirement. The scorecard is a ready-made answer to "evaluate NFS".

Performance and optimisations

NFS moves data in 8 KB blocks, and UDP packets are extended to 9 KB so that one block fits in one RPC; version 3 can negotiate larger sizes. Every operation also acts as an implicit getattr, because replies carry attributes.

Measurements found two problems: frequent getattr calls (the cache validation of 8.9), and slow write-through writes. Writes are only about 5% of calls, and the slow writes were later fixed by commit. Meanwhile lookup makes up about 50% of server calls, because of step-by-step pathname translation. Even so, large NFS servers sustain very high loads.

ONE BLOCK PER RPC UDP packet, extended to 9 KB RPC one 8 KB block version 3 can negotiate larger sizes SHARE OF SERVER CALLS (MEASURED) lookup about 50% all other calls write: about 5% step-by-step pathname translation is expensive
Where the server's time goes. Half of all calls are lookups, a direct cost of resolving one component per RPC; writes are rare. Lookup caching (8.8) is what keeps this bearable.

The scorecard

Access transparencyYES VFS + normal system calls: programs run unchanged Location transparencyNOT ENFORCED each client mounts where it likes; same only with same mount tables Mobility transparencyNO moving a file system means updating every client's mount table ScalabilityGOOD, COULD BE BETTER add hardware or servers; limited by hot-spot files ReplicationREAD-ONLY ONLY automounter copies; NIS replicates key-value data (master–slave) HeterogeneityGOOD runs on almost every OS and platform Fault toleranceACCEPTABLE stateless, idempotent; hard mounts hinder graceful handling ConsistencyTUNABLE approximately one-copy, tuned through t SecurityAVAILABLE Kerberos or RPCSEC_GSS, but often not deployed EfficiencyGOOD many tuning options green = met · gold = partly met · red = not met
NFS against the requirements. Its strengths (access transparency, heterogeneity, fault tolerance, efficiency) come from the VFS and the stateless, idempotent server. Its weaknesses are naming (per-client mounts) and copies (no replication of updates, approximate consistency).

A few lines deserve unpacking. Location transparency is not enforced because each client mounts remote file systems wherever it likes, so the same file can have different pathnames on different clients; a uniform name space is achievable only if every client uses the same mount tables. Mobility transparency is absent because moving a file system to another server means editing every client's mount table. Scalability is limited by hot-spot files: a single heavily used file lives on one server, and adding servers doesn't spread its load. Fault tolerance is acceptable: the server is stateless and its operations idempotent, so clients resume after a server restart, and client crashes don't affect the server at all; hard mounts, however, hinder graceful handling. NIS (Network Information Service) is a companion service that replicates simple key-value databases on a master–slave basis.

Check yourself · Self-test Q9Evaluate NFS against the DFS requirements.

NFS provides access transparency, but location transparency is not enforced and there is no mobility transparency. Scalability is good but limited by hot spots, and replication is read-only. Heterogeneity is good, and so is fault tolerance, because the server is stateless and its operations idempotent. Consistency is approximate and tunable, security is available via Kerberos, and efficiency is good.

Check yourselfTwo clients mount the same NFS export, one at /usr/staff and the other at /home. Which transparency does this show NFS lacks?
  1. Access transparency
  2. Location transparency (a uniform name space)
  3. Performance transparency
  4. Heterogeneity

B. The same file has different pathnames on the two clients, so there is no uniform name space. NFS still has access transparency (A): both clients use the same calls.

Check yourselfAbout what share of NFS server calls are lookups, and why so many?

About 50%, because pathnames are translated one component per RPC at the client (8.8). Writes, by contrast, are only about 5% of calls.

In short
  • 8 KB blocks in 9 KB UDP packets; replies act as implicit getattr; lookup is about half of all server calls, writes about 5%.
  • Strong: access transparency, heterogeneity, fault tolerance, efficiency. Weak: location (not enforced), mobility (none), replication (read-only), consistency (approximate, tunable via t), security (available but often not deployed).
8.11

AFS, and NFS vs AFS

NFS pays for its stateless server with constant polling: every client keeps asking "has this changed?". The Andrew File System (AFS), from Carnegie Mellon University (CMU), asks the opposite question: what if the server remembered who has a copy and told them when it changed? AFS also gives transparent access to UNIX files, but its main design goal is scalability, which it achieves by minimising client–server interaction. AFS is not on the slides (CDK §12.4), so it is low priority; you need just enough to answer "compare NFS and AFS".

How AFS works

  • Whole-file serving and caching. AFS transfers whole files (in 64 KB chunks in AFS-3) and caches them on the client's local disk, where the cache survives reboots.
  • Venus and Vice. Venus is the client process, playing the part of the client module; Vice is the server process. The kernel intercepts open and close on shared files (those under /cmu) and passes them to Venus.
  • Volumes. Files are grouped into volumes (AFS's file groups) and identified by a 96-bit fid (file identifier).
  • Open, read/write, close. When open is called and there is no valid cached copy, Venus fetches the whole file. read and write then use the local copy, with no server contact. On close, a modified file is sent back to the server.
WORKSTATION (CLIENT) User program open, read, close Venus client process (the client module) UNIX kernel intercepts open/close on /cmu open, close local disk cache: whole files, survives reboots NETWORK SERVER Vice server process keeps callback lists volumes · 96-bit fids fetch (open, no valid copy) whole file + callback promise store (close, if modified) callback: file changed
Whole files, cached on disk, with the server keeping track. Compare with the NFS figure in 8.6: there the client module is in the kernel and moves blocks; here Venus is a user process that moves whole files, and the server (Vice) remembers who holds a copy.

The callback promise

When Vice sends a file to a client, it gives a callback promise: a promise to notify that client if another client updates the file. When a client stores a new version, Vice sends a callback to every other holder, and their promises become cancelled. So on open, Venus uses its cached copy if the promise is still valid, and re-fetches the file if the promise was cancelled. In case a callback message was lost, promises are also revalidated after a client reboot, or after T minutes in which nothing has been heard from the server.

AFS callbacks, one message at a time
Venus on A Vice Venus on B promise: valid promise: cancelled promise: valid (new) callbacks: {A} callbacks: {A, B} promise: valid 1 · open F: fetch F (v1) + callback promise read/write the local copy, no server 2 · open F: fetch F (v1) + callback promise 3 · close: store F (v2) 4 · callback: F changed 5 · open F: promise cancelled, fetch F (v2) + new promise
  1. Venus on workstation A, the server process Vice, and Venus on workstation B. Nobody has file F cached yet.
  2. A's program opens F. Venus has no valid copy, so it fetches the whole file. Vice sends F with a callback promise and adds A to F's callback list: this is server state.
  3. A's reads and writes go to the local copy on A's disk. No messages at all; and further opens of F also need no server contact while the promise is valid.
  4. B opens F and fetches it the same way, getting its own promise. Vice's list for F is now {A, B}.
  5. B modifies F, and on close Venus stores the new version, v2, at Vice.
  6. Vice sends a callback to every other holder, here A. A's promise becomes cancelled. A's program, if it still has F open, keeps using its old copy: consistency is only enforced at open and close.
  7. Next time A opens F, Venus sees the promise is cancelled and re-fetches v2 with a new promise. The server was contacted only because something changed.

Semantics: last close wins

Consistency is enforced only at open and close. If there are concurrent writers on different hosts, the last close wins, and the other updates are silently lost.

A server B open open editing A's local copy editing B's local copy close F = vA close F = vB A's changes are silently lost
Whole-file caching means whole-file overwrites. Each close stores the whole file, so the later store replaces the earlier one entirely. AFS assumes this is rare, because sharing is rare in typical UNIX use.

NFS vs AFS

The key contrast is in how copies are checked. NFS keeps its server stateless and makes clients poll; AFS keeps callback lists at the server so that it can notify clients only when a file changes.

NFS client NFS server getattr unchanged getattr unchanged getattr changed! traffic even when nothing changes
NFS: the client polls. Stateless server, but a getattr every time an entry is older than t.
Venus Vice (silence while the file is unchanged) callback: changed one message, only on change
AFS: the server calls back. A stateful server, but no traffic until something actually changes.
NFSAFS
CachingBlocks, in memoryWhole files, on local disk
ServerStatelessStateful (callback lists)
ValidationClient polls (getattr, t)Server callbacks, sent only when a file changes
ConsistencyStale for up to tOpen/close semantics; last close wins
Name spacePer-client mountsUniform shared space
ScalabilityLimited by polling and hot spotsBetter (one benchmark: 40% server load vs 100% for NFS)

AFS's design rests on assumptions about typical UNIX workloads: mostly small files, reads about 6× more common than writes, mostly sequential access, little sharing, and bursty reuse (a file used now is likely to be used again soon). Under those assumptions whole-file caching is a big win. It is therefore unsuitable for databases, which are large, shared and updated in small pieces.

Check yourself · Self-test Q10Compare the NFS and AFS consistency mechanisms. Which scales better?

NFS has a stateless server and caches blocks, and its clients poll with timestamps (Tc, Tm, t). AFS caches whole files on disk and relies on callback promises from a stateful server, which gives open/close semantics in which the last close wins.

AFS scales better. It contacts the server only on change, and opening a valid cached file needs no server contact at all.

Check yourselfTwo users on different AFS workstations edit the same file at the same time. A closes first, then B. What does the server end up with?

B's version. Each close stores the whole file, so the last close wins and A's changes are silently lost. AFS only enforces consistency at open and close.

Check yourselfWhich is true of AFS but not of NFS?
  1. The server keeps no per-client state.
  2. Clients cache blocks in memory.
  3. The server keeps callback lists and notifies clients when a file changes.
  4. Clients send getattr to validate their caches.

C. A, B and D describe NFS. AFS's server is stateful precisely because it remembers who holds each file.

Later developments (brief)

The designs above have since been extended in several directions, which you only need to know by name.

NFS successors

Closing the gaps

Spritely NFS adds open/close and callbacks for exact one-copy semantics; NQNFS uses leases (time-limited promises); WebNFS allows direct Internet access; NFSv4 supports callbacks or leases and transparent migration.
Storage organisation

Under the server

RAID combines striping (spreading data over disks) with redundancy; log-structured file storage; and serverless designs such as xFS and Frangipani.
Cloud (slides)

EFS vs S3

Amazon EFS is a shared file system mounted via NFSv4, suited to existing software that expects a shared directory. Amazon S3 is an object store of buckets and keys with a GET/PUT API, suited to storing and fetching whole objects. The choice comes down to interface and semantics.
In short
  • AFS: whole files cached on local disk; Venus (client) and Vice (server); volumes and 96-bit fids; fetch on open, store on close.
  • Callback promises from a stateful server replace NFS's polling; consistency only at open/close, and the last close wins.
  • AFS scales better (server contacted only on change) but suits typical UNIX workloads, not databases. Later: Spritely NFS, NQNFS leases, WebNFS, NFSv4; RAID, log-structured, serverless; EFS (NFSv4) vs S3 (objects).
8.12

Tutorial 8, Dropbox and Pitfalls

The Week 9 tutorial (Tutorial 08) asked four questions and then looked at how a real file-synchronisation service, Dropbox, is built. The answers below are the tutors' official answers from Ed, which staff confirmed are examinable. Questions 2–4 and the Dropbox case study go beyond the lecture material earlier in this Part. Question 1 (the transparencies) is answered in 8.3.

T8-2 and T8-3 · Absolute names vs mount points

An absolute name gives a file's complete address, server plus path, in the form <machine name : path name>, for example fileserver3:/data/report.txt. Mount points are what Sun NFS uses: the client creates local names that refer to remote locations, and at boot each local name is bound to its remote name. The OS keeps a mount table saying which server and path each mount point maps to.

open("fileserver3:/data/report.txt") fileserver3 fileserver4 file moves every hard-coded reference breaks
Absolute name. The name is the location, so the file cannot move.
open("/data/…") mount table /data → fileserver3 (next boot: fileserver4) fileserver3 fileserver4 the program's name never changes
Mount point. The name has no location in it; only the client's table does.
Absolute names · advantages

It is trivial to find a file once you have its name. There is no extra state: each name is self-contained, so no global state is needed. They give greater scalability, and are easy to add and delete.

Absolute names · disadvantages

No location transparency: the file is location-dependent and cannot move without breaking every reference to it. Less resilient to failure: if the named server is down, there is no alternative.

Mount points · advantages

Names contain no information about the file's location, so the remote location can change between reboots.

Mount points · disadvantages

Hard to maintain: there is a table on every client, and behaviour is unclear when machines fail or files migrate. Confusing: two different local names can map to the same remote file, and one file can have different names on different clients (Part IX).

T8-4 · Remote access vs client caching

Compare two DFS designs: (A) every read() and write() goes to the server by RPC, and clients do no caching; (B) clients cache files and perform file operations locally.

client no cache server bottleneck read() write() read() + consistent view for everyone − network on every call: slow
(A) Remote access, no caching. One copy, so consistent; but slow.
client cached file read() write() done locally server less load occasionally + local operations: much faster − copies hard to keep consistent
(B) Client caching. Fast, but the copies can disagree.
Official answer · T8-4

Design (A): the advantage is a consistent view of the file system, since every client sees the latest writes. The disadvantage is performance: network access on every call, and the server becomes a bottleneck.

Design (B) is the reverse. Its advantage is that local operations give much better performance, and the server has less load. Its disadvantages: it leaves open what happens when the client fails and where the client should store cached files, and it is hard to keep local copies consistent with the remote copy.

Real systems sit between the two: NFS caches blocks and validates them with timestamps (8.9), while AFS caches whole files and uses callbacks (8.11).

Case study: Dropbox

The model. A client daemon (a background process) watches a local "sync" folder. Changes are uploaded automatically, and new versions are downloaded automatically. Every user's computer holds a complete copy of their Dropbox.

Scale and goals. Dropbox has over 100 million users and handles 1 billion files a day. It needs a small client that uses few resources, must cope with a possibly low-capacity network link to the user, and needs a scalable back end. 99% of the code is Python.

Traffic. Traffic flows only when something changes, and each change is one upload and one download. The read/write ratio is therefore about 1:1, unlike typical read-heavy services (compare AFS's 6:1 assumption). Compression reduces the traffic.

Dropbox upload: only the blocks the server lacks
client metaserver blockserver b1 b2 b3 → h1, h2, h3 1 · commit(file, [h1, h2, h3]) need blocks h2, h3 h1 already known 2 · blocks h2, h3 (at most about 8 MB per request) stored 3 · commit(file, [h1, h2, h3]) ok
  1. Three parties: the client daemon, the metaserver (metadata: which files exist and which blocks make them up) and the blockserver (the block contents).
  2. The client splits the file into blocks and computes a hash of each: h1, h2, h3. A hash is a short fingerprint of a block's contents.
  3. It asks the metaserver to commit the file, listing the hashes.
  4. The metaserver replies "need blocks" for any hashes it doesn't know. In this example it already has h1 (perhaps from an earlier version), so it asks for h2 and h3 only.
  5. The client sends those blocks to the blockserver, at most about 8 MB per request.
  6. The client commits again, and this time it succeeds. Blocks whose hashes are already known are never re-sent: this is deduplication.

Download takes three steps:

  1. The client periodically polls the metaserver, listing the files it knows about.
  2. The metaserver returns information on new files.
  3. The client fetches the blocks it lacks from the blockserver and rebuilds the file using ordinary local system calls (open(), write()).

Streaming sync: other clients start downloading blocks before the upload has committed, so large files sync sooner. LAN sync: clients broadcast on the local network by UDP; a response leads to a TCP connection, and the blocks are then pulled over HTTP from the peer instead of from the cloud.

Architecture evolution

The Dropbox back end went through five versions as it grew, and each step illustrates a concept from the course.

v1 · mid 2007 0 users one server runs web, app, MySQL and sync bottleneck + single point of failure v2 · late 2007 about 0 users file data → Amazon S3 database on its own machine; clients poll 4 MB chunks, hashed, stored once separate roles; deduplication v3 · early 2008 50k users notification server replaces polling metaserver (local) + blockserver (Amazon) metadata vs data; push, not pull v4 · late 2008 about 100k users load balancer; more meta- and blockservers blockservers RPC to metaservers memcache before the DB multiple servers; caching v5 · early 2012 over 50M users two-level hierarchy of notification servers, about 1M connections each hierarchy for scalability
Growing pains, one concept each (gold). Note v3: splitting metadata (metaserver) from data (blockserver) is the same separation as the directory service and flat file service in 8.4.

In words: v1 shows that a single server is a bottleneck and a single point of failure. In v2 the server ran out of disk, so file data moved to Amazon S3, a key-value store, and the overloaded database moved to its own machine; files were split into 4 MB chunks with a hash per chunk, and identical chunks stored once. v3 replaced polling by a notification server (push instead of pull) and split metadata from data. v4 put more servers behind a load balancer, had blockservers send RPCs to metaservers instead of accessing the database, and put memcache in front of the database (caching, Part II). v5 introduced a two-level hierarchy of notification servers, about 1 million connections per server: hierarchy for scalability (Part I).

Check yourself · Ed practice quiz 4Files are referenced as <server_name>:<path>, hard-coded in clients, so moving files breaks references. What naming scheme is this, and what does it lack?

Absolute naming; it provides no location transparency.

Check yourself · Ed practice quiz 5Every read() and write() goes to the server by RPC with no caching: consistent, but slow under load. What kind of design is this?

A stateless RPC-based DFS with no caching (design (A) of T8-4): it gives a consistent view, but performance suffers from constant network access and server load.

Check yourselfWhy does Dropbox's upload send hashes to the metaserver before sending any blocks?

So the metaserver can reply "need blocks" only for hashes it doesn't already know. Blocks with known hashes are never re-sent: this deduplication saves traffic on the user's possibly slow link.

Common pitfalls

Lose-marks list
  • "Stateless" means the server keeps no per-client session state. It does not mean nothing is kept: files are still stored.
  • Create is not idempotent. The flat Read and Write operations are idempotent only because the position is explicit.
  • The VFS gives access transparency. Location transparency is a separate matter, and it depends on clients mounting consistently.
  • Server write-through and commit are about persistence, whereas client validation is about consistency between clients.
In short
  • Absolute names: simple, stateless, scalable, but no location transparency and no fallback. Mount points: location-free names, but a table on every client and confusing aliases.
  • No caching (A) is consistent but slow; caching (B) is fast but hard to keep consistent. NFS and AFS sit in between.
  • Dropbox: hashed blocks with deduplication, metaserver vs blockserver, polling then notification servers, and a back end that grew by separating roles, caching and hierarchy.