Zswapの設定メモ

ややメモリ不足気味な環境、コンテナでのテスト中にスワップをガリガリさせないためにzswapを有効にしてみました。調べた内容をメモ。

### zswapとzramの違い

- zswap: memory managerのswap前段に置かれる圧縮バッファとして動作するのでswap専用
- zram(zram_swap): 圧縮オンメモリブロックデバイスなのでswap以外の任意のファイルシステムを置ける

#### zswapの挙動

zswapは実スワップの前段で実際のスワップ発生を回避するためのバッファのように動作する。実メモリが不足したときはLRUで最も古い圧縮済みページから実スワップに退避することができる。

zramは実質RAMディスクに置かれたスワップパーティションの一つなので、実際にどのページがzram上の圧縮スワップにスワップアウトされるかは（swap priority以外では）制御されていない。そのため、一旦zramが比較的古いページで一杯になると後からスワップされるページが実swapに直接書かれてしまう、といった優先度逆転（LRU inversion）が起こることがある。

どちらの手法もスワップ発生が低頻度ながら定常的に発生することが許容される、少ないメモリ、低アクティビティな環境が主な対象になる。

### 基本設定

```shell
#echo 20 > /sys/module/zswap/parameters/max_pool_percent
echo z3fold > /sys/module/zswap/parameters/zpool
echo lz4 > /sys/module/zswap/parameters/compressor
echo 1 > /sys/module/zswap/parameters/enabled
```

#### zpool

圧縮後のページをどのようにメモリ上に配置するか管理している。

実ページ1個（4KB)に`zbud`は圧縮ページ2個を、`z3fold`は3個を詰め込む事ができる。つまりlz4など圧縮アルゴリズム自体の圧縮率がいくら良くても、実効圧縮比はそれぞれ2倍,3倍が上限となる。

スワップという性能特性上、空き領域探索、デフラグメント・コンパクションの容易さから、実ページに整数個の圧縮ページを置くzbudかz3foldが用いられる。
参考: https://elinux.org/images/d/d3/Z3fold.pdf

`zsmalloc`は複数ページに渡って隙間なく圧縮ページを置くことでlz4使用時3~4倍ほどの圧縮比を得られる。一方、LRUに必要なデータを保持していないため実質効率が時間経過に伴って悪化しやすい、コンパクション時負荷が大きい点が不利となる。CPUよりもとにかくメモリが厳しい場合向け。

なおmax_pool_percentは圧縮後のzpool消費メモリを指定する。圧縮前のスワップパーティションのサイズで指定するzramとは異なるので注意。

#### compressor
圧縮方式。

手元の環境では`lz4`で圧縮率が3倍に達してしまうため、`z3fold`との組み合わせでは他を選択する余地に乏しい。

`lz4hc`はlz4より圧縮率が良いが、1桁ほど圧縮が遅く今どきのCPUでも数十MB/sとHDDと同程度の性能になる。microsdなど致命的に遅いストレージしかない場合向けか。

### 統計値

```shell
# grep -R . /sys/kernel/debug/zswap
/sys/kernel/debug/zswap/same_filled_pages:965
/sys/kernel/debug/zswap/stored_pages:19924
/sys/kernel/debug/zswap/pool_total_size:39059456
/sys/kernel/debug/zswap/duplicate_entry:0
/sys/kernel/debug/zswap/written_back_pages:0
/sys/kernel/debug/zswap/reject_compress_poor:13
/sys/kernel/debug/zswap/reject_kmemcache_fail:0
/sys/kernel/debug/zswap/reject_alloc_fail:0
/sys/kernel/debug/zswap/reject_reclaim_fail:0
/sys/kernel/debug/zswap/pool_limit_hit:0
```

pool_total_sizeがzswapが使用しているメモリ。stored_pagesが4KBページ数なので実効圧縮率は `stored_pages*4096/pool_total_size`。これはlz4などの素の圧縮率ではなくzpoolのメモリ管理オーバーヘッド込の値なので、swap使用量が減少しているタイミングではフラグメントの分だけ実効圧縮率が悪化する。

reject_*はそれぞれの理由でzswapを経由せずスワップに退避されたページ数カウンタ。これが急激に増え続けるようならzswapの使用は諦めたほうがいい。

same_filled_pagesは同一値で埋まったページ(全部NULの繰り返しなど）を通常のpoolとは別リストで管理しているもの。

### 実効圧縮率
自分の使用ケース、コンテナを複数立ち上げている＝ランダムデータ率が低く圧縮しやすいシステムアプリ主体の場合、起動後数分で安定し

* lz4+z3fold: 1/2.7 ~ 1/2.9
* lz4+zsmalloc: 1/3.5 ~ 1/4.0

ほどの圧縮率が得られた。lz4で1/3を超えているためz3fold使う上ではlz4で十分。zsmallocをさらに効率よく使いたければlz4hc、今後zpoolが対応すればzstdが候補になるかもしれない。

### バグ？

Ubuntu 18.04 bionicの4.15で試していたところ、メモリ負荷が上がるタイミングでBUGも何も出ず固まることが何度か発生した。実環境で試すのはおすすめできない。
Ubuntu 18.10 cosmicの4.18では同じシナリオで再現できなかったので修正されているのかも。
少なくともbionicに入っているz3foldがダメのようだ。zbudかzsmallocにすることで1月ほど回避できていた。だいたいhwe-edgeに移行してしまったので現状不明。

Ubuntu 20.04 FocalのHWE 5.11.0でzsmallocを使うと負荷時にswapin/outでスタックする。修正は5.13.2にマージされたようだ。
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/mm/zswap.c?id=46b76f2e09dc35f70aca2f4349eb0d158f53fe93

### 参考
- https://www.kernel.org/doc/Documentation/vm/zswap.txt kernel doc
- https://wiki.archlinux.org/index.php/zswap zswapの基本情報
- https://lwn.net/Articles/748681/ lz4含むメモリ圧縮ベンチマーク
- https://elinux.org/images/d/d3/Z3fold.pdf z3fold