| Introduction | |
| ============ | |
| dm-cache is a device mapper target written by Joe Thornber, Heinz | |
| Mauelshagen, and Mike Snitzer. | |
| It aims to improve performance of a block device (eg, a spindle) by | |
| dynamically migrating some of its data to a faster, smaller device | |
| (eg, an SSD). | |
| This device-mapper solution allows us to insert this caching at | |
| different levels of the dm stack, for instance above the data device for | |
| a thin-provisioning pool. Caching solutions that are integrated more | |
| closely with the virtual memory system should give better performance. | |
| The target reuses the metadata library used in the thin-provisioning | |
| library. | |
| The decision as to what data to migrate and when is left to a plug-in | |
| policy module. Several of these have been written as we experiment, | |
| and we hope other people will contribute others for specific io | |
| scenarios (eg. a vm image server). | |
| Glossary | |
| ======== | |
| Migration - Movement of the primary copy of a logical block from one | |
| device to the other. | |
| Promotion - Migration from slow device to fast device. | |
| Demotion - Migration from fast device to slow device. | |
| The origin device always contains a copy of the logical block, which | |
| may be out of date or kept in sync with the copy on the cache device | |
| (depending on policy). | |
| Design | |
| ====== | |
| Sub-devices | |
| ----------- | |
| The target is constructed by passing three devices to it (along with | |
| other parameters detailed later): | |
| 1. An origin device - the big, slow one. | |
| 2. A cache device - the small, fast one. | |
| 3. A small metadata device - records which blocks are in the cache, | |
| which are dirty, and extra hints for use by the policy object. | |
| This information could be put on the cache device, but having it | |
| separate allows the volume manager to configure it differently, | |
| e.g. as a mirror for extra robustness. This metadata device may only | |
| be used by a single cache device. | |
| Fixed block size | |
| ---------------- | |
| The origin is divided up into blocks of a fixed size. This block size | |
| is configurable when you first create the cache. Typically we've been | |
| using block sizes of 256KB - 1024KB. The block size must be between 64 | |
| (32KB) and 2097152 (1GB) and a multiple of 64 (32KB). | |
| Having a fixed block size simplifies the target a lot. But it is | |
| something of a compromise. For instance, a small part of a block may be | |
| getting hit a lot, yet the whole block will be promoted to the cache. | |
| So large block sizes are bad because they waste cache space. And small | |
| block sizes are bad because they increase the amount of metadata (both | |
| in core and on disk). | |
| Cache operating modes | |
| --------------------- | |
| The cache has three operating modes: writeback, writethrough and | |
| passthrough. | |
| If writeback, the default, is selected then a write to a block that is | |
| cached will go only to the cache and the block will be marked dirty in | |
| the metadata. | |
| If writethrough is selected then a write to a cached block will not | |
| complete until it has hit both the origin and cache devices. Clean | |
| blocks should remain clean. | |
| If passthrough is selected, useful when the cache contents are not known | |
| to be coherent with the origin device, then all reads are served from | |
| the origin device (all reads miss the cache) and all writes are | |
| forwarded to the origin device; additionally, write hits cause cache | |
| block invalidates. To enable passthrough mode the cache must be clean. | |
| Passthrough mode allows a cache device to be activated without having to | |
| worry about coherency. Coherency that exists is maintained, although | |
| the cache will gradually cool as writes take place. If the coherency of | |
| the cache can later be verified, or established through use of the | |
| "invalidate_cblocks" message, the cache device can be transitioned to | |
| writethrough or writeback mode while still warm. Otherwise, the cache | |
| contents can be discarded prior to transitioning to the desired | |
| operating mode. | |
| A simple cleaner policy is provided, which will clean (write back) all | |
| dirty blocks in a cache. Useful for decommissioning a cache or when | |
| shrinking a cache. Shrinking the cache's fast device requires all cache | |
| blocks, in the area of the cache being removed, to be clean. If the | |
| area being removed from the cache still contains dirty blocks the resize | |
| will fail. Care must be taken to never reduce the volume used for the | |
| cache's fast device until the cache is clean. This is of particular | |
| importance if writeback mode is used. Writethrough and passthrough | |
| modes already maintain a clean cache. Future support to partially clean | |
| the cache, above a specified threshold, will allow for keeping the cache | |
| warm and in writeback mode during resize. | |
| Migration throttling | |
| -------------------- | |
| Migrating data between the origin and cache device uses bandwidth. | |
| The user can set a throttle to prevent more than a certain amount of | |
| migration occurring at any one time. Currently we're not taking any | |
| account of normal io traffic going to the devices. More work needs | |
| doing here to avoid migrating during those peak io moments. | |
| For the time being, a message "migration_threshold <#sectors>" | |
| can be used to set the maximum number of sectors being migrated, | |
| the default being 204800 sectors (or 100MB). | |
| Updating on-disk metadata | |
| ------------------------- | |
| On-disk metadata is committed every time a FLUSH or FUA bio is written. | |
| If no such requests are made then commits will occur every second. This | |
| means the cache behaves like a physical disk that has a volatile write | |
| cache. If power is lost you may lose some recent writes. The metadata | |
| should always be consistent in spite of any crash. | |
| The 'dirty' state for a cache block changes far too frequently for us | |
| to keep updating it on the fly. So we treat it as a hint. In normal | |
| operation it will be written when the dm device is suspended. If the | |
| system crashes all cache blocks will be assumed dirty when restarted. | |
| Per-block policy hints | |
| ---------------------- | |
| Policy plug-ins can store a chunk of data per cache block. It's up to | |
| the policy how big this chunk is, but it should be kept small. Like the | |
| dirty flags this data is lost if there's a crash so a safe fallback | |
| value should always be possible. | |
| For instance, the 'mq' policy, which is currently the default policy, | |
| uses this facility to store the hit count of the cache blocks. If | |
| there's a crash this information will be lost, which means the cache | |
| may be less efficient until those hit counts are regenerated. | |
| Policy hints affect performance, not correctness. | |
| Policy messaging | |
| ---------------- | |
| Policies will have different tunables, specific to each one, so we | |
| need a generic way of getting and setting these. Device-mapper | |
| messages are used. Refer to cache-policies.txt. | |
| Discard bitset resolution | |
| ------------------------- | |
| We can avoid copying data during migration if we know the block has | |
| been discarded. A prime example of this is when mkfs discards the | |
| whole block device. We store a bitset tracking the discard state of | |
| blocks. However, we allow this bitset to have a different block size | |
| from the cache blocks. This is because we need to track the discard | |
| state for all of the origin device (compare with the dirty bitset | |
| which is just for the smaller cache device). | |
| Target interface | |
| ================ | |
| Constructor | |
| ----------- | |
| cache <metadata dev> <cache dev> <origin dev> <block size> | |
| <#feature args> [<feature arg>]* | |
| <policy> <#policy args> [policy args]* | |
| metadata dev : fast device holding the persistent metadata | |
| cache dev : fast device holding cached data blocks | |
| origin dev : slow device holding original data blocks | |
| block size : cache unit size in sectors | |
| #feature args : number of feature arguments passed | |
| feature args : writethrough or passthrough (The default is writeback.) | |
| policy : the replacement policy to use | |
| #policy args : an even number of arguments corresponding to | |
| key/value pairs passed to the policy | |
| policy args : key/value pairs passed to the policy | |
| E.g. 'sequential_threshold 1024' | |
| See cache-policies.txt for details. | |
| Optional feature arguments are: | |
| writethrough : write through caching that prohibits cache block | |
| content from being different from origin block content. | |
| Without this argument, the default behaviour is to write | |
| back cache block contents later for performance reasons, | |
| so they may differ from the corresponding origin blocks. | |
| passthrough : a degraded mode useful for various cache coherency | |
| situations (e.g., rolling back snapshots of | |
| underlying storage). Reads and writes always go to | |
| the origin. If a write goes to a cached origin | |
| block, then the cache block is invalidated. | |
| To enable passthrough mode the cache must be clean. | |
| A policy called 'default' is always registered. This is an alias for | |
| the policy we currently think is giving best all round performance. | |
| As the default policy could vary between kernels, if you are relying on | |
| the characteristics of a specific policy, always request it by name. | |
| Status | |
| ------ | |
| <metadata block size> <#used metadata blocks>/<#total metadata blocks> | |
| <cache block size> <#used cache blocks>/<#total cache blocks> | |
| <#read hits> <#read misses> <#write hits> <#write misses> | |
| <#demotions> <#promotions> <#dirty> <#features> <features>* | |
| <#core args> <core args>* <policy name> <#policy args> <policy args>* | |
| <cache metadata mode> | |
| metadata block size : Fixed block size for each metadata block in | |
| sectors | |
| #used metadata blocks : Number of metadata blocks used | |
| #total metadata blocks : Total number of metadata blocks | |
| cache block size : Configurable block size for the cache device | |
| in sectors | |
| #used cache blocks : Number of blocks resident in the cache | |
| #total cache blocks : Total number of cache blocks | |
| #read hits : Number of times a READ bio has been mapped | |
| to the cache | |
| #read misses : Number of times a READ bio has been mapped | |
| to the origin | |
| #write hits : Number of times a WRITE bio has been mapped | |
| to the cache | |
| #write misses : Number of times a WRITE bio has been | |
| mapped to the origin | |
| #demotions : Number of times a block has been removed | |
| from the cache | |
| #promotions : Number of times a block has been moved to | |
| the cache | |
| #dirty : Number of blocks in the cache that differ | |
| from the origin | |
| #feature args : Number of feature args to follow | |
| feature args : 'writethrough' (optional) | |
| #core args : Number of core arguments (must be even) | |
| core args : Key/value pairs for tuning the core | |
| e.g. migration_threshold | |
| policy name : Name of the policy | |
| #policy args : Number of policy arguments to follow (must be even) | |
| policy args : Key/value pairs e.g. sequential_threshold | |
| cache metadata mode : ro if read-only, rw if read-write | |
| In serious cases where even a read-only mode is deemed unsafe | |
| no further I/O will be permitted and the status will just | |
| contain the string 'Fail'. The userspace recovery tools | |
| should then be used. | |
| needs_check : 'needs_check' if set, '-' if not set | |
| A metadata operation has failed, resulting in the needs_check | |
| flag being set in the metadata's superblock. The metadata | |
| device must be deactivated and checked/repaired before the | |
| cache can be made fully operational again. '-' indicates | |
| needs_check is not set. | |
| Messages | |
| -------- | |
| Policies will have different tunables, specific to each one, so we | |
| need a generic way of getting and setting these. Device-mapper | |
| messages are used. (A sysfs interface would also be possible.) | |
| The message format is: | |
| <key> <value> | |
| E.g. | |
| dmsetup message my_cache 0 sequential_threshold 1024 | |
| Invalidation is removing an entry from the cache without writing it | |
| back. Cache blocks can be invalidated via the invalidate_cblocks | |
| message, which takes an arbitrary number of cblock ranges. Each cblock | |
| range's end value is "one past the end", meaning 5-10 expresses a range | |
| of values from 5 to 9. Each cblock must be expressed as a decimal | |
| value, in the future a variant message that takes cblock ranges | |
| expressed in hexidecimal may be needed to better support efficient | |
| invalidation of larger caches. The cache must be in passthrough mode | |
| when invalidate_cblocks is used. | |
| invalidate_cblocks [<cblock>|<cblock begin>-<cblock end>]* | |
| E.g. | |
| dmsetup message my_cache 0 invalidate_cblocks 2345 3456-4567 5678-6789 | |
| Examples | |
| ======== | |
| The test suite can be found here: | |
| https://github.com/jthornber/device-mapper-test-suite | |
| dmsetup create my_cache --table '0 41943040 cache /dev/mapper/metadata \ | |
| /dev/mapper/ssd /dev/mapper/origin 512 1 writeback default 0' | |
| dmsetup create my_cache --table '0 41943040 cache /dev/mapper/metadata \ | |
| /dev/mapper/ssd /dev/mapper/origin 1024 1 writeback \ | |
| mq 4 sequential_threshold 1024 random_threshold 8' |