ZRAM について軽く調べてみた

#初めに

久しぶりに Fedora (fedora 33) を触ったところ、swap が swap-on-zram という見慣れない形式に変わっていたので zram について調べてみました

---
#サマリー

- メモリ上にブロックデバイスを構成し、そこに書き込むデータを圧縮することにより高速な I/O とメモリ領域の有効活用を行う技術

要は圧縮機能付き RAM Disk です。 swap や /tmp, /var の一部キャッシュなどの用途に使われることが想定されているようです。
「swap をメモリ上に置く」と聞くと本末転倒な印象を受けますが、メモリ上のあまり使われていないブロックを圧縮する、というとすんなり理解ができると思います。（ただしこれは正確な表現ではありません）
また、圧縮する際に中間データなどを Disk 上に置かないようにすることにより、Disk I/O を排除し高速化が図られています。
Disk 上の swap 領域との I/O よりも、メモリ上の圧縮/展開の方が高速なため、「メモリ上の swap 領域」というものが実現されます。

イメージとしてはこんな感じです　↓
![20201205_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/e95c0cad-2771-2b69-a104-4b69e327203c.png)

メモリを大量に使い swap まで含めた処理を行うような場合、Disk に swap out するよりも zram 上に swap out する方が（圧縮処理の時間はかかるものの）速いうえに、圧縮することにより swap も含めた仮想メモリ全体の容量も増えることになります。

以下、swap からは離れて、zram 自体について、[zram: Compressed RAM based block devices](https://www.kernel.org/doc/Documentation/blockdev/zram.txt) に沿ってみていきたいと思います。

#環境
fedora 33 ( on KVM )
4GB メモリ

```shell-session:lsmem
# lsmem
RANGE                                 SIZE      STATE REMOVABLE BLOCK
0x0000000000000000-0x000000007fffffff   2G オンライン       yes  0-15
0x0000000100000000-0x000000017fffffff   2G オンライン       yes 32-47

メモリブロックサイズ  128M
Total online memory:       4G
Total offline memory:      0B
```

```shell-session:zramctl
# zramctl --output-all
NAME       DISKSIZE DATA COMPR ALGORITHM STREAMS ZERO-PAGES TOTAL MEM-LIMIT MEM-USED MIGRATED MOUNTPOINT
/dev/zram0     1.9G   4K   74B lzo-rle         2          0   12K        0B      12K       0B [SWAP]


# zramctl -h　　　　（※抜粋）

利用可能な出力の列:
        NAME  zram デバイス名
    DISKSIZE  圧縮前のデータ容量による制限
        DATA  圧縮前の格納されたデータのサイズ
       COMPR  圧縮後の格納されたデータのサイズ
   ALGORITHM  選択された圧縮アルゴリズム
     STREAMS  並行して実行される圧縮オペレーションの数
  ZERO-PAGES  empty pages with no allocated memory
       TOTAL  all memory including allocator fragmentation and metadata overhead
   MEM-LIMIT  memory limit used to store compressed data
    MEM-USED  memory zram have been consumed to store compressed data
    MIGRATED  number of objects migrated by compaction
  MOUNTPOINT  マウントされている場所
```
※ zramctl は util-linux パッケージに含まれています


```shell-session:free状況
# free -m
              total        used        free      shared  buff/cache   available
Mem:           3928         165        2705           0        1057        3538
Swap:          1963           0        1963
```

#zram 試してみた

`free -m` を見る限り、 メモリ量としては 4GB と認識されているようです。
また、 `vmstat 1 1` を見ても、 おおよそ 4GB ありそうです。
そのため、正確な根拠はありませんが、OS から見たメモリ量としては 4GB のままのようです。
(なお、zram を削除しても free -m の total 値は変わりませんでした）

## zram の作成（zramctl)
swap は操作が面倒なので、ファイルシステムとして作成してみます。
下記の例の zram0 は swap on zram で利用されています。

zramctl コマンドで追加することができます。

```shell-session:zram追加
# zramctl -f -s 500M
/dev/zram1
```

##zram の構成概要確認

zram の各種内容は `zramctl --output-all` で確認できます。

```shell-session:zram確認
# zramctl --output-all
NAME       DISKSIZE DATA COMPR ALGORITHM STREAMS ZERO-PAGES TOTAL MEM-LIMIT MEM-USED MIGRATED MOUNTPOINT
/dev/zram1     500M   0B    0B lzo-rle         2          0    0B        0B       0B       0B
/dev/zram0     1.9G   4K   74B lzo-rle         2          0   12K        0B      12K       0B [SWAP]
```
まだどこにもマウントしていないので、 MOUNT-POINT はブランクです。
また、ドキュメントによると、メモリブロックサイズの倍数に切り上げられるそうです。

##ファイルシステムの作成

mkfs コマンド等でファイルシステムを作成すると、圧縮 RAM Disk として利用可能です。

```shell-session:xfs作成
# mkfs.xfs /dev/zram1
meta-data=/dev/zram1             isize=512    agcount=4, agsize=32000 blks
         =                       sectsz=4096  attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1
data     =                       bsize=4096   blocks=128000, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=1221, version=2
         =                       sectsz=4096  sunit=1 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
Discarding blocks...Done.
```

ファイルシステムのマウント

```shell-session:マウント
# mkdir /data/zram1
# mount /dev/zram1 /data/zram1
# df -h
ファイルシス                    サイズ  使用  残り 使用% マウント位置
/dev/zram1                        496M   29M  467M    6% /data/zram1
```

##zram ファイルシステムを埋めてみる

`dd` コマンドを使って、ファイルシステムをデータで埋めてみます。


```shell-session:dd
# pwd
/data/zram1

# dd if=/dev/urandom of=/data/zram1/randfile bs=1M count=1024
dd: '/data/zram1/randfile' の書き込みエラー: デバイスに空き領域がありません
467+0 レコード入力
466+0 レコード出力
488701952 bytes (489 MB, 466 MiB) copied, 3.4548 s, 141 MB/s

# df -h .
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/zram1       496M  495M  428K  100% /data/zram1

# zramctl
NAME       ALGORITHM DISKSIZE  DATA  COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram1 lzo-rle       500M  469M 466.1M 466.3M       2 /data/zram1
/dev/zram0 lzo-rle       1.9G    4K    74B    12K       2 [SWAP]
```

zramctl で見る限り、 データは 469MiB あり、 圧縮して 466.1MiB のようです。
urandom でランダム文字列を作成しているため、圧縮効率は低いです。

いったん削除してから zero で書き込みしてみます。

まず、ファイルを削除します。

```shell-session:rm
# rm randfile
rm: 通常ファイル 'randfile' を削除しますか? y

# df -h .
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/zram1       496M   29M  467M    6% /data/zram1

# zramctl
NAME       ALGORITHM DISKSIZE  DATA  COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram1 lzo-rle       500M  469M 466.1M 466.3M       2 /data/zram1
```

ファイルを削除すると、 `df` の空き容量は増えますが、 `zramctl` の DATA サイズは減りません。
これは rm でファイルの i-node が削除されているだけで実データが残っているからだと思います。

次に、 zero で埋めてみます。

```shell-session:zero埋め
# dd if=/dev/zero of=/data/zram1/zerofile bs=1M count=1024
dd: '/data/zram1/zerofile' の書き込みエラー: デバイスに空き領域がありません
467+0 レコード入力
466+0 レコード出力
488701952 bytes (489 MB, 466 MiB) copied, 0.364888 s, 1.3 GB/s

# df -h .
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/zram1       496M  495M  428K  100% /data/zram1

# zramctl
NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram1 lzo-rle       500M  469M   89K  256K       2 /data/zram1
/dev/zram0 lzo-rle       1.9G    4K   74B   12K       2 [SWAP]

```

`df` コマンドでは 100% になっています。
`zramctl` で見ると、 Data は 496MiB で urandom パターンと同じですが、圧縮されて 89KiB になっているようです。

結論としては、ブロックデバイスとして確保され、ユーザーとしての利用では指定したサイズとして利用可能ですが
物理的には圧縮されている分消費が少ないようです。



##zram削除 (zramctl)

zram の削除は `zramctl -r` で削除します。　
いきなり打ってエラーを起こしているのは unmount せずに実行したらどうなるのかを見たかっただけで
決して ***忘れていた*** わけではありません。はい。

```shell-session:zram削除
# zramctl -r /dev/zram1
zramctl: /dev/zram1: リセットに失敗しました: デバイスもしくはリソースがビジー状態です

# umount /data/zram1
# zramctl -r /dev/zram1

# zramctl
NAME       ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle       1.9G   4K   74B   12K       2 [SWAP]
```

##zram とOS再起動

これも気になるところですね。 zram 再作成してデータを入れた状態を作ります。

```shell-session
# dd if=/dev/urandom of=/data/zram1/randfile bs=1M count=1024
dd: '/data/zram1/randfile' の書き込みエラー: デバイスに空き領域がありません
467+0 レコード入力
466+0 レコード出力
488701952 bytes (489 MB, 466 MiB) copied, 2.59002 s, 189 MB/s

# df -h .
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/zram1       496M  495M  428K  100% /data/zram1

# ls -l
合計 477248
-rw-r--r-- 1 root root 488701952 12月  5 23:46 randfile

```

この状態で OS 再起動します。

```shell-session
# shutdown -r now
...(略)

# ls -l /dev/zr*
brw-rw---- 1 root disk 251, 0 12月  5 23:47 /dev/zram0
```

デバイスレベルで消えているようです。まぁ、当たり前かもしれません。
（正直なところ、デバイスは残っていて中身だけ消えるのか、と思っていました。。）



##zram の構成詳細確認

zram の構成については `/sys/block/zramX/` 以下のファイルで確認することができます。

```shell-session:zram構成
# cd /sys/block/zram1
# ls
alignment_offset  compact            disksize           ext_range  inflight   max_comp_streams  power      reset   stat
bdi               debug_stat         events             hidden     initstate  mem_limit         queue      ro      subsystem
capability        dev                events_async       holders    integrity  mem_used_max      range      size    trace
comp_algorithm    discard_alignment  events_poll_msecs  idle       io_stat    mm_stat           removable  slaves  uevent
```

[zram: Compressed RAM based block devices](https://www.kernel.org/doc/Documentation/blockdev/zram.txt)に記載があったのは以下のものです

|ファイル|RW|概要|
|:--|:--|:--|
|disksize|RW|デバイスのサイズの確認・設定|
|reset|WO|デバイスのリセット（消す）|
|mem_limit|WO|圧縮データを格納するのに使えるメモリの最大容量|
|max_comp_streams|RW|圧縮処理の並列実行数|
|comp_algorithm|RW|圧縮アルゴリズムの表示/指定|
|compact|WO|メモリコンパクションの実行トリガー|
|backing_dev|RW|zram からの書き出し先ストレージデバイス|
|mm_stat|RO|メモリの使用状況|

`cat mm_stat` を行った際の表示の内容について補足します。
(下記は zram0 (swap on zram) の例です )

```shell-session
# cat mm_stat
    4096       74    12288        0    12288        0        0        0
```

|項目（左から順）|内容|
|:--|:--|
|orig_data_size|非圧縮状態でのサイズ（重複ページを除く） 単位：byte|
|compr_data_size|圧縮サイズ|
|mem_used_total|アロケートされたメモリサイズ。フラグメンテーションやオーバーヘッドを含む。（よってデータサイズ合計と一致しない）。 スペースの利用効率は compr_data_size と mem_used_total で算出される。単位：byte|
|mem_limit|圧縮データの格納に利用できるメモリの最大サイズ|
|mem_used_max|データ格納のために消費されたメモリの最大サイズ|
|same_pages|同じ内容のページの数。（1ページだけ保持され、他は同じなので保持されない）|
|pages_compacted|メモリのコンパクションで解放されたページ数|
|huge_pages|圧縮が効かないページ数|

他にも stat, io_stat, bd_stat などがありいろいろな情報が確認できます。
詳しくは [zram: Compressed RAM based block devices](https://www.kernel.org/doc/Documentation/blockdev/zram.txt) をご覧ください。

#zram 構成ファイルを用いたデバイスの操作

## zram 追加

ここでは、 zram1 を zramctl を使わずに追加します。  `/sys/class/zram-control/hot_add` を読むだけでデバイスが増えます。
（ある意味恐ろしい）
先程紹介した `/sys/block/zramX/ ` ではなく、追加削除は `/sys/class/zram-control/` 以下の hot_add, hot_remove で行います。

```shell-session:hot-add
# ls /dev/zram*
/dev/zram0
# cat /sys/class/zram-control/hot_add
1
# ls /dev/zram*
/dev/zram0  /dev/zram1
```

## zram のサイズ指定

一度デバイスができてからは、 デバイスの関連ファイルで操作します。 `/sys/block/zramX/` 以下です。

```shell-session
# cat /sys/block/zram1/disksize
0

# echo 512M > /sys/block/zram1/disksize

# cat /sys/block/zram1/disksize
536870912
```

## zram 圧縮アルゴリズム確認・指定

当記事では記載していませんが zramctl でも指定可能です

ファイルで指定する場合は、一度 reset する必要があるようです。
（ zramctl コマンドでの reset はデバイスごと消えますが、　ここでのリセットはデバイスは消えず、
　後で紹介する hot_remove で消えます）

```shell-session
# cat /sys/block/zram1/comp_algorithm
lzo [lzo-rle] lz4 lz4hc 842 zstd

# echo lz4 > /sys/block/zram1/comp_algorithm
-bash: echo: 書き込みエラー: デバイスもしくはリソースがビジー状態です

# echo 1 > /sys/block/zram1/reset

# ls -l /dev/zra*
brw-rw---- 1 root disk 251, 0 12月  5 23:47 /dev/zram0
brw-rw---- 1 root disk 251, 1 12月  6 00:17 /dev/zram1


# echo lz4 > /sys/block/zram1/comp_algorithm

# cat /sys/block/zram1/comp_algorithm
lzo lzo-rle [lz4] lz4hc 842 zstd

```

[] の位置が変わっているのがわかると思います。あと当然ではありますが、 reset したときにデータは消えますのでご注意ください。

## zram 削除

削除は hot_remove を使います。　場所が違いますのでご注意を

```shell-session
# ls -l /dev/zra*
brw-rw---- 1 root disk 251, 0 12月  5 23:47 /dev/zram0
brw-rw---- 1 root disk 251, 1 12月  6 00:17 /dev/zram1

# echo 1 > /sys/class/zram-control/hot_remove

# ls -l /dev/zra*
brw-rw---- 1 root disk 251, 0 12月  5 23:47 /dev/zram0

```

この記事では、 swap on zram というところから zram の操作について簡単に調べたことをまとめました。
ここでは確認していませんが、 Backing Device を用意してディスクにデータを吐き出させるようなことも
できるようです。
詳しくは参考のドキュメントをご覧ください。

---
参考）
■Compcache: in-memory compressed swapping
https://lwn.net/Articles/334649/
■Zram
https://ja.wikipedia.org/wiki/Zram
■zram: Compressed RAM based block devices
https://www.kernel.org/doc/Documentation/blockdev/zram.txt

----