.\"	$NetBSD: 2.t,v 1.3 2003/08/07 10:30:54 agc Exp $
.\"
.\" Copyright (c) 1993
.\"	The Regents of the University of California.  All rights reserved.
.\"
.\" This document is derived from software contributed to Berkeley by
.\" Rick Macklem at The University of Guelph.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\"    notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\"    notice, this list of conditions and the following disclaimer in the
.\"    documentation and/or other materials provided with the distribution.
.\" 3. Neither the name of the University nor the names of its contributors
.\"    may be used to endorse or promote products derived from this software
.\"    without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\"	@(#)2.t	8.1 (Berkeley) 6/8/93
.\"
.sh 1 "Not Quite NFS, Crash Tolerant Cache Consistency for NFS"
.pp
Not Quite NFS (NQNFS) is an NFS like protocol designed to maintain full cache
consistency between clients in a crash tolerant manner.
It is an adaptation of the NFS protocol such that the server supports both NFS
and NQNFS clients while maintaining full consistency between the server and
NQNFS clients.
This section borrows heavily from work done on Spritely-NFS [Srinivasan89],
but uses Leases [Gray89] to avoid the need to recover server state information
after a crash.
The reader is strongly encouraged to read these references before
trying to grasp the material presented here.
.sh 2 "Overview"
.pp
The protocol maintains cache consistency by using a somewhat
Sprite [Nelson88] like protocol,
but is based on short term leases\** instead of hard state information
about open files.
.(f
\** A lease is a ticket permitting an activity that is
valid until some expiry time.
.)f
The basic principal is that the protocol will disable client caching of a
file whenever that file is write shared\**.
.(f
\** Write sharing occurs when at least one client is modifying a file while
other client(s) are reading the file.
.)f
Whenever a client wishes to cache data for a file it must hold a valid lease.
There are three types of leases: read caching, write caching and non-caching.
The latter type requires that all file operations be done synchronously with
the server via. RPCs.
A read caching lease allows for client data caching, but no file modifications
may be done.
A write caching lease allows for client caching of writes,
but requires that all writes be pushed to the server when the lease expires.
If a client has dirty buffers\**
.(f
\** Cached write data is not yet pushed (written) to the server.
.)f
when a write cache lease has almost expired, it will attempt to
extend the lease but is required to push the dirty buffers if extension fails.
A client gets leases by either doing a \fBGetLease RPC\fR or by piggybacking
a \fBGetLease Request\fR onto another RPC. Piggybacking is supported for the
frequent RPCs Getattr, Setattr, Lookup, Readlink, Read, Write and Readdir
in an effort to minimize the number of \fBGetLease RPCs\fR required.
All leases are at the granularity of a file, since all NFS RPCs operate on
individual files and NFS has no intrinsic notion of a file hierarchy.
Directories, symbolic links and file attributes may be read cached but
are not write cached.
The exception here is the attribute file_size, which is updated during cached
writing on the client to reflect a growing file.
.pp
It is the server's responsibility to ensure that consistency is maintained
among the NQNFS clients by disabling client caching whenever a server file
operation would cause inconsistencies.
The possibility of inconsistencies occurs whenever a client has
a write caching lease and any other client,
or local operations on the server,
tries to access the file or when
a modify operation is attempted on a file being read cached by client(s).
At this time, the server sends an \fBeviction notice\fR to all clients holding
the lease and then waits for lease termination.
Lease termination occurs when a \fBvacated the premises\fR message has been
received from all the clients that have signed the lease or when the lease
expires via. timeout.
The message pair \fBeviction notice\fR and \fBvacated the premises\fR roughly
correspond to a Sprite server\(->client callback, but are not implemented as an
actual RPC, to avoid the server waiting indefinitely for a reply from a dead
client.
.pp
Server consistency checking can be viewed as issuing intrinsic leases for a
file operation for the duration of the operation only. For example, the
\fBCreate RPC\fR will get an intrinsic write lease on the directory in which
the file is being created, disabling client read caches for that directory.
.pp
By relegating this responsibility to the server, consistency between the
server and NQNFS clients is maintained when NFS clients are modifying the
file system as well.\**
.(f
\** The NFS clients will continue to be \fIapproximately\fR consistent with
the server.
.)f
.pp
The leases are issued as time intervals to avoid the requirement of time of day
clock synchronization. There are three important time constants known to
the server. The \fBmaximum_lease_term\fR sets an upper bound on lease duration.
The \fBclock_skew\fR is added to all lease terms on the server to correct for
differing clock speeds between the client and server and \fBwrite_slack\fR is
the number of seconds the server is willing to wait for a client with
an expired write caching lease to push dirty writes.
.pp
The server maintains a \fBmodify_revision\fR number for each file. It is
defined as a unsigned quadword integer that is never zero and that must
increase whenever the corresponding file is modified on the server.
It is used
by the client to determine whether or not cached data for the file is
stale.
Generating this value is easier said than done. The current implementation
uses the following technique, which is believed to be adequate.
The high order longword is stored in the ufs inode and is initialized to one
when an inode is first allocated.
The low order longword is stored in main memory only and is initialized to
zero when an inode is read in from disk.
When the file is modified for the first time within a given second of
wall clock time, the high order longword is incremented by one and
the low order longword reset to zero.
For subsequent modifications within the same second of wall clock
time, the low order longword is incremented. If the low order longword wraps
around to zero, the high order longword is incremented again.
Since the high order longword only increments once per second and the inode
is pushed to disk frequently during file modification, this implies
0 \(<= Current\(miDisk \(<= 5.
When the inode is read in from disk, 10
is added to the high order longword, which ensures that the quadword
is greater than any value it could have had before a crash.
This introduces apparent modifications every time the inode falls out of
the LRU inode cache, but this should only reduce the client caching performance
by a (hopefully) small margin.
.sh 2 "Crash Recovery and other Failure Scenarios"
.pp
The server must maintain the state of all the current leases held by clients.
The nice thing about short term leases is that maximum_lease_term seconds
after the server stops issuing leases, there are no current leases left.
As such, server crash recovery does not require any state recovery. After
rebooting, the server refuses to service any RPCs except for writes until
write_slack seconds after the last lease would have expired\**.
.(f
\** The last lease expiry time may be safely estimated as
"boottime+maximum_lease_term+clock_skew" for machines that cannot store
it in nonvolatile RAM.
.)f
By then, the server would not have any outstanding leases to recover the
state of and the clients have had at least write_slack seconds to push dirty
writes to the server and get the server sync'd up to date. After this, the
server simply services requests in a manner similar to NFS.
In an effort to minimize the effect of "recovery storms" [Baker91],
the server replies \fBtry_again_later\fR to the RPCs it is not
yet ready to service.
.pp
After a client crashes, the server may have to wait for a lease to timeout
before servicing a request if write sharing of a file with a cachable lease
on the client is about to occur.
As for the client, it simply starts up getting any leases it now needs. Any
outstanding leases for that client on the server prior to the crash will either be renewed or expire
via timeout.
.pp
Certain network partitioning failures are more problematic. If a client to
server network connection is severed just before a write caching lease expires,
the client cannot push the dirty writes to the server. After the lease expires
on the server, the server permits other clients to access the file with the
potential of getting stale data. Unfortunately I believe this failure scenario
is intrinsic in any delay write caching scheme unless the server is required to
wait \fBforever\fR for a client to regain contact\**.
.(f
\** Gray and Cheriton avoid this problem by using a \fBwrite through\fR policy.
.)f
Since the write caching lease has expired on the client,
it will sync up with the
server as soon as the network connection has been re-established.
.pp
There is another failure condition that can occur when the server is congested.
The worst case scenario would have the client pushing dirty writes to the server
but a large request queue on the server delays these writes for more than
\fBwrite_slack\fR seconds. It is hoped that a congestion control scheme using
the \fBtry_again_later\fR RPC reply after booting combined with
the following lease termination rule for write caching leases
can minimize the risk of this occurrence.
A write caching lease is only terminated on the server when there are have
been no writes to the file and the server has not been overloaded during
the previous write_slack seconds. The server has not been overloaded
is approximated by a test for sleeping nfsd(s) at the end of the write_slack
period.
.sh 2 "Server Disk Full"
.pp
There is a serious unresolved problem for delayed write caching with respect to
server disk space allocation.
When the disk on the file server is full, delayed write RPCs can fail
due to "out of space".
For NFS, this occurrence results in an error return from the close system
call on the file, since the dirty blocks are pushed on close.
Processes writing important files can check for this error return
to ensure that the file was written successfully.
For NQNFS, the dirty blocks are not pushed on close and as such the client
may not attempt the write RPC until after the process has done the close
which implies no error return from the close.
For the current prototype,
the only solution is to modify programs writing important
file(s) to call fsync and check for an error return from it instead of close.
.sh 2 "Protocol Details"
.pp
The protocol specification is identical to that of NFS [Sun89] except for
the following changes.
.ip \(bu
RPC Information
.(l
        Program Number 300105
        Version Number 1
.)l
.ip \(bu
Readdir_and_Lookup RPC
.(l
        struct readdirlookargs {
                fhandle file;
                nfscookie cookie;
                unsigned count;
                unsigned duration;
        };

        struct entry {
                unsigned cachable;
                unsigned duration;
                modifyrev rev;
                fhandle entry_fh;
                nqnfs_fattr entry_attrib;
                unsigned fileid;
                filename name;
                nfscookie cookie;
                entry *nextentry;
        };

        union readdirlookres switch (stat status) {
        case NFS_OK:
                struct {
                        entry *entries;
                        bool eof;
                } readdirlookok;
        default:
                void;
        };

        readdirlookres
        NQNFSPROC_READDIRLOOK(readdirlookargs) = 18;
.)l
Reads entries in a directory in a manner analogous to the NFSPROC_READDIR RPC
in NFS, but returns the file handle and attributes of each entry as well.
This allows the attribute and lookup caches to be primed.
.ip \(bu
Get Lease RPC
.(l
        struct getleaseargs {
                fhandle file;
                cachetype readwrite;
                unsigned duration;
        };

        union getleaseres switch (stat status) {
        case NFS_OK:
                bool cachable;
                unsigned duration;
                modifyrev rev;
                nqnfs_fattr attributes;
        default:
                void;
        };

        getleaseres
        NQNFSPROC_GETLEASE(getleaseargs) = 19;
.)l
Gets a lease for "file" valid for "duration" seconds from when the lease
was issued on the server\**.
.(f
\** To be safe, the client may only assume that the lease is valid
for ``duration'' seconds from when the RPC request was sent to the server.
.)f
The lease permits client caching if "cachable" is true.
The modify revision level and attributes for the file are also returned.
.ip \(bu
Eviction Message
.(l
        void
        NQNFSPROC_EVICTED (fhandle) = 21;
.)l
This message is sent from the server to the client. When the client receives
the message, it should flush data associated with the file represented by
"fhandle" from its caches and then send the \fBVacated Message\fR back to
the server. Flushing includes pushing any dirty writes via. write RPCs.
.ip \(bu
Vacated Message
.(l
        void
        NQNFSPROC_VACATED (fhandle) = 20;
.)l
This message is sent from the client to the server in response to the
\fBEviction Message\fR. See above.
.ip \(bu
Access RPC
.(l
        struct accessargs {
                fhandle file;
                bool read_access;
                bool write_access;
                bool exec_access;
        };

        stat
        NQNFSPROC_ACCESS(accessargs) = 22;
.)l
The access RPC does permission checking on the server for the given type
of access required by the client for the file.
Use of this RPC avoids accessibility problems caused by client->server uid
mapping.
.ip \(bu
Piggybacked Get Lease Request
.pp
The piggybacked get lease request is functionally equivalent to the Get Lease
RPC except that is attached to one of the other NQNFS RPC requests as follows.
A getleaserequest is prepended to all of the request arguments for NQNFS
and a getleaserequestres is inserted in all NFS result structures just after
the "stat" field only if "stat == NFS_OK".
.(l
        union getleaserequest switch (cachetype type) {
        case NQLREAD:
        case NQLWRITE:
                unsigned duration;
        default:
                void;
        };

        union getleaserequestres switch (cachetype type) {
        case NQLREAD:
        case NQLWRITE:
                bool cachable;
                unsigned duration;
                modifyrev rev;
        default:
                void;
        };
.)l
The get lease request applies to the file that the attached RPC operates on
and the file attributes remain in the same location as for the NFS RPC reply
structure.
.ip \(bu
Three additional "stat" values
.pp
Three additional values have been added to the enumerated type "stat".
.(l
        NQNFS_EXPIRED=500
        NQNFS_TRYLATER=501
        NQNFS_AUTHERR=502
.)l
The "expired" value indicates that a lease has expired.
The "try later"
value is returned by the server when it wishes the client to retry the
RPC request after a short delay. It is used during crash recovery (Section 2)
and may also be useful for server congestion control.
The "authetication error" value is returned for kerberized mount points to
indicate that there is no cached authentication mapping and a Kerberos ticket
for the principal is required.
.sh 2 "Data Types"
.ip \(bu
cachetype
.(l
        enum cachetype {
                NQLNONE = 0,
                NQLREAD = 1,
                NQLWRITE = 2
        };
.)l
Type of lease requested. NQLNONE is used to indicate no piggybacked lease
request.
.ip \(bu
modifyrev
.(l
        typedef unsigned hyper modifyrev;
.)l
The "modifyrev" is a unsigned quadword integer value that is never zero
and increases every time the corresponding file is modified on the server.
.ip \(bu
nqnfs_time
.(l
        struct nqnfs_time {
                unsigned seconds;
                unsigned nano_seconds;
        };
.)l
For NQNFS times are handled at nano second resolution instead of micro second
resolution for NFS.
.ip \(bu
nqnfs_fattr
.(l
        struct nqnfs_fattr {
                ftype type;
                unsigned mode;
                unsigned nlink;
                unsigned uid;
                unsigned gid;
                unsigned hyper size;
                unsigned blocksize;
                unsigned rdev;
                unsigned hyper bytes;
                unsigned fsid;
                unsigned fileid;
                nqnfs_time atime;
                nqnfs_time mtime;
                nqnfs_time ctime;
                unsigned flags;
                unsigned generation;
                modifyrev rev;
        };
.)l
The nqnfs_fattr structure is modified from the NFS fattr so that it stores
the file size as a 64bit quantity and the storage occupied as a 64bit number
of bytes. It also has fields added for the 4.4BSD va_flags and va_gen fields
as well as the file's modify rev level.
.ip \(bu
nqnfs_sattr
.(l
        struct nqnfs_sattr {
                unsigned mode;
                unsigned uid;
                unsigned gid;
                unsigned hyper size;
                nqnfs_time atime;
                nqnfs_time mtime;
                unsigned flags;
                unsigned rdev;
        };
.)l
The nqnfs_sattr structure is modified from the NFS sattr structure in the
same manner as fattr.
.lp
The arguments to several of the NFS RPCs have been modified as well. Mostly,
these are minor changes to use 64bit file offsets or similar. The modified
argument structures follow.
.ip \(bu
Lookup RPC
.(l
        struct lookup_diropargs {
                unsigned duration;
                fhandle dir;
                filename name;
        };

        union lookup_diropres switch (stat status) {
        case NFS_OK:
                struct {
                        union getleaserequestres lookup_lease;
                        fhandle file;
                        nqnfs_fattr attributes;
                } lookup_diropok;
        default:
                void;
        };

.)l
The additional "duration" argument tells the server to get a lease for the
name being looked up if it is non-zero and the lease is specified
in "lookup_lease".
.ip \(bu
Read RPC
.(l
        struct nqnfs_readargs {
                fhandle file;
                unsigned hyper offset;
                unsigned count;
        };
.)l
.ip \(bu
Write RPC
.(l
        struct nqnfs_writeargs {
                fhandle file;
                unsigned hyper offset;
                bool append;
                nfsdata data;
        };
.)l
The "append" argument is true for apeend only write operations.
.ip \(bu
Get Filesystem Attributes RPC
.(l
        union nqnfs_statfsres (stat status) {
        case NFS_OK:
                struct {
                        unsigned tsize;
                        unsigned bsize;
                        unsigned blocks;
                        unsigned bfree;
                        unsigned bavail;
                        unsigned files;
                        unsigned files_free;
                } info;
        default:
                void;
        };
.)l
The "files" field is the number of files in the file system and the "files_free"
is the number of additional files that can be created.
.sh 1 "Summary"
.pp
The configuration and tuning of an NFS environment tends to be a bit of a
mystic art, but hopefully this paper along with the man pages and other
reading will be helpful. Good Luck.
