trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a..@apache.org
Subject git commit: DOC: Large update of cache architure.
Date Tue, 15 Oct 2013 01:49:33 GMT
Updated Branches:
  refs/heads/master 30fcc2b2e -> 5c39d024c


DOC: Large update of cache architure.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/5c39d024
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/5c39d024
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/5c39d024

Branch: refs/heads/master
Commit: 5c39d024cd6bdd3aaa1b224904d23368b7d3d614
Parents: 30fcc2b
Author: Alan M. Carroll <amc@network-geographics.com>
Authored: Mon Oct 14 09:44:51 2013 -0500
Committer: Alan M. Carroll <amc@network-geographics.com>
Committed: Mon Oct 14 20:49:07 2013 -0500

----------------------------------------------------------------------
 doc/arch/cache/cache-arch.en.rst                | 378 +++++++++++++++----
 doc/arch/cache/cache-data-structures.en.rst     |  10 +-
 .../images/ats-cache-doc-layout-pre-3-2-0.png   | Bin 7731 -> 0 bytes
 doc/arch/cache/images/ats-cache-doc-layout.png  | Bin 9470 -> 0 bytes
 doc/arch/cache/images/ats-cache-layout.jpg      | Bin 55045 -> 0 bytes
 .../cache/images/ats-cache-storage-units.png    | Bin 6190 -> 0 bytes
 .../cache/images/cache-directory-structure.png  | Bin 0 -> 28553 bytes
 .../cache/images/cache-doc-layout-3-2-0.png     | Bin 0 -> 7357 bytes
 .../cache/images/cache-doc-layout-4-0-1.png     | Bin 0 -> 8524 bytes
 doc/arch/cache/images/cache-multi-fragment.png  | Bin 0 -> 47782 bytes
 doc/arch/cache/images/cache-span-layout.png     | Bin 0 -> 8533 bytes
 doc/arch/cache/images/cache-spans.png           | Bin 0 -> 5085 bytes
 doc/arch/cache/images/cache-stripe-layout.png   | Bin 0 -> 11594 bytes
 doc/glossary.en.rst                             |  13 +-
 14 files changed, 314 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/cache-arch.en.rst
----------------------------------------------------------------------
diff --git a/doc/arch/cache/cache-arch.en.rst b/doc/arch/cache/cache-arch.en.rst
index e249604..cc7395a 100755
--- a/doc/arch/cache/cache-arch.en.rst
+++ b/doc/arch/cache/cache-arch.en.rst
@@ -1,19 +1,19 @@
 .. Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
- 
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
    http://www.apache.org/licenses/LICENSE-2.0
- 
-  Unless required by applicable law or agreed to in writing,
-  software distributed under the License is distributed on an
-  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  KIND, either express or implied.  See the License for the
-  specific language governing permissions and limitations
-  under the License.
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
 
 Cache Architecture
 ******************
@@ -23,9 +23,20 @@ Cache Architecture
 Introduction
 ~~~~~~~~~~~~
 
-In addition to an HTTP proxy, |ATS| is also an HTTP cache. |TS| can cache any octet stream although it currently supports only those octet streams delivered by the HTTP protocol. When such a stream is cached it is termed an *object* in the cache. The server from which the content was retrieved is called the *origin server*. A stored object contains the octet stream content and the HTTP request and response headers. Each object is identified by a globally unique value called a *cache key*. By default this is generated by taking the `MD5 hash <http://www.openssl.org/docs/crypto/md5.html>`_ of the URI used to retrieve the content from the origin server.
+In addition to an HTTP proxy, |ATS| is also an HTTP cache. |TS| can cache any octet stream although it currently
+supports only those octet streams delivered by the HTTP protocol. When such a stream is cached (along with the HTTP
+protocol headers) it is termed an *object* in the cache. Each object is identified by a globally unique value called a
+*cache key*. By default this is generated by taking the `MD5 hash <http://www.openssl.org/docs/crypto/md5.html>`_ of the
+URI used to retrieve the content from the origin server.
+
+The purpose of this document is to describe the basic structure and implementation details of the |TS| cache.
+Configuration of the cache will be discussed only to the extent needed to understand the internal mechanisms. This
+document will be useful primarily to |TS| developers working on the |TS| codebase or plugins for |TS|. It is assumed the
+reader is already familiar with the :ref:`admin-guide` and specifically with :ref:`http-proxy-caching` and
+:ref:`configuring-the-cache` along with the associated configuration files and values.
 
-The purpose of this document is to provide implementation details of the |TS| cache. Configuration of the cache will be discussed only to the extent needed to understand the internal mechanisms. This documeint will be useful primarily to |TS| developers working on the |TS| codebase or plugins for |TS|. It is assumed the reader is already familiar with the :ref:`admin-guide` and specifically with :ref:`http-proxy-caching` and :ref:`configuring-the-cache`, along with the associated configuration files and values.
+Unfortunately the internal terminology is not particularly consistent so this document will frequently use terms in
+different ways than they are used in the code in an attempt to create some consistency.
 
 Cache Layout
 ~~~~~~~~~~~~
@@ -35,107 +46,221 @@ The first step in understanding cache operations is to understand the data struc
 Cache storage
 =============
 
-The raw storage for the |TS| cache is configured in :file:`storage.config`. This defines the "storage units" used by the cache. The storage units are treated as undifferentiated byte ranges.
+The raw storage for the |TS| cache is configured in :file:`storage.config`. Each line in the file defines a :term:`cache span` which is treated as an undifferentiated unit of storage.
 
-.. figure:: images/ats-cache-storage-units.png
+.. figure:: images/cache-spans.png
    :align: center
 
-   Example cache storage units
+   Two cache spans
 
-This storage is used by defining *volumes* in :file:`volume.config`. Volumes can be defined by a percentage of the total storage or an absolute amount of storage. Each volume is striped across the storage units so that every volume will be on every storage unit for robustness and performance.
+This storage organized in to a set of :term:`cache volume`\ s which are defined in :file:`volume.config`. Cache volumes
+can be defined by a percentage of the total storage or an absolute amount of storage. By default each cache volume is spread across all of the cache spans for robustness. The intersection of a cache volume and a cache span is a :term:`cache stripe`. Each cache span is divided in to cache stripes and each cache volume is a collection of those stripes.
 
-If the volumes for the example storage units were defined as
+If the cache volumes for the example cache spans were defined as
 
 .. image:: images/ats-cache-volume-definition.png
    :align: center
 
 then the actual layout would look like
 
-.. image:: images/ats-cache-volume-layout.png
+.. image:: images/cache-span-layout.png
    :align: center
 
-A cached object is stored entirely in a single volume and a single storage unit, objects are never split across volumes. The storage that holds a single object is called a *document*. Objects are assigned to volumes automatically based on a hash of the URI used to retrieve the object from the origin server. It is possible to configure this to a limited extent in :file:`hosting.config` which supports content from specific host or domain to be stored on specific volumes.
+A cached object is stored entirely in a single stripe, and therefore in a single cache span - objects are never split
+across cache volumes. Objects are assigned to a stripe (and hence to a cache volume) automatically based on a hash of
+the URI used to retrieve the object from the origin server. It is possible to configure this to a limited extent in
+:file:`hosting.config` which supports content from specific host or domain to be stored on specific volumes. In
+addition, as of version 4.0.1 it is possible to control which cache spans (and hence, which cache stripes) are contained
+in a specific cache volume.
 
-Volume Structure
+The layout and structure of the cache spans, the cache volumes, and the cache stripes that compose them are derived
+entirely from the :file:`storage.config` and :file:`cache.config` and is recomputed from scratch when the
+:process:`traffic_server` is started. Therefore any change to those files can (and almost always will) invalidate the
+existing cache in its entirety.
+
+Stripe Structure
 ================
 
-Volumes are treated as an undifferentiated span of bytes. Internally each stripe on each storage unit is treated almost entirely independently. The data structures described in this section are duplicated for each volume stripe, the part of a volume that resides in a single storage unit. This is how the term "volume" and :cpp:class:`Vol` are used inside the code. What a user thinks of as a volume of the cache is stored in the little used :cpp:class:`CacheVol`.
+|TS| treats the storage associated with a cache stripe as an undifferentiated span of bytes. Internally each stripe is
+treated almost entirely independently. The data structures described in this section are duplicated for each stripe.
+Interally the term "volume" is used for these stripes and implemented primarily in :cpp:class:`Vol`. What a user thinks
+of as a volume (what this document calls a "cache volume") is represented by :cpp:class:`CacheVol`.
 
-.. index: write cursor
-.. _write-cursor:
+.. note::
 
-Each storage unit in a volume is divided in to two areas -- content and directory. The directory area is used to maintain disk backups of the :ref:`in memory directory <volume-directory>`. The content area stores the actual objects and is used as a circular buffer where new documents overwrite the least recently cached documents. In particular no operating system file structure is present inside a cache volume. The location in a volume where new cache data is written is called the *write cursor*. This means that objects can be de facto evicted from cache even if they have not expired if the data is overwritten by the write cursor.
+   Stripe assignment must be done before working with an object because the directory is local to the stripe. Any cached
+   objects for which the stripe assignment is changed are effectively lost as their directory data will not be found in
+   the new stripe.
 
-.. figure:: images/ats-cache-write-cursor.png
+.. index:: cache directory
+.. _cache-directory:
+
+Cache Directory
+---------------
+
+.. index:: directory entry
+.. index:: fragment
+.. index:: cache ID
+
+.. _fragment:
+
+Content in a stripe is tracked via a directory. We call each element of the directory a "directory entry" and each is
+represented by :cpp:class:`Dir`. Each entry refers to a chunk of contiguous storage in the cache. These are referred to
+variously as "fragments", "segments", "docs" / "documents", and a few other things. This document will use the term
+"fragment" as that is the most common reference in the code. Overall the directory is treated as a hash with a "cache
+ID" as the key. A cache ID is a 128 value generated in various ways depending on context. This key is reduced and used
+as an index in to the directory to locate an entry in the standard way.
+
+The directory is used as a memory resident structure which means a directory entry is as small as possible (currently 10
+bytes). This forces some compromises on the data that can be stored there. On the hand this means that most cache misses
+do not require disk I/O which has a large performance benefit.
+
+An additional point is the directory is always fully sized. Once a stripe is initialized the directory size is
+fixed and is never changed. This size is related (roughly linearly) to the size of the stripe. It is for this reason the
+memory footprint of |TS| depends strongly on the size of the disk cache. Because the directory size does not change,
+neither does this memory requirement so |TS| does not consume more memory as more content is stored in the cache. If
+there is enough memory to run |TS| with an empty cache there is enough to run it with a full cache.
+
+.. figure:: images/cache-directory-structure.png
    :align: center
 
-   The write cursor and documents in the cache.
+Each entry stores the cache ID as the key, along with an offset in to the stripe and a size. The size stored in the
+directory entry is an :ref:`approximate size <dir-size>` which is at least as big as the actual data. Exact size data is
+stored in the fragment on disk.
 
-.. index:: volume directory
-.. _volume-directory:
+.. note::
 
-To find objects each volume has a directory of all documents in that volume. This directory is kept memory resident which means cache misses do not cause disk I/O. A side effect of this is that increasing the size of the cache (not storing more objects) increases the memory footprint of Traffic Server. Every document consumes at least one directory entry, although larger documents can require more entries.
+   Data in HTTP headers cannot be examined without disk I/O. This includes the original URL for the object. The original
+   source of the cache ID is not stored anywhere.
 
-.. index:: cache key
-.. _cache-key:
+The entries in a directory are grouped. The first level grouping is a *bucket*. This is a fixed number (currently 4 -
+defined a ``DIR_DEPTH``) of entries. The index generated from a cache ID is used as a bucket index (not an entry index).
+Buckets are grouped in to *segments*. All segments in a stripe have the same number of buckets. The number of segments
+in a stripe is chosen so that each segment has as many buckets as possible without exceeeding 65535 entries in a
+segment. Note that all segments in the same stripe will have the same number of buckets.
 
-The directory is a hash table with a 128 bit key. This kind of key is referred to as a *cache key*. The cache key for an object is used to locate the corresponding directory entry after volume assignment [#]_. This entry in turn references a span in the volume content area which contains the object header and possibly the object as well. The size stored in the directory entry is an :ref:`approximate size <dir-size>` which is at least as big as the actual data on disk. The document header on disk contains metadata for the document including the exact size of the entire document, and the HTTP headers associated with the object.
+Each entry has a previous and next index value which is used to link the entries in the same segment. The index size is
+16 bits which suffices to index any entry in the same segment. The stripe header contains an array of entry indices
+which are used as the roots of a free list. When a stripe is initialized all entries in a segment are put in the
+corresponding free list rooted in the stripe header. Entries are removed from this list when used and returned when no
+longer in use. Entries are allocated from the bucket indexed by a cache key if possible. The entries in the bucket are
+searched first and if any are on the free list, that entry is used. If none are available than the first entry on the
+segment free list is used. This entry is attached to the bucket via the same next and previous indices used for the free
+list so that it can be found when doing a lookup of a cache ID.
 
-.. note:: Data in HTTP headers cannot be examined without disk I/O. This includes the original URL for the object, as only the cache key (possibly) derived from it is stored in memory.
+Storage Layout
+--------------
 
-For persistence the directory is stored on disk in copies (A and B), one of which is "clean" and the other of which is being written from memory. These are stored in the directory section of the volume.
+The storage layout is the stripe metadata followed by cached content. The metadata consists of three parts - the stripe
+header, the directory, and the stripe footer. The metadata is stored twice. The header and the footer are instances of
+:cpp:class:`VolHeaderFooter`. This is a stub structure which can have a trailing variable sized array. This array is
+used as the segment free lists in the directory. Each contains the segment index of the first element of the free list
+for the segment. The footer is a copy of the header without the segment free lists. This makes the size of the header
+dependent on the directory but not the footer.
 
-.. figure:: images/ats-cache-volume-directory.png
+.. figure:: images/cache-stripe-layout.png
    :align: center
 
-   Volume directory structure
+Each stripe has several values that describe its basic layout.
+
+skip
+   The start of stripe data. This represents either space reserved at the start of a physical device to avoid problems
+   with the host operating system, or an offset representing use of space in the cache span by other stripes.
 
-The total size of the directory (the number of entries) is computed by taking the size of the volume and dividing by the average object size. The directory always consumes this amount of memory which has the effect that if cache size is increased so is the memory requirement for |TS|. The average object size defaults to 8000 bytes but can be configured using the value::
+start
+   The offset for the start of the content, after the stripe metadata.
+
+length
+   Total number of bytes in the stripe. :cpp:member:`Vol::len`.
+
+data length
+   Total number of blocks in the stripe available for content storage. :cpp:member:`Vol::data_blocks`.
+
+.. note:: Great care must be taken with sizes and lengths in the cache code because there are at least three different metrics (bytes, cache blocks, store blocks) used in various places.
+
+The total size of the directory (the number of entries) is computed by taking the size of the stripe and dividing by the
+average object size. The directory always consumes this amount of memory which has the effect that if cache size is
+increased so is the memory requirement for |TS|. The average object size defaults to 8000 bytes but can be configured
+using the value::
 
    proxy.config.cache.min_average_object_size
 
-in :file:`records.config`. Increasing the average object size will reduce the memory footprint of the directory at the expense of reducing the number of distinct objects that can be stored in the cache [#]_.
+in :file:`records.config`. Increasing the average object size will reduce the memory footprint of the directory at the
+expense of reducing the number of distinct objects that can be stored in the cache [#]_.
 
-.. note:: Cache data on disk is never updated.
+.. index: write cursor
+.. _write-cursor:
+
+The content area stores the actual objects and is used as a circular buffer where new objects overwrite the least
+recently cached objects. The location in a volume where new cache data is written is called the *write cursor*. This
+means that objects can be de facto evicted from cache even if they have not expired if the data is overwritten by the
+write cursor. If an object is overwritten this is not detected at that time and the directory is not updated. Instead it
+will be noted if the object is accessed in the future and the disk read of the fragment fails.
+
+.. figure:: images/ats-cache-write-cursor.png
+   :align: center
 
-This is a key thing to keep in mind. What appear to be updates (such as doing a refresh on stale content and getting back a 304) are actually new copies of data being written at the write cursor. The originals are left as "dead" space which will be consumed when the write cursor arrives at that disk location. Once the volume directory is updated (in memory!) the original object in the cache is effectively destroyed. This is the general space management techinque used in other cases as well. If an object needs to removed from cache, only its volume directory entry is changed. No other work (and *particulary* no disk I/O) needs to be done.
+   The write cursor and documents in the cache.
 
-.. [#] Because each storage unit in each volume has a separate directory, the assignment must be done before the directory lookup.
+.. note:: Cache data on disk is never updated.
 
-.. [#] An interesting potential optimization would be configuring average object size per cache volume.
+This is a key thing to keep in mind. What appear to be updates (such as doing a refresh on stale content and getting
+back a 304) are actually new copies of data being written at the write cursor. The originals are left as "dead" space
+which will be consumed when the write cursor arrives at that disk location. Once the stripe directory is updated (in
+memory!) the original fragment in the cache is effectively destroyed. This is the general space management techinque
+used in other cases as well. If an object needs to removed from cache, only the directory needs to be changed. No other
+work (and *particularly* no disk I/O) needs to be done.
 
 Object Structure
 ================
 
-Objects are stored as two types of data, metadata and content data. Metadata is all the data about the object and the content and includes the HTTP headers.  The content data is the content of the object, the actual data delivered to the client as the object.
+Objects are stored as two types of data, metadata and content data. Metadata is all the data about the object and the
+content and includes the HTTP headers. The content data is the content of the object, the octet stream delivered to the
+client as the object.
 
-Objects are rooted in a :cpp:class:Doc structure stored in the cache. This is termed the "first ``Doc``" and always contains the metadata. It is always accessed first for any object. This ``Doc`` is located by doing a lookup of the corresponding cache key in the volume directory which specifies the location and approximate size of the ``Doc``. The ``Doc`` itself has fully accurate size data of both that specific ``Doc`` and the object.
+Objects are rooted in a :cpp:class:`Doc` structure stored in the cache. :cpp:class:`Doc` serves as the header data for a
+fragment and is contained at the start of every fragment. The first fragment for an object is termed the "first ``Doc``"
+and always contains the object metadata. Any operation on the object will read this fragment first. The fragment is
+located by converting the cache key for the object to a cache ID and then doing a lookup for a directory entry with that
+key. The directory entry has the offset and approximate size of the first fragment which is then read from the disk. This fragment will contain the request header and response along with overall object properties (such as content length).
 
 .. index:: alternate
 
-|TS| supports `varying content <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44>`_ for objects. These are called *alternates*. All metadata for all alternates is stored in the first ``Doc`` including the set of alternates and the HTTP headers for them. This enables `alternate selection <http://trafficserver.apache.org/docs/trunk/sdk/http-hooks-and-transactions/http-alternate-selection.en.html>`_ to be done after the initial read from disk. An object that has more than one alternate will have the alternate content stored separately from the first ``Doc``. For objects with only one alternate the content may or may not be in the same (first) fragment as the metadata. Each separate alternate content is allocated a volume directory entry and the key for that entry is stored in the first ``Doc`` metadata.
+|TS| supports `varying content <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44>`_ for objects. These
+are called *alternates*. All metadata for all alternates is stored in the first fragment including the set of alternates
+and the HTTP headers for them. This enables `alternate selection
+<http://trafficserver.apache.org/docs/trunk/sdk/http-hooks-and-transactions/http-alternate-selection.en.html>`_ to be
+done after the initial read from disk. An object that has more than one alternate will have the alternate content stored
+separately from the first fragment. For objects with only one alternate the content may or may not be in the same (first)
+fragment as the metadata. Each separate alternate content is allocated a directory entry and the key for that
+entry is stored in the first fragment metadata.
 
-Prior to version 3.2.0 only the header data (not the fragment data) was stored in the :cpp:class:`CacheHTTPInfoVector` class which was marshaled to a variable length area of the on disk image.
+Prior to version 4.0.1 the header data was stored in the :cpp:class:`CacheHTTPInfoVector` class which was marshaled to a variable length area of the on disk image, followed by information about additional fragments if needed to store the object.
 
-.. figure:: images/ats-cache-doc-layout-pre-3-2-0.png
+.. figure:: images/cache-doc-layout-3-2-0.png
    :align: center
 
-   ``Doc`` layout, pre 3.2.0
+   ``Doc`` layout 3.2.0
 
-This had the problem that with only one fragment table it could not be reliable accurate for objects with more than one alternate [#]_. Therefore the fragment data was moved from being a separate variable length section of the metadata to being directly incorporated in to the :cpp:class:`CacheHTTPInfoVector` along with the header data, yielding a layout of the following form.
+This had the problem that with only one fragment table it could not be reliable for objects with more than one alternate
+[#]_. Therefore the fragment data was moved from being a separate variable length section of the metadata to being
+directly incorporated in to the :cpp:class:`CacheHTTPInfoVector`, yielding a layout of the following form.
 
-.. figure:: images/ats-cache-doc-layout.png
+.. figure:: images/cache-doc-layout-4-0-1.png
    :align: center
 
-   ``Doc`` layout, 3.2.0
+   ``Doc`` layout 4.0.1
+
+Each element in the vector contains for each alternate, in addition to the HTTP headers and the fragment table (if any), a cache key. This cache key identifies a directory entry that is referred to as the "earliest ``Doc``". This is the location where the content for the alternate begins.
 
-Each element in the vector contains for each alternate, in addition to the HTTP headers and the fragment table (if any), a cache key. This cache key identifies a volume directory entry that is referred to as the "earliest ``Doc``". This is the location where the content for the alternate begins.
+When the object is first cached, it will have a single alternate and that will be stored (if not too large) in first ``Doc``. This is termed a *resident alternate* in the code. This can only happen on the initial store of the object. If the metadata is updated (such as a ``304`` response to an ``If-Modified-Since`` request) then unless the object is small, the object data will be left in the original fragment and a new fragment written as the first fragment, making the alternate non-resident. "Small" is defined as less than the value :ts:cv:`proxy.config.cache.alt_rewrite_max_size`.
 
-When the object is first cached, it will have a single alternate and that will be stored (if not too large) in first ``Doc``. This is termed a *resident alternate* in the code. Resident alternates are not linked and the next time the header information is updated the object content will be separated.
+.. note::
 
-.. note:: The :cpp:class:`CacheHTTPInfoVector` is stored only in the first ``Doc``. Subsequent ``Doc`` instances will have an ``hlen`` of zero.
+   The :cpp:class:`CacheHTTPInfoVector` is stored only in the first ``Doc``. Subsequent ``Doc`` instances for the
+   object, including the earliest ``Doc``, should have an ``hlen`` of zero and if not, it is ignored.
 
-Large objects are split in to *fragments* when written to the cache. Each fragment has its own entry in the volume directory. This is indicated by a total document length that is longer than the content in first ``Doc`` or an earliest ``Doc``. In such a case a fragment offset table is stored. This contains the byte offset in the object content of the first byte of content data for each fragment past the first (as the offset for the first is always zero). This allows range requests to be serviced much more efficiently for large objects, as intermediate fragments can be skipped the first fragment with relevant data loaded next after the first/earliest ``Doc``.  The last fragment in the sequence is detected by the fragment size and offset reaching the end of the total size of the object, there is no explicit end mark. Each fragment is computationally chained from the previous in that the cache key for fragment N is computed by::
+Large objects are split in to multiple fragments when written to the cache. This is indicated by a total document length that is longer than the content in first ``Doc`` or an earliest ``Doc``. In such a case a fragment offset table is stored. This contains the byte offset in the object content of the first byte of content data for each fragment past the first (as the offset for the first is always zero). This allows range requests to be serviced much more efficiently for large objects, as intermediate fragments can be skipped the first fragment with relevant data loaded next after the first/earliest ``Doc``.  The last fragment in the sequence is detected by the fragment size and offset reaching the end of the total size of the object, there is no explicit end mark. Each fragment is computationally chained from the previous in that the cache key for fragment N is computed by::
 
    key_for_N_plus_one = next_key(key_for_N);
 
@@ -143,15 +268,23 @@ where ``next_key`` is a global function that deterministically computes a new ca
 
 Objects with multiple fragments are laid out such that the data fragments (including the earliest ``Doc``) are written first and the first ``Doc`` is written last. When read from disk, both the first and earliest ``Doc`` are validated (tested to ensure that they haven't been overwritten by the write cursor) to verify that the entire document is present on disk (as they bookend the other fragments - the write cursor cannot overwrite them without overwriting at leastone of the verified ``Doc`` instances). Note that while the fragments of a single object are ordered they are not necessarily contiguous as data from different objects are interleaved as the data arrives in |TS|.
 
-.. index:: pinned
+.. figure:: images/cache-multi-fragment.png
+   :align: center
 
-Documents which are "pinned" into the cache must not be overwritten so they are "evacuated" from in front of the write cursor. Each fragment is read and rewritten. There is a special lookup mechanism for objects that are being evacuated so that they can be found in memory rather than the potentially unreliable disk regions. The cache scans ahead of the write cursor to discover pinned objects as there is a dead zone immediately before the write cursor from which data cannot be evacuated. Evacuated data is read from disk and placed in the write queue and written as its turn comes up.
+   Multi-alternate and multi-fragment object storage
 
-It appears that objects can only be pinned via the :file:`cache.config` file and if the value::
+.. index:: pinned
 
-   proxy.config.cache.permit.pinning
+Documents which are "pinned" into the cache must not be overwritten so they are "evacuated" from in front of the write
+cursor. Each fragment is read and rewritten. There is a special lookup mechanism for objects that are being evacuated so
+that they can be found in memory rather than the potentially unreliable disk regions. The cache scans ahead of the write
+cursor to discover pinned objects as there is a dead zone immediately before the write cursor from which data cannot be
+evacuated. Evacuated data is read from disk and placed in the write queue and written as its turn comes up.
 
-is set to non-zero (it is zero by default). Objects which are in use when the write cursor is near use the same underlying evacuation mechanism but are handled automatically and not via the explicit ``pinned`` bit in :cpp:class:`Dir`.
+It appears that objects can only be pinned via the :file:`cache.config` file and if the
+:ts:cv:`proxy.config.cache.permit.pinning` is set to non-zero (it is zero by default). Objects which are in use when the
+write cursor is near use the same underlying evacuation mechanism but are handled automatically and not via the explicit
+``pinned`` bit in :cpp:class:`Dir`.
 
 .. [#] It could, under certain circumstances, be accurate for none of the alternates.
 
@@ -381,22 +514,44 @@ If this is zero then the built caclulations are used which compare the freshness
 
 If the object is not stale then it is served to the client. If stale the client request may be changed to an ``If Modified Since`` request to revalidate.
 
-The request is served using a standard virtual connection tunnel (``HttpTunnel``) with the :cpp:class:`CacheVC` acting as the producer and the client ``NetVC`` acting as the sink. If the request is a range request this can be modified with a transform to select the appropriate parts of the object or, if the request contains a single range, it can use the range acceleration.
+The request is served using a standard virtual connection tunnel (``HttpTunnel``) with the :cpp:class:`CacheVC` acting
+as the producer and the client ``NetVC`` acting as the sink. If the request is a range request this can be modified with
+a transform to select the appropriate parts of the object or, if the request contains a single range, it can use the
+range acceleration.
 
-Range acceleration is done by consulting a fragment offset table attached to the earliest ``Doc`` which contains offsets for all fragments past the first. This allows loading the fragment containing the first requested byte immediately rather than performing reads on the intermediate fragments.
+Range acceleration is done by consulting a fragment offset table attached to the earliest ``Doc`` which contains offsets
+for all fragments past the first. This allows loading the fragment containing the first requested byte immediately
+rather than performing reads on the intermediate fragments.
 
 Cache Write
 ===========
 
-Writing to cache is handled by an instance of the class :cpp:class:`CacheVC`. This is a virtual connection which receives data and writes it to cache, acting as a sink. For a standard transaction data transfers between virtual connections (*VConns*) are handled by :ccp:class:HttpTunnel. Writing to cache is done by attaching a ``CacheVC`` instance as a tunnel consumer. It therefore operates in parallel with the virtual connection that transfers data to the client. The data does not flow to the cache and then to the client, it is split and goes both directions in parallel. This avoids any data synchronization issues between the two.
+Writing to cache is handled by an instance of the class :cpp:class:`CacheVC`. This is a virtual connection which
+receives data and writes it to cache, acting as a sink. For a standard transaction data transfers between virtual
+connections (*VConns*) are handled by :cpp:class:HttpTunnel. Writing to cache is done by attaching a ``CacheVC``
+instance as a tunnel consumer. It therefore operates in parallel with the virtual connection that transfers data to the
+client. The data does not flow to the cache and then to the client, it is split and goes both directions in parallel.
+This avoids any data synchronization issues between the two.
 
 .. sidebar:: Writing to disk
 
-   The actual write to disk is handled in a separate thread dedicated to I/O operations, the AIO threads. The cache logic marshals the data and then hands the operation off to the AIO thread which signals back once the operation completes.
+   The actual write to disk is handled in a separate thread dedicated to I/O operations, the AIO threads. The cache
+   logic marshals the data and then hands the operation off to the AIO thread which signals back once the operation
+   completes.
 
-While each ``CacheVC`` handles its transactions independently, they do interact at the volume level as each ``CacheVC`` makes calls to the volume object to write its data to the volume content. The ``CacheVC`` accumulates data internally until either the transaction is complete or the amount of data to write exceeds the target fragment size. In the former case the entire object is submitted to the volume to be written. In the latter case a target fragment size amount of data is submitted and the ``CacheVC`` continues to operate on subsequent data. The volume in turn places these write requests in an holding area called the `aggregation buffer`_.
+While each ``CacheVC`` handles its transactions independently, they do interact at the volume level as each ``CacheVC``
+makes calls to the volume object to write its data to the volume content. The ``CacheVC`` accumulates data internally
+until either the transaction is complete or the amount of data to write exceeds the target fragment size. In the former
+case the entire object is submitted to the volume to be written. In the latter case a target fragment size amount of
+data is submitted and the ``CacheVC`` continues to operate on subsequent data. The volume in turn places these write
+requests in an holding area called the `aggregation buffer`_.
 
-For objects under the target fragment size there is no consideration of order, the object is simply written to the volume content. For larger objects the earliest ``Doc`` is written first and the first ``Doc`` written last. This provides some detection ability should the object be overwritten. Because of the nature of the write cursor no fragment after the first fragment (in the earliest ``Doc``) can be overwritten without also overwriting that first fragment (since we know at the time the object was finalized in the cache the write cursor was at the position of the first ``Doc``).
+For objects under the target fragment size there is no consideration of order, the object is simply written to the
+volume content. For larger objects the earliest ``Doc`` is written first and the first ``Doc`` written last. This
+provides some detection ability should the object be overwritten. Because of the nature of the write cursor no fragment
+after the first fragment (in the earliest ``Doc``) can be overwritten without also overwriting that first fragment
+(since we know at the time the object was finalized in the cache the write cursor was at the position of the first
+``Doc``).
 
 .. note:: It is the responsibility of the ``CacheVC`` to not submit writes that exceed the target fragment size.
 
@@ -411,31 +566,94 @@ Cache write also covers the case where an existing object in the cache is modifi
 * An alternate of the object is retrieved from an origin server and added to the object.
 * An alternate of the object is removed (e.g., due to a ``DELETE`` request).
 
-In every case the metadata for the object must be modified. Because |TS| never updates data already in the cache this means the first ``Doc`` will be written to the cache again and the volume directory entry updated. Because a client request has already been processed the first ``Doc`` has been read from cache and is in memory. The alternate vector is updated as appropriate (an entry added or removed, or changed to contain the new HTTP headers), and then written to disk. It is possible for multiple alternates to be updated by different ``CacheVC`` instances at the same time. The only contention is the first ``Doc``, the rest of the data for each alternate is completely independent.
+In every case the metadata for the object must be modified. Because |TS| never updates data already in the cache this
+means the first ``Doc`` will be written to the cache again and the volume directory entry updated. Because a client
+request has already been processed the first ``Doc`` has been read from cache and is in memory. The alternate vector is
+updated as appropriate (an entry added or removed, or changed to contain the new HTTP headers), and then written to
+disk. It is possible for multiple alternates to be updated by different ``CacheVC`` instances at the same time. The only
+contention is the first ``Doc``, the rest of the data for each alternate is completely independent.
 
 .. _aggregation-buffer:
 
 Aggregation Buffer
 ------------------
 
-Disk writes to cache are handled through an *aggregation buffer*. There is one for each :cpp:class:`Vol` instance.
-To minimize the number of system calls data is written to disk in units of roughly :ref:`target fragment size <target-fragment-size>` bytes. The algorithm used is simple - data is piled up in the aggregation buffer until no more will fit without going over the target fragment size, at which point the buffer is written to disk and the volume directory entries for objects with data in the buffer are updated with the actual disk locations for those objects (which are determined by the write to disk action). After the buffer is written it is cleared and process repeats. There is a special lookup table for the aggregation buffer so that object lookup can find cache data in that memory.
+Disk writes to cache are handled through an *aggregation buffer*. There is one for each :cpp:class:`Vol` instance. To
+minimize the number of system calls data is written to disk in units of roughly :ref:`target fragment size
+<target-fragment-size>` bytes. The algorithm used is simple - data is piled up in the aggregation buffer until no more
+will fit without going over the targer fragment size, at which point the buffer is written to disk and the volume
+directory entries for objects with data in the buffer are updated with the actual disk locations for those objects
+(which are determined by the write to disk action). After the buffer is written it is cleared and process repeats. There
+is a special lookup table for the aggregation buffer so that object lookup can find cache data in that memory.
 
-Because data in the aggregation buffer is visible to other parts of the cache, particularly `cache lookup`_, there is no need to push a partial filled aggregation buffer to disk. In effect any such data is effectively memory cached until enough additional cache content arrives to fill the buffer.
+Because data in the aggregation buffer is visible to other parts of the cache, particularly `cache lookup`_, there is no
+need to push a partial filled aggregation buffer to disk. In effect any such data is effectively memory cached until
+enough additional cache content arrives to fill the buffer.
 
-The target fragment size has little effect on small objects because the fragmen size is used only to parcel out disk write operations. For larger objects the effect very significant as it causes those objects to be broken up in to fragments at different locations on in the volume. Each fragment write has its own entry in the volume directory which are computationally chained (each cache key is computed from the previous one). If possible a fragment table is accumulated in the first ``Doc`` which has the offsets of the first byte for each fragment.
+The target fragment size has little effect on small objects because the fragment sized is used only to parcel out disk
+write operations. For larger objects the effect very significant as it causes those objects to be broken up in to
+fragments at different locations on in the volume. Each fragment write has its own entry in the volume directory which
+are computational chained (each cache key is computed from the previous one). If possible a fragment table is
+accumulated in the earliest ``Doc`` which has the offsets of the first byte for each fragment.
 
 Evacuation
 ----------
 
-By default the write cursor will overwrite (de facto evict from cache) objects as it proceeds once it has gone around the volume content at least once. In some cases this is not acceptable and the object is *evacuated* by reading it from the cache and then writing it back to cache which moves the physical storage of the object from in front of the write cursor to behind the write cursor. Objects that are evacuated are those that are active in either a read or write operation, or objects that are pinned [#]_.
-
-Evacuation starts by dividing up the volume content in to a set of regions of ``EVACUATION_BUCKET_SIZE`` bytes. The :cpp:member:`Vol::evacuate` member is an array with an element for each region. Each element is a doubly linked list of :cpp:class:`EvacuationBlock` instances. Each instance contains a :cpp:class:`Dir` that specifies the document to evacuate. Objects to be evacuated are described in an ``EvacuationBlock`` which is put into an evacuation bucket based on the offset of the storage location.
-
-There are two types of evacuations, reader based and forced. The ``EvacuationBlock`` has a reader count to track this. If the reader count is zero, then it is a forced evacuation and the target, if it exists, will be evacuated when the write cursor gets close. If the reader value is non-zero then it is a count of entities that are currently expecting to be able to read the object. Readers increment the count when they require read access to the object, or create the ``EvacuationBlock`` with a count of 1. When a reader is finished with the object it decrements the count and removes the ``EvacuationBlock`` if the count goes to zero. If the ``EvacuationBlock`` already exists with a count of zero, the count is not modified and the number of readers is not tracked, so the evacuation be valid as long as the object exists.
+By default the write cursor will overwrite (de facto evict from cache) objects as it proceeds once it has gone around
+the volume content at least once. In some cases this is not acceptable and the object is *evacuated* by reading it from
+the cache and then writing it back to cache which moves the physical storage of the object from in front of the write
+cursor to behind the write cursor. Objects that are evacuated are those that are active in either a read or write
+operation, or objects that are pinned [#]_.
+
+Evacuation starts by dividing up the volume content in to a set of regions of ``EVACUATION_BUCKET_SIZE`` bytes. The
+:cpp:member:`Vol::evacuate` member is an array with an element for each region. Each element is a doubly linked list of
+:cpp:class:`EvacuationBlock` instances. Each instance contains a :cpp:class:`Dir` that specifies the document to
+evacuate. Objects to be evacuated are descrinbed in an ``EvacuationBlock`` which is put in to an evacuation bucket based
+on the offset of the storage location.
+
+There are two types of evacuations, reader based and forced. The ``EvacuationBlock`` has a reader count to track this.
+If the reader count is zero, then it is a forced evacuation and the the target, if it exists, will be evacuated when the
+write cursor gets close. If the reader value is non-zero then it is a count of entities that are currently expecting to
+be able to read the object. Readers increment the count when they require read access to the object, or create the
+``EvacuationBlock`` with a count of 1. When a reader is finished with the object it decrements the count and removes the
+``EvacuationBlock`` if the count goes to zero. If the ``EvacuationBlock`` already exists with a count of zero, the count
+is not modified and the number of readers is not tracked, so the evacuation be valid as long as the object exists.
 
 Objects are evacuated as the write cursor approaches. The volume calculates the current amount of
 
 Before doing a write, the method :cpp:func:`Vol::evac_range()` is called to start an evacuation. If an eva
 
-.. [#] `Work is under way <https://issues.apache.org/jira/browse/TS-2020>`_ on extending this to include objects that are in the ram cache.
+Initialization
+==============
+
+Initialization starts with an instance of :cpp:class:`Store` reading the storage configuration file, by default
+:file:`storage.config`. For each valid element in the file an instance of :cpp:class:`Span` is created. These are of
+basically four types,
+
+* File
+* Directory
+* Disk
+* Raw device
+
+After setting all the `Span` instances they are grouped by device id to internal linked lists attached to the
+:cpp:member:`Store::disk` array [#]_. Spans that refer to the same directory, disk, or raw device are coalesced in to a
+single span. Spans that refer to the same file with overlapping offsets are also coalesced [#]_. This is all done in :c:func:`ink_cache_init()` called during startup.
+
+After configuration initialization the cache processor is started by calling :ccp:func:`CacheProcessor::start()`. This
+does a number of things.
+
+For each valid span, an instance of :cpp:class:`CacheDisk` is created. This class is a continuation and so can be used
+to perform potentially blocking operations on the span. This what is passed to the AIO threads to be called when an I/O
+operation completes. These are then dispatched to AIO threads to perform storage unit initialization. After all of those
+have completed, the resulting storage is distributed across the volumes in :c:func:`cplist_reconfigure`. The :cpp:class:`CacheVol` instances are created at this time.
+
+.. rubric:: Footnotes
+
+.. [#] `Work is under way <https://issues.apache.org/jira/browse/TS-2020>`_ on extending this to include objects that
+   are in the ram cache.
+
+.. [#] This linked list is mostly ignored in later processing, causing all but one file or directory storage units on
+   the same device to be ignored. See `TS-1869 <https://issues.apache.org/jira/browse/TS-1869>`_.
+
+.. [#] It is unclear to me how that can happen, as the offsets are computed later and should all be zero at the time the
+   spans are coalesced, and as far as I can tell the sort / coalesce is only done during initialization.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/cache-data-structures.en.rst
----------------------------------------------------------------------
diff --git a/doc/arch/cache/cache-data-structures.en.rst b/doc/arch/cache/cache-data-structures.en.rst
index 6c93ebf..6ce9bc8 100755
--- a/doc/arch/cache/cache-data-structures.en.rst
+++ b/doc/arch/cache/cache-data-structures.en.rst
@@ -5,9 +5,9 @@
   to you under the Apache License, Version 2.0 (the
   "License"); you may not use this file except in compliance
   with the License.  You may obtain a copy of the License at
- 
+
    http://www.apache.org/licenses/LICENSE-2.0
- 
+
   Unless required by applicable law or agreed to in writing,
   software distributed under the License is distributed on an
   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -89,6 +89,10 @@ Cache Data Structures
 
       Array of of :cpp:class:`EvacuationBlock` buckets. This is sized so there is one bucket for every evacuation span.
 
+   .. cpp:member:: off_t len
+
+      Length of stripe in bytes.
+
 .. cpp:function:: int Vol::evac_range(off_t low, off_t high, int evac_phase)
 
    Start an evacuation if there is any :cpp:class:`EvacuationBlock` in the range from *low* to *high*. Return 0 if no evacuation was started, non-zero otherwise.
@@ -153,6 +157,8 @@ Cache Data Structures
 
       Unknown. (A checksum of some sort)
 
+.. cpp:class:: VolHeaderFooter
+
 .. rubric:: Footnotes
 
 .. [#] Changed in version 3.2.0. This previously resided in the first ``Doc`` but that caused different alternates to share the same fragment table.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/ats-cache-doc-layout-pre-3-2-0.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/ats-cache-doc-layout-pre-3-2-0.png b/doc/arch/cache/images/ats-cache-doc-layout-pre-3-2-0.png
deleted file mode 100755
index d7b2c4f..0000000
Binary files a/doc/arch/cache/images/ats-cache-doc-layout-pre-3-2-0.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/ats-cache-doc-layout.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/ats-cache-doc-layout.png b/doc/arch/cache/images/ats-cache-doc-layout.png
deleted file mode 100755
index 438ed77..0000000
Binary files a/doc/arch/cache/images/ats-cache-doc-layout.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/ats-cache-layout.jpg
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/ats-cache-layout.jpg b/doc/arch/cache/images/ats-cache-layout.jpg
deleted file mode 100644
index 3c13487..0000000
Binary files a/doc/arch/cache/images/ats-cache-layout.jpg and /dev/null differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/ats-cache-storage-units.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/ats-cache-storage-units.png b/doc/arch/cache/images/ats-cache-storage-units.png
deleted file mode 100644
index 47e1bda..0000000
Binary files a/doc/arch/cache/images/ats-cache-storage-units.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-directory-structure.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-directory-structure.png b/doc/arch/cache/images/cache-directory-structure.png
new file mode 100755
index 0000000..8719d1e
Binary files /dev/null and b/doc/arch/cache/images/cache-directory-structure.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-doc-layout-3-2-0.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-doc-layout-3-2-0.png b/doc/arch/cache/images/cache-doc-layout-3-2-0.png
new file mode 100755
index 0000000..d758342
Binary files /dev/null and b/doc/arch/cache/images/cache-doc-layout-3-2-0.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-doc-layout-4-0-1.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-doc-layout-4-0-1.png b/doc/arch/cache/images/cache-doc-layout-4-0-1.png
new file mode 100755
index 0000000..d97b154
Binary files /dev/null and b/doc/arch/cache/images/cache-doc-layout-4-0-1.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-multi-fragment.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-multi-fragment.png b/doc/arch/cache/images/cache-multi-fragment.png
new file mode 100755
index 0000000..164d7d6
Binary files /dev/null and b/doc/arch/cache/images/cache-multi-fragment.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-span-layout.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-span-layout.png b/doc/arch/cache/images/cache-span-layout.png
new file mode 100644
index 0000000..317d69a
Binary files /dev/null and b/doc/arch/cache/images/cache-span-layout.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-spans.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-spans.png b/doc/arch/cache/images/cache-spans.png
new file mode 100644
index 0000000..6a5e5f8
Binary files /dev/null and b/doc/arch/cache/images/cache-spans.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/arch/cache/images/cache-stripe-layout.png
----------------------------------------------------------------------
diff --git a/doc/arch/cache/images/cache-stripe-layout.png b/doc/arch/cache/images/cache-stripe-layout.png
new file mode 100755
index 0000000..73b4140
Binary files /dev/null and b/doc/arch/cache/images/cache-stripe-layout.png differ

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5c39d024/doc/glossary.en.rst
----------------------------------------------------------------------
diff --git a/doc/glossary.en.rst b/doc/glossary.en.rst
index 0970090..a3180f6 100644
--- a/doc/glossary.en.rst
+++ b/doc/glossary.en.rst
@@ -40,20 +40,23 @@ Glossary
    cache volume
       Persistent storage for the cache, defined and manipulable by the user.
       Cache volumes are defined in :file:`volume.config`. A cache volume is
-      spread across :term:`storage unit`\ s to increase performance through
+      spread across :term:`cache span`\ s to increase performance through
       parallel I/O. Storage units can be split across cache volumes. Each
-      such part of a storage unit in a cache volume is a :term:`volume`.
+      such part of a storage unit in a cache volume is a :term:`cache stripe`.
 
-   volume
+   cache stripe
       A homogenous persistent store for the cache. A volume always resides
       entirely on a single physical device and is treated as an
       undifferentiated span of bytes.
 
-      See also :term:`storage unit`, :term:`cache volume`
+      See also :term:`cache span`, :term:`cache volume`
 
-   storage unit
+   cache span
       The physical storage described by a single line in :file:`storage.config`.
 
+   storage unit
+      Obsolete term for :term:`cache span`.
+
    revalidation
       Verifying that a currently cached object is still valid. This is usually done using an `If-Modified-Since
       <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25>`_ request which allows the origin server to


Mime
View raw message