Print this page
3740 Poor ZFS send / receive performance due to snapshot hold / release processing
Submitted by: Steven Hartland <steven.hartland@multiplay.co.uk>

@@ -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];

@@ -940,45 +941,23 @@
         nvlist_free(thisdbg);
 
         return (0);
 }
 
-static int
-hold_for_send(zfs_handle_t *zhp, send_dump_data_t *sdd)
+static void
+gather_holds(zfs_handle_t *zhp, send_dump_data_t *sdd)
 {
-        zfs_handle_t *pzhp;
-        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,
+         * zfs_send() only sets snapholds for sends that need them,
          * e.g. replication and doall.
          */
-        if (sdd->cleanup_fd == -1)
-                return (0);
-
-        thissnap = strchr(zhp->zfs_name, '@') + 1;
-        *(thissnap - 1) = '\0';
-        pzhp = zfs_open(zhp->zfs_hdl, zhp->zfs_name, ZFS_TYPE_DATASET);
-        *(thissnap - 1) = '@';
-
-        /*
-         * 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);
-                zfs_close(pzhp);
-        }
+        if (sdd->snapholds == NULL)
+                return;
 
-        return (error);
+        fnvlist_add_string(sdd->snapholds, zhp->zfs_name, sdd->holdtag);
 }
 
 static void *
 send_progress_thread(void *arg)
 {

@@ -1041,21 +1020,16 @@
         thissnap = strchr(zhp->zfs_name, '@') + 1;
         isfromsnap = (sdd->fromsnap != NULL &&
             strcmp(sdd->fromsnap, thissnap) == 0);
 
         if (!sdd->seenfrom && isfromsnap) {
-                err = hold_for_send(zhp, sdd);
-                if (err == 0) {
+                gather_holds(zhp, sdd);
                         sdd->seenfrom = B_TRUE;
                         (void) strcpy(sdd->prevsnap, thissnap);
-                        sdd->prevsnap_obj = zfs_prop_get_int(zhp,
-                            ZFS_PROP_OBJSETID);
-                } else if (err == ENOENT) {
-                        err = 0;
-                }
+                sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
                 zfs_close(zhp);
-                return (err);
+                return (0);
         }
 
         if (sdd->seento || !sdd->seenfrom) {
                 zfs_close(zhp);
                 return (0);

@@ -1102,18 +1076,11 @@
                  */
                 zfs_close(zhp);
                 return (0);
         }
 
-        err = hold_for_send(zhp, sdd);
-        if (err) {
-                if (err == ENOENT)
-                        err = 0;
-                zfs_close(zhp);
-                return (err);
-        }
-
+        gather_holds(zhp, sdd);
         fromorigin = sdd->prevsnap[0] == '\0' &&
             (sdd->fromorigin || sdd->replicate);
 
         if (sdd->verbose) {
                 uint64_t size;

@@ -1540,12 +1507,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 +1532,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 gather 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_nvl(zhp, 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) {