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,800 **** --- 791,801 ---- 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,966 **** 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. */ ! 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); --- 950,965 ---- int error = 0; char *thissnap; assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT); /* ! * 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->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,980 **** /* * 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); } return (error); } --- 968,979 ---- /* * It's OK if the parent no longer exists. The send code will * handle that error. */ if (pzhp) { ! error = zfs_hold_add(pzhp, thissnap, sdd->holdtag, B_TRUE, ! sdd->snapholds); zfs_close(pzhp); } return (error); }
*** 1540,1551 **** --- 1539,1552 ---- 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,1572 **** --- 1564,1597 ---- 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) {