mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-27 11:29:36 +03:00
920dd524fb
Currently, ZIL blocks are spread over vdevs using hint block pointers managed by the ZIL commit code and passed to metaslab_alloc(). Spreading log blocks accross vdevs is important for performance: indeed, using mutliple disks in parallel decreases the ZIL commit latency, which is the main performance metric for synchronous writes. However, the current implementation suffers from the following issues: 1) It would be best if the ZIL module was not aware of such low-level details. They should be handled by the ZIO and metaslab modules; 2) Because the hint block pointer is managed per log, simultaneous commits from multiple logs might use the same vdevs at the same time, which is inefficient; 3) Because dmu_write() does not honor the block pointer hint, indirect writes are not spread. The naive solution of rotating the metaslab rotor each time a block is allocated for the ZIL or dmu_sync() doesn't work in practice because the first ZIL block to be written is actually allocated during the previous commit. Consequently, when metaslab_alloc() decides the vdev for this block, it will do so while a bunch of other allocations are happening at the same time (from dmu_sync() and other ZILs). This means the vdev for this block is chosen more or less at random. When the next commit happens, there is a high chance (especially when the number of blocks per commit is slightly less than the number of the disks) that one disk will have to write two blocks (with a potential seek) while other disks are sitting idle, which defeats spreading and increases the commit latency. This commit introduces a new concept in the metaslab allocator: fastwrites. Basically, each top-level vdev maintains a counter indicating the number of synchronous writes (from dmu_sync() and the ZIL) which have been allocated but not yet completed. When the metaslab is called with the FASTWRITE flag, it will choose the vdev with the least amount of pending synchronous writes. If there are multiple vdevs with the same value, the first matching vdev (starting from the rotor) is used. Once metaslab_alloc() has decided which vdev the block is allocated to, it updates the fastwrite counter for this vdev. The rationale goes like this: when an allocation is done with FASTWRITE, it "reserves" the vdev until the data is written. Until then, all future allocations will naturally avoid this vdev, even after a full rotation of the rotor. As a result, pending synchronous writes at a given point in time will be nicely spread over all vdevs. This contrasts with the previous algorithm, which is based on the implicit assumption that blocks are written instantaneously after they're allocated. metaslab_fastwrite_mark() and metaslab_fastwrite_unmark() are used to manually increase or decrease fastwrite counters, respectively. They should be used with caution, as there is no per-BP tracking of fastwrite information, so leaks and "double-unmarks" are possible. There is, however, an assert in the vdev teardown code which will fire if the fastwrite counters are not zero when the pool is exported or the vdev removed. Note that as stated above, marking is also done implictly by metaslab_alloc(). ZIO also got a new FASTWRITE flag; when it is used, ZIO will pass it to the metaslab when allocating (assuming ZIO does the allocation, which is only true in the case of dmu_sync). This flag will also trigger an unmark when zio_done() fires. A side-effect of the new algorithm is that when a ZIL stops being used, its last block can stay in the pending state (allocated but not yet written) for a long time, polluting the fastwrite counters. To avoid that, I've implemented a somewhat crude but working solution which unmarks these pending blocks in zil_sync(), thus guaranteeing that linguering fastwrites will get pruned at each sync event. The best performance improvements are observed with pools using a large number of top-level vdevs and heavy synchronous write workflows (especially indirect writes and concurrent writes from multiple ZILs). Real-life testing shows a 200% to 300% performance increase with indirect writes and various commit sizes. Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Issue #1013
93 lines
2.9 KiB
C
93 lines
2.9 KiB
C
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
/*
|
|
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
|
* Use is subject to license terms.
|
|
* Copyright (c) 2011 by Delphix. All rights reserved.
|
|
*/
|
|
|
|
#ifndef _SYS_METASLAB_IMPL_H
|
|
#define _SYS_METASLAB_IMPL_H
|
|
|
|
#include <sys/metaslab.h>
|
|
#include <sys/space_map.h>
|
|
#include <sys/vdev.h>
|
|
#include <sys/txg.h>
|
|
#include <sys/avl.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct metaslab_class {
|
|
spa_t *mc_spa;
|
|
metaslab_group_t *mc_rotor;
|
|
space_map_ops_t *mc_ops;
|
|
uint64_t mc_aliquot;
|
|
uint64_t mc_alloc; /* total allocated space */
|
|
uint64_t mc_deferred; /* total deferred frees */
|
|
uint64_t mc_space; /* total space (alloc + free) */
|
|
uint64_t mc_dspace; /* total deflated space */
|
|
kmutex_t mc_fastwrite_lock;
|
|
};
|
|
|
|
struct metaslab_group {
|
|
kmutex_t mg_lock;
|
|
avl_tree_t mg_metaslab_tree;
|
|
uint64_t mg_aliquot;
|
|
uint64_t mg_bonus_area;
|
|
uint64_t mg_alloc_failures;
|
|
int64_t mg_bias;
|
|
int64_t mg_activation_count;
|
|
metaslab_class_t *mg_class;
|
|
vdev_t *mg_vd;
|
|
metaslab_group_t *mg_prev;
|
|
metaslab_group_t *mg_next;
|
|
};
|
|
|
|
/*
|
|
* Each metaslab's free space is tracked in 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.
|
|
* 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.
|
|
*/
|
|
struct metaslab {
|
|
kmutex_t ms_lock; /* metaslab lock */
|
|
space_map_obj_t ms_smo; /* synced 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_freemap[TXG_SIZE]; /* freed this txg */
|
|
space_map_t ms_defermap[TXG_DEFER_SIZE]; /* deferred frees */
|
|
space_map_t ms_map; /* in-core free space map */
|
|
int64_t ms_deferspace; /* sum of ms_defermap[] space */
|
|
uint64_t ms_weight; /* weight vs. others in group */
|
|
metaslab_group_t *ms_group; /* metaslab group */
|
|
avl_node_t ms_group_node; /* node in metaslab group tree */
|
|
txg_node_t ms_txg_node; /* per-txg dirty metaslab links */
|
|
};
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* _SYS_METASLAB_IMPL_H */
|