Print this page
Optimize creation and removal of temporary "user holds" placed on
snapshots by a zfs send, by ensuring all the required holds and
releases are done in a single dsl_sync_task.
Creation now collates the required holds during a dry run and
then uses a single lzc_hold call via zfs_hold_apply instead of
processing each snapshot in turn.
Defered (on exit) cleanup by the kernel is also now done in
dsl_sync_task by reusing dsl_dataset_user_release.
On a test with 11 volumes in a tree each with 8 snapshots on a
single HDD zpool this reduces the time required to perform a full
send from 20 seconds to under 0.8 seconds.
For reference eliminating the hold entirely reduces this 0.15
seconds.
While I'm here:-
* Remove some unused structures
* Fix nvlist_t leak in zfs_release_one

@@ -791,10 +791,11 @@
         boolean_t seenfrom, seento, replicate, doall, fromorigin;
         boolean_t verbose, dryrun, parsable, progress;
         int outfd;
         boolean_t err;
         nvlist_t *fss;
+        nvlist_t *snapholds;
         avl_tree_t *fsavl;
         snapfilter_cb_t *filter_cb;
         void *filter_cb_arg;
         nvlist_t *debugnv;
         char holdtag[ZFS_MAXNAMELEN];

@@ -949,18 +950,16 @@
         int error = 0;
         char *thissnap;
 
         assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
 
-        if (sdd->dryrun)
-                return (0);
-
         /*
-         * zfs_send() only opens a cleanup_fd for sends that need it,
-         * e.g. replication and doall.
+         * We process if snapholds is not NULL even if on a dry run as
+         * this is used to pre-calculate the required holds so they can
+         * be processed in one kernel request
          */
-        if (sdd->cleanup_fd == -1)
+        if (sdd->snapholds == NULL)
                 return (0);
 
         thissnap = strchr(zhp->zfs_name, '@') + 1;
         *(thissnap - 1) = '\0';
         pzhp = zfs_open(zhp->zfs_hdl, zhp->zfs_name, ZFS_TYPE_DATASET);

@@ -969,12 +968,12 @@
         /*
          * It's OK if the parent no longer exists.  The send code will
          * handle that error.
          */
         if (pzhp) {
-                error = zfs_hold(pzhp, thissnap, sdd->holdtag,
-                    B_FALSE, B_TRUE, sdd->cleanup_fd);
+                error = zfs_hold_add(pzhp, thissnap, sdd->holdtag, B_TRUE,
+                    sdd->snapholds);
                 zfs_close(pzhp);
         }
 
         return (error);
 }

@@ -1540,12 +1539,14 @@
                 sdd.cleanup_fd = open(ZFS_DEV, O_RDWR|O_EXCL);
                 if (sdd.cleanup_fd < 0) {
                         err = errno;
                         goto stderr_out;
                 }
+                sdd.snapholds = fnvlist_alloc();
         } else {
                 sdd.cleanup_fd = -1;
+                sdd.snapholds = NULL;
         }
         if (flags->verbose) {
                 /*
                  * Do a verbose no-op dry run to get all the verbose output
                  * before generating any data.  Then do a non-verbose real

@@ -1563,10 +1564,34 @@
                         zfs_nicenum(sdd.size, buf, sizeof (buf));
                         (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
                             "total estimated size is %s\n"), buf);
                 }
         }
+
+        if (sdd.snapholds != NULL) {
+                /* Holds are required */
+                if (!flags->verbose) {
+                        /*
+                         * A verbose dry run wasn't done so do a non-verbose
+                         * dry run to collate snapshot hold's.
+                         */
+                        sdd.dryrun = B_TRUE;
+                        err = dump_filesystems(zhp, &sdd);
+                        sdd.dryrun = flags->dryrun;
+                }
+
+                if (err != 0) {
+                        fnvlist_free(sdd.snapholds);
+                        goto stderr_out;
+                }
+
+                err = zfs_hold_apply(zhp, B_TRUE, sdd.cleanup_fd, sdd.snapholds);
+                fnvlist_free(sdd.snapholds);
+                if (err != 0)
+                        goto stderr_out;
+        }
+        
         err = dump_filesystems(zhp, &sdd);
         fsavl_destroy(fsavl);
         nvlist_free(fss);
 
         if (flags->dedup) {