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) {