mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
Log Spacemap Project
= Motivation At Delphix we've seen a lot of customer systems where fragmentation is over 75% and random writes take a performance hit because a lot of time is spend on I/Os that update on-disk space accounting metadata. Specifically, we seen cases where 20% to 40% of sync time is spend after sync pass 1 and ~30% of the I/Os on the system is spent updating spacemaps. The problem is that these pools have existed long enough that we've touched almost every metaslab at least once, and random writes scatter frees across all metaslabs every TXG, thus appending to their spacemaps and resulting in many I/Os. To give an example, assuming that every VDEV has 200 metaslabs and our writes fit within a single spacemap block (generally 4K) we have 200 I/Os. Then if we assume 2 levels of indirection, we need 400 additional I/Os and since we are talking about metadata for which we keep 2 extra copies for redundancy we need to triple that number, leading to a total of 1800 I/Os per VDEV every TXG. We could try and decrease the number of metaslabs so we have less I/Os per TXG but then each metaslab would cover a wider range on disk and thus would take more time to be loaded in memory from disk. In addition, after it's loaded, it's range tree would consume more memory. Another idea would be to just increase the spacemap block size which would allow us to fit more entries within an I/O block resulting in fewer I/Os per metaslab and a speedup in loading time. The problem is still that we don't deal with the number of I/Os going up as the number of metaslabs is increasing and the fact is that we generally write a lot to a few metaslabs and a little to the rest of them. Thus, just increasing the block size would actually waste bandwidth because we won't be utilizing our bigger block size. = About this patch This patch introduces the Log Spacemap project which provides the solution to the above problem while taking into account all the aforementioned tradeoffs. The details on how it achieves that can be found in the references sections below and in the code (see Big Theory Statement in spa_log_spacemap.c). Even though the change is fairly constraint within the metaslab and lower-level SPA codepaths, there is a side-change that is user-facing. The change is that VDEV IDs from VDEV holes will no longer be reused. To give some background and reasoning for this, when a log device is removed and its VDEV structure was replaced with a hole (or was compacted; if at the end of the vdev array), its vdev_id could be reused by devices added after that. Now with the pool-wide space maps recording the vdev ID, this behavior can cause problems (e.g. is this entry referring to a segment in the new vdev or the removed log?). Thus, to simplify things the ID reuse behavior is gone and now vdev IDs for top-level vdevs are truly unique within a pool. = Testing The illumos implementation of this feature has been used internally for a year and has been in production for ~6 months. For this patch specifically there don't seem to be any regressions introduced to ZTS and I have been running zloop for a week without any related problems. = Performance Analysis (Linux Specific) All performance results and analysis for illumos can be found in the links of the references. Redoing the same experiments in Linux gave similar results. Below are the specifics of the Linux run. After the pool reached stable state the percentage of the time spent in pass 1 per TXG was 64% on average for the stock bits while the log spacemap bits stayed at 95% during the experiment (graph: sdimitro.github.io/img/linux-lsm/PercOfSyncInPassOne.png). Sync times per TXG were 37.6 seconds on average for the stock bits and 22.7 seconds for the log spacemap bits (related graph: sdimitro.github.io/img/linux-lsm/SyncTimePerTXG.png). As a result the log spacemap bits were able to push more TXGs, which is also the reason why all graphs quantified per TXG have more entries for the log spacemap bits. Another interesting aspect in terms of txg syncs is that the stock bits had 22% of their TXGs reach sync pass 7, 55% reach sync pass 8, and 20% reach 9. The log space map bits reached sync pass 4 in 79% of their TXGs, sync pass 7 in 19%, and sync pass 8 at 1%. This emphasizes the fact that not only we spend less time on metadata but we also iterate less times to convergence in spa_sync() dirtying objects. [related graphs: stock- sdimitro.github.io/img/linux-lsm/NumberOfPassesPerTXGStock.png lsm- sdimitro.github.io/img/linux-lsm/NumberOfPassesPerTXGLSM.png] Finally, the improvement in IOPs that the userland gains from the change is approximately 40%. There is a consistent win in IOPS as you can see from the graphs below but the absolute amount of improvement that the log spacemap gives varies within each minute interval. sdimitro.github.io/img/linux-lsm/StockVsLog3Days.png sdimitro.github.io/img/linux-lsm/StockVsLog10Hours.png = Porting to Other Platforms For people that want to port this commit to other platforms below is a list of ZoL commits that this patch depends on: Make zdb results for checkpoint tests consistentdb587941c5Update vdev_is_spacemap_addressable() for new spacemap encoding419ba59145Simplify spa_sync by breaking it up to smaller functions8dc2197b7bFactor metaslab_load_wait() in metaslab_load()b194fab0fbRename range_tree_verify to range_tree_verify_not_presentdf72b8bebeChange target size of metaslabs from 256GB to 16GBc853f382dbzdb -L should skip leak detection altogether21e7cf5da8vs_alloc can underflow in L2ARC vdevs7558997d2fSimplify log vdev removal code6c926f426aGet rid of space_map_update() for ms_synced_length425d3237eeIntroduce auxiliary metaslab histograms928e8ad47dError path in metaslab_load_impl() forgets to drop ms_sync_lock8eef997679= References Background, Motivation, and Internals of the Feature - OpenZFS 2017 Presentation: youtu.be/jj2IxRkl5bQ - Slides: slideshare.net/SerapheimNikolaosDim/zfs-log-spacemaps-project Flushing Algorithm Internals & Performance Results (Illumos Specific) - Blogpost: sdimitro.github.io/post/zfs-lsm-flushing/ - OpenZFS 2018 Presentation: youtu.be/x6D2dHRjkxw - Slides: slideshare.net/SerapheimNikolaosDim/zfs-log-spacemap-flushing-algorithm Upstream Delphix Issues: DLPX-51539, DLPX-59659, DLPX-57783, DLPX-61438, DLPX-41227, DLPX-59320 DLPX-63385 Reviewed-by: Sean Eric Fagan <sef@ixsystems.com> Reviewed-by: Matt Ahrens <matt@delphix.com> Reviewed-by: George Wilson <gwilson@delphix.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Serapheim Dimitropoulos <serapheim@delphix.com> Closes #8442
This commit is contained in:
committed by
Brian Behlendorf
parent
df834a7ccc
commit
93e28d661e
@@ -76,6 +76,7 @@ $(MODULE)-objs += spa_checkpoint.o
|
||||
$(MODULE)-objs += spa_config.o
|
||||
$(MODULE)-objs += spa_errlog.o
|
||||
$(MODULE)-objs += spa_history.o
|
||||
$(MODULE)-objs += spa_log_spacemap.o
|
||||
$(MODULE)-objs += spa_misc.o
|
||||
$(MODULE)-objs += spa_stats.o
|
||||
$(MODULE)-objs += space_map.o
|
||||
|
||||
@@ -1483,7 +1483,7 @@ dmu_objset_sync_dnodes(multilist_sublist_t *list, dmu_tx_t *tx)
|
||||
ASSERT(dn->dn_dbuf->db_data_pending);
|
||||
/*
|
||||
* Initialize dn_zio outside dnode_sync() because the
|
||||
* meta-dnode needs to set it ouside dnode_sync().
|
||||
* meta-dnode needs to set it outside dnode_sync().
|
||||
*/
|
||||
dn->dn_zio = dn->dn_dbuf->db_data_pending->dr_zio;
|
||||
ASSERT(dn->dn_zio);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2011, 2019 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2013 Steven Hartland. All rights reserved.
|
||||
* Copyright (c) 2014 Spectra Logic Corporation, All rights reserved.
|
||||
* Copyright 2016 Nexenta Systems, Inc. All rights reserved.
|
||||
@@ -757,7 +757,7 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg)
|
||||
dp->dp_mos_uncompressed_delta = 0;
|
||||
}
|
||||
|
||||
if (!multilist_is_empty(mos->os_dirty_dnodes[txg & TXG_MASK])) {
|
||||
if (dmu_objset_is_dirty(mos, txg)) {
|
||||
dsl_pool_sync_mos(dp, tx);
|
||||
}
|
||||
|
||||
|
||||
+770
-206
File diff suppressed because it is too large
Load Diff
+80
-4
@@ -23,7 +23,7 @@
|
||||
* Use is subject to license terms.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2013, 2017 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2013, 2019 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
@@ -578,10 +578,10 @@ range_tree_vacate(range_tree_t *rt, range_tree_func_t *func, void *arg)
|
||||
void
|
||||
range_tree_walk(range_tree_t *rt, range_tree_func_t *func, void *arg)
|
||||
{
|
||||
range_seg_t *rs;
|
||||
|
||||
for (rs = avl_first(&rt->rt_root); rs; rs = AVL_NEXT(&rt->rt_root, rs))
|
||||
for (range_seg_t *rs = avl_first(&rt->rt_root); rs;
|
||||
rs = AVL_NEXT(&rt->rt_root, rs)) {
|
||||
func(arg, rs->rs_start, rs->rs_end - rs->rs_start);
|
||||
}
|
||||
}
|
||||
|
||||
range_seg_t *
|
||||
@@ -596,6 +596,12 @@ range_tree_space(range_tree_t *rt)
|
||||
return (rt->rt_space);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
range_tree_numsegs(range_tree_t *rt)
|
||||
{
|
||||
return ((rt == NULL) ? 0 : avl_numnodes(&rt->rt_root));
|
||||
}
|
||||
|
||||
boolean_t
|
||||
range_tree_is_empty(range_tree_t *rt)
|
||||
{
|
||||
@@ -667,3 +673,73 @@ range_tree_span(range_tree_t *rt)
|
||||
{
|
||||
return (range_tree_max(rt) - range_tree_min(rt));
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove any overlapping ranges between the given segment [start, end)
|
||||
* from removefrom. Add non-overlapping leftovers to addto.
|
||||
*/
|
||||
void
|
||||
range_tree_remove_xor_add_segment(uint64_t start, uint64_t end,
|
||||
range_tree_t *removefrom, range_tree_t *addto)
|
||||
{
|
||||
avl_index_t where;
|
||||
range_seg_t starting_rs = {
|
||||
.rs_start = start,
|
||||
.rs_end = start + 1
|
||||
};
|
||||
|
||||
range_seg_t *curr = avl_find(&removefrom->rt_root,
|
||||
&starting_rs, &where);
|
||||
|
||||
if (curr == NULL)
|
||||
curr = avl_nearest(&removefrom->rt_root, where, AVL_AFTER);
|
||||
|
||||
range_seg_t *next;
|
||||
for (; curr != NULL; curr = next) {
|
||||
next = AVL_NEXT(&removefrom->rt_root, curr);
|
||||
|
||||
if (start == end)
|
||||
return;
|
||||
VERIFY3U(start, <, end);
|
||||
|
||||
/* there is no overlap */
|
||||
if (end <= curr->rs_start) {
|
||||
range_tree_add(addto, start, end - start);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t overlap_start = MAX(curr->rs_start, start);
|
||||
uint64_t overlap_end = MIN(curr->rs_end, end);
|
||||
uint64_t overlap_size = overlap_end - overlap_start;
|
||||
ASSERT3S(overlap_size, >, 0);
|
||||
range_tree_remove(removefrom, overlap_start, overlap_size);
|
||||
|
||||
if (start < overlap_start)
|
||||
range_tree_add(addto, start, overlap_start - start);
|
||||
|
||||
start = overlap_end;
|
||||
}
|
||||
VERIFY3P(curr, ==, NULL);
|
||||
|
||||
if (start != end) {
|
||||
VERIFY3U(start, <, end);
|
||||
range_tree_add(addto, start, end - start);
|
||||
} else {
|
||||
VERIFY3U(start, ==, end);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For each entry in rt, if it exists in removefrom, remove it
|
||||
* from removefrom. Otherwise, add it to addto.
|
||||
*/
|
||||
void
|
||||
range_tree_remove_xor_add(range_tree_t *rt, range_tree_t *removefrom,
|
||||
range_tree_t *addto)
|
||||
{
|
||||
for (range_seg_t *rs = avl_first(&rt->rt_root); rs;
|
||||
rs = AVL_NEXT(&rt->rt_root, rs)) {
|
||||
range_tree_remove_xor_add_segment(rs->rs_start, rs->rs_end,
|
||||
removefrom, addto);
|
||||
}
|
||||
}
|
||||
|
||||
+128
-25
@@ -1420,19 +1420,88 @@ spa_config_parse(spa_t *spa, vdev_t **vdp, nvlist_t *nv, vdev_t *parent,
|
||||
return (0);
|
||||
}
|
||||
|
||||
static boolean_t
|
||||
spa_should_flush_logs_on_unload(spa_t *spa)
|
||||
{
|
||||
if (!spa_feature_is_active(spa, SPA_FEATURE_LOG_SPACEMAP))
|
||||
return (B_FALSE);
|
||||
|
||||
if (!spa_writeable(spa))
|
||||
return (B_FALSE);
|
||||
|
||||
if (!spa->spa_sync_on)
|
||||
return (B_FALSE);
|
||||
|
||||
if (spa_state(spa) != POOL_STATE_EXPORTED)
|
||||
return (B_FALSE);
|
||||
|
||||
if (zfs_keep_log_spacemaps_at_export)
|
||||
return (B_FALSE);
|
||||
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Opens a transaction that will set the flag that will instruct
|
||||
* spa_sync to attempt to flush all the metaslabs for that txg.
|
||||
*/
|
||||
static void
|
||||
spa_unload_log_sm_flush_all(spa_t *spa)
|
||||
{
|
||||
dmu_tx_t *tx = dmu_tx_create_dd(spa_get_dsl(spa)->dp_mos_dir);
|
||||
VERIFY0(dmu_tx_assign(tx, TXG_WAIT));
|
||||
|
||||
ASSERT3U(spa->spa_log_flushall_txg, ==, 0);
|
||||
spa->spa_log_flushall_txg = dmu_tx_get_txg(tx);
|
||||
|
||||
dmu_tx_commit(tx);
|
||||
txg_wait_synced(spa_get_dsl(spa), spa->spa_log_flushall_txg);
|
||||
}
|
||||
|
||||
static void
|
||||
spa_unload_log_sm_metadata(spa_t *spa)
|
||||
{
|
||||
void *cookie = NULL;
|
||||
spa_log_sm_t *sls;
|
||||
while ((sls = avl_destroy_nodes(&spa->spa_sm_logs_by_txg,
|
||||
&cookie)) != NULL) {
|
||||
VERIFY0(sls->sls_mscount);
|
||||
kmem_free(sls, sizeof (spa_log_sm_t));
|
||||
}
|
||||
|
||||
for (log_summary_entry_t *e = list_head(&spa->spa_log_summary);
|
||||
e != NULL; e = list_head(&spa->spa_log_summary)) {
|
||||
VERIFY0(e->lse_mscount);
|
||||
list_remove(&spa->spa_log_summary, e);
|
||||
kmem_free(e, sizeof (log_summary_entry_t));
|
||||
}
|
||||
|
||||
spa->spa_unflushed_stats.sus_nblocks = 0;
|
||||
spa->spa_unflushed_stats.sus_memused = 0;
|
||||
spa->spa_unflushed_stats.sus_blocklimit = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Opposite of spa_load().
|
||||
*/
|
||||
static void
|
||||
spa_unload(spa_t *spa)
|
||||
{
|
||||
int i;
|
||||
|
||||
ASSERT(MUTEX_HELD(&spa_namespace_lock));
|
||||
ASSERT(spa_state(spa) != POOL_STATE_UNINITIALIZED);
|
||||
|
||||
spa_import_progress_remove(spa_guid(spa));
|
||||
spa_load_note(spa, "UNLOADING");
|
||||
|
||||
/*
|
||||
* If the log space map feature is enabled and the pool is getting
|
||||
* exported (but not destroyed), we want to spend some time flushing
|
||||
* as many metaslabs as we can in an attempt to destroy log space
|
||||
* maps and save import time.
|
||||
*/
|
||||
if (spa_should_flush_logs_on_unload(spa))
|
||||
spa_unload_log_sm_flush_all(spa);
|
||||
|
||||
/*
|
||||
* Stop async tasks.
|
||||
*/
|
||||
@@ -1454,16 +1523,15 @@ spa_unload(spa_t *spa)
|
||||
}
|
||||
|
||||
/*
|
||||
* Even though vdev_free() also calls vdev_metaslab_fini, we need
|
||||
* to call it earlier, before we wait for async i/o to complete.
|
||||
* This ensures that there is no async metaslab prefetching, by
|
||||
* calling taskq_wait(mg_taskq).
|
||||
* This ensures that there is no async metaslab prefetching
|
||||
* while we attempt to unload the spa.
|
||||
*/
|
||||
if (spa->spa_root_vdev != NULL) {
|
||||
spa_config_enter(spa, SCL_ALL, spa, RW_WRITER);
|
||||
for (int c = 0; c < spa->spa_root_vdev->vdev_children; c++)
|
||||
vdev_metaslab_fini(spa->spa_root_vdev->vdev_child[c]);
|
||||
spa_config_exit(spa, SCL_ALL, spa);
|
||||
for (int c = 0; c < spa->spa_root_vdev->vdev_children; c++) {
|
||||
vdev_t *vc = spa->spa_root_vdev->vdev_child[c];
|
||||
if (vc->vdev_mg != NULL)
|
||||
taskq_wait(vc->vdev_mg->mg_taskq);
|
||||
}
|
||||
}
|
||||
|
||||
if (spa->spa_mmp.mmp_thread)
|
||||
@@ -1517,13 +1585,14 @@ spa_unload(spa_t *spa)
|
||||
}
|
||||
|
||||
ddt_unload(spa);
|
||||
spa_unload_log_sm_metadata(spa);
|
||||
|
||||
/*
|
||||
* Drop and purge level 2 cache
|
||||
*/
|
||||
spa_l2cache_drop(spa);
|
||||
|
||||
for (i = 0; i < spa->spa_spares.sav_count; i++)
|
||||
for (int i = 0; i < spa->spa_spares.sav_count; i++)
|
||||
vdev_free(spa->spa_spares.sav_vdevs[i]);
|
||||
if (spa->spa_spares.sav_vdevs) {
|
||||
kmem_free(spa->spa_spares.sav_vdevs,
|
||||
@@ -1536,7 +1605,7 @@ spa_unload(spa_t *spa)
|
||||
}
|
||||
spa->spa_spares.sav_count = 0;
|
||||
|
||||
for (i = 0; i < spa->spa_l2cache.sav_count; i++) {
|
||||
for (int i = 0; i < spa->spa_l2cache.sav_count; i++) {
|
||||
vdev_clear_stats(spa->spa_l2cache.sav_vdevs[i]);
|
||||
vdev_free(spa->spa_l2cache.sav_vdevs[i]);
|
||||
}
|
||||
@@ -3723,6 +3792,13 @@ spa_ld_load_vdev_metadata(spa_t *spa)
|
||||
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, error));
|
||||
}
|
||||
|
||||
error = spa_ld_log_spacemaps(spa);
|
||||
if (error != 0) {
|
||||
spa_load_failed(spa, "spa_ld_log_sm_data failed [error=%d]",
|
||||
error);
|
||||
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, error));
|
||||
}
|
||||
|
||||
/*
|
||||
* Propagate the leaf DTLs we just loaded all the way up the vdev tree.
|
||||
*/
|
||||
@@ -5864,7 +5940,7 @@ spa_reset(char *pool)
|
||||
int
|
||||
spa_vdev_add(spa_t *spa, nvlist_t *nvroot)
|
||||
{
|
||||
uint64_t txg, id;
|
||||
uint64_t txg;
|
||||
int error;
|
||||
vdev_t *rvd = spa->spa_root_vdev;
|
||||
vdev_t *vd, *tvd;
|
||||
@@ -5939,19 +6015,9 @@ spa_vdev_add(spa_t *spa, nvlist_t *nvroot)
|
||||
}
|
||||
|
||||
for (int c = 0; c < vd->vdev_children; c++) {
|
||||
|
||||
/*
|
||||
* Set the vdev id to the first hole, if one exists.
|
||||
*/
|
||||
for (id = 0; id < rvd->vdev_children; id++) {
|
||||
if (rvd->vdev_child[id]->vdev_ishole) {
|
||||
vdev_free(rvd->vdev_child[id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tvd = vd->vdev_child[c];
|
||||
vdev_remove_child(vd, tvd);
|
||||
tvd->vdev_id = id;
|
||||
tvd->vdev_id = rvd->vdev_children;
|
||||
vdev_add_child(rvd, tvd);
|
||||
vdev_config_dirty(tvd);
|
||||
}
|
||||
@@ -7597,6 +7663,18 @@ spa_sync_deferred_frees(spa_t *spa, dmu_tx_t *tx)
|
||||
if (spa_sync_pass(spa) != 1)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Note:
|
||||
* If the log space map feature is active, we stop deferring
|
||||
* frees to the next TXG and therefore running this function
|
||||
* would be considered a no-op as spa_deferred_bpobj should
|
||||
* not have any entries.
|
||||
*
|
||||
* That said we run this function anyway (instead of returning
|
||||
* immediately) for the edge-case scenario where we just
|
||||
* activated the log space map feature in this TXG but we have
|
||||
* deferred frees from the previous TXG.
|
||||
*/
|
||||
zio_t *zio = zio_root(spa, NULL, NULL, 0);
|
||||
VERIFY3U(bpobj_iterate(&spa->spa_deferred_bpobj,
|
||||
spa_free_sync_cb, zio, tx), ==, 0);
|
||||
@@ -8187,7 +8265,14 @@ spa_sync_iterate_to_convergence(spa_t *spa, dmu_tx_t *tx)
|
||||
spa_errlog_sync(spa, txg);
|
||||
dsl_pool_sync(dp, txg);
|
||||
|
||||
if (pass < zfs_sync_pass_deferred_free) {
|
||||
if (pass < zfs_sync_pass_deferred_free ||
|
||||
spa_feature_is_active(spa, SPA_FEATURE_LOG_SPACEMAP)) {
|
||||
/*
|
||||
* If the log space map feature is active we don't
|
||||
* care about deferred frees and the deferred bpobj
|
||||
* as the log space map should effectively have the
|
||||
* same results (i.e. appending only to one object).
|
||||
*/
|
||||
spa_sync_frees(spa, free_bpl, tx);
|
||||
} else {
|
||||
/*
|
||||
@@ -8204,6 +8289,8 @@ spa_sync_iterate_to_convergence(spa_t *spa, dmu_tx_t *tx)
|
||||
svr_sync(spa, tx);
|
||||
spa_sync_upgrades(spa, tx);
|
||||
|
||||
spa_flush_metaslabs(spa, tx);
|
||||
|
||||
vdev_t *vd = NULL;
|
||||
while ((vd = txg_list_remove(&spa->spa_vdev_txg_list, txg))
|
||||
!= NULL)
|
||||
@@ -8453,6 +8540,7 @@ spa_sync(spa_t *spa, uint64_t txg)
|
||||
while ((vd = txg_list_remove(&spa->spa_vdev_txg_list, TXG_CLEAN(txg)))
|
||||
!= NULL)
|
||||
vdev_sync_done(vd, txg);
|
||||
spa_sync_close_syncing_log_sm(spa);
|
||||
|
||||
spa_update_dspace(spa);
|
||||
|
||||
@@ -8639,6 +8727,21 @@ spa_has_active_shared_spare(spa_t *spa)
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
spa_total_metaslabs(spa_t *spa)
|
||||
{
|
||||
vdev_t *rvd = spa->spa_root_vdev;
|
||||
|
||||
uint64_t m = 0;
|
||||
for (uint64_t c = 0; c < rvd->vdev_children; c++) {
|
||||
vdev_t *vd = rvd->vdev_child[c];
|
||||
if (!vdev_is_concrete(vd))
|
||||
continue;
|
||||
m += vd->vdev_ms_count;
|
||||
}
|
||||
return (m);
|
||||
}
|
||||
|
||||
sysevent_t *
|
||||
spa_event_create(spa_t *spa, vdev_t *vd, nvlist_t *hist_nvl, const char *name)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+29
-3
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2011, 2019 by Delphix. All rights reserved.
|
||||
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
|
||||
* Copyright (c) 2014 Spectra Logic Corporation, All rights reserved.
|
||||
* Copyright 2013 Saso Kiselkov. All rights reserved.
|
||||
@@ -64,7 +64,7 @@
|
||||
/*
|
||||
* SPA locking
|
||||
*
|
||||
* There are four basic locks for managing spa_t structures:
|
||||
* There are three basic locks for managing spa_t structures:
|
||||
*
|
||||
* spa_namespace_lock (global mutex)
|
||||
*
|
||||
@@ -613,6 +613,15 @@ spa_deadman(void *arg)
|
||||
MSEC_TO_TICK(zfs_deadman_checktime_ms));
|
||||
}
|
||||
|
||||
int
|
||||
spa_log_sm_sort_by_txg(const void *va, const void *vb)
|
||||
{
|
||||
const spa_log_sm_t *a = va;
|
||||
const spa_log_sm_t *b = vb;
|
||||
|
||||
return (AVL_CMP(a->sls_txg, b->sls_txg));
|
||||
}
|
||||
|
||||
/*
|
||||
* Create an uninitialized spa_t with the given name. Requires
|
||||
* spa_namespace_lock. The caller must ensure that the spa_t doesn't already
|
||||
@@ -640,6 +649,7 @@ spa_add(const char *name, nvlist_t *config, const char *altroot)
|
||||
mutex_init(&spa->spa_suspend_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||
mutex_init(&spa->spa_vdev_top_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||
mutex_init(&spa->spa_feat_stats_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||
mutex_init(&spa->spa_flushed_ms_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||
|
||||
cv_init(&spa->spa_async_cv, NULL, CV_DEFAULT, NULL);
|
||||
cv_init(&spa->spa_evicting_os_cv, NULL, CV_DEFAULT, NULL);
|
||||
@@ -685,6 +695,12 @@ spa_add(const char *name, nvlist_t *config, const char *altroot)
|
||||
avl_create(&spa->spa_alloc_trees[i], zio_bookmark_compare,
|
||||
sizeof (zio_t), offsetof(zio_t, io_alloc_node));
|
||||
}
|
||||
avl_create(&spa->spa_metaslabs_by_flushed, metaslab_sort_by_flushed,
|
||||
sizeof (metaslab_t), offsetof(metaslab_t, ms_spa_txg_node));
|
||||
avl_create(&spa->spa_sm_logs_by_txg, spa_log_sm_sort_by_txg,
|
||||
sizeof (spa_log_sm_t), offsetof(spa_log_sm_t, sls_node));
|
||||
list_create(&spa->spa_log_summary, sizeof (log_summary_entry_t),
|
||||
offsetof(log_summary_entry_t, lse_node));
|
||||
|
||||
/*
|
||||
* Every pool starts with the default cachefile
|
||||
@@ -748,7 +764,7 @@ spa_remove(spa_t *spa)
|
||||
spa_config_dirent_t *dp;
|
||||
|
||||
ASSERT(MUTEX_HELD(&spa_namespace_lock));
|
||||
ASSERT(spa->spa_state == POOL_STATE_UNINITIALIZED);
|
||||
ASSERT(spa_state(spa) == POOL_STATE_UNINITIALIZED);
|
||||
ASSERT3U(zfs_refcount_count(&spa->spa_refcount), ==, 0);
|
||||
|
||||
nvlist_free(spa->spa_config_splitting);
|
||||
@@ -775,6 +791,9 @@ spa_remove(spa_t *spa)
|
||||
kmem_free(spa->spa_alloc_trees, spa->spa_alloc_count *
|
||||
sizeof (avl_tree_t));
|
||||
|
||||
avl_destroy(&spa->spa_metaslabs_by_flushed);
|
||||
avl_destroy(&spa->spa_sm_logs_by_txg);
|
||||
list_destroy(&spa->spa_log_summary);
|
||||
list_destroy(&spa->spa_config_list);
|
||||
list_destroy(&spa->spa_leaf_list);
|
||||
|
||||
@@ -799,6 +818,7 @@ spa_remove(spa_t *spa)
|
||||
cv_destroy(&spa->spa_scrub_io_cv);
|
||||
cv_destroy(&spa->spa_suspend_cv);
|
||||
|
||||
mutex_destroy(&spa->spa_flushed_ms_lock);
|
||||
mutex_destroy(&spa->spa_async_lock);
|
||||
mutex_destroy(&spa->spa_errlist_lock);
|
||||
mutex_destroy(&spa->spa_errlog_lock);
|
||||
@@ -2570,6 +2590,12 @@ spa_missing_tvds_allowed(spa_t *spa)
|
||||
return (spa->spa_missing_tvds_allowed);
|
||||
}
|
||||
|
||||
space_map_t *
|
||||
spa_syncing_log_sm(spa_t *spa)
|
||||
{
|
||||
return (spa->spa_syncing_log_sm);
|
||||
}
|
||||
|
||||
void
|
||||
spa_set_missing_tvds(spa_t *spa, uint64_t missing)
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* Use is subject to license terms.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2012, 2018 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2012, 2019 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
@@ -1067,3 +1067,11 @@ space_map_length(space_map_t *sm)
|
||||
{
|
||||
return (sm != NULL ? sm->sm_phys->smp_length : 0);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
space_map_nblocks(space_map_t *sm)
|
||||
{
|
||||
if (sm == NULL)
|
||||
return (0);
|
||||
return (DIV_ROUND_UP(space_map_length(sm), sm->sm_blksz));
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Portions Copyright 2011 Martin Matuska
|
||||
* Copyright (c) 2012, 2017 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2012, 2019 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
@@ -272,7 +272,7 @@ txg_sync_stop(dsl_pool_t *dp)
|
||||
ASSERT3U(tx->tx_threads, ==, 2);
|
||||
|
||||
/*
|
||||
* We need to ensure that we've vacated the deferred space_maps.
|
||||
* We need to ensure that we've vacated the deferred metaslab trees.
|
||||
*/
|
||||
txg_wait_synced(dp, tx->tx_open_txg + TXG_DEFER_SIZE);
|
||||
|
||||
|
||||
+36
-4
@@ -76,7 +76,7 @@ int vdev_validate_skip = B_FALSE;
|
||||
* Since the DTL space map of a vdev is not expected to have a lot of
|
||||
* entries, we default its block size to 4K.
|
||||
*/
|
||||
int vdev_dtl_sm_blksz = (1 << 12);
|
||||
int zfs_vdev_dtl_sm_blksz = (1 << 12);
|
||||
|
||||
/*
|
||||
* Rate limit slow IO (delay) events to this many per second.
|
||||
@@ -99,7 +99,7 @@ int zfs_scan_ignore_errors = 0;
|
||||
* the end of each transaction can benefit from a higher I/O bandwidth
|
||||
* (e.g. vdev_obsolete_sm), thus we default their block size to 128K.
|
||||
*/
|
||||
int vdev_standard_sm_blksz = (1 << 17);
|
||||
int zfs_vdev_standard_sm_blksz = (1 << 17);
|
||||
|
||||
/*
|
||||
* Tunable parameter for debugging or performance analysis. Setting this
|
||||
@@ -924,6 +924,7 @@ vdev_free(vdev_t *vd)
|
||||
if (vd->vdev_mg != NULL) {
|
||||
vdev_metaslab_fini(vd);
|
||||
metaslab_group_destroy(vd->vdev_mg);
|
||||
vd->vdev_mg = NULL;
|
||||
}
|
||||
|
||||
ASSERT0(vd->vdev_stat.vs_space);
|
||||
@@ -1353,6 +1354,13 @@ vdev_metaslab_init(vdev_t *vd, uint64_t txg)
|
||||
if (txg == 0)
|
||||
spa_config_exit(spa, SCL_ALLOC, FTAG);
|
||||
|
||||
/*
|
||||
* Regardless whether this vdev was just added or it is being
|
||||
* expanded, the metaslab count has changed. Recalculate the
|
||||
* block limit.
|
||||
*/
|
||||
spa_log_sm_set_blocklimit(spa);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -2867,7 +2875,7 @@ vdev_dtl_sync(vdev_t *vd, uint64_t txg)
|
||||
if (vd->vdev_dtl_sm == NULL) {
|
||||
uint64_t new_object;
|
||||
|
||||
new_object = space_map_alloc(mos, vdev_dtl_sm_blksz, tx);
|
||||
new_object = space_map_alloc(mos, zfs_vdev_dtl_sm_blksz, tx);
|
||||
VERIFY3U(new_object, !=, 0);
|
||||
|
||||
VERIFY0(space_map_open(&vd->vdev_dtl_sm, mos, new_object,
|
||||
@@ -2881,7 +2889,7 @@ vdev_dtl_sync(vdev_t *vd, uint64_t txg)
|
||||
range_tree_walk(rt, range_tree_add, rtsync);
|
||||
mutex_exit(&vd->vdev_dtl_lock);
|
||||
|
||||
space_map_truncate(vd->vdev_dtl_sm, vdev_dtl_sm_blksz, tx);
|
||||
space_map_truncate(vd->vdev_dtl_sm, zfs_vdev_dtl_sm_blksz, tx);
|
||||
space_map_write(vd->vdev_dtl_sm, rtsync, SM_ALLOC, SM_NO_VDEVID, tx);
|
||||
range_tree_vacate(rtsync, NULL, NULL);
|
||||
|
||||
@@ -3172,6 +3180,25 @@ vdev_validate_aux(vdev_t *vd)
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
vdev_destroy_ms_flush_data(vdev_t *vd, dmu_tx_t *tx)
|
||||
{
|
||||
objset_t *mos = spa_meta_objset(vd->vdev_spa);
|
||||
|
||||
if (vd->vdev_top_zap == 0)
|
||||
return;
|
||||
|
||||
uint64_t object = 0;
|
||||
int err = zap_lookup(mos, vd->vdev_top_zap,
|
||||
VDEV_TOP_ZAP_MS_UNFLUSHED_PHYS_TXGS, sizeof (uint64_t), 1, &object);
|
||||
if (err == ENOENT)
|
||||
return;
|
||||
|
||||
VERIFY0(dmu_object_free(mos, object, tx));
|
||||
VERIFY0(zap_remove(mos, vd->vdev_top_zap,
|
||||
VDEV_TOP_ZAP_MS_UNFLUSHED_PHYS_TXGS, tx));
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the objects used to store this vdev's spacemaps, and the array
|
||||
* that points to them.
|
||||
@@ -3199,6 +3226,7 @@ vdev_destroy_spacemaps(vdev_t *vd, dmu_tx_t *tx)
|
||||
|
||||
kmem_free(smobj_array, array_bytes);
|
||||
VERIFY0(dmu_object_free(mos, vd->vdev_ms_array, tx));
|
||||
vdev_destroy_ms_flush_data(vd, tx);
|
||||
vd->vdev_ms_array = 0;
|
||||
}
|
||||
|
||||
@@ -4762,6 +4790,10 @@ module_param(zfs_vdev_default_ms_count, int, 0644);
|
||||
MODULE_PARM_DESC(zfs_vdev_default_ms_count,
|
||||
"Target number of metaslabs per top-level vdev");
|
||||
|
||||
module_param(zfs_vdev_default_ms_shift, int, 0644);
|
||||
MODULE_PARM_DESC(zfs_vdev_default_ms_shift,
|
||||
"Default limit for metaslab size");
|
||||
|
||||
module_param(zfs_vdev_min_ms_count, int, 0644);
|
||||
MODULE_PARM_DESC(zfs_vdev_min_ms_count,
|
||||
"Minimum number of metaslabs per top-level vdev");
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2017 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
|
||||
* Copyright (c) 2014, 2019 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
@@ -825,7 +826,7 @@ vdev_indirect_sync_obsolete(vdev_t *vd, dmu_tx_t *tx)
|
||||
VERIFY0(vdev_obsolete_sm_object(vd, &obsolete_sm_object));
|
||||
if (obsolete_sm_object == 0) {
|
||||
obsolete_sm_object = space_map_alloc(spa->spa_meta_objset,
|
||||
vdev_standard_sm_blksz, tx);
|
||||
zfs_vdev_standard_sm_blksz, tx);
|
||||
|
||||
ASSERT(vd->vdev_top_zap != 0);
|
||||
VERIFY0(zap_add(vd->vdev_spa->spa_meta_objset, vd->vdev_top_zap,
|
||||
|
||||
@@ -1203,6 +1203,7 @@ vdev_remove_complete(spa_t *spa)
|
||||
vdev_metaslab_fini(vd);
|
||||
metaslab_group_destroy(vd->vdev_mg);
|
||||
vd->vdev_mg = NULL;
|
||||
spa_log_sm_set_blocklimit(spa);
|
||||
}
|
||||
ASSERT0(vd->vdev_stat.vs_space);
|
||||
ASSERT0(vd->vdev_stat.vs_dspace);
|
||||
@@ -1461,6 +1462,10 @@ spa_vdev_remove_thread(void *arg)
|
||||
VERIFY0(space_map_load(msp->ms_sm,
|
||||
svr->svr_allocd_segs, SM_ALLOC));
|
||||
|
||||
range_tree_walk(msp->ms_unflushed_allocs,
|
||||
range_tree_add, svr->svr_allocd_segs);
|
||||
range_tree_walk(msp->ms_unflushed_frees,
|
||||
range_tree_remove, svr->svr_allocd_segs);
|
||||
range_tree_walk(msp->ms_freeing,
|
||||
range_tree_remove, svr->svr_allocd_segs);
|
||||
|
||||
@@ -1685,6 +1690,11 @@ spa_vdev_remove_cancel_sync(void *arg, dmu_tx_t *tx)
|
||||
mutex_enter(&svr->svr_lock);
|
||||
VERIFY0(space_map_load(msp->ms_sm,
|
||||
svr->svr_allocd_segs, SM_ALLOC));
|
||||
|
||||
range_tree_walk(msp->ms_unflushed_allocs,
|
||||
range_tree_add, svr->svr_allocd_segs);
|
||||
range_tree_walk(msp->ms_unflushed_frees,
|
||||
range_tree_remove, svr->svr_allocd_segs);
|
||||
range_tree_walk(msp->ms_freeing,
|
||||
range_tree_remove, svr->svr_allocd_segs);
|
||||
|
||||
@@ -1813,19 +1823,14 @@ vdev_remove_make_hole_and_free(vdev_t *vd)
|
||||
uint64_t id = vd->vdev_id;
|
||||
spa_t *spa = vd->vdev_spa;
|
||||
vdev_t *rvd = spa->spa_root_vdev;
|
||||
boolean_t last_vdev = (id == (rvd->vdev_children - 1));
|
||||
|
||||
ASSERT(MUTEX_HELD(&spa_namespace_lock));
|
||||
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
|
||||
|
||||
vdev_free(vd);
|
||||
|
||||
if (last_vdev) {
|
||||
vdev_compact_children(rvd);
|
||||
} else {
|
||||
vd = vdev_alloc_common(spa, id, 0, &vdev_hole_ops);
|
||||
vdev_add_child(rvd, vd);
|
||||
}
|
||||
vd = vdev_alloc_common(spa, id, 0, &vdev_hole_ops);
|
||||
vdev_add_child(rvd, vd);
|
||||
vdev_config_dirty(rvd);
|
||||
|
||||
/*
|
||||
@@ -1887,7 +1892,28 @@ spa_vdev_remove_log(vdev_t *vd, uint64_t *txg)
|
||||
vdev_dirty_leaves(vd, VDD_DTL, *txg);
|
||||
vdev_config_dirty(vd);
|
||||
|
||||
/*
|
||||
* When the log space map feature is enabled we look at
|
||||
* the vdev's top_zap to find the on-disk flush data of
|
||||
* the metaslab we just flushed. Thus, while removing a
|
||||
* log vdev we make sure to call vdev_metaslab_fini()
|
||||
* first, which removes all metaslabs of this vdev from
|
||||
* spa_metaslabs_by_flushed before vdev_remove_empty()
|
||||
* destroys the top_zap of this log vdev.
|
||||
*
|
||||
* This avoids the scenario where we flush a metaslab
|
||||
* from the log vdev being removed that doesn't have a
|
||||
* top_zap and end up failing to lookup its on-disk flush
|
||||
* data.
|
||||
*
|
||||
* We don't call metaslab_group_destroy() right away
|
||||
* though (it will be called in vdev_free() later) as
|
||||
* during metaslab_sync() of metaslabs from other vdevs
|
||||
* we may touch the metaslab group of this vdev through
|
||||
* metaslab_class_histogram_verify()
|
||||
*/
|
||||
vdev_metaslab_fini(vd);
|
||||
spa_log_sm_set_blocklimit(spa);
|
||||
|
||||
spa_vdev_config_exit(spa, NULL, *txg, 0, FTAG);
|
||||
|
||||
|
||||
+8
-3
@@ -1117,10 +1117,16 @@ zio_free(spa_t *spa, uint64_t txg, const blkptr_t *bp)
|
||||
* deferred, and which will not need to do a read (i.e. not GANG or
|
||||
* DEDUP), can be processed immediately. Otherwise, put them on the
|
||||
* in-memory list for later processing.
|
||||
*
|
||||
* Note that we only defer frees after zfs_sync_pass_deferred_free
|
||||
* when the log space map feature is disabled. [see relevant comment
|
||||
* in spa_sync_iterate_to_convergence()]
|
||||
*/
|
||||
if (BP_IS_GANG(bp) || BP_GET_DEDUP(bp) ||
|
||||
if (BP_IS_GANG(bp) ||
|
||||
BP_GET_DEDUP(bp) ||
|
||||
txg != spa->spa_syncing_txg ||
|
||||
spa_sync_pass(spa) >= zfs_sync_pass_deferred_free) {
|
||||
(spa_sync_pass(spa) >= zfs_sync_pass_deferred_free &&
|
||||
!spa_feature_is_active(spa, SPA_FEATURE_LOG_SPACEMAP))) {
|
||||
bplist_append(&spa->spa_free_bplist[txg & TXG_MASK], bp);
|
||||
} else {
|
||||
VERIFY0(zio_wait(zio_free_sync(NULL, spa, txg, bp, 0)));
|
||||
@@ -1136,7 +1142,6 @@ zio_free_sync(zio_t *pio, spa_t *spa, uint64_t txg, const blkptr_t *bp,
|
||||
|
||||
ASSERT(!BP_IS_HOLE(bp));
|
||||
ASSERT(spa_syncing_txg(spa) == txg);
|
||||
ASSERT(spa_sync_pass(spa) < zfs_sync_pass_deferred_free);
|
||||
|
||||
if (BP_IS_EMBEDDED(bp))
|
||||
return (zio_null(pio, spa, NULL, NULL, NULL, 0));
|
||||
|
||||
Reference in New Issue
Block a user