えんでぃの技術ブログ

えんでぃの技術ブログ

ネットワークエンジニアの視点で、IT系のお役立ち情報を提供する技術ブログです。

zram swappingによるRAM節約、高速I/O【Fedora33以降】

swap

お伝えしたいこと

Fedora33以降では、ディスク上に割り当てたswap領域 (swapパーティション、swapファイル) をほぼ使わなくなりました。
代わりにzramと呼ばれるメモリ上の領域を使います。

もちろん、仕組み上ディスクを利用したswapよりもzramの方が従来よりもメモリ消費量は少し上がります。
しかし、zramに入るデータは圧縮される上、メモリなのでI/O性能が良くなることが期待されています。
メモリ容量の少ない環境においては、この圧縮機構によって寧ろメモリ容量を節約できてしまいます。

そんなzramを使ったswapの画期的な実装について、今回は簡単に触れたいと思います (※)。

(※) ちなみにWikipedia - zramによれば、zram自体は2014/3/30にLinux kernel 3.14でzramは安定していると判断され、メインラインにリリースされています。実は結構歴史がある技術なんですね

Fedora32以前の挙動

概要

恐らく、今動作している大半のLinuxのデフォルトに該当する挙動です。
swap領域はディスク上に作成します。
作成方法は、以下の流れです。

  1. swap専用のパーティション、またはファイルを用意する
  2. swap用にフォーマットする
  3. OS起動時にswap領域を自動認識させる
  4. swap領域を有効化する

RHEL8の公式手順1を参考にしつつ、コマンド例を簡単に示します。
既に知っているという方は、#Fedora33以降の挙動までスキップしてください。

swap用のパーティション、またはファイルを用意する

swapファイルを使う場合は、ddコマンドでファイルを作成しておきます。
以下のコマンドでは1KiBのブロックを1048576 (1024*1024) 個生成し、1GiBのswapfileを生成しています。

sudo dd if=/dev/zero of=/swapfile bs=1024 count=1048576
sudo chmod 600 /swapfile

swapパーティションを作る場合は、fdiskなどでパーティションを作っておきます。
パーティションの作成方法については、ここでは割愛します。
必要に応じて、過去の記事を参考にしてください。
ここでは、/dev/vda6パーティションを作成し、swap用に割り当てたとします。

swap用にフォーマットする

mkswapコマンドにファイルパス、またはデバイスファイルを指定してフォーマットします。

sudo mkswap /swapfile
# または、sudo mkswap /dev/vda6 (swapパーティションの場合)

OS起動時にswap領域を自動認識させる

/etc/fstabに以下の行を追加します。

/swapfile swap swap defaults 0 0
# または、/dev/vda6 swap swap defaults 0 0 (swapパーティションの場合)

/etc/fstabを編集したら、systemdが変更を認識できるように以下のコマンドを実行し、各unitに設定ファイルを再読込させます。

sudo systemctl daemon-reload

swap領域を有効化する

swaponコマンドでswap領域をその場で有効化します。
OS再起動は不要です。

sudo swapon /swapfile
# または、sudo swapon /dev/vda6 (swapパーティションの場合)

確認

swapon --showでswap領域が認識されたことを確認できます。
freeでも、swap領域が増えたことを確認できます。

swapon --show
# NAME      TYPE      SIZE USED PRIO
# /swapfile partition   1G   0B   -2

free -h
#       total  used   free  shared  buff/cache  available
# Mem:   62Gi  17Gi   14Gi   1.3Gi        30Gi       43Gi
# Swap: 1.0Gi    0B  1.0Gi

Fedora33以降の挙動

概要

Fedora33では、デフォルト設定でzramと呼ばれる領域がメモリ上に作成され、swap領域として使用されます。
実はswap領域にはpriorityというパラメータがあり、複数のswap領域が存在する場合にはpriorityが高い領域から優先的に使用されます。
デフォルトでは、以下のpriority値になっていました。

swapの作成場所 priority値
zram 100
ディスク
(swap file, swap partition)
-2

つまり、Fedora33以降では、ディスク上のswap領域は基本的に使われません。
zram上のswap領域が枯渇した場合のみ、ディスク上のswap領域が使われ始めます。
滅多にないと思いますが、priorityが同じデバイスが複数あった場合にはラウンドロビンになります。
※この挙動は、man 2 swaponに書いてあります

swapのpriorityは、zramについてはzram-generator.confで (#後述)、ディスクについては/etc/fstabswaponのオプションで指定可能です。
ディスクのpriorityのデフォルト値は、man mkswapによると本来-1のはずです。
ただ、実機を見ると-2になっていたので、実質的にzram以外のswapはデフォルトで最小のpriorityになります。
-2に制御しているところがどこにあるかまではわかりませんでした。

zramによるswap作成を無効化してFedora32以前の動作に戻すことも可能ですが、メリットもあるので不具合等なければそのまま使うのが良いでしょう。

以下のセクションで、仕様の詳細に触れていきたいと思います。

段々深くなっていきますが、#zramとは#zramの特徴はオススメです。
Fedoraを初めとするRed Hat系のディストリビューションをお使いの方は是非目を通してください。

なお、本記事ではFedoraのmanやリリースノートを主な情報源としています。
Linux Kernelのレイヤーでの深い解説については、後述の#参考記事にわかりやすい記事がありますので、そちらを参考にしてください。

zramとは

zramとはRAM上に動的に生成されたブロックデバイスです。
zram上に格納されるデータは全て圧縮され、対応する無圧縮のメモリ使用量と比較して、zramにswap outされた時のデータサイズは小さくなります。

「RAM上にブロックデバイスを動的に生成する」といえばRAM Diskというものもありますが、zramの場合はデータを圧縮するため、容量効率が良くなります。

バイスファイルは、/dev/zramN (Nは0以上の整数)で、Fedoraでは/dev/zram0がデフォルトで作成されています。

Linux Kernelには、zswapという似たような技術もありますが、zramとは別物です。2, 3

zramの特徴

zram視点でメリット/デメリットを列挙します。

- 詳細
Disk I/Oが少なくなる。
swap outを伴うI/O処理が高速化される。
Flashバイス (SSDなど) を使用している場合も、繰り返しの書き込みに伴う故障/性能劣化 (wearing out) を軽減する効果がある
RAM容量を効率的に活用できる。
zramも結局のところRAM上にswapをするので、RAM容量を消費する。
しかし、データ圧縮によってswap outされた領域が約1/2程度のサイズになるので、メモリが少ない環境において十分に役に立つ (参考)
データ退避の効率が50%程度 (目安)。
1つ上の行の言い換えになる。
データ圧縮効率が50%ということは、サイズ100のデータをswap outすることで、サイズ50のメモリ容量を消費するということ。
ディスク退避する場合は、サイズ100のデータをswap outした場合のメモリ消費量は0。
この点も考慮して、zramのswap sizeはディスクの場合と比べて2倍程度で検討するのが良いという考え方もありそう (えんでぃ主観)
圧縮処理に多少のリソースを使う。
メモリのオーバーヘッドは0.1%程度 (1GiBのswapあたり、メタデータなどにより1MiBを追加で消費) です。
CPUにも負荷がかかります。
従って、CPU/メモリの負荷を考慮すると、zramのサイズを極端に大きくすべきではありません (参考)
× 圧縮効率の悪いデータとは相性が悪い。
効率の悪いデータを頑張って圧縮しても、CPU時間を浪費するだけになってしまう...可能性がある (参考)。
しかし、Fedora33で試したところ実際は圧縮効率が悪いケースは観測されなかったという報告もあり、Fedora34以降はよりzramを積極活用するようチューニングされた (参考)

まとめると、全体的にメリットが大きいですが以下の点には注意が必要です。

  • ディスクに退避する場合と比較してRAM消費量が大きくなる
  • CPU使用率が上がる

逆に、主観ですがCPU、メモリ共に余裕のある設計であれば、あまり気にしなくても良いと思います。

zramの設定ファイル

zramのデフォルトの挙動は、/usr/lib/systemd/zram-generator.confに記載があります。
このファイルを直接編集することは推奨されていません。
編集方法については、次の#zram-generator.confの設定変更にて触れます。

Fedora34以降のファイルの中身は、以下のようになっています。

[zram0]
zram-fraction = 1.0
max-zram-size = 8192

ちなみに、Fedora33時点では以下の設定内容でした。
/dev/zram0を作成し、後はデフォルト値を使用していました。

[zram0]

[zramN] (Nは0以上の整数)の配下にパラメータを設定することが可能です。
man zram-generator.confによると、デフォルト値は以下のようになっています。
※Fedora33では、man 5 zram-generatorで確認できます
※手元のFedora35で使っているカーネルバージョンは、5.14.16-301.fc35.x86_64です
※swapと関係ないパラメータはいくつか省略しています

パラメータ名 詳細
host-memory-limit zramに割り当て可能な仮想メモリ容量 (物理メモリ容量 + swapメモリ容量) の上限を指定する。
/proc/meminfo相当の情報で、詳細はman procを参照。
単位はMB。
この上限を超えると、zramデバイスはそもそも生成しなくなる。
デフォルト値はnoneのため、制限なし
zram-fraction zramデバイスのサイズを決定する係数 (比例定数) を指定する。
0以上の小数が入る。
デフォルト値は0.5。
例えばホストのRAMサイズが8GiBで、zram-fraction=0.5の場合、zramサイズは4GiBとなる。
zram-fraction=1.0の場合、ホストのRAMサイズと同等のzramサイズになる。
実際のzramサイズは、この上でmax-zram-sizeの影響も受けて最終的に決定される
max-zram-size zram-fractionで得られたzramサイズの上限値を指定する。
単位はMB。
noneを指定すると上限無しになる。
デフォルト値は4096
compression-algorithm 圧縮処理のアルゴリズムを指定する。
デフォルト値はカーネルによって決まる。
※Fedora35ではlzo-rleでした
他に指定可能なアルゴリズムは、/sys/block/zram0/comp_algorithmから読み取れる。
※Fedora35では、lzo lzo-rle lz4 lz4hc 842 zstdでした
swap-priority swapのpriority値を指定する。
-1〜32667の間で指定可能。
デフォルト値は100。
/etc/fstabでswapのpriorityを指定していない場合は、ディスクベースのswapは負のpriority値を持ちます。従って、デフォルトの挙動ではディスクよりもzramのswapが優先的に使用されます

なお、本セクションで述べた「zramデバイスのサイズ」とは、圧縮前のサイズです (#後述のzramctlのDISKSIZE相当)。
実際に消費されるRAMサイズ (zramctlのCOMPRやTOTAL相当) は、zramデバイスのサイズよりも小さくなります。
Fedora34以降で採用されたzram-fraction = 1.0という設定は確かにアグレッシブではありますが、実際にzramctlで圧縮効率を計測して1/2や1/3であったことから、ある程度勝算を持ってのパラメータ設計となっています4

まとめると、zram-generator.confのデフォルト設定 (=Fedora33のデフォルト) では以下のような動作になります。
※zramのサイズはRAMサイズの0.5倍。ただし、4GiBが上限

zram device size [MB]
    ^
    │
 4G>│               ooooooooooooo
    │             o
    │           o
    │         o
 2G>│       o
    │     o
    │   o
512M>│ o
    0───────────────────────> total usable RAM [MB]
      ^     ^       ^
      1G    4G      8G

Fedora35のデフォルト設定はzram-fraction=1.0, max-zram-size=8192のため、以下のようなグラフになります。

zram device size [MB]
    ^
    │
 8G>│               ooooooooooooo
    │             o
    │           o
    │         o
 4G>│       o
    │     o
    │   o
 1G>│ o
    0───────────────────────> total usable RAM [MB]
      ^     ^       ^
      1G    4G      8G

zram-generator.confの設定変更

本セクションの情報は、man zram-generator.confに基づいています。

設定ファイルの配置場所は、これだけあるようです。

  • /usr/lib/systemd/zram-generator.conf
  • /usr/local/lib/systemd/zram-generator.conf
  • /etc/systemd/zram-generator.conf
  • /run/systemd/zram-generator.conf

  • /usr/lib/systemd/zram-generator.conf.d/*.conf

  • /usr/local/lib/systemd/zram-generator.conf.d/*.conf
  • /etc/systemd/zram-generator.conf.d/*.conf
  • /run/systemd/zram-generator.conf.d/*.conf

デフォルト設定は、/usr/lib/systemd/zram-generator.confにあります。
その後、パッケージインストールなどによって設定ファイルが追加配置される場合は、/usr/local/lib/systemd/zram-generator.conf.d/*.confにファイルが追加されていきます。

*.conf *.confのファイルは文字列ソート順に読み込まれます。
後から読み込まれた設定値が先に読み込んだ設定値を上書きします。

手動で設定変更する場合は、/etc/systemd/zram-generator.conf/etc/systemd/zram-generator.conf.d/*.confに記載します。
/etc配下にファイルを配置すると、/usr/lib配下のファイルが読み込まれなくなります。
従って、既存設定が必要な場合はそちらも忘れずに書く必要があります。

裏を返せば、sudo touch /etc/systemd/zram-generator.confで空のファイルを生成すれば、デフォルトの/dev/zram0を作成するという挙動がなくなります。
つまり、zramによるswap動作を無効化できます5
実際にファイルを生成し、OS再起動したら反映できました。

zram、swapの状態確認

zramctlswapon --showでswapの状態を確認できます。

  • zramctl: swapに関わらず全てのサイズが0以上のzramデバイスを表示
  • swapon --show: zramに関わらず全てのswap領域を表示

手元のFedora35で実行したログを掲載します。

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

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

swapon --show
# NAME       TYPE      SIZE USED PRIO
# /swapfile  file        2G   0B   -2
# /dev/zram0 partition   8G   0B  100

zramctlの出力について補足します。
zramctl --helpから確認できます6

項目が多いですが、実用上はNAME, DISKSIZE, DATA, TOTALあたりを見れば十分だと思います。
zramctl --output-allを使うことは基本ありません。

列名 今回の値 意味
NAME /dev/zram0 zramデバイス
DISKSIZE 8G zramに格納可能なデータサイズの上限 (未圧縮換算)
DATA 4K zramに実際に格納されているデータサイズ (未圧縮換算)
COMPR 74B zramに実際に格納されているデータサイズ (圧縮済換算)
ALGORITHM lzo-rle 圧縮に使用しているアルゴリズム
STREAMS 8 圧縮処理の同時並列実行数
ZERO-PAGES 0 page frameと紐付いていないswap page数 (※)
TOTAL 12K COMPR (実データ) に、メモリフラグメンテーションメタデータなどのオーバーヘッドを加えた正味のデータサイズ (圧縮済換算)
MEM-LIMIT 0B データ格納に使えるメモリ容量の上限 (圧縮済換算)
zram-generator.confhost-memory-limitなど変えてみましたが、この値とは関係ありませんでした
MEM-USED 12L zramが圧縮データを格納するために使用しているメモリサイズ
※TOTALとの違いがいまいちわかりません
MIGRATED 0B memory compactionによって移動されたメモリのサイズ1
MOUNTPOINT [SWAP] zramデバイスがマウントされている場所 (※2)

(※1)
swap pageとは、swap領域上のvirtual pageのことだと理解しています。
page frameとは、物理メモリ上の最小アドレス単位です。
virtual pageとは、仮想メモリ上の最小アドレス単位で、OSが認識するメモリアドレス単位でもあります。7,
仮想メモリとは、OSが認識している抽象化されたメモリで、物理的に複数枚に分かれたメモリやswapなどを単一のアドレスで管理するものです8
memory pageについてより深く知りたい方は、Wikipedia - Memory pagingもチェックしてみてください

(※2)
zramそのものはブロックデバイスを生成する仕組みで、swapだけに使われるとは限りません

今回はzramctlコマンドを参照目的で使いましたが、このコマンドによりzramの動作を動的に変更することも可能です。

参考記事

この記事よりも先に、@shimauma_Zzzzzさんがzramについて深く解説されていました。
Linux Kernelのドキュメントまで踏み込んで、低レイヤーから仕様を解説してくださっています。
/proc周りの値の意味や、echoで書き換えた時の挙動まで解説されていて、zram-generatorの内部処理をより具体的にイメージできました。
この記事を書くに先立ち、私も大いに勉強させていただきました。
ありがとうございます。

qiita.com

(参考) swap領域の一般論

参考情報として、swapに関する雑多な情報をこちらに書きます。
参考元URLなどは特にありません。
カジュアルなまとめとして見てください。

swap領域とは、基本的にメモリ容量が足りなくなった時にメモリ上のデータが退避される領域です。
ファイルシステムによってはキャッシュに使われることもあるようで、メモリが枯渇していなくても少量使われることがあると聞いたことがあります。

多くの構成では、swap領域はディスク上に作成されるため、メモリと比較してI/Oが遅いです。
特にハードディスクを使っている環境において、メモリ不足に起因するswap退避が大量に発生すると性能劣化が顕著になると言われています。

性能観点でのswap領域に関する議論は、様々なブログや掲示板で展開されています。
例えば...

  • 今どきのメモリ容量は十分に大きいので、swap領域を作る必要はない (過去に口頭で言われたことがあります)
  • RHELについては、RAMサイズの半分のswap領域を作成することが推奨。メモリ8GiB以上の場合は、4GiBのswap領域が一つの目安9
  • swappinessを変更することで、swap発生頻度を調整する主張もある (全ての主張は、ブログや掲示板など非公式のものです)
    • swappinessをデフォルトの60よりも小さく、1〜3にすべきという意見がある (根拠は怪しい)
    • 変えるべきではないとの意見もある
    • zramを使う場合は最大の100でも良いという意見もある
    • swappinessの挙動の理解は非常に難しい。親切なドキュメントはない。ソースコードを追えたとしても難しい
    • swappinessの挙動は、過去に大幅に変わったことがある。今ある解説記事も最新とは限らない

このような形で、意見を総合して明確な結論を出すことは難しいです。
明確な根拠はありませんが、私は以下の方針としました。

  • swappinessはいじらない
  • swap領域のサイズは、RHELの推奨に従う (検証サーバの場合は1GiBなどにすることも多い)

Fedora33以降の場合はzramに任せておけば自動的に上記の構成になるので、楽で良いですね。
時代が進むに連れて仕組みが整備されているので、理由がなければ標準に乗るのが一番です。

まとめ

Fedora34、Fedora35で大幅に挙動が変わったzram, swap周りについて紹介しました。
RHEL9も同様の設計になった場合、swapfileやswap partitionを作るのはやめようかな...と思い始めています。

今後の動きが楽しみですね。