mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-24 11:18:52 +03:00
Improve ZFS objset sync parallelism
As part of transaction group commit, dsl_pool_sync() sequentially calls dsl_dataset_sync() for each dirty dataset, which subsequently calls dmu_objset_sync(). dmu_objset_sync() in turn uses up to 75% of CPU cores to run sync_dnodes_task() in taskq threads to sync the dirty dnodes (files). There are two problems: 1. Each ZVOL in a pool is a separate dataset/objset having a single dnode. This means the objsets are synchronized serially, which leads to a bottleneck of ~330K blocks written per second per pool. 2. In the case of multiple dirty dnodes/files on a dataset/objset on a big system they will be sync'd in parallel taskq threads. However, it is inefficient to to use 75% of CPU cores of a big system to do that, because of (a) bottlenecks on a single write issue taskq, and (b) allocation throttling. In addition, if not for the allocation throttling sorting write requests by bookmarks (logical address), writes for different files may reach space allocators interleaved, leading to unwanted fragmentation. The solution to both problems is to always sync no more and (if possible) no fewer dnodes at the same time than there are allocators the pool. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <mav@FreeBSD.org> Signed-off-by: Edmund Nadolski <edmund.nadolski@ixsystems.com> Closes #15197
This commit is contained in:
+11
-20
@@ -140,11 +140,6 @@ uint_t zfs_delay_min_dirty_percent = 60;
|
||||
*/
|
||||
uint64_t zfs_delay_scale = 1000 * 1000 * 1000 / 2000;
|
||||
|
||||
/*
|
||||
* This determines the number of threads used by the dp_sync_taskq.
|
||||
*/
|
||||
static int zfs_sync_taskq_batch_pct = 75;
|
||||
|
||||
/*
|
||||
* These tunables determine the behavior of how zil_itxg_clean() is
|
||||
* called via zil_clean() in the context of spa_sync(). When an itxg
|
||||
@@ -214,9 +209,7 @@ dsl_pool_open_impl(spa_t *spa, uint64_t txg)
|
||||
txg_list_create(&dp->dp_early_sync_tasks, spa,
|
||||
offsetof(dsl_sync_task_t, dst_node));
|
||||
|
||||
dp->dp_sync_taskq = taskq_create("dp_sync_taskq",
|
||||
zfs_sync_taskq_batch_pct, minclsyspri, 1, INT_MAX,
|
||||
TASKQ_THREADS_CPU_PCT);
|
||||
dp->dp_sync_taskq = spa_sync_tq_create(spa, "dp_sync_taskq");
|
||||
|
||||
dp->dp_zil_clean_taskq = taskq_create("dp_zil_clean_taskq",
|
||||
zfs_zil_clean_taskq_nthr_pct, minclsyspri,
|
||||
@@ -409,7 +402,7 @@ dsl_pool_close(dsl_pool_t *dp)
|
||||
txg_list_destroy(&dp->dp_dirty_dirs);
|
||||
|
||||
taskq_destroy(dp->dp_zil_clean_taskq);
|
||||
taskq_destroy(dp->dp_sync_taskq);
|
||||
spa_sync_tq_destroy(dp->dp_spa);
|
||||
|
||||
/*
|
||||
* We can't set retry to TRUE since we're explicitly specifying
|
||||
@@ -674,7 +667,7 @@ dsl_early_sync_task_verify(dsl_pool_t *dp, uint64_t txg)
|
||||
void
|
||||
dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
{
|
||||
zio_t *zio;
|
||||
zio_t *rio; /* root zio for all dirty dataset syncs */
|
||||
dmu_tx_t *tx;
|
||||
dsl_dir_t *dd;
|
||||
dsl_dataset_t *ds;
|
||||
@@ -704,9 +697,10 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
}
|
||||
|
||||
/*
|
||||
* Write out all dirty blocks of dirty datasets.
|
||||
* Write out all dirty blocks of dirty datasets. Note, this could
|
||||
* create a very large (+10k) zio tree.
|
||||
*/
|
||||
zio = zio_root(dp->dp_spa, NULL, NULL, ZIO_FLAG_MUSTSUCCEED);
|
||||
rio = zio_root(dp->dp_spa, NULL, NULL, ZIO_FLAG_MUSTSUCCEED);
|
||||
while ((ds = txg_list_remove(&dp->dp_dirty_datasets, txg)) != NULL) {
|
||||
/*
|
||||
* We must not sync any non-MOS datasets twice, because
|
||||
@@ -715,9 +709,9 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
*/
|
||||
ASSERT(!list_link_active(&ds->ds_synced_link));
|
||||
list_insert_tail(&synced_datasets, ds);
|
||||
dsl_dataset_sync(ds, zio, tx);
|
||||
dsl_dataset_sync(ds, rio, tx);
|
||||
}
|
||||
VERIFY0(zio_wait(zio));
|
||||
VERIFY0(zio_wait(rio));
|
||||
|
||||
/*
|
||||
* Update the long range free counter after
|
||||
@@ -748,13 +742,13 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
* user accounting information (and we won't get confused
|
||||
* about which blocks are part of the snapshot).
|
||||
*/
|
||||
zio = zio_root(dp->dp_spa, NULL, NULL, ZIO_FLAG_MUSTSUCCEED);
|
||||
rio = zio_root(dp->dp_spa, NULL, NULL, ZIO_FLAG_MUSTSUCCEED);
|
||||
while ((ds = txg_list_remove(&dp->dp_dirty_datasets, txg)) != NULL) {
|
||||
objset_t *os = ds->ds_objset;
|
||||
|
||||
ASSERT(list_link_active(&ds->ds_synced_link));
|
||||
dmu_buf_rele(ds->ds_dbuf, ds);
|
||||
dsl_dataset_sync(ds, zio, tx);
|
||||
dsl_dataset_sync(ds, rio, tx);
|
||||
|
||||
/*
|
||||
* Release any key mappings created by calls to
|
||||
@@ -767,7 +761,7 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
key_mapping_rele(dp->dp_spa, ds->ds_key_mapping, ds);
|
||||
}
|
||||
}
|
||||
VERIFY0(zio_wait(zio));
|
||||
VERIFY0(zio_wait(rio));
|
||||
|
||||
/*
|
||||
* Now that the datasets have been completely synced, we can
|
||||
@@ -1481,9 +1475,6 @@ ZFS_MODULE_PARAM(zfs, zfs_, dirty_data_sync_percent, UINT, ZMOD_RW,
|
||||
ZFS_MODULE_PARAM(zfs, zfs_, delay_scale, U64, ZMOD_RW,
|
||||
"How quickly delay approaches infinity");
|
||||
|
||||
ZFS_MODULE_PARAM(zfs, zfs_, sync_taskq_batch_pct, INT, ZMOD_RW,
|
||||
"Max percent of CPUs that are used to sync dirty data");
|
||||
|
||||
ZFS_MODULE_PARAM(zfs_zil, zfs_zil_, clean_taskq_nthr_pct, INT, ZMOD_RW,
|
||||
"Max percent of CPUs that are used per dp_sync_taskq");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user