hammer2 - slave sync adjustments, doc update
authorMatthew Dillon <dillon@apollo.backplane.com>
Thu, 9 Apr 2015 06:58:54 +0000 (23:58 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Thu, 9 Apr 2015 07:05:03 +0000 (00:05 -0700)
* Fix the hammer2_cluster*() API, do not multiply-ref underlying chains
  when a cluster is multiply-ref'd.  Fixes issues when underlying chain
  elements of the cluster are replaced during operations.

* Fix frontend writing issues when multiple slaves are present.

* Adjust documentation a bit.  Get rid of the 256-way split description
  for copyid use, we aren't going to do things that way so the size for
  each physical volume can be up to ~2^64 (maybe ~2^63 to be safe).

* Add a HAMMER2_LOOKUP_NODIRECT flag to prevent recursively returning
  the inode and remove the broken (cluster == cparent) test in the slave-sync
  code.  Doh, cluster structures are independently allocated, test was
  broken.

* Fix several other slave-sync issues when multiple slaves are present.
  (there are still some issues remaining).

* Fix accounting for the number of mounts using a physical device
  (hmp), rename pmp_count to mount_count for clarity.

sys/vfs/hammer2/DESIGN
sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_chain.c
sys/vfs/hammer2/hammer2_cluster.c
sys/vfs/hammer2/hammer2_syncthr.c
sys/vfs/hammer2/hammer2_vfsops.c

index 83ee8a1..5a69f2d 100644 (file)
   layer then the actual number of physical disks that can be associated
   with a single H2 filesystem is unbounded.
 
-  H2 handles this by interleaving the 64-bit address space 256-ways on
-  level-1 (2GB) boundaries.  Thus, a single physical disk from H2's point
-  of view can be sized up to 2^56 which is 64 Petabytes, and the total
-  filesystem size can be up to 16 Exabytes.
+  H2 puts an 8-bit copyid in the blockref structure to represent potentially
+  multiple copies of a block.  The copyid corresponds to a configuration
+  specification in the volume header.  The full algorithm has not been
+  specced yet.
 
   Copies support is implemented by having multiple blockref entries for
   the same key, each with a different copyid.  The copyid represents which
@@ -215,7 +215,7 @@ Clearly this method requires intermediate modifications to the chain to be
 cached so multiple modifications can be aggregated prior to being
 synchronized.  One advantage, however, is that the normal buffer cache can
 be used and intermediate elements can be retired to disk by H2 or the OS
-at any time.  This means that HAMMER2 has very resource overhead from the
+at any time.  This means that HAMMER2 has very low resource overhead from the
 point of view of the operating system.  Unlike HAMMER1 which had to lock
 dirty buffers in memory for long periods of time, HAMMER2 has no such
 requirement.
@@ -311,9 +311,16 @@ without adding to the I/O we already have to do.
                            DIRECTORIES AND INODES
 
 Directories are hashed, and another major design element is that directory
-entries ARE INODES.  They are one and the same, with a special placemarker
+entries ARE inodes.  They are one and the same, with a special placemarker
 for hardlinks.  Inodes are 1KB.
 
+Hardlinks are implemented with placemarkers as directory entries which simply
+represent the inode number.  The actual file resides in a parent directory
+that is common to all hardlinks to that file.  If the hardlinks are all within
+a single directory, the actual hardlink inode is in that directory.  The
+hardlink target, as we call it, is a hidden directory entry in a common parent
+whos key is basically just the inode number itself, so lookups are fast.
+
 Half of the inode structure (512 bytes) is used to hold top-level blockrefs
 to the radix block tree representing the file contents.  Files which are
 less than or equal to 512 bytes in size will simply store the file contents
@@ -323,23 +330,26 @@ in this area instead of a blockref array.  So files <= 512 bytes take only
 Inode numbers are not spatially referenced, which complicates NFS servers
 but doesn't complicate anything else.  The inode number is stored in the
 inode itself, an absolute necessity required to properly support HAMMER2s
-hugely flexible snapshots.
+hugely flexible snapshots.  I would like to support NFS services but it
+would require (probably) a lookaside index in the root for inode lookups
+and might not happen quickly.
 
                                    RECOVERY
 
 H2 allows freemap flushes to lag behind topology flushes.  The freemap flush
-tracks a separate transaction id in the volume header.
+tracks a separate transaction id (via mirror_tid) in the volume header.
 
 On mount, HAMMER2 will first locate the highest-sequenced check-code-validated
 volume header from the 4 copies available (if the filesystem is big enough,
 e.g. > ~10GB or so, there will be 4 copies of the volume header).
 
 HAMMER2 will then run an incremental scan of the topology for mirror_tid
-transaction ids between the last freemap flush and the current topology in
-order to update the freemap.  Because this scan is incremental the
-worst-case time to run the scan is the time it takes to scan the meta-data
-for all changes made between the last freemap flush and the last topology
-flush.
+transaction ids between the last freemap flush tid and the last topology
+flush tid in order to synchronize the freemap.  Because this scan is
+incremental the time it takes to run will be relatively short and well-bounded
+at mount-time.  This is NOT fsck.  Freemap flushes can be avoided for any
+number of normal topology flushes but should still occur frequently enough
+to avoid long recovery times in case of a crash.
 
 The filesystem is then ready for use.
 
@@ -352,33 +362,31 @@ to greatly improve I/O performance (particularly by laying inodes down next
 to each other which has a huge effect on directory scans).
 
 The current implementation of HAMMER2 implements a fixed block size of 64KB
-in order to allow aliasing of hammer2_dio's in its IO subsystem.  This way
-we don't have to worry about matching the buffer cache / DIO cache to the
-variable block size of underlying elements.
+in order to allow the mapping of hammer2_dio's in its IO subsystem to
+conumers that might desire different sizes.  This way we don't have to
+worry about matching the buffer cache / DIO cache to the variable block
+size of underlying elements.
+
+The biggest issue we are avoiding by having a fixed 64KB I/O size is not
+actually to help nominal front-end access issue but instead to reduce the
+complexity when blocks are freed and reused for another purpose.  HAMMER1
+had to have specialized code to check for and invalidate buffer cache buffers
+in the free/reuse case.  HAMMER2 does not need such code.
+
+That said, HAMMER2 places no major restrictions on mixing block sizes within
+a 64KB block.  The only restriction is that a HAMMER2 block cannot cross
+a 64KB boundary.  The soft restrictions the block allocator puts in place
+exist primarily for performance reasons (i.e. try to collect 1K inodes
+together).  The 2MB freemap zone granularity should work very well in this
+regard.
 
 HAMMER2 also allows OS support for ganging buffers together into even
-larger blocks for I/O, OS-supported read-ahead, and other performance
-features typically provided by the OS at the block-level.
-
-                                 HARDLINKS
-
-Hardlinks are a particularly sticky problem for HAMMER2 due to the lack of
-a spatial reference to the inode number.  We do not want to have to have
-an index of inode numbers for any basic HAMMER2 feature if we can help it.
-
-Hardlinks are handled by placing the inode for a multiply-hardlinked file
-in the closest common parent directory.  If "a/x" and "a/y" are hardlinked
-the inode for the hardlinked file will be placed in directory "a", e.g.
-"a/<inode_number>".  The actual file inode will be an invisible out-of-band
-entry in the directory.  The directory entries "a/x" and "a/y" will be given
-the same inode number but in fact they are only placemarks that cause
-HAMMER2 to recurse upwards through the directory tree to find the invisible
-real inode.
-
-Because directories are hashed and a different namespace (hash key range)
-is used for hardlinked inodes, standard directory scans are able to trivially
-skip this invisible namespace and inode-specific lookups can restrict their
-lookup to within this space.  No linear scans are needed.
+larger blocks for I/O (OS buffer cache 'clustering'), OS-supported read-ahead,
+OS-driven asynchronous retirement, and other performance features typically
+provided by the OS at the block-level to ensure smooth system operation.
+
+By avoiding wiring buffers/memory and allowing these features to run normally,
+HAMMER2 winds up with very low OS overhead.
 
                                FREEMAP NOTES
 
index f452547..cb1ddd9 100644 (file)
@@ -424,9 +424,14 @@ RB_PROTOTYPE(hammer2_chain_tree, hammer2_chain, rbnode, hammer2_chain_cmp);
  * NOTE: MATCHIND allows an indirect block / freemap node to be returned
  *      when the passed key range matches the radix.  Remember that key_end
  *      is inclusive (e.g. {0x000,0xFFF}, not {0x000,0x1000}).
+ *
+ * NOTE: NODIRECT prevents a lookup of offset 0 in an inode from returning
+ *      the inode itself if the inode is in DIRECTDATA mode (i.e. file is
+ *      <= 512 bytes).
  */
 #define HAMMER2_LOOKUP_NOLOCK          0x00000001      /* ref only */
 #define HAMMER2_LOOKUP_NODATA          0x00000002      /* data left NULL */
+#define HAMMER2_LOOKUP_NODIRECT                0x00000004      /* no offset=0 DD */
 #define HAMMER2_LOOKUP_SHARED          0x00000100
 #define HAMMER2_LOOKUP_MATCHIND                0x00000200      /* return all chains */
 #define HAMMER2_LOOKUP_UNUSED0400      0x00000400
@@ -767,7 +772,7 @@ struct hammer2_syncthr {
        kdmsg_state_t   *span;
        thread_t        td;
        uint32_t        flags;
-       uint32_t        unused01;
+       int             depth;
        hammer2_trans_t trans;
        struct lock     lk;
 };
@@ -797,7 +802,7 @@ typedef struct hammer2_syncthr hammer2_syncthr_t;
 struct hammer2_dev {
        struct vnode    *devvp;         /* device vnode */
        int             ronly;          /* read-only mount */
-       int             pmp_count;      /* number of actively mounted PFSs */
+       int             mount_count;    /* number of actively mounted PFSs */
        TAILQ_ENTRY(hammer2_dev) mntentry; /* hammer2_mntlist */
 
        struct malloc_type *mchain;
@@ -872,7 +877,7 @@ hammer2_chain_wrok(hammer2_chain_t *chain)
  *         synchronization thread.
  *
  * WARNING! The chains making up pfs->iroot's cluster are accounted for in
- *         hammer2_dev->pmp_count when the pfs is associated with a mount
+ *         hammer2_dev->mount_count when the pfs is associated with a mount
  *         point.
  */
 struct hammer2_pfs {
index 2c0ca68..38cd7c0 100644 (file)
@@ -1513,7 +1513,7 @@ hammer2_chain_getparent(hammer2_chain_t **parentp, int how)
  * XXX Depending on where the error occurs we should allow continued iteration.
  *
  * On return (*key_nextp) will point to an iterative value for key_beg.
- * (If NULL is returned (*key_nextp) is set to key_end).
+ * (If NULL is returned (*key_nextp) is set to (key_end + 1)).
  *
  * This function will also recurse up the chain if the key is not within the
  * current parent's range.  (*parentp) can never be set to NULL.  An iteration
@@ -1597,6 +1597,11 @@ again:
                 * This is only applicable to regular files and softlinks.
                 */
                if (parent->data->ipdata.op_flags & HAMMER2_OPFLAG_DIRECTDATA) {
+                       if (flags & HAMMER2_LOOKUP_NODIRECT) {
+                               chain = NULL;
+                               *key_nextp = key_end + 1;
+                               goto done;
+                       }
                        hammer2_chain_ref(parent);
                        if ((flags & HAMMER2_LOOKUP_NOLOCK) == 0)
                                hammer2_chain_lock(parent, how_always);
index 8c2382b..f3c067c 100644 (file)
@@ -356,15 +356,17 @@ hammer2_cluster_from_chain(hammer2_chain_t *chain)
 void
 hammer2_cluster_ref(hammer2_cluster_t *cluster)
 {
+       atomic_add_int(&cluster->refs, 1);
+#if 0
        hammer2_chain_t *chain;
        int i;
 
-       atomic_add_int(&cluster->refs, 1);
        for (i = 0; i < cluster->nchains; ++i) {
                chain = cluster->array[i].chain;
                if (chain)
                        hammer2_chain_ref(chain);
        }
+#endif
 }
 
 /*
@@ -382,17 +384,19 @@ hammer2_cluster_drop(hammer2_cluster_t *cluster)
        int i;
 
        KKASSERT(cluster->refs > 0);
-       for (i = 0; i < cluster->nchains; ++i) {
-               chain = cluster->array[i].chain;
-               if (chain) {
-                       hammer2_chain_drop(chain);
-                       if (cluster->refs == 1)
-                               cluster->array[i].chain = NULL;
-               }
-       }
        if (atomic_fetchadd_int(&cluster->refs, -1) == 1) {
                cluster->focus = NULL;          /* safety XXX chg to assert */
                cluster->focus_index = 0;
+
+               for (i = 0; i < cluster->nchains; ++i) {
+                       chain = cluster->array[i].chain;
+                       if (chain) {
+                               hammer2_chain_drop(chain);
+                               cluster->array[i].chain = NULL; /* safety */
+                       }
+               }
+               cluster->nchains = 0;                           /* safety */
+
                kfree(cluster, M_HAMMER2);
                /* cluster is invalid */
        }
index be37b9f..05ec4db 100644 (file)
@@ -292,15 +292,8 @@ hammer2_sync_slaves(hammer2_syncthr_t *thr, hammer2_cluster_t *cparent,
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                         HAMMER2_KEY_MIN, HAMMER2_KEY_MAX,
                                         HAMMER2_LOOKUP_NODATA |
-                                        HAMMER2_LOOKUP_NOLOCK);
-
-       /*
-        * Ignore degenerate DIRECTDATA case for file inode
-        */
-       if (cluster == cparent) {
-               hammer2_cluster_drop(cluster);
-               cluster = NULL;
-       }
+                                        HAMMER2_LOOKUP_NOLOCK |
+                                        HAMMER2_LOOKUP_NODIRECT);
 
        /*
         * Scan elements
@@ -420,13 +413,17 @@ hammer2_sync_slaves(hammer2_syncthr_t *thr, hammer2_cluster_t *cparent,
                         * Recurse on inode.  Avoid unnecessarily blocking
                         * operations by temporarily unlocking the parent.
                         */
-                       if (dorecursion) {
+                       if (dorecursion && thr->depth > 20) {
+                               kprintf("depth limit reached\n");
+                       } else if (dorecursion) {
                                hammer2_cluster_unlock(cparent);
                                scluster = hammer2_cluster_copy(cluster);
                                hammer2_cluster_lock(scluster,
                                                     HAMMER2_RESOLVE_ALWAYS);
+                               ++thr->depth;
                                nerror = hammer2_sync_slaves(thr, scluster,
                                                             errors);
+                               --thr->depth;
                                hammer2_cluster_unlock(scluster);
                                hammer2_cluster_drop(scluster);
                                /* XXX modify_tid on scluster */
index b152a9b..fd32081 100644 (file)
@@ -444,6 +444,13 @@ hammer2_pfsalloc(hammer2_cluster_t *cluster,
                        pmp->iroot->cluster.array[j].chain = rchain;
                        pmp->pfs_types[j] = ripdata->pfs_type;
 
+                       /*
+                        * If the PFS is already mounted we must account
+                        * for the mount_count here.
+                        */
+                       if (pmp->mp)
+                               ++rchain->hmp->mount_count;
+
                        /*
                         * May have to fixup dirty chain tracking.  Previous
                         * pmp was NULL so nothing to undo.
@@ -711,6 +718,8 @@ hammer2_vfs_mount(struct mount *mp, char *path, caddr_t data,
                        pmp = MPTOPMP(mp);
                        cluster = &pmp->iroot->cluster;
                        for (i = 0; i < cluster->nchains; ++i) {
+                               if (cluster->array[i].chain == NULL)
+                                       continue;
                                hmp = cluster->array[i].chain->hmp;
                                devvp = hmp->devvp;
                                error = hammer2_remount(hmp, mp, path,
@@ -1551,7 +1560,11 @@ hammer2_compress_and_write(struct buf *bp, hammer2_trans_t *trans,
 
                /* XXX hackx */
 
+               if ((cluster->array[i].flags & HAMMER2_CITEM_FEMOD) == 0)
+                       continue;
                chain = cluster->array[i].chain;        /* XXX */
+               if (chain == NULL)
+                       continue;
                KKASSERT(chain->flags & HAMMER2_CHAIN_MODIFIED);
 
                switch(chain->bref.type) {
@@ -1747,7 +1760,11 @@ hammer2_write_bp(hammer2_cluster_t *cluster, struct buf *bp, int ioflag,
        error = 0;      /* XXX TODO below */
 
        for (i = 0; i < cluster->nchains; ++i) {
+               if ((cluster->array[i].flags & HAMMER2_CITEM_FEMOD) == 0)
+                       continue;
                chain = cluster->array[i].chain;        /* XXX */
+               if (chain == NULL)
+                       continue;
                KKASSERT(chain->flags & HAMMER2_CHAIN_MODIFIED);
 
                switch(chain->bref.type) {
@@ -1902,7 +1919,7 @@ failed:
  * Mount helper, hook the system mount into our PFS.
  * The mount lock is held.
  *
- * We must bump the pmp_count on related devices for any
+ * We must bump the mount_count on related devices for any
  * mounted PFSs.
  */
 static
@@ -1916,14 +1933,17 @@ hammer2_mount_helper(struct mount *mp, hammer2_pfs_t *pmp)
         mp->mnt_data = (qaddr_t)pmp;
        pmp->mp = mp;
 
+       /*
+        * After pmp->mp is set we have to adjust hmp->mount_count.
+        */
        cluster = &pmp->iroot->cluster;
        for (i = 0; i < cluster->nchains; ++i) {
                rchain = cluster->array[i].chain;
                if (rchain == NULL)
                        continue;
-               ++rchain->hmp->pmp_count;
-               kprintf("hammer2_mount hmp=%p ++pmp_count=%d\n",
-                       rchain->hmp, rchain->hmp->pmp_count);
+               ++rchain->hmp->mount_count;
+               kprintf("hammer2_mount hmp=%p ++mount_count=%d\n",
+                       rchain->hmp, rchain->hmp->mount_count);
        }
 }
 
@@ -1937,8 +1957,9 @@ hammer2_mount_helper(struct mount *mp, hammer2_pfs_t *pmp)
  *
  * If pmp is supplied multiple devices might be backing the PFS and each
  * must be disconnect.  This might not be the last PFS using some of the
- * underlying devices.  Also, we have to adjust our hmp->pmp_count accounting
- * for the devices backing the pmp which is now undergoing an unmount.
+ * underlying devices.  Also, we have to adjust our hmp->mount_count
+ * accounting for the devices backing the pmp which is now undergoing an
+ * unmount.
  */
 static
 void
@@ -1953,8 +1974,8 @@ hammer2_unmount_helper(struct mount *mp, hammer2_pfs_t *pmp, hammer2_dev_t *hmp)
 
        /*
         * If no device supplied this is a high-level unmount and we have to
-        * to disconnect the mount, adjust pmp_count, and locate devices that
-        * might now have no mounts.
+        * to disconnect the mount, adjust mount_count, and locate devices
+        * that might now have no mounts.
         */
        if (pmp) {
                KKASSERT(hmp == NULL);
@@ -1962,19 +1983,23 @@ hammer2_unmount_helper(struct mount *mp, hammer2_pfs_t *pmp, hammer2_dev_t *hmp)
                pmp->mp = NULL;
                mp->mnt_data = NULL;
 
+               /*
+                * After pmp->mp is cleared we have to account for
+                * mount_count.
+                */
                cluster = &pmp->iroot->cluster;
                for (i = 0; i < cluster->nchains; ++i) {
                        rchain = cluster->array[i].chain;
                        if (rchain == NULL)
                                continue;
-                       --rchain->hmp->pmp_count;
-                       kprintf("hammer2_unmount hmp=%p --pmp_count=%d\n",
-                               rchain->hmp, rchain->hmp->pmp_count);
+                       --rchain->hmp->mount_count;
+                       kprintf("hammer2_unmount hmp=%p --mount_count=%d\n",
+                               rchain->hmp, rchain->hmp->mount_count);
                        /* scrapping hmp now may invalidate the pmp */
                }
 again:
                TAILQ_FOREACH(hmp, &hammer2_mntlist, mntentry) {
-                       if (hmp->pmp_count == 0) {
+                       if (hmp->mount_count == 0) {
                                hammer2_unmount_helper(NULL, NULL, hmp);
                                goto again;
                        }
@@ -1986,8 +2011,9 @@ again:
         * Try to terminate the block device.  We can't terminate it if
         * there are still PFSs referencing it.
         */
-       kprintf("hammer2_unmount hmp=%p pmp_count=%d\n", hmp, hmp->pmp_count);
-       if (hmp->pmp_count)
+       kprintf("hammer2_unmount hmp=%p mount_count=%d\n",
+               hmp, hmp->mount_count);
+       if (hmp->mount_count)
                return;
 
        hammer2_pfsfree_scan(hmp);