dm-cacheの技術概要と構築手順

# 0. はじめに

## 0-1. この記事について

自分の勉強用サーバのSSDが容量不足となったためディスクの拡張が必要となりコスト的な面から、HDD+SSDの構成を使ってみようと思いました。

このページでは、その際に調べた内容や、実際の構築手順を記載します。
間違っていたり、誤解を誘発しそうな記述があれば、指摘をいただけると幸いです。

## 0-2. 環境について

この記事に記載のコマンドやログ等は、　CentOS7 (7.5)で確認しています。

## 0-3. Linuxにおけるディスクキャッシュ技術
Linuxには、安価で遅いHDDと、高速だけど高価なSSD、小さなサイズのSSDをHDDのキャッシュに使うための技術が、いくつか存在します。

 - **dm-cache** （一番古い。2006年からある）
 - **bcache** （比較的新しい。dm-cacheより高速と評判）
 - **Flashcache** （Facebookによって開発された。最近は下火？）
 - **EnanceIO** （Flashcacheを元に開発された。らしい。あまり情報がない）

ググって調べた限り、実際に使用されている例が多いのは、dm-cacheかbcacheです。

dm-cacheは、IBMによって開発され、その後Linux Kernel 3.9に追加されました。RedHatが正式にサポートしてるところが売りです。つまり実績が多い。多分。

bcacheはLinux Kernel 3.10で追加され、Ubuntuなどで利用例が多く人気のようです。しかし残念なことに、現時点（2018/06）では、RedHat系での利用はちょっと面倒臭いようです。

私はCentOSを使っているので、dm-cacheを選択しました。

# 1. dm-cacheの技術概要

## 1-1. dm-cacheの構成要素

dm-cacheでは、速度の異なるストレージを、3つのデバイスに分割して考えます。

 - **Origin device** - これが主となるストレージで、主にHDD（低速で大容量なデバイス）を指します
 - **Cache device** - 高速なキャッシュ機能を提供するストレージ。通常はSSDです。
 - **Metadeta device** - キャッシュデータ管理のためにあるごく小さなストレージ（データベース）であり、キャッシュデータのヒット情報や、Dirty情報（まだOrigin deviceへ反映されていないキャッシュ）が保存されています。

## 1-2. dm-cacheの特徴
dm-cacheのキャッシュは、Origin deviceから読み込まれたデータを何でも即キャッシュする、**というわけではありません**。

キャッシュされるには、そのデータが１回ではなく、何度か読み込まれ「これはキャッシングしたほうがいいな」という判定を超えなければ、キャッシングされません。

したがって、**dm-cacheは、短期間に作成/１回読み込まれてすぐ廃棄されるような類のデータには向きません**。

残念なことに、インターネット上にはdm-cacheを構築して「１回だけ」テストをしたり、もしくは『都度異なるデータを書き込む/読み込む』ようなタイプのベンチマークによって評価していることがありますが、 それではdm-cacheのパフォーマンスは発揮できません。

dm-cacheを使用するかは、この特徴を理解して、キャッシュするデータが合っているか検討が必要です。

より詳細な説明が、RedHat Blogの[Improving read performance with dm-cache
](https://www.redhat.com/en/blog/improving-read-performance-dm-cache)に記載されています。dm-cacheに興味のある人は、これをぜひ一読いただくのが良いかと思います。

## 1-3. 書き込み時(write)のキャッシュ動作について

書き込み時に、SSDキャッシュに高速に書き込んで、後からHDDへ書き込むという動作も可能です。 （"**write back**"モード）

ただし、書き込みが高速化しますが、障害には弱くなります。そのため、このモードはオプションとなっており、標準では"**write through**"になります。書き込み時はSSDとHDDの両方へ書き込みをするタイプです。（つまりHDDの書き込み完了までの時間が、実質的な書き込み速度になる）。

## 1-4. dm-cacheの効果

以下は、私が構築したdm-cacheを使ったストレージ（HDD+SSD）上での、KVM仮想マシン(CentOS)の起動時間グラフです。

赤い線は、同じ仮想マシンをdm-cache無しで純粋にHDD上から起動した場合の時間で４２秒です。

オレンジの線は、同じく、純粋にSSD上から起動した場合の時間で25秒です。

![dm-cache_centos.png](https://qiita-image-store.s3.amazonaws.com/0/157891/83f0a6a6-1897-bad8-3cfb-e808506cb72e.png)

 - 計測方法
    - "echo 3 > /proc/sys/vm/drop_caches" を行なってページキャッシュ（メモリキャッシュ）をクリアしてから、仮想マシンを起動。
    - 同時に毎秒１回の 80/tcp へのポートノックを開始
    - 仮想マシン上で　は、起動時に自動的にApacheが起動する。
    - Apacheが起動すると、ポートノックに成功するようになるので、10回連続でポートノックに成功したら、起動完了と判定.

上記の通り、１回目では、全くHDDから起動したケースと同じで、２回目以降、徐々にキャッシュが進み、４回目でほぼSSDと等速になりました。

# 2. dm-cacheの構築手順

私がdm-cacheを構築した時の手順について記載します。dm-cacheの構築手順については、すでに　Qiitaに[[CentOS] dm-cacheを使用してSSDをHDDのキャッシュにする
](https://qiita.com/sengoku/items/e85526bb7d578aee295a) という記事を書かれてい方がいらっしゃるので、そちらを参照するのが良いかと思います。

私は、上記の記事と、RedHatが公開している[DM_Cache](https://people.redhat.com/mskinner/rhug/q1.2016/dm-cache.pdf)というPDFを参考にしました。

## 2-1. dm-cache構築の道具（コマンド類）
dm-cacheは、LVM(Logical Volume Manager)の一機能として実装されています。そのため、dm-cacheを使ったストレージの構築とは、（ちょっと特殊な）LVMボリュームを構築することにあたります。

このためか、インターネットでは、dm-cacheについて「LVM-Cache」と記載されていたりすることもあります。

## 2-2. 構築のイメージ

HDDは"/dev/sda1"に、SSDは"/dev/sdb1"に、それぞれ接続されているものとして、SSDをCache兼Metadeta用としました。

まず、これら二つのデバイスをLVMのPV(Physical Volume)に登録して、それらを同一のVG(Volume Group)にセットします。

![dm-cache_image_1.png](https://qiita-image-store.s3.amazonaws.com/0/157891/f964c02d-988e-1a54-afc7-50a0aa735a0c.png)

上記はあまり適切なイメージではないかもしれませんが、これでlv0をLogical Volumeとしてマウントすると、その領域はSSDによるキャッシュが有効になります。

## 2-3. 構築手順（コマンド）

1. LVMの"issue_discards" 設定を変更

    ```shell-session
    # vi /etc/lvm/lvm.conf
    issue_discards = 0 を
    issue_discards = 1 に変更。
    
    # dracut -f
    # sync
    # reboot
    ```

2. PVの作成

    ```shell-session
    # pvcreate /dev/sda1
      Physical volume "/dev/sda1" successfully created.
    
    # pvcreate /dev/sdb1
      Physical volume "/dev/sdb1" successfully created.
    ```

3. VGの作成

    ```shell-session
    # vgcreate hybrid_vg0 /dev/sda1 /dev/sdb1
      Volume group "hybrid_vg0" successfully created .
    ```

4. LVの作成

    ```shell-session
    # lvcreate -l 100%FREE -n lv0 hybrid_vg0 /dev/sda1
      Logical volume "lv0" created.
    
    # lvcreate -L 2G -n  cache_meta hybrid_vg0 /dev/sdb1
      Logical volume "cache_meta" created.
    
    # lvcreate -L 200G -n cache hybrid_vg0 /dev/sdb1
      Logical volume "cache" created.
    ```

    ※ ここでMeta Data用のLVを作成する際、サイズを指定する必要がありますが、どれくらいのサイズが最適なのかは、根拠を含めた情報が見つかりませんでした。キャッシュ用LVのサイズの1%が上限、という情報があったので、キャッシュ用LVサイズを200GB、MetaData用LVのサイズは2GBとしました。

5. cache pool の作成

    ```shell-session
    # lvconvert --type cache-pool --cachemode writeback --poolmetadata /dev/hybrid_vg0/cache_meta --chunksize 256 /dev/hybrid_vg0/cache
    WARNING: Converting hybrid_vg0/cache and hybrid_vg0/cache_meta to cache pool's data and metadata volumes with metadata wiping.
    THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.)
Do you really want to convert hybrid_vg0/cache and hybrid_vg0/cache_meta? [y/n]: y
    Converted hybrid_vg0/cache and hybrid_vg0/cache_meta to cache pool.
    ```

    ここの　"**--cachemode**"　で、書き込み時のモード（writethough, writeback) を指定します。指定しない場合、writethroughになります。


6. HDD側Logical Volumeに、作成したcache poolを紐付け

    ```shell-session
    # lvconvert --type cache --cachepool /dev/hybrid_vg0/cache /dev/hybrid_vg0/lv0
    　　Do you want wipe existing metadata of cache pool hybrid_vg0/cache? [y/n]: y
    　　Logical volume hybrid_vg0/lv0 is now cached.
    ```

## 2-4. 構築したLogical Poolの確認

構築したLVは、lvs コマンドで確認できます。

Dataカラムは、現在のキャッシュ領域の使用量を表しているようです。（正式な情報は見つかりませんでした。）

```shell-session
# lvs -a hybrid_vg0
  LV              VG         Attr       LSize   Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  [cache]         hybrid_vg0 Cwi---C--- 200.00g                     6.84   0.48            0.00            
  [cache_cdata]   hybrid_vg0 Cwi-ao---- 200.00g                                                            
  [cache_cmeta]   hybrid_vg0 ewi-ao----   2.00g                                                            
  lv0             hybrid_vg0 Cwi-aoC---  <1.82t [cache] [lv0_corig] 6.84   0.48            0.00            
  [lv0_corig]     hybrid_vg0 owi-aoC---  <1.82t                                                            
  [lvol0_pmspare] hybrid_vg0 ewi-------   2.00g  
```

# 3. 構築後の操作

## 3-1. Cache Poolの取り外し

キャッシュを無効にしたいときは、次のコマンドでCache Poolを外すことができます。また、再度Cache Poolを接続することもできますが、一度取り外した後に再接続する場合、キャッシュは消去されます。（検証目的で、キャッシュを一旦削除したい場合にも有効です。）

```shell-session
# lvconvert --splitcache hybrid_vg0/lv0
  Flushing 0 blocks for cache hybrid_vg0/lv0.
  Logical volume hybrid_vg0/lv0 is not cached and cache pool hybrid_vg0/cache is unused.
```

取り外した結果。Cache Pool自体は残ってます。

```shell-session
# lvs -a hybrid_vg0
  LV              VG         Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  cache           hybrid_vg0 Cwi---C--- 200.00g
  [cache_cdata]   hybrid_vg0 Cwi------- 200.00g
  [cache_cmeta]   hybrid_vg0 ewi-------   2.00g
  lv0             hybrid_vg0 -wi-ao----  <1.82t
  [lvol0_pmspare] hybrid_vg0 ewi-------   2.00g
```

再度キャッシュを有効にしたいときは、構築時と同じコマンドで、LVを結合します。

```shell-session
# lvconvert --type cache --cachepool /dev/hybrid_vg0/cache /dev/hybrid_vg0/lv0
Do you want wipe existing metadata of cache pool hybrid_vg0/cache? [y/n]: y
  Logical volume hybrid_vg0/lv0 is now cached.

# lvs -a hybrid_vg0
  LV              VG         Attr       LSize   Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  [cache]         hybrid_vg0 Cwi---C--- 200.00g                     0.01   0.32            0.00
  [cache_cdata]   hybrid_vg0 Cwi-ao---- 200.00g
  [cache_cmeta]   hybrid_vg0 ewi-ao----   2.00g
  lv0             hybrid_vg0 Cwi-aoC---  <1.82t [cache] [lv0_corig] 0.01   0.32            0.00
  [lv0_corig]     hybrid_vg0 owi-aoC---  <1.82t
  [lvol0_pmspare] hybrid_vg0 ewi-------   2.00g
```

## 3-1. 一度作成したLogical Volumeのcache modeを確認する方法（2018/10/7 追記）

一度cache付き論理ボリュームを作成した後、ボリュームのcache modeを確認をしたいときは、lvsコマンドで、次のような引数を使います。
（"hybrid_vg0"の部分は、確認したいボリューム名）

```shell-session
# lvs -o+cache_mode  hybrid_vg0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert CacheMode
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 90.22  0.48            0.06             writeback
```

右端にcache modeが出力されます。（この出力例では "writeback"）

#4. メモ：遅延書き込みが発生している状態

write backモードで利用している状態で大きなデータの書き込みを行うと、コピープロセスの完了後も、SSDからHDDへの書き込みを継続します。（遅延書き込み）。

この状況は、lvsコマンドで出力される"Cpy%Sync" というカラムで確認できるようです。（正式なドキュメントなどは見つかりませんでした。）。徐々に0%に近づいていくのが解ります。

```shell-session
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            69.32
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            69.03
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            68.96
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            67.11
 ・
　　・
　　・
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            5.10
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            4.40
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            3.66
# lvs -a hybrid_vg0/lv0
  LV   VG         Attr       LSize  Pool    Origin      Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  hybrid_vg0 Cwi-aoC--- <1.82t [cache] [lv0_corig] 100.00 0.32            0.00

```

# 参考情報

 - Qiita： [[CentOS] dm-cacheを使用してSSDをHDDのキャッシュにする
](https://qiita.com/sengoku/items/e85526bb7d578aee295a)
 - RED HAT: [DM_Cache](https://people.redhat.com/mskinner/rhug/q1.2016/dm-cache.pdf)
 - RED HAT BLOG:  [Improving read performance with dm-cache
](https://www.redhat.com/en/blog/improving-read-performance-dm-cache)
 - Aralakh Company Blog: [LVM-Cache](https://dranek.com/blog/2015/May/lvm-cache/)
 - bootloader blog: [SSD Caching Using dm-cache Tutorial
](https://blog.kylemanna.com/linux/ssd-caching-using-dmcache-tutorial/)
 - Fibrevillage: [How to remove cache of a cache Logivcal Volume
](http://fibrevillage.com/storage/464-how-to-remove-cache-of-a-cache-logivcal-volume)
 - Wikipedia: [https://en.wikipedia.org/wiki/Dm-cache](https://en.wikipedia.org/wiki/Dm-cache) 
 - man7.org : [LVMCACHE(7)](http://man7.org/linux/man-pages/man7/lvmcache.7.html)