Finally drop long disabled vdev cache.
It was a vdev level read cache, designed to aggregate many small reads by speculatively issuing bigger reads instead and caching the result. But since it has almost no idea about what is going on with exception of ZIO_FLAG_DONT_CACHE flag set by higher layers, it was found to make more harm than good, for which reason it was disabled for the past 12 years. These days we have much better instruments to enlarge the I/Os, such as speculative and prescient prefetches, I/O scheduler, I/O aggregation etc. Besides just the dead code removal this removes one extra mutex lock/unlock per write inside vdev_cache_write(), not otherwise disabled and trying to do some work. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Alexander Motin <mav@FreeBSD.org> Sponsored by: iXsystems, Inc. Closes #14953
This commit is contained in:
parent
6db4ed51d6
commit
70ea484e3e
@ -64,7 +64,6 @@ SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
|
||||
SECTION_PATHS = {'arc': 'arcstats',
|
||||
'dmu': 'dmu_tx',
|
||||
'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
|
||||
'vdev': 'vdev_cache_stats',
|
||||
'zfetch': 'zfetchstats',
|
||||
'zil': 'zil'}
|
||||
|
||||
@ -90,8 +89,6 @@ if sys.platform.startswith('freebsd'):
|
||||
# Requires py36-sysctl on FreeBSD
|
||||
import sysctl
|
||||
|
||||
VDEV_CACHE_SIZE = 'vdev.cache_size'
|
||||
|
||||
def is_value(ctl):
|
||||
return ctl.type != sysctl.CTLTYPE_NODE
|
||||
|
||||
@ -135,8 +132,6 @@ elif sys.platform.startswith('linux'):
|
||||
SPL_PATH = '/sys/module/spl/parameters'
|
||||
TUNABLES_PATH = '/sys/module/zfs/parameters'
|
||||
|
||||
VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
|
||||
|
||||
def load_kstats(section):
|
||||
path = os.path.join(KSTAT_PATH, section)
|
||||
with open(path) as f:
|
||||
@ -952,35 +947,6 @@ def section_tunables(*_):
|
||||
print()
|
||||
|
||||
|
||||
def section_vdev(kstats_dict):
|
||||
"""Collect information on VDEV caches"""
|
||||
|
||||
# Currently [Nov 2017] the VDEV cache is disabled, because it is actually
|
||||
# harmful. When this is the case, we just skip the whole entry. See
|
||||
# https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
|
||||
# for details
|
||||
tunables = get_vdev_params()
|
||||
|
||||
if tunables[VDEV_CACHE_SIZE] == '0':
|
||||
print('VDEV cache disabled, skipping section\n')
|
||||
return
|
||||
|
||||
vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
|
||||
|
||||
vdev_cache_total = int(vdev_stats['hits']) +\
|
||||
int(vdev_stats['misses']) +\
|
||||
int(vdev_stats['delegations'])
|
||||
|
||||
prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
|
||||
prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
|
||||
f_hits(vdev_stats['hits']))
|
||||
prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
|
||||
f_hits(vdev_stats['misses']))
|
||||
prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
|
||||
f_hits(vdev_stats['delegations']))
|
||||
print()
|
||||
|
||||
|
||||
def section_zil(kstats_dict):
|
||||
"""Collect information on the ZFS Intent Log. Some of the information
|
||||
taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
|
||||
@ -1008,7 +974,6 @@ section_calls = {'arc': section_arc,
|
||||
'l2arc': section_l2arc,
|
||||
'spl': section_spl,
|
||||
'tunables': section_tunables,
|
||||
'vdev': section_vdev,
|
||||
'zil': section_zil}
|
||||
|
||||
|
||||
|
@ -8546,9 +8546,9 @@ zdb_read_block(char *thing, spa_t *spa)
|
||||
*/
|
||||
zio_nowait(zio_vdev_child_io(zio, bp, vd, offset, pabd,
|
||||
psize, ZIO_TYPE_READ, ZIO_PRIORITY_SYNC_READ,
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_PROPAGATE |
|
||||
ZIO_FLAG_DONT_RETRY | ZIO_FLAG_CANFAIL | ZIO_FLAG_RAW |
|
||||
ZIO_FLAG_OPTIONAL, NULL, NULL));
|
||||
ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY |
|
||||
ZIO_FLAG_CANFAIL | ZIO_FLAG_RAW | ZIO_FLAG_OPTIONAL,
|
||||
NULL, NULL));
|
||||
}
|
||||
|
||||
error = zio_wait(zio);
|
||||
@ -8642,7 +8642,6 @@ zdb_read_block(char *thing, spa_t *spa)
|
||||
zio_nowait(zio_vdev_child_io(czio, bp, vd,
|
||||
offset, pabd, psize, ZIO_TYPE_READ,
|
||||
ZIO_PRIORITY_SYNC_READ,
|
||||
ZIO_FLAG_DONT_CACHE |
|
||||
ZIO_FLAG_DONT_PROPAGATE |
|
||||
ZIO_FLAG_DONT_RETRY |
|
||||
ZIO_FLAG_CANFAIL | ZIO_FLAG_RAW |
|
||||
|
@ -68,7 +68,6 @@ enum scope_prefix_types {
|
||||
zfs_trim,
|
||||
zfs_txg,
|
||||
zfs_vdev,
|
||||
zfs_vdev_cache,
|
||||
zfs_vdev_file,
|
||||
zfs_vdev_mirror,
|
||||
zfs_vnops,
|
||||
|
@ -1174,10 +1174,6 @@ extern void zep_to_zb(uint64_t dataset, zbookmark_err_phys_t *zep,
|
||||
zbookmark_phys_t *zb);
|
||||
extern void name_to_errphys(char *buf, zbookmark_err_phys_t *zep);
|
||||
|
||||
/* vdev cache */
|
||||
extern void vdev_cache_stat_init(void);
|
||||
extern void vdev_cache_stat_fini(void);
|
||||
|
||||
/* vdev mirror */
|
||||
extern void vdev_mirror_stat_init(void);
|
||||
extern void vdev_mirror_stat_fini(void);
|
||||
|
@ -158,12 +158,6 @@ extern boolean_t vdev_allocatable(vdev_t *vd);
|
||||
extern boolean_t vdev_accessible(vdev_t *vd, zio_t *zio);
|
||||
extern boolean_t vdev_is_spacemap_addressable(vdev_t *vd);
|
||||
|
||||
extern void vdev_cache_init(vdev_t *vd);
|
||||
extern void vdev_cache_fini(vdev_t *vd);
|
||||
extern boolean_t vdev_cache_read(zio_t *zio);
|
||||
extern void vdev_cache_write(zio_t *zio);
|
||||
extern void vdev_cache_purge(vdev_t *vd);
|
||||
|
||||
extern void vdev_queue_init(vdev_t *vd);
|
||||
extern void vdev_queue_fini(vdev_t *vd);
|
||||
extern zio_t *vdev_queue_io(zio_t *zio);
|
||||
|
@ -57,8 +57,6 @@ extern "C" {
|
||||
* Forward declarations that lots of things need.
|
||||
*/
|
||||
typedef struct vdev_queue vdev_queue_t;
|
||||
typedef struct vdev_cache vdev_cache_t;
|
||||
typedef struct vdev_cache_entry vdev_cache_entry_t;
|
||||
struct abd;
|
||||
|
||||
extern uint_t zfs_vdev_queue_depth_pct;
|
||||
@ -132,23 +130,6 @@ typedef const struct vdev_ops {
|
||||
/*
|
||||
* Virtual device properties
|
||||
*/
|
||||
struct vdev_cache_entry {
|
||||
struct abd *ve_abd;
|
||||
uint64_t ve_offset;
|
||||
clock_t ve_lastused;
|
||||
avl_node_t ve_offset_node;
|
||||
avl_node_t ve_lastused_node;
|
||||
uint32_t ve_hits;
|
||||
uint16_t ve_missed_update;
|
||||
zio_t *ve_fill_io;
|
||||
};
|
||||
|
||||
struct vdev_cache {
|
||||
avl_tree_t vc_offset_tree;
|
||||
avl_tree_t vc_lastused_tree;
|
||||
kmutex_t vc_lock;
|
||||
};
|
||||
|
||||
typedef struct vdev_queue_class {
|
||||
uint32_t vqc_active;
|
||||
|
||||
@ -443,7 +424,6 @@ struct vdev {
|
||||
boolean_t vdev_resilver_deferred; /* resilver deferred */
|
||||
boolean_t vdev_kobj_flag; /* kobj event record */
|
||||
vdev_queue_t vdev_queue; /* I/O deadline schedule queue */
|
||||
vdev_cache_t vdev_cache; /* physical block cache */
|
||||
spa_aux_vdev_t *vdev_aux; /* for l2cache and spares vdevs */
|
||||
zio_t *vdev_probe_zio; /* root of current probe */
|
||||
vdev_aux_t vdev_label_aux; /* on-disk aux state */
|
||||
|
@ -190,7 +190,6 @@ typedef uint64_t zio_flag_t;
|
||||
#define ZIO_FLAG_SPECULATIVE (1ULL << 8)
|
||||
#define ZIO_FLAG_CONFIG_WRITER (1ULL << 9)
|
||||
#define ZIO_FLAG_DONT_RETRY (1ULL << 10)
|
||||
#define ZIO_FLAG_DONT_CACHE (1ULL << 11)
|
||||
#define ZIO_FLAG_NODATA (1ULL << 12)
|
||||
#define ZIO_FLAG_INDUCE_DAMAGE (1ULL << 13)
|
||||
#define ZIO_FLAG_IO_ALLOCATING (1ULL << 14)
|
||||
|
@ -135,7 +135,6 @@ nodist_libzpool_la_SOURCES = \
|
||||
module/zfs/uberblock.c \
|
||||
module/zfs/unique.c \
|
||||
module/zfs/vdev.c \
|
||||
module/zfs/vdev_cache.c \
|
||||
module/zfs/vdev_draid.c \
|
||||
module/zfs/vdev_draid_rand.c \
|
||||
module/zfs/vdev_indirect.c \
|
||||
|
@ -2028,21 +2028,6 @@ Max vdev I/O aggregation size.
|
||||
.It Sy zfs_vdev_aggregation_limit_non_rotating Ns = Ns Sy 131072 Ns B Po 128 KiB Pc Pq uint
|
||||
Max vdev I/O aggregation size for non-rotating media.
|
||||
.
|
||||
.It Sy zfs_vdev_cache_bshift Ns = Ns Sy 16 Po 64 KiB Pc Pq uint
|
||||
Shift size to inflate reads to.
|
||||
.
|
||||
.It Sy zfs_vdev_cache_max Ns = Ns Sy 16384 Ns B Po 16 KiB Pc Pq uint
|
||||
Inflate reads smaller than this value to meet the
|
||||
.Sy zfs_vdev_cache_bshift
|
||||
size
|
||||
.Pq default Sy 64 KiB .
|
||||
.
|
||||
.It Sy zfs_vdev_cache_size Ns = Ns Sy 0 Pq uint
|
||||
Total size of the per-disk cache in bytes.
|
||||
.Pp
|
||||
Currently this feature is disabled, as it has been found to not be helpful
|
||||
for performance and in some cases harmful.
|
||||
.
|
||||
.It Sy zfs_vdev_mirror_rotating_inc Ns = Ns Sy 0 Pq int
|
||||
A number by which the balancing algorithm increments the load calculation for
|
||||
the purpose of selecting the least busy mirror member when an I/O operation
|
||||
|
@ -456,7 +456,6 @@ ZIO_FLAG_CANFAIL:0x00000080
|
||||
ZIO_FLAG_SPECULATIVE:0x00000100
|
||||
ZIO_FLAG_CONFIG_WRITER:0x00000200
|
||||
ZIO_FLAG_DONT_RETRY:0x00000400
|
||||
ZIO_FLAG_DONT_CACHE:0x00000800
|
||||
ZIO_FLAG_NODATA:0x00001000
|
||||
ZIO_FLAG_INDUCE_DAMAGE:0x00002000
|
||||
|
||||
|
@ -382,7 +382,6 @@ ZFS_OBJS := \
|
||||
uberblock.o \
|
||||
unique.o \
|
||||
vdev.o \
|
||||
vdev_cache.o \
|
||||
vdev_draid.o \
|
||||
vdev_draid_rand.o \
|
||||
vdev_indirect.o \
|
||||
|
@ -308,7 +308,6 @@ SRCS+= abd.c \
|
||||
uberblock.c \
|
||||
unique.c \
|
||||
vdev.c \
|
||||
vdev_cache.c \
|
||||
vdev_draid.c \
|
||||
vdev_draid_rand.c \
|
||||
vdev_indirect.c \
|
||||
|
@ -872,8 +872,6 @@ SYSCTL_INT(_vfs_zfs, OID_AUTO, validate_skip,
|
||||
"Enable to bypass vdev_validate().");
|
||||
/* END CSTYLED */
|
||||
|
||||
/* vdev_cache.c */
|
||||
|
||||
/* vdev_mirror.c */
|
||||
|
||||
/* vdev_queue.c */
|
||||
|
@ -6106,8 +6106,7 @@ arc_read(zio_t *pio, spa_t *spa, const blkptr_t *bp,
|
||||
asize, abd,
|
||||
ZIO_CHECKSUM_OFF,
|
||||
l2arc_read_done, cb, priority,
|
||||
zio_flags | ZIO_FLAG_DONT_CACHE |
|
||||
ZIO_FLAG_CANFAIL |
|
||||
zio_flags | ZIO_FLAG_CANFAIL |
|
||||
ZIO_FLAG_DONT_PROPAGATE |
|
||||
ZIO_FLAG_DONT_RETRY, B_FALSE);
|
||||
acb->acb_zio_head = rzio;
|
||||
@ -10177,8 +10176,7 @@ l2arc_dev_hdr_read(l2arc_dev_t *dev)
|
||||
err = zio_wait(zio_read_phys(NULL, dev->l2ad_vdev,
|
||||
VDEV_LABEL_START_SIZE, l2dhdr_asize, abd,
|
||||
ZIO_CHECKSUM_LABEL, NULL, NULL, ZIO_PRIORITY_SYNC_READ,
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_CANFAIL |
|
||||
ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY |
|
||||
ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY |
|
||||
ZIO_FLAG_SPECULATIVE, B_FALSE));
|
||||
|
||||
abd_free(abd);
|
||||
@ -10498,11 +10496,10 @@ l2arc_log_blk_fetch(vdev_t *vd, const l2arc_log_blkptr_t *lbp,
|
||||
cb = kmem_zalloc(sizeof (l2arc_read_callback_t), KM_SLEEP);
|
||||
cb->l2rcb_abd = abd_get_from_buf(lb, asize);
|
||||
pio = zio_root(vd->vdev_spa, l2arc_blk_fetch_done, cb,
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE |
|
||||
ZIO_FLAG_DONT_RETRY);
|
||||
ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY);
|
||||
(void) zio_nowait(zio_read_phys(pio, vd, lbp->lbp_daddr, asize,
|
||||
cb->l2rcb_abd, ZIO_CHECKSUM_OFF, NULL, NULL,
|
||||
ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_DONT_CACHE | ZIO_FLAG_CANFAIL |
|
||||
ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL |
|
||||
ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY, B_FALSE));
|
||||
|
||||
return (pio);
|
||||
|
@ -1371,8 +1371,8 @@ do_corrective_recv(struct receive_writer_arg *rwa, struct drr_write *drrw,
|
||||
dnode_t *dn;
|
||||
abd_t *abd = rrd->abd;
|
||||
zio_cksum_t bp_cksum = bp->blk_cksum;
|
||||
zio_flag_t flags = ZIO_FLAG_SPECULATIVE |
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_RETRY | ZIO_FLAG_CANFAIL;
|
||||
zio_flag_t flags = ZIO_FLAG_SPECULATIVE | ZIO_FLAG_DONT_RETRY |
|
||||
ZIO_FLAG_CANFAIL;
|
||||
|
||||
if (rwa->raw)
|
||||
flags |= ZIO_FLAG_RAW;
|
||||
|
@ -2438,7 +2438,6 @@ spa_init(spa_mode_t mode)
|
||||
zio_init();
|
||||
dmu_init();
|
||||
zil_init();
|
||||
vdev_cache_stat_init();
|
||||
vdev_mirror_stat_init();
|
||||
vdev_raidz_math_init();
|
||||
vdev_file_init();
|
||||
@ -2462,7 +2461,6 @@ spa_fini(void)
|
||||
spa_evict_all();
|
||||
|
||||
vdev_file_fini();
|
||||
vdev_cache_stat_fini();
|
||||
vdev_mirror_stat_fini();
|
||||
vdev_raidz_math_fini();
|
||||
chksum_fini();
|
||||
|
@ -715,7 +715,6 @@ vdev_alloc_common(spa_t *spa, uint_t id, uint64_t guid, vdev_ops_t *ops)
|
||||
offsetof(struct vdev, vdev_dtl_node));
|
||||
vd->vdev_stat.vs_timestamp = gethrtime();
|
||||
vdev_queue_init(vd);
|
||||
vdev_cache_init(vd);
|
||||
|
||||
return (vd);
|
||||
}
|
||||
@ -1096,7 +1095,6 @@ vdev_free(vdev_t *vd)
|
||||
* Clean up vdev structure.
|
||||
*/
|
||||
vdev_queue_fini(vd);
|
||||
vdev_cache_fini(vd);
|
||||
|
||||
if (vd->vdev_path)
|
||||
spa_strfree(vd->vdev_path);
|
||||
@ -1720,8 +1718,7 @@ vdev_probe(vdev_t *vd, zio_t *zio)
|
||||
vps = kmem_zalloc(sizeof (*vps), KM_SLEEP);
|
||||
|
||||
vps->vps_flags = ZIO_FLAG_CANFAIL | ZIO_FLAG_PROBE |
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_AGGREGATE |
|
||||
ZIO_FLAG_TRYHARD;
|
||||
ZIO_FLAG_DONT_AGGREGATE | ZIO_FLAG_TRYHARD;
|
||||
|
||||
if (spa_config_held(spa, SCL_ZIO, RW_WRITER)) {
|
||||
/*
|
||||
@ -2612,8 +2609,6 @@ vdev_close(vdev_t *vd)
|
||||
|
||||
vd->vdev_ops->vdev_op_close(vd);
|
||||
|
||||
vdev_cache_purge(vd);
|
||||
|
||||
/*
|
||||
* We record the previous state before we close it, so that if we are
|
||||
* doing a reopen(), we don't generate FMA ereports if we notice that
|
||||
|
@ -1,436 +0,0 @@
|
||||
/*
|
||||
* 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 https://opensource.org/licenses/CDDL-1.0.
|
||||
* 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) 2013, 2016 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
#include <sys/spa.h>
|
||||
#include <sys/vdev_impl.h>
|
||||
#include <sys/zio.h>
|
||||
#include <sys/kstat.h>
|
||||
#include <sys/abd.h>
|
||||
|
||||
/*
|
||||
* Virtual device read-ahead caching.
|
||||
*
|
||||
* This file implements a simple LRU read-ahead cache. When the DMU reads
|
||||
* a given block, it will often want other, nearby blocks soon thereafter.
|
||||
* We take advantage of this by reading a larger disk region and caching
|
||||
* the result. In the best case, this can turn 128 back-to-back 512-byte
|
||||
* reads into a single 64k read followed by 127 cache hits; this reduces
|
||||
* latency dramatically. In the worst case, it can turn an isolated 512-byte
|
||||
* read into a 64k read, which doesn't affect latency all that much but is
|
||||
* terribly wasteful of bandwidth. A more intelligent version of the cache
|
||||
* could keep track of access patterns and not do read-ahead unless it sees
|
||||
* at least two temporally close I/Os to the same region. Currently, only
|
||||
* metadata I/O is inflated. A further enhancement could take advantage of
|
||||
* more semantic information about the I/O. And it could use something
|
||||
* faster than an AVL tree; that was chosen solely for convenience.
|
||||
*
|
||||
* There are five cache operations: allocate, fill, read, write, evict.
|
||||
*
|
||||
* (1) Allocate. This reserves a cache entry for the specified region.
|
||||
* We separate the allocate and fill operations so that multiple threads
|
||||
* don't generate I/O for the same cache miss.
|
||||
*
|
||||
* (2) Fill. When the I/O for a cache miss completes, the fill routine
|
||||
* places the data in the previously allocated cache entry.
|
||||
*
|
||||
* (3) Read. Read data from the cache.
|
||||
*
|
||||
* (4) Write. Update cache contents after write completion.
|
||||
*
|
||||
* (5) Evict. When allocating a new entry, we evict the oldest (LRU) entry
|
||||
* if the total cache size exceeds zfs_vdev_cache_size.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These tunables are for performance analysis.
|
||||
*/
|
||||
/*
|
||||
* All i/os smaller than zfs_vdev_cache_max will be turned into
|
||||
* 1<<zfs_vdev_cache_bshift byte reads by the vdev_cache (aka software
|
||||
* track buffer). At most zfs_vdev_cache_size bytes will be kept in each
|
||||
* vdev's vdev_cache.
|
||||
*
|
||||
* TODO: Note that with the current ZFS code, it turns out that the
|
||||
* vdev cache is not helpful, and in some cases actually harmful. It
|
||||
* is better if we disable this. Once some time has passed, we should
|
||||
* actually remove this to simplify the code. For now we just disable
|
||||
* it by setting the zfs_vdev_cache_size to zero. Note that Solaris 11
|
||||
* has made these same changes.
|
||||
*/
|
||||
static uint_t zfs_vdev_cache_max = 1 << 14; /* 16KB */
|
||||
static uint_t zfs_vdev_cache_size = 0;
|
||||
static uint_t zfs_vdev_cache_bshift = 16;
|
||||
|
||||
#define VCBS (1 << zfs_vdev_cache_bshift) /* 64KB */
|
||||
|
||||
static kstat_t *vdc_ksp = NULL;
|
||||
|
||||
typedef struct vdc_stats {
|
||||
kstat_named_t vdc_stat_delegations;
|
||||
kstat_named_t vdc_stat_hits;
|
||||
kstat_named_t vdc_stat_misses;
|
||||
} vdc_stats_t;
|
||||
|
||||
static vdc_stats_t vdc_stats = {
|
||||
{ "delegations", KSTAT_DATA_UINT64 },
|
||||
{ "hits", KSTAT_DATA_UINT64 },
|
||||
{ "misses", KSTAT_DATA_UINT64 }
|
||||
};
|
||||
|
||||
#define VDCSTAT_BUMP(stat) atomic_inc_64(&vdc_stats.stat.value.ui64);
|
||||
|
||||
static inline int
|
||||
vdev_cache_offset_compare(const void *a1, const void *a2)
|
||||
{
|
||||
const vdev_cache_entry_t *ve1 = (const vdev_cache_entry_t *)a1;
|
||||
const vdev_cache_entry_t *ve2 = (const vdev_cache_entry_t *)a2;
|
||||
|
||||
return (TREE_CMP(ve1->ve_offset, ve2->ve_offset));
|
||||
}
|
||||
|
||||
static int
|
||||
vdev_cache_lastused_compare(const void *a1, const void *a2)
|
||||
{
|
||||
const vdev_cache_entry_t *ve1 = (const vdev_cache_entry_t *)a1;
|
||||
const vdev_cache_entry_t *ve2 = (const vdev_cache_entry_t *)a2;
|
||||
|
||||
int cmp = TREE_CMP(ve1->ve_lastused, ve2->ve_lastused);
|
||||
if (likely(cmp))
|
||||
return (cmp);
|
||||
|
||||
/*
|
||||
* Among equally old entries, sort by offset to ensure uniqueness.
|
||||
*/
|
||||
return (vdev_cache_offset_compare(a1, a2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Evict the specified entry from the cache.
|
||||
*/
|
||||
static void
|
||||
vdev_cache_evict(vdev_cache_t *vc, vdev_cache_entry_t *ve)
|
||||
{
|
||||
ASSERT(MUTEX_HELD(&vc->vc_lock));
|
||||
ASSERT3P(ve->ve_fill_io, ==, NULL);
|
||||
ASSERT3P(ve->ve_abd, !=, NULL);
|
||||
|
||||
avl_remove(&vc->vc_lastused_tree, ve);
|
||||
avl_remove(&vc->vc_offset_tree, ve);
|
||||
abd_free(ve->ve_abd);
|
||||
kmem_free(ve, sizeof (vdev_cache_entry_t));
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate an entry in the cache. At the point we don't have the data,
|
||||
* we're just creating a placeholder so that multiple threads don't all
|
||||
* go off and read the same blocks.
|
||||
*/
|
||||
static vdev_cache_entry_t *
|
||||
vdev_cache_allocate(zio_t *zio)
|
||||
{
|
||||
vdev_cache_t *vc = &zio->io_vd->vdev_cache;
|
||||
uint64_t offset = P2ALIGN(zio->io_offset, VCBS);
|
||||
vdev_cache_entry_t *ve;
|
||||
|
||||
ASSERT(MUTEX_HELD(&vc->vc_lock));
|
||||
|
||||
if (zfs_vdev_cache_size == 0)
|
||||
return (NULL);
|
||||
|
||||
/*
|
||||
* If adding a new entry would exceed the cache size,
|
||||
* evict the oldest entry (LRU).
|
||||
*/
|
||||
if ((avl_numnodes(&vc->vc_lastused_tree) << zfs_vdev_cache_bshift) >
|
||||
zfs_vdev_cache_size) {
|
||||
ve = avl_first(&vc->vc_lastused_tree);
|
||||
if (ve->ve_fill_io != NULL)
|
||||
return (NULL);
|
||||
ASSERT3U(ve->ve_hits, !=, 0);
|
||||
vdev_cache_evict(vc, ve);
|
||||
}
|
||||
|
||||
ve = kmem_zalloc(sizeof (vdev_cache_entry_t), KM_SLEEP);
|
||||
ve->ve_offset = offset;
|
||||
ve->ve_lastused = ddi_get_lbolt();
|
||||
ve->ve_abd = abd_alloc_for_io(VCBS, B_TRUE);
|
||||
|
||||
avl_add(&vc->vc_offset_tree, ve);
|
||||
avl_add(&vc->vc_lastused_tree, ve);
|
||||
|
||||
return (ve);
|
||||
}
|
||||
|
||||
static void
|
||||
vdev_cache_hit(vdev_cache_t *vc, vdev_cache_entry_t *ve, zio_t *zio)
|
||||
{
|
||||
uint64_t cache_phase = P2PHASE(zio->io_offset, VCBS);
|
||||
|
||||
ASSERT(MUTEX_HELD(&vc->vc_lock));
|
||||
ASSERT3P(ve->ve_fill_io, ==, NULL);
|
||||
|
||||
if (ve->ve_lastused != ddi_get_lbolt()) {
|
||||
avl_remove(&vc->vc_lastused_tree, ve);
|
||||
ve->ve_lastused = ddi_get_lbolt();
|
||||
avl_add(&vc->vc_lastused_tree, ve);
|
||||
}
|
||||
|
||||
ve->ve_hits++;
|
||||
abd_copy_off(zio->io_abd, ve->ve_abd, 0, cache_phase, zio->io_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill a previously allocated cache entry with data.
|
||||
*/
|
||||
static void
|
||||
vdev_cache_fill(zio_t *fio)
|
||||
{
|
||||
vdev_t *vd = fio->io_vd;
|
||||
vdev_cache_t *vc = &vd->vdev_cache;
|
||||
vdev_cache_entry_t *ve = fio->io_private;
|
||||
zio_t *pio;
|
||||
|
||||
ASSERT3U(fio->io_size, ==, VCBS);
|
||||
|
||||
/*
|
||||
* Add data to the cache.
|
||||
*/
|
||||
mutex_enter(&vc->vc_lock);
|
||||
|
||||
ASSERT3P(ve->ve_fill_io, ==, fio);
|
||||
ASSERT3U(ve->ve_offset, ==, fio->io_offset);
|
||||
ASSERT3P(ve->ve_abd, ==, fio->io_abd);
|
||||
|
||||
ve->ve_fill_io = NULL;
|
||||
|
||||
/*
|
||||
* Even if this cache line was invalidated by a missed write update,
|
||||
* any reads that were queued up before the missed update are still
|
||||
* valid, so we can satisfy them from this line before we evict it.
|
||||
*/
|
||||
zio_link_t *zl = NULL;
|
||||
while ((pio = zio_walk_parents(fio, &zl)) != NULL)
|
||||
vdev_cache_hit(vc, ve, pio);
|
||||
|
||||
if (fio->io_error || ve->ve_missed_update)
|
||||
vdev_cache_evict(vc, ve);
|
||||
|
||||
mutex_exit(&vc->vc_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read data from the cache. Returns B_TRUE cache hit, B_FALSE on miss.
|
||||
*/
|
||||
boolean_t
|
||||
vdev_cache_read(zio_t *zio)
|
||||
{
|
||||
vdev_cache_t *vc = &zio->io_vd->vdev_cache;
|
||||
vdev_cache_entry_t *ve, ve_search;
|
||||
uint64_t cache_offset = P2ALIGN(zio->io_offset, VCBS);
|
||||
zio_t *fio;
|
||||
uint64_t cache_phase __maybe_unused = P2PHASE(zio->io_offset, VCBS);
|
||||
|
||||
ASSERT3U(zio->io_type, ==, ZIO_TYPE_READ);
|
||||
|
||||
if (zfs_vdev_cache_size == 0)
|
||||
return (B_FALSE);
|
||||
|
||||
if (zio->io_flags & ZIO_FLAG_DONT_CACHE)
|
||||
return (B_FALSE);
|
||||
|
||||
if (zio->io_size > zfs_vdev_cache_max)
|
||||
return (B_FALSE);
|
||||
|
||||
/*
|
||||
* If the I/O straddles two or more cache blocks, don't cache it.
|
||||
*/
|
||||
if (P2BOUNDARY(zio->io_offset, zio->io_size, VCBS))
|
||||
return (B_FALSE);
|
||||
|
||||
ASSERT3U(cache_phase + zio->io_size, <=, VCBS);
|
||||
|
||||
mutex_enter(&vc->vc_lock);
|
||||
|
||||
ve_search.ve_offset = cache_offset;
|
||||
ve = avl_find(&vc->vc_offset_tree, &ve_search, NULL);
|
||||
|
||||
if (ve != NULL) {
|
||||
if (ve->ve_missed_update) {
|
||||
mutex_exit(&vc->vc_lock);
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
if ((fio = ve->ve_fill_io) != NULL) {
|
||||
zio_vdev_io_bypass(zio);
|
||||
zio_add_child(zio, fio);
|
||||
mutex_exit(&vc->vc_lock);
|
||||
VDCSTAT_BUMP(vdc_stat_delegations);
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
vdev_cache_hit(vc, ve, zio);
|
||||
zio_vdev_io_bypass(zio);
|
||||
|
||||
mutex_exit(&vc->vc_lock);
|
||||
VDCSTAT_BUMP(vdc_stat_hits);
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
ve = vdev_cache_allocate(zio);
|
||||
|
||||
if (ve == NULL) {
|
||||
mutex_exit(&vc->vc_lock);
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
fio = zio_vdev_delegated_io(zio->io_vd, cache_offset,
|
||||
ve->ve_abd, VCBS, ZIO_TYPE_READ, ZIO_PRIORITY_NOW,
|
||||
ZIO_FLAG_DONT_CACHE, vdev_cache_fill, ve);
|
||||
|
||||
ve->ve_fill_io = fio;
|
||||
zio_vdev_io_bypass(zio);
|
||||
zio_add_child(zio, fio);
|
||||
|
||||
mutex_exit(&vc->vc_lock);
|
||||
zio_nowait(fio);
|
||||
VDCSTAT_BUMP(vdc_stat_misses);
|
||||
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update cache contents upon write completion.
|
||||
*/
|
||||
void
|
||||
vdev_cache_write(zio_t *zio)
|
||||
{
|
||||
vdev_cache_t *vc = &zio->io_vd->vdev_cache;
|
||||
vdev_cache_entry_t *ve, ve_search;
|
||||
uint64_t io_start = zio->io_offset;
|
||||
uint64_t io_end = io_start + zio->io_size;
|
||||
uint64_t min_offset = P2ALIGN(io_start, VCBS);
|
||||
uint64_t max_offset = P2ROUNDUP(io_end, VCBS);
|
||||
avl_index_t where;
|
||||
|
||||
ASSERT3U(zio->io_type, ==, ZIO_TYPE_WRITE);
|
||||
|
||||
mutex_enter(&vc->vc_lock);
|
||||
|
||||
ve_search.ve_offset = min_offset;
|
||||
ve = avl_find(&vc->vc_offset_tree, &ve_search, &where);
|
||||
|
||||
if (ve == NULL)
|
||||
ve = avl_nearest(&vc->vc_offset_tree, where, AVL_AFTER);
|
||||
|
||||
while (ve != NULL && ve->ve_offset < max_offset) {
|
||||
uint64_t start = MAX(ve->ve_offset, io_start);
|
||||
uint64_t end = MIN(ve->ve_offset + VCBS, io_end);
|
||||
|
||||
if (ve->ve_fill_io != NULL) {
|
||||
ve->ve_missed_update = 1;
|
||||
} else {
|
||||
abd_copy_off(ve->ve_abd, zio->io_abd,
|
||||
start - ve->ve_offset, start - io_start,
|
||||
end - start);
|
||||
}
|
||||
ve = AVL_NEXT(&vc->vc_offset_tree, ve);
|
||||
}
|
||||
mutex_exit(&vc->vc_lock);
|
||||
}
|
||||
|
||||
void
|
||||
vdev_cache_purge(vdev_t *vd)
|
||||
{
|
||||
vdev_cache_t *vc = &vd->vdev_cache;
|
||||
vdev_cache_entry_t *ve;
|
||||
|
||||
mutex_enter(&vc->vc_lock);
|
||||
while ((ve = avl_first(&vc->vc_offset_tree)) != NULL)
|
||||
vdev_cache_evict(vc, ve);
|
||||
mutex_exit(&vc->vc_lock);
|
||||
}
|
||||
|
||||
void
|
||||
vdev_cache_init(vdev_t *vd)
|
||||
{
|
||||
vdev_cache_t *vc = &vd->vdev_cache;
|
||||
|
||||
mutex_init(&vc->vc_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||
|
||||
avl_create(&vc->vc_offset_tree, vdev_cache_offset_compare,
|
||||
sizeof (vdev_cache_entry_t),
|
||||
offsetof(struct vdev_cache_entry, ve_offset_node));
|
||||
|
||||
avl_create(&vc->vc_lastused_tree, vdev_cache_lastused_compare,
|
||||
sizeof (vdev_cache_entry_t),
|
||||
offsetof(struct vdev_cache_entry, ve_lastused_node));
|
||||
}
|
||||
|
||||
void
|
||||
vdev_cache_fini(vdev_t *vd)
|
||||
{
|
||||
vdev_cache_t *vc = &vd->vdev_cache;
|
||||
|
||||
vdev_cache_purge(vd);
|
||||
|
||||
avl_destroy(&vc->vc_offset_tree);
|
||||
avl_destroy(&vc->vc_lastused_tree);
|
||||
|
||||
mutex_destroy(&vc->vc_lock);
|
||||
}
|
||||
|
||||
void
|
||||
vdev_cache_stat_init(void)
|
||||
{
|
||||
vdc_ksp = kstat_create("zfs", 0, "vdev_cache_stats", "misc",
|
||||
KSTAT_TYPE_NAMED, sizeof (vdc_stats) / sizeof (kstat_named_t),
|
||||
KSTAT_FLAG_VIRTUAL);
|
||||
if (vdc_ksp != NULL) {
|
||||
vdc_ksp->ks_data = &vdc_stats;
|
||||
kstat_install(vdc_ksp);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vdev_cache_stat_fini(void)
|
||||
{
|
||||
if (vdc_ksp != NULL) {
|
||||
kstat_delete(vdc_ksp);
|
||||
vdc_ksp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ZFS_MODULE_PARAM(zfs_vdev, zfs_vdev_, cache_max, UINT, ZMOD_RW,
|
||||
"Inflate reads small than max");
|
||||
|
||||
ZFS_MODULE_PARAM(zfs_vdev, zfs_vdev_, cache_size, UINT, ZMOD_RD,
|
||||
"Total size of the per-disk cache");
|
||||
|
||||
ZFS_MODULE_PARAM(zfs_vdev, zfs_vdev_, cache_bshift, UINT, ZMOD_RW,
|
||||
"Shift size to inflate reads too");
|
@ -748,8 +748,7 @@ vdev_queue_aggregate(vdev_queue_t *vq, zio_t *zio)
|
||||
|
||||
aio = zio_vdev_delegated_io(first->io_vd, first->io_offset,
|
||||
abd, size, first->io_type, zio->io_priority,
|
||||
flags | ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_QUEUE,
|
||||
vdev_queue_agg_io_done, NULL);
|
||||
flags | ZIO_FLAG_DONT_QUEUE, vdev_queue_agg_io_done, NULL);
|
||||
aio->io_timestamp = first->io_timestamp;
|
||||
|
||||
nio = first;
|
||||
@ -907,7 +906,7 @@ vdev_queue_io(zio_t *zio)
|
||||
ASSERT(zio->io_priority == ZIO_PRIORITY_TRIM);
|
||||
}
|
||||
|
||||
zio->io_flags |= ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_QUEUE;
|
||||
zio->io_flags |= ZIO_FLAG_DONT_QUEUE;
|
||||
zio->io_timestamp = gethrtime();
|
||||
|
||||
mutex_enter(&vq->vq_lock);
|
||||
|
@ -1617,12 +1617,6 @@ zio_read_bp_init(zio_t *zio)
|
||||
ASSERT3P(zio->io_bp, ==, &zio->io_bp_copy);
|
||||
}
|
||||
|
||||
if (!DMU_OT_IS_METADATA(BP_GET_TYPE(bp)) && BP_GET_LEVEL(bp) == 0)
|
||||
zio->io_flags |= ZIO_FLAG_DONT_CACHE;
|
||||
|
||||
if (BP_GET_TYPE(bp) == DMU_OT_DDT_ZAP)
|
||||
zio->io_flags |= ZIO_FLAG_DONT_CACHE;
|
||||
|
||||
if (BP_GET_DEDUP(bp) && zio->io_child_type == ZIO_CHILD_LOGICAL)
|
||||
zio->io_pipeline = ZIO_DDT_READ_PIPELINE;
|
||||
|
||||
@ -3955,9 +3949,6 @@ zio_vdev_io_start(zio_t *zio)
|
||||
zio->io_type == ZIO_TYPE_WRITE ||
|
||||
zio->io_type == ZIO_TYPE_TRIM)) {
|
||||
|
||||
if (zio->io_type == ZIO_TYPE_READ && vdev_cache_read(zio))
|
||||
return (zio);
|
||||
|
||||
if ((zio = vdev_queue_io(zio)) == NULL)
|
||||
return (NULL);
|
||||
|
||||
@ -3994,9 +3985,6 @@ zio_vdev_io_done(zio_t *zio)
|
||||
vd->vdev_ops != &vdev_draid_spare_ops) {
|
||||
vdev_queue_io_done(zio);
|
||||
|
||||
if (zio->io_type == ZIO_TYPE_WRITE)
|
||||
vdev_cache_write(zio);
|
||||
|
||||
if (zio_injection_enabled && zio->io_error == 0)
|
||||
zio->io_error = zio_handle_device_injections(vd, zio,
|
||||
EIO, EILSEQ);
|
||||
@ -4106,8 +4094,7 @@ zio_vdev_io_assess(zio_t *zio)
|
||||
ASSERT(!(zio->io_flags & ZIO_FLAG_DONT_QUEUE)); /* not a leaf */
|
||||
ASSERT(!(zio->io_flags & ZIO_FLAG_IO_BYPASS)); /* not a leaf */
|
||||
zio->io_error = 0;
|
||||
zio->io_flags |= ZIO_FLAG_IO_RETRY |
|
||||
ZIO_FLAG_DONT_CACHE | ZIO_FLAG_DONT_AGGREGATE;
|
||||
zio->io_flags |= ZIO_FLAG_IO_RETRY | ZIO_FLAG_DONT_AGGREGATE;
|
||||
zio->io_stage = ZIO_STAGE_VDEV_IO_START >> 1;
|
||||
zio_taskq_dispatch(zio, ZIO_TASKQ_ISSUE,
|
||||
zio_requeue_io_start_cut_in_line);
|
||||
|
Loading…
Reference in New Issue
Block a user