3552 condensing one space map burns 3 seconds of CPU in spa_sync() thread
3564 spa_sync() spends 5-10% of its time in metaslab_sync() (when not condensing)
Reviewed by: Adam Leventhal <ahl@delphix.com>
Reviewed by: Dan Kimmel <dan.kimmel@delphix.com>
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Approved by: Richard Lowe <richlowe@richlowe.net>

References:
  illumos/illumos-gate@16a4a80742
  https://www.illumos.org/issues/3552
  https://www.illumos.org/issues/3564

Ported-by: Tim Chase <tim@chase2k.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #1513
This commit is contained in:
George Wilson 2013-06-14 22:30:35 -05:00 committed by Brian Behlendorf
parent c99c90015e
commit e51be06697
6 changed files with 327 additions and 112 deletions

View File

@ -548,7 +548,7 @@ static void
dump_metaslab_stats(metaslab_t *msp) dump_metaslab_stats(metaslab_t *msp)
{ {
char maxbuf[32]; char maxbuf[32];
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
avl_tree_t *t = sm->sm_pp_root; avl_tree_t *t = sm->sm_pp_root;
int free_pct = sm->sm_space * 100 / sm->sm_size; int free_pct = sm->sm_space * 100 / sm->sm_size;
@ -564,7 +564,7 @@ dump_metaslab(metaslab_t *msp)
{ {
vdev_t *vd = msp->ms_group->mg_vd; vdev_t *vd = msp->ms_group->mg_vd;
spa_t *spa = vd->vdev_spa; spa_t *spa = vd->vdev_spa;
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_obj_t *smo = &msp->ms_smo; space_map_obj_t *smo = &msp->ms_smo;
char freebuf[32]; char freebuf[32];
@ -2194,11 +2194,11 @@ zdb_leak_init(spa_t *spa, zdb_cb_t *zcb)
for (m = 0; m < vd->vdev_ms_count; m++) { for (m = 0; m < vd->vdev_ms_count; m++) {
metaslab_t *msp = vd->vdev_ms[m]; metaslab_t *msp = vd->vdev_ms[m];
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
space_map_unload(&msp->ms_map); space_map_unload(msp->ms_map);
VERIFY(space_map_load(&msp->ms_map, VERIFY(space_map_load(msp->ms_map,
&zdb_space_map_ops, SM_ALLOC, &msp->ms_smo, &zdb_space_map_ops, SM_ALLOC, &msp->ms_smo,
spa->spa_meta_objset) == 0); spa->spa_meta_objset) == 0);
msp->ms_map.sm_ppd = vd; msp->ms_map->sm_ppd = vd;
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
} }
} }
@ -2223,7 +2223,7 @@ zdb_leak_fini(spa_t *spa)
for (m = 0; m < vd->vdev_ms_count; m++) { for (m = 0; m < vd->vdev_ms_count; m++) {
metaslab_t *msp = vd->vdev_ms[m]; metaslab_t *msp = vd->vdev_ms[m];
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
space_map_unload(&msp->ms_map); space_map_unload(msp->ms_map);
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
} }
} }

View File

@ -67,20 +67,38 @@ struct metaslab_group {
}; };
/* /*
* Each metaslab's free space is tracked in space map object in the MOS, * Each metaslab maintains an in-core free map (ms_map) that contains the
* which is only updated in syncing context. Each time we sync a txg, * current list of free segments. As blocks are allocated, the allocated
* segment is removed from the ms_map and added to a per txg allocation map.
* As blocks are freed, they are added to the per txg free map. These per
* txg maps allow us to process all allocations and frees in syncing context
* where it is safe to update the on-disk space maps.
*
* Each metaslab's free space is tracked in a space map object in the MOS,
* which is only updated in syncing context. Each time we sync a txg,
* we append the allocs and frees from that txg to the space map object. * we append the allocs and frees from that txg to the space map object.
* When the txg is done syncing, metaslab_sync_done() updates ms_smo * When the txg is done syncing, metaslab_sync_done() updates ms_smo
* to ms_smo_syncing. Everything in ms_smo is always safe to allocate. * to ms_smo_syncing. Everything in ms_smo is always safe to allocate.
*
* To load the in-core free map we read the space map object from disk.
* This object contains a series of alloc and free records that are
* combined to make up the list of all free segments in this metaslab. These
* segments are represented in-core by the ms_map and are stored in an
* AVL tree.
*
* As the space map objects grows (as a result of the appends) it will
* eventually become space-inefficient. When the space map object is
* zfs_condense_pct/100 times the size of the minimal on-disk representation,
* we rewrite it in its minimized form.
*/ */
struct metaslab { struct metaslab {
kmutex_t ms_lock; /* metaslab lock */ kmutex_t ms_lock; /* metaslab lock */
space_map_obj_t ms_smo; /* synced space map object */ space_map_obj_t ms_smo; /* synced space map object */
space_map_obj_t ms_smo_syncing; /* syncing space map object */ space_map_obj_t ms_smo_syncing; /* syncing space map object */
space_map_t ms_allocmap[TXG_SIZE]; /* allocated this txg */ space_map_t *ms_allocmap[TXG_SIZE]; /* allocated this txg */
space_map_t ms_freemap[TXG_SIZE]; /* freed this txg */ space_map_t *ms_freemap[TXG_SIZE]; /* freed this txg */
space_map_t ms_defermap[TXG_DEFER_SIZE]; /* deferred frees */ space_map_t *ms_defermap[TXG_DEFER_SIZE]; /* deferred frees */
space_map_t ms_map; /* in-core free space map */ space_map_t *ms_map; /* in-core free space map */
int64_t ms_deferspace; /* sum of ms_defermap[] space */ int64_t ms_deferspace; /* sum of ms_defermap[] space */
uint64_t ms_weight; /* weight vs. others in group */ uint64_t ms_weight; /* weight vs. others in group */
metaslab_group_t *ms_group; /* metaslab group */ metaslab_group_t *ms_group; /* metaslab group */

View File

@ -40,17 +40,17 @@ extern "C" {
typedef const struct space_map_ops space_map_ops_t; typedef const struct space_map_ops space_map_ops_t;
typedef struct space_map { typedef struct space_map {
avl_tree_t sm_root; /* AVL tree of map segments */ avl_tree_t sm_root; /* offset-ordered segment AVL tree */
uint64_t sm_space; /* sum of all segments in the map */ uint64_t sm_space; /* sum of all segments in the map */
uint64_t sm_start; /* start of map */ uint64_t sm_start; /* start of map */
uint64_t sm_size; /* size of map */ uint64_t sm_size; /* size of map */
uint8_t sm_shift; /* unit shift */ uint8_t sm_shift; /* unit shift */
uint8_t sm_pad[3]; /* unused */
uint8_t sm_loaded; /* map loaded? */ uint8_t sm_loaded; /* map loaded? */
uint8_t sm_loading; /* map loading? */ uint8_t sm_loading; /* map loading? */
uint8_t sm_condensing; /* map condensing? */
kcondvar_t sm_load_cv; /* map load completion */ kcondvar_t sm_load_cv; /* map load completion */
space_map_ops_t *sm_ops; /* space map block picker ops vector */ space_map_ops_t *sm_ops; /* space map block picker ops vector */
avl_tree_t *sm_pp_root; /* picker-private AVL tree */ avl_tree_t *sm_pp_root; /* size-ordered, picker-private tree */
void *sm_ppd; /* picker-private data */ void *sm_ppd; /* picker-private data */
kmutex_t *sm_lock; /* pointer to lock that protects map */ kmutex_t *sm_lock; /* pointer to lock that protects map */
} space_map_t; } space_map_t;
@ -149,6 +149,7 @@ extern void space_map_add(space_map_t *sm, uint64_t start, uint64_t size);
extern void space_map_remove(space_map_t *sm, uint64_t start, uint64_t size); extern void space_map_remove(space_map_t *sm, uint64_t start, uint64_t size);
extern boolean_t space_map_contains(space_map_t *sm, extern boolean_t space_map_contains(space_map_t *sm,
uint64_t start, uint64_t size); uint64_t start, uint64_t size);
extern void space_map_swap(space_map_t **msrc, space_map_t **mdest);
extern void space_map_vacate(space_map_t *sm, extern void space_map_vacate(space_map_t *sm,
space_map_func_t *func, space_map_t *mdest); space_map_func_t *func, space_map_t *mdest);
extern void space_map_walk(space_map_t *sm, extern void space_map_walk(space_map_t *sm,

View File

@ -48,6 +48,14 @@
uint64_t metaslab_aliquot = 512ULL << 10; uint64_t metaslab_aliquot = 512ULL << 10;
uint64_t metaslab_gang_bang = SPA_MAXBLOCKSIZE + 1; /* force gang blocks */ uint64_t metaslab_gang_bang = SPA_MAXBLOCKSIZE + 1; /* force gang blocks */
/*
* The in-core space map representation is more compact than its on-disk form.
* The zfs_condense_pct determines how much more compact the in-core
* space_map representation must be before we compact it on-disk.
* Values should be greater than or equal to 100.
*/
int zfs_condense_pct = 200;
/* /*
* This value defines the number of allowed allocation failures per vdev. * This value defines the number of allowed allocation failures per vdev.
* If a device reaches this threshold in a given txg then we consider skipping * If a device reaches this threshold in a given txg then we consider skipping
@ -204,9 +212,9 @@ metaslab_compare(const void *x1, const void *x2)
/* /*
* If the weights are identical, use the offset to force uniqueness. * If the weights are identical, use the offset to force uniqueness.
*/ */
if (m1->ms_map.sm_start < m2->ms_map.sm_start) if (m1->ms_map->sm_start < m2->ms_map->sm_start)
return (-1); return (-1);
if (m1->ms_map.sm_start > m2->ms_map.sm_start) if (m1->ms_map->sm_start > m2->ms_map->sm_start)
return (1); return (1);
ASSERT3P(m1, ==, m2); ASSERT3P(m1, ==, m2);
@ -739,14 +747,15 @@ metaslab_init(metaslab_group_t *mg, space_map_obj_t *smo,
* addition of new space; and for debugging, it ensures that we'd * addition of new space; and for debugging, it ensures that we'd
* data fault on any attempt to use this metaslab before it's ready. * data fault on any attempt to use this metaslab before it's ready.
*/ */
space_map_create(&msp->ms_map, start, size, msp->ms_map = kmem_zalloc(sizeof (space_map_t), KM_PUSHPAGE);
space_map_create(msp->ms_map, start, size,
vd->vdev_ashift, &msp->ms_lock); vd->vdev_ashift, &msp->ms_lock);
metaslab_group_add(mg, msp); metaslab_group_add(mg, msp);
if (metaslab_debug && smo->smo_object != 0) { if (metaslab_debug && smo->smo_object != 0) {
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
VERIFY(space_map_load(&msp->ms_map, mg->mg_class->mc_ops, VERIFY(space_map_load(msp->ms_map, mg->mg_class->mc_ops,
SM_FREE, smo, spa_meta_objset(vd->vdev_spa)) == 0); SM_FREE, smo, spa_meta_objset(vd->vdev_spa)) == 0);
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
} }
@ -775,22 +784,27 @@ metaslab_fini(metaslab_t *msp)
int t; int t;
vdev_space_update(mg->mg_vd, vdev_space_update(mg->mg_vd,
-msp->ms_smo.smo_alloc, 0, -msp->ms_map.sm_size); -msp->ms_smo.smo_alloc, 0, -msp->ms_map->sm_size);
metaslab_group_remove(mg, msp); metaslab_group_remove(mg, msp);
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
space_map_unload(&msp->ms_map); space_map_unload(msp->ms_map);
space_map_destroy(&msp->ms_map); space_map_destroy(msp->ms_map);
kmem_free(msp->ms_map, sizeof (*msp->ms_map));
for (t = 0; t < TXG_SIZE; t++) { for (t = 0; t < TXG_SIZE; t++) {
space_map_destroy(&msp->ms_allocmap[t]); space_map_destroy(msp->ms_allocmap[t]);
space_map_destroy(&msp->ms_freemap[t]); space_map_destroy(msp->ms_freemap[t]);
kmem_free(msp->ms_allocmap[t], sizeof (*msp->ms_allocmap[t]));
kmem_free(msp->ms_freemap[t], sizeof (*msp->ms_freemap[t]));
} }
for (t = 0; t < TXG_DEFER_SIZE; t++) for (t = 0; t < TXG_DEFER_SIZE; t++) {
space_map_destroy(&msp->ms_defermap[t]); space_map_destroy(msp->ms_defermap[t]);
kmem_free(msp->ms_defermap[t], sizeof (*msp->ms_defermap[t]));
}
ASSERT0(msp->ms_deferspace); ASSERT0(msp->ms_deferspace);
@ -809,7 +823,7 @@ static uint64_t
metaslab_weight(metaslab_t *msp) metaslab_weight(metaslab_t *msp)
{ {
metaslab_group_t *mg = msp->ms_group; metaslab_group_t *mg = msp->ms_group;
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_obj_t *smo = &msp->ms_smo; space_map_obj_t *smo = &msp->ms_smo;
vdev_t *vd = mg->mg_vd; vdev_t *vd = mg->mg_vd;
uint64_t weight, space; uint64_t weight, space;
@ -869,7 +883,7 @@ metaslab_prefetch(metaslab_group_t *mg)
* Prefetch the next potential metaslabs * Prefetch the next potential metaslabs
*/ */
for (msp = avl_first(t), m = 0; msp; msp = AVL_NEXT(t, msp), m++) { for (msp = avl_first(t), m = 0; msp; msp = AVL_NEXT(t, msp), m++) {
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_obj_t *smo = &msp->ms_smo; space_map_obj_t *smo = &msp->ms_smo;
/* If we have reached our prefetch limit then we're done */ /* If we have reached our prefetch limit then we're done */
@ -890,7 +904,7 @@ static int
metaslab_activate(metaslab_t *msp, uint64_t activation_weight) metaslab_activate(metaslab_t *msp, uint64_t activation_weight)
{ {
metaslab_group_t *mg = msp->ms_group; metaslab_group_t *mg = msp->ms_group;
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_ops_t *sm_ops = msp->ms_group->mg_class->mc_ops; space_map_ops_t *sm_ops = msp->ms_group->mg_class->mc_ops;
int t; int t;
@ -908,7 +922,7 @@ metaslab_activate(metaslab_t *msp, uint64_t activation_weight)
return (error); return (error);
} }
for (t = 0; t < TXG_DEFER_SIZE; t++) for (t = 0; t < TXG_DEFER_SIZE; t++)
space_map_walk(&msp->ms_defermap[t], space_map_walk(msp->ms_defermap[t],
space_map_claim, sm); space_map_claim, sm);
} }
@ -939,11 +953,158 @@ metaslab_passivate(metaslab_t *msp, uint64_t size)
* this metaslab again. In that case, it had better be empty, * this metaslab again. In that case, it had better be empty,
* or we would be leaving space on the table. * or we would be leaving space on the table.
*/ */
ASSERT(size >= SPA_MINBLOCKSIZE || msp->ms_map.sm_space == 0); ASSERT(size >= SPA_MINBLOCKSIZE || msp->ms_map->sm_space == 0);
metaslab_group_sort(msp->ms_group, msp, MIN(msp->ms_weight, size)); metaslab_group_sort(msp->ms_group, msp, MIN(msp->ms_weight, size));
ASSERT((msp->ms_weight & METASLAB_ACTIVE_MASK) == 0); ASSERT((msp->ms_weight & METASLAB_ACTIVE_MASK) == 0);
} }
/*
* Determine if the in-core space map representation can be condensed on-disk.
* We would like to use the following criteria to make our decision:
*
* 1. The size of the space map object should not dramatically increase as a
* result of writing out our in-core free map.
*
* 2. The minimal on-disk space map representation is zfs_condense_pct/100
* times the size than the in-core representation (i.e. zfs_condense_pct = 110
* and in-core = 1MB, minimal = 1.1.MB).
*
* Checking the first condition is tricky since we don't want to walk
* the entire AVL tree calculating the estimated on-disk size. Instead we
* use the size-ordered AVL tree in the space map and calculate the
* size required for the largest segment in our in-core free map. If the
* size required to represent that segment on disk is larger than the space
* map object then we avoid condensing this map.
*
* To determine the second criterion we use a best-case estimate and assume
* each segment can be represented on-disk as a single 64-bit entry. We refer
* to this best-case estimate as the space map's minimal form.
*/
static boolean_t
metaslab_should_condense(metaslab_t *msp)
{
space_map_t *sm = msp->ms_map;
space_map_obj_t *smo = &msp->ms_smo_syncing;
space_seg_t *ss;
uint64_t size, entries, segsz;
ASSERT(MUTEX_HELD(&msp->ms_lock));
ASSERT(sm->sm_loaded);
/*
* Use the sm_pp_root AVL tree, which is ordered by size, to obtain
* the largest segment in the in-core free map. If the tree is
* empty then we should condense the map.
*/
ss = avl_last(sm->sm_pp_root);
if (ss == NULL)
return (B_TRUE);
/*
* Calculate the number of 64-bit entries this segment would
* require when written to disk. If this single segment would be
* larger on-disk than the entire current on-disk structure, then
* clearly condensing will increase the on-disk structure size.
*/
size = (ss->ss_end - ss->ss_start) >> sm->sm_shift;
entries = size / (MIN(size, SM_RUN_MAX));
segsz = entries * sizeof (uint64_t);
return (segsz <= smo->smo_objsize &&
smo->smo_objsize >= (zfs_condense_pct *
sizeof (uint64_t) * avl_numnodes(&sm->sm_root)) / 100);
}
/*
* Condense the on-disk space map representation to its minimized form.
* The minimized form consists of a small number of allocations followed by
* the in-core free map.
*/
static void
metaslab_condense(metaslab_t *msp, uint64_t txg, dmu_tx_t *tx)
{
spa_t *spa = msp->ms_group->mg_vd->vdev_spa;
space_map_t *freemap = msp->ms_freemap[txg & TXG_MASK];
space_map_t condense_map;
space_map_t *sm = msp->ms_map;
objset_t *mos = spa_meta_objset(spa);
space_map_obj_t *smo = &msp->ms_smo_syncing;
int t;
ASSERT(MUTEX_HELD(&msp->ms_lock));
ASSERT3U(spa_sync_pass(spa), ==, 1);
ASSERT(sm->sm_loaded);
spa_dbgmsg(spa, "condensing: txg %llu, msp[%llu] %p, "
"smo size %llu, segments %lu", txg,
(msp->ms_map->sm_start / msp->ms_map->sm_size), msp,
smo->smo_objsize, avl_numnodes(&sm->sm_root));
/*
* Create an map that is a 100% allocated map. We remove segments
* that have been freed in this txg, any deferred frees that exist,
* and any allocation in the future. Removing segments should be
* a relatively inexpensive operation since we expect these maps to
* a small number of nodes.
*/
space_map_create(&condense_map, sm->sm_start, sm->sm_size,
sm->sm_shift, sm->sm_lock);
space_map_add(&condense_map, condense_map.sm_start,
condense_map.sm_size);
/*
* Remove what's been freed in this txg from the condense_map.
* Since we're in sync_pass 1, we know that all the frees from
* this txg are in the freemap.
*/
space_map_walk(freemap, space_map_remove, &condense_map);
for (t = 0; t < TXG_DEFER_SIZE; t++)
space_map_walk(msp->ms_defermap[t],
space_map_remove, &condense_map);
for (t = 1; t < TXG_CONCURRENT_STATES; t++)
space_map_walk(msp->ms_allocmap[(txg + t) & TXG_MASK],
space_map_remove, &condense_map);
/*
* We're about to drop the metaslab's lock thus allowing
* other consumers to change it's content. Set the
* space_map's sm_condensing flag to ensure that
* allocations on this metaslab do not occur while we're
* in the middle of committing it to disk. This is only critical
* for the ms_map as all other space_maps use per txg
* views of their content.
*/
sm->sm_condensing = B_TRUE;
mutex_exit(&msp->ms_lock);
space_map_truncate(smo, mos, tx);
mutex_enter(&msp->ms_lock);
/*
* While we would ideally like to create a space_map representation
* that consists only of allocation records, doing so can be
* prohibitively expensive because the in-core free map can be
* large, and therefore computationally expensive to subtract
* from the condense_map. Instead we sync out two maps, a cheap
* allocation only map followed by the in-core free map. While not
* optimal, this is typically close to optimal, and much cheaper to
* compute.
*/
space_map_sync(&condense_map, SM_ALLOC, smo, mos, tx);
space_map_vacate(&condense_map, NULL, NULL);
space_map_destroy(&condense_map);
space_map_sync(sm, SM_FREE, smo, mos, tx);
sm->sm_condensing = B_FALSE;
spa_dbgmsg(spa, "condensed: txg %llu, msp[%llu] %p, "
"smo size %llu", txg,
(msp->ms_map->sm_start / msp->ms_map->sm_size), msp,
smo->smo_objsize);
}
/* /*
* Write a metaslab to disk in the context of the specified transaction group. * Write a metaslab to disk in the context of the specified transaction group.
*/ */
@ -953,18 +1114,29 @@ metaslab_sync(metaslab_t *msp, uint64_t txg)
vdev_t *vd = msp->ms_group->mg_vd; vdev_t *vd = msp->ms_group->mg_vd;
spa_t *spa = vd->vdev_spa; spa_t *spa = vd->vdev_spa;
objset_t *mos = spa_meta_objset(spa); objset_t *mos = spa_meta_objset(spa);
space_map_t *allocmap = &msp->ms_allocmap[txg & TXG_MASK]; space_map_t *allocmap = msp->ms_allocmap[txg & TXG_MASK];
space_map_t *freemap = &msp->ms_freemap[txg & TXG_MASK]; space_map_t **freemap = &msp->ms_freemap[txg & TXG_MASK];
space_map_t *freed_map = &msp->ms_freemap[TXG_CLEAN(txg) & TXG_MASK]; space_map_t **freed_map = &msp->ms_freemap[TXG_CLEAN(txg) & TXG_MASK];
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_obj_t *smo = &msp->ms_smo_syncing; space_map_obj_t *smo = &msp->ms_smo_syncing;
dmu_buf_t *db; dmu_buf_t *db;
dmu_tx_t *tx; dmu_tx_t *tx;
int t;
ASSERT(!vd->vdev_ishole); ASSERT(!vd->vdev_ishole);
if (allocmap->sm_space == 0 && freemap->sm_space == 0) /*
* This metaslab has just been added so there's no work to do now.
*/
if (*freemap == NULL) {
ASSERT3P(allocmap, ==, NULL);
return;
}
ASSERT3P(allocmap, !=, NULL);
ASSERT3P(*freemap, !=, NULL);
ASSERT3P(*freed_map, !=, NULL);
if (allocmap->sm_space == 0 && (*freemap)->sm_space == 0)
return; return;
/* /*
@ -992,49 +1164,36 @@ metaslab_sync(metaslab_t *msp, uint64_t txg)
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
space_map_walk(freemap, space_map_add, freed_map); if (sm->sm_loaded && spa_sync_pass(spa) == 1 &&
metaslab_should_condense(msp)) {
if (sm->sm_loaded && spa_sync_pass(spa) == 1 && smo->smo_objsize >= metaslab_condense(msp, txg, tx);
2 * sizeof (uint64_t) * avl_numnodes(&sm->sm_root)) { } else {
/* space_map_sync(allocmap, SM_ALLOC, smo, mos, tx);
* The in-core space map representation is twice as compact space_map_sync(*freemap, SM_FREE, smo, mos, tx);
* as the on-disk one, so it's time to condense the latter
* by generating a pure allocmap from first principles.
*
* This metaslab is 100% allocated,
* minus the content of the in-core map (sm),
* minus what's been freed this txg (freed_map),
* minus deferred frees (ms_defermap[]),
* minus allocations from txgs in the future
* (because they haven't been committed yet).
*/
space_map_vacate(allocmap, NULL, NULL);
space_map_vacate(freemap, NULL, NULL);
space_map_add(allocmap, allocmap->sm_start, allocmap->sm_size);
space_map_walk(sm, space_map_remove, allocmap);
space_map_walk(freed_map, space_map_remove, allocmap);
for (t = 0; t < TXG_DEFER_SIZE; t++)
space_map_walk(&msp->ms_defermap[t],
space_map_remove, allocmap);
for (t = 1; t < TXG_CONCURRENT_STATES; t++)
space_map_walk(&msp->ms_allocmap[(txg + t) & TXG_MASK],
space_map_remove, allocmap);
mutex_exit(&msp->ms_lock);
space_map_truncate(smo, mos, tx);
mutex_enter(&msp->ms_lock);
} }
space_map_sync(allocmap, SM_ALLOC, smo, mos, tx); space_map_vacate(allocmap, NULL, NULL);
space_map_sync(freemap, SM_FREE, smo, mos, tx);
/*
* For sync pass 1, we avoid walking the entire space map and
* instead will just swap the pointers for freemap and
* freed_map. We can safely do this since the freed_map is
* guaranteed to be empty on the initial pass.
*/
if (spa_sync_pass(spa) == 1) {
ASSERT0((*freed_map)->sm_space);
ASSERT0(avl_numnodes(&(*freed_map)->sm_root));
space_map_swap(freemap, freed_map);
} else {
space_map_vacate(*freemap, space_map_add, *freed_map);
}
ASSERT0(msp->ms_allocmap[txg & TXG_MASK]->sm_space);
ASSERT0(msp->ms_freemap[txg & TXG_MASK]->sm_space);
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
VERIFY(0 == dmu_bonus_hold(mos, smo->smo_object, FTAG, &db)); VERIFY0(dmu_bonus_hold(mos, smo->smo_object, FTAG, &db));
dmu_buf_will_dirty(db, tx); dmu_buf_will_dirty(db, tx);
ASSERT3U(db->db_size, >=, sizeof (*smo)); ASSERT3U(db->db_size, >=, sizeof (*smo));
bcopy(smo, db->db_data, sizeof (*smo)); bcopy(smo, db->db_data, sizeof (*smo));
@ -1052,9 +1211,9 @@ metaslab_sync_done(metaslab_t *msp, uint64_t txg)
{ {
space_map_obj_t *smo = &msp->ms_smo; space_map_obj_t *smo = &msp->ms_smo;
space_map_obj_t *smosync = &msp->ms_smo_syncing; space_map_obj_t *smosync = &msp->ms_smo_syncing;
space_map_t *sm = &msp->ms_map; space_map_t *sm = msp->ms_map;
space_map_t *freed_map = &msp->ms_freemap[TXG_CLEAN(txg) & TXG_MASK]; space_map_t *freed_map = msp->ms_freemap[TXG_CLEAN(txg) & TXG_MASK];
space_map_t *defer_map = &msp->ms_defermap[txg % TXG_DEFER_SIZE]; space_map_t *defer_map = msp->ms_defermap[txg % TXG_DEFER_SIZE];
metaslab_group_t *mg = msp->ms_group; metaslab_group_t *mg = msp->ms_group;
vdev_t *vd = mg->mg_vd; vdev_t *vd = mg->mg_vd;
int64_t alloc_delta, defer_delta; int64_t alloc_delta, defer_delta;
@ -1066,19 +1225,30 @@ metaslab_sync_done(metaslab_t *msp, uint64_t txg)
/* /*
* If this metaslab is just becoming available, initialize its * If this metaslab is just becoming available, initialize its
* allocmaps and freemaps and add its capacity to the vdev. * allocmaps, freemaps, and defermap and add its capacity to the vdev.
*/ */
if (freed_map->sm_size == 0) { if (freed_map == NULL) {
ASSERT(defer_map == NULL);
for (t = 0; t < TXG_SIZE; t++) { for (t = 0; t < TXG_SIZE; t++) {
space_map_create(&msp->ms_allocmap[t], sm->sm_start, msp->ms_allocmap[t] = kmem_zalloc(sizeof (space_map_t),
KM_PUSHPAGE);
space_map_create(msp->ms_allocmap[t], sm->sm_start,
sm->sm_size, sm->sm_shift, sm->sm_lock); sm->sm_size, sm->sm_shift, sm->sm_lock);
space_map_create(&msp->ms_freemap[t], sm->sm_start, msp->ms_freemap[t] = kmem_zalloc(sizeof (space_map_t),
KM_PUSHPAGE);
space_map_create(msp->ms_freemap[t], sm->sm_start,
sm->sm_size, sm->sm_shift, sm->sm_lock); sm->sm_size, sm->sm_shift, sm->sm_lock);
} }
for (t = 0; t < TXG_DEFER_SIZE; t++) for (t = 0; t < TXG_DEFER_SIZE; t++) {
space_map_create(&msp->ms_defermap[t], sm->sm_start, msp->ms_defermap[t] = kmem_zalloc(sizeof (space_map_t),
KM_PUSHPAGE);
space_map_create(msp->ms_defermap[t], sm->sm_start,
sm->sm_size, sm->sm_shift, sm->sm_lock); sm->sm_size, sm->sm_shift, sm->sm_lock);
}
freed_map = msp->ms_freemap[TXG_CLEAN(txg) & TXG_MASK];
defer_map = msp->ms_defermap[txg % TXG_DEFER_SIZE];
vdev_space_update(vd, 0, 0, sm->sm_size); vdev_space_update(vd, 0, 0, sm->sm_size);
} }
@ -1088,8 +1258,8 @@ metaslab_sync_done(metaslab_t *msp, uint64_t txg)
vdev_space_update(vd, alloc_delta + defer_delta, defer_delta, 0); vdev_space_update(vd, alloc_delta + defer_delta, defer_delta, 0);
ASSERT(msp->ms_allocmap[txg & TXG_MASK].sm_space == 0); ASSERT(msp->ms_allocmap[txg & TXG_MASK]->sm_space == 0);
ASSERT(msp->ms_freemap[txg & TXG_MASK].sm_space == 0); ASSERT(msp->ms_freemap[txg & TXG_MASK]->sm_space == 0);
/* /*
* If there's a space_map_load() in progress, wait for it to complete * If there's a space_map_load() in progress, wait for it to complete
@ -1123,7 +1293,7 @@ metaslab_sync_done(metaslab_t *msp, uint64_t txg)
int evictable = 1; int evictable = 1;
for (t = 1; t < TXG_CONCURRENT_STATES; t++) for (t = 1; t < TXG_CONCURRENT_STATES; t++)
if (msp->ms_allocmap[(txg + t) & TXG_MASK].sm_space) if (msp->ms_allocmap[(txg + t) & TXG_MASK]->sm_space)
evictable = 0; evictable = 0;
if (evictable && !metaslab_debug) if (evictable && !metaslab_debug)
@ -1149,7 +1319,7 @@ metaslab_sync_reassess(metaslab_group_t *mg)
for (m = 0; m < vd->vdev_ms_count; m++) { for (m = 0; m < vd->vdev_ms_count; m++) {
metaslab_t *msp = vd->vdev_ms[m]; metaslab_t *msp = vd->vdev_ms[m];
if (msp->ms_map.sm_start > mg->mg_bonus_area) if (msp->ms_map->sm_start > mg->mg_bonus_area)
break; break;
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
@ -1170,7 +1340,7 @@ metaslab_distance(metaslab_t *msp, dva_t *dva)
{ {
uint64_t ms_shift = msp->ms_group->mg_vd->vdev_ms_shift; uint64_t ms_shift = msp->ms_group->mg_vd->vdev_ms_shift;
uint64_t offset = DVA_GET_OFFSET(dva) >> ms_shift; uint64_t offset = DVA_GET_OFFSET(dva) >> ms_shift;
uint64_t start = msp->ms_map.sm_start >> ms_shift; uint64_t start = msp->ms_map->sm_start >> ms_shift;
if (msp->ms_group->mg_vd->vdev_id != DVA_GET_VDEV(dva)) if (msp->ms_group->mg_vd->vdev_id != DVA_GET_VDEV(dva))
return (1ULL << 63); return (1ULL << 63);
@ -1257,6 +1427,16 @@ metaslab_group_alloc(metaslab_group_t *mg, uint64_t psize, uint64_t asize,
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
/*
* If this metaslab is currently condensing then pick again as
* we can't manipulate this metaslab until it's committed
* to disk.
*/
if (msp->ms_map->sm_condensing) {
mutex_exit(&msp->ms_lock);
continue;
}
/* /*
* Ensure that the metaslab we have selected is still * Ensure that the metaslab we have selected is still
* capable of handling our request. It's possible that * capable of handling our request. It's possible that
@ -1283,20 +1463,20 @@ metaslab_group_alloc(metaslab_group_t *mg, uint64_t psize, uint64_t asize,
continue; continue;
} }
if ((offset = space_map_alloc(&msp->ms_map, asize)) != -1ULL) if ((offset = space_map_alloc(msp->ms_map, asize)) != -1ULL)
break; break;
atomic_inc_64(&mg->mg_alloc_failures); atomic_inc_64(&mg->mg_alloc_failures);
metaslab_passivate(msp, space_map_maxsize(&msp->ms_map)); metaslab_passivate(msp, space_map_maxsize(msp->ms_map));
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
} }
if (msp->ms_allocmap[txg & TXG_MASK].sm_space == 0) if (msp->ms_allocmap[txg & TXG_MASK]->sm_space == 0)
vdev_dirty(mg->mg_vd, VDD_METASLAB, msp, txg); vdev_dirty(mg->mg_vd, VDD_METASLAB, msp, txg);
space_map_add(&msp->ms_allocmap[txg & TXG_MASK], offset, asize); space_map_add(msp->ms_allocmap[txg & TXG_MASK], offset, asize);
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
@ -1546,13 +1726,13 @@ metaslab_free_dva(spa_t *spa, const dva_t *dva, uint64_t txg, boolean_t now)
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
if (now) { if (now) {
space_map_remove(&msp->ms_allocmap[txg & TXG_MASK], space_map_remove(msp->ms_allocmap[txg & TXG_MASK],
offset, size); offset, size);
space_map_free(&msp->ms_map, offset, size); space_map_free(msp->ms_map, offset, size);
} else { } else {
if (msp->ms_freemap[txg & TXG_MASK].sm_space == 0) if (msp->ms_freemap[txg & TXG_MASK]->sm_space == 0)
vdev_dirty(vd, VDD_METASLAB, msp, txg); vdev_dirty(vd, VDD_METASLAB, msp, txg);
space_map_add(&msp->ms_freemap[txg & TXG_MASK], offset, size); space_map_add(msp->ms_freemap[txg & TXG_MASK], offset, size);
} }
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);
@ -1587,10 +1767,10 @@ metaslab_claim_dva(spa_t *spa, const dva_t *dva, uint64_t txg)
mutex_enter(&msp->ms_lock); mutex_enter(&msp->ms_lock);
if ((txg != 0 && spa_writeable(spa)) || !msp->ms_map.sm_loaded) if ((txg != 0 && spa_writeable(spa)) || !msp->ms_map->sm_loaded)
error = metaslab_activate(msp, METASLAB_WEIGHT_SECONDARY); error = metaslab_activate(msp, METASLAB_WEIGHT_SECONDARY);
if (error == 0 && !space_map_contains(&msp->ms_map, offset, size)) if (error == 0 && !space_map_contains(msp->ms_map, offset, size))
error = ENOENT; error = ENOENT;
if (error || txg == 0) { /* txg == 0 indicates dry run */ if (error || txg == 0) { /* txg == 0 indicates dry run */
@ -1598,12 +1778,12 @@ metaslab_claim_dva(spa_t *spa, const dva_t *dva, uint64_t txg)
return (error); return (error);
} }
space_map_claim(&msp->ms_map, offset, size); space_map_claim(msp->ms_map, offset, size);
if (spa_writeable(spa)) { /* don't dirty if we're zdb(1M) */ if (spa_writeable(spa)) { /* don't dirty if we're zdb(1M) */
if (msp->ms_allocmap[txg & TXG_MASK].sm_space == 0) if (msp->ms_allocmap[txg & TXG_MASK]->sm_space == 0)
vdev_dirty(vd, VDD_METASLAB, msp, txg); vdev_dirty(vd, VDD_METASLAB, msp, txg);
space_map_add(&msp->ms_allocmap[txg & TXG_MASK], offset, size); space_map_add(msp->ms_allocmap[txg & TXG_MASK], offset, size);
} }
mutex_exit(&msp->ms_lock); mutex_exit(&msp->ms_lock);

View File

@ -107,6 +107,7 @@ space_map_add(space_map_t *sm, uint64_t start, uint64_t size)
int merge_before, merge_after; int merge_before, merge_after;
ASSERT(MUTEX_HELD(sm->sm_lock)); ASSERT(MUTEX_HELD(sm->sm_lock));
VERIFY(!sm->sm_condensing);
VERIFY(size != 0); VERIFY(size != 0);
VERIFY3U(start, >=, sm->sm_start); VERIFY3U(start, >=, sm->sm_start);
VERIFY3U(end, <=, sm->sm_start + sm->sm_size); VERIFY3U(end, <=, sm->sm_start + sm->sm_size);
@ -175,6 +176,7 @@ space_map_remove(space_map_t *sm, uint64_t start, uint64_t size)
int left_over, right_over; int left_over, right_over;
ASSERT(MUTEX_HELD(sm->sm_lock)); ASSERT(MUTEX_HELD(sm->sm_lock));
VERIFY(!sm->sm_condensing);
VERIFY(size != 0); VERIFY(size != 0);
VERIFY(P2PHASE(start, 1ULL << sm->sm_shift) == 0); VERIFY(P2PHASE(start, 1ULL << sm->sm_shift) == 0);
VERIFY(P2PHASE(size, 1ULL << sm->sm_shift) == 0); VERIFY(P2PHASE(size, 1ULL << sm->sm_shift) == 0);
@ -243,6 +245,20 @@ space_map_contains(space_map_t *sm, uint64_t start, uint64_t size)
return (ss != NULL && ss->ss_start <= start && ss->ss_end >= end); return (ss != NULL && ss->ss_start <= start && ss->ss_end >= end);
} }
void
space_map_swap(space_map_t **msrc, space_map_t **mdst)
{
space_map_t *sm;
ASSERT(MUTEX_HELD((*msrc)->sm_lock));
ASSERT0((*mdst)->sm_space);
ASSERT0(avl_numnodes(&(*mdst)->sm_root));
sm = *msrc;
*msrc = *mdst;
*mdst = sm;
}
void void
space_map_vacate(space_map_t *sm, space_map_func_t *func, space_map_t *mdest) space_map_vacate(space_map_t *sm, space_map_func_t *func, space_map_t *mdest)
{ {
@ -424,9 +440,9 @@ space_map_sync(space_map_t *sm, uint8_t maptype,
space_map_obj_t *smo, objset_t *os, dmu_tx_t *tx) space_map_obj_t *smo, objset_t *os, dmu_tx_t *tx)
{ {
spa_t *spa = dmu_objset_spa(os); spa_t *spa = dmu_objset_spa(os);
void *cookie = NULL; avl_tree_t *t = &sm->sm_root;
space_seg_t *ss; space_seg_t *ss;
uint64_t bufsize, start, size, run_len, delta, sm_space; uint64_t bufsize, start, size, run_len, total, sm_space, nodes;
uint64_t *entry, *entry_map, *entry_map_end; uint64_t *entry, *entry_map, *entry_map_end;
ASSERT(MUTEX_HELD(sm->sm_lock)); ASSERT(MUTEX_HELD(sm->sm_lock));
@ -455,13 +471,14 @@ space_map_sync(space_map_t *sm, uint8_t maptype,
SM_DEBUG_SYNCPASS_ENCODE(spa_sync_pass(spa)) | SM_DEBUG_SYNCPASS_ENCODE(spa_sync_pass(spa)) |
SM_DEBUG_TXG_ENCODE(dmu_tx_get_txg(tx)); SM_DEBUG_TXG_ENCODE(dmu_tx_get_txg(tx));
delta = 0; total = 0;
nodes = avl_numnodes(&sm->sm_root);
sm_space = sm->sm_space; sm_space = sm->sm_space;
while ((ss = avl_destroy_nodes(&sm->sm_root, &cookie)) != NULL) { for (ss = avl_first(t); ss != NULL; ss = AVL_NEXT(t, ss)) {
size = ss->ss_end - ss->ss_start; size = ss->ss_end - ss->ss_start;
start = (ss->ss_start - sm->sm_start) >> sm->sm_shift; start = (ss->ss_start - sm->sm_start) >> sm->sm_shift;
delta += size; total += size;
size >>= sm->sm_shift; size >>= sm->sm_shift;
while (size) { while (size) {
@ -483,7 +500,6 @@ space_map_sync(space_map_t *sm, uint8_t maptype,
start += run_len; start += run_len;
size -= run_len; size -= run_len;
} }
kmem_cache_free(space_seg_cache, ss);
} }
if (entry != entry_map) { if (entry != entry_map) {
@ -499,12 +515,11 @@ space_map_sync(space_map_t *sm, uint8_t maptype,
* Ensure that the space_map's accounting wasn't changed * Ensure that the space_map's accounting wasn't changed
* while we were in the middle of writing it out. * while we were in the middle of writing it out.
*/ */
VERIFY3U(nodes, ==, avl_numnodes(&sm->sm_root));
VERIFY3U(sm->sm_space, ==, sm_space); VERIFY3U(sm->sm_space, ==, sm_space);
VERIFY3U(sm->sm_space, ==, total);
zio_buf_free(entry_map, bufsize); zio_buf_free(entry_map, bufsize);
sm->sm_space -= delta;
VERIFY0(sm->sm_space);
} }
void void

View File

@ -1855,6 +1855,7 @@ vdev_dtl_sync(vdev_t *vd, uint64_t txg)
space_map_truncate(smo, mos, tx); space_map_truncate(smo, mos, tx);
space_map_sync(&smsync, SM_ALLOC, smo, mos, tx); space_map_sync(&smsync, SM_ALLOC, smo, mos, tx);
space_map_vacate(&smsync, NULL, NULL);
space_map_destroy(&smsync); space_map_destroy(&smsync);