深夜2時、けたたましいPagerDutyのアラートで目が覚めました。PostgreSQLとRedisを稼働させているクラスタノードが赤信号を灯しています。Load averageは1.8から一気に28.5へと跳ね上がり、APIは504タイムアウトを連発。大急ぎでSSH接続しdmesg -Tを実行すると、見慣れた悪夢が目に飛び込んできました。Out of Memory (OOM) Killerが無慈悲にもワーカープロセスを強制終了していたのです。8GBのRAMは完全に枯渇。カーネルがストレージ上のswapfileとメモリページを必死に行き来させていたため、Disk I/Oは100%に達し、%iowaitは85%まで急上昇していました。
Linuxにおける3つのメモリ救済アプローチ
サーバーのRAMが不足した際、エンジニアが検討すべき技術的選択肢は主に3つあります。
1. ディスク上のSwapファイルまたはSwapパーティションの拡張
最もクラシカルなアプローチです。fallocateでストレージ上に4GB〜8GBのファイルを確保し、フォーマットしてswaponで有効化します。
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
2. Zswap(Swap用圧縮キャッシュ)
ZswapはRAMと物理ストレージの間のバッファ層として機能します。カーネルがメモリページをスワップアウトしようとすると、zswapはそのページを圧縮してRAM上のキャッシュ領域に保持します。このキャッシュ領域が満杯になった場合にのみ、圧縮データが物理ディスク上のswapfileへとフラッシュされます。
3. ZRAM(圧縮RAMブロックデバイス)
ZRAMはRAM上に完全に常駐する仮想ブロックデバイス(/dev/zram0など)を作成します。ここに書き込まれるすべてのデータは、zstdやlz4といった超高速アルゴリズムによってリアルタイムに圧縮されます。カーネルからは通常のSwapパーティションとして認識されますが、アクセスレイテンシは物理ディスクのミリ秒(ms)単位ではなく、マイクロ秒(µs)単位に抑えられます。
徹底比較:各方式のメリットとデメリット
各ソリューションにはそれぞれ適したユースケースが存在します。
従来のディスクSwap
- メリット: 30秒もあればセットアップ完了。空きディスク容量が許す限り自在に拡張可能で、物理RAMを追加で1MBも消費しません。
- デメリット: 読み書き速度がRAMと比べて数千倍遅くなります。RAMが枯渇するとディスクスラッシング(disk thrashing)という破滅的状況に陥り、I/Oが完全に詰まり、CPUが
iowait状態に張り付いてサーバーが無応答になります。また安価なSSDの場合、1日に数十GBものSwap書き込みが継続的に発生すると、TBW(Total Bytes Written: 総書き込み容量)の寿命を急激に縮めてしまいます。
Zswap
- メリット: ディスクへの書き込み量を約60〜80%削減可能。万が一のセーフティネットとしてディスク上に大容量のswapfileを常備しておきたいシステムに最適です。
- デメリット: 動作にはディスク上のswapfileまたはSwapパーティションが必須です。また、トラフィックが急増した際にRAMからディスクへページを退避させる処理が挟まることで、マイクロスタッター(瞬間的なプチフリーズ)が発生する場合があります。
ZRAM
- メリット: 物理RAMに迫る圧倒的な処理速度。テキスト、ソースコード、JSONペイロード、インメモリキャッシュなどのデータであれば、実効圧縮率は2:1〜3.5:1に達します。つまり、4GBの物理RAMで8GB〜14GB相当の非圧縮データを収容可能です。すべての処理がRAM内で完結するため、SSDの寿命劣化も一切ありません。
- デメリット: データの圧縮・展開処理により、CPUサイクルを2〜5%程度余分に消費します。既存のサーバーですでにCPU使用率が95〜100%に張り付いているような場合は、
zstdではなくlz4の採用を検討すべきです。
なぜZRAMが最強の切り札となるのか?
以前、2コア / 4GB RAMのVPSでMagento 2とMySQLを運用していたことがありました。深夜のタイムセールが始まるたび、アクセス数が普段の4倍に跳ね上がり、RAMは数分で蒸発。NVMe SSD上の4GB swapfileがフル稼働し始めると、iowaitは40%に達し、最終的にMySQLがOOM Killerによってダウンしていました。
そこでZRAM(4GB、zstdアルゴリズム)へと移行したところ、状況は一変しました。同規模のスパイクアクセスが発生しても、システムは圧縮された2.5GB以上のSwapデータをRAM内に軽々と処理し、CPU使用率の上昇はわずか3%程度にとどまりました。何より重要なのは、Load averageが2.5未満に安定し、プロセスが1つもkillされなくなったことです。
小規模なVPS(1GB〜4GB RAM)や、多数のサイドカーコンテナが稼働するDocker/K8sノードにおいて、ZRAMは最もコストパフォーマンスに優れた生命線です。高価なサーバースペックへのアップグレードに踏み切る前に、OOM Killerの脅威を未然に防ぐことができます。
ZRAM設定ハンズオン手順
方法1: zram-toolsによるクイック導入(Ubuntu / Debian)
Debian系ディストリビューションでは、便利なsystemdサービスを提供するzram-toolsパッケージが利用可能です。
sudo apt update
sudo apt install -y zram-tools
設定ファイルを編集します。
sudo nano /etc/default/zramswap
以下の設定パラメータを記述します。
# 圧縮アルゴリズム: zstd(高圧縮率)または lz4(最高速、CPU低負荷)
ALGO=zstd
# 物理RAM総容量の50%をZRAMに割り当て
PERCENT=50
# ディスク上のswapfile(通常は-2)よりも高い優先度を設定
PRIORITY=100
サービスを再起動して変更を反映させます。
sudo systemctl restart zramswap
sudo systemctl status zramswap
方法2: カーネルモジュールによる手動設定(CentOS、RHEL、AlmaLinux、Archなど全ディストリビューション対応)
パッケージマネージャーに管理ツールがない環境でも、数行のコマンドでカーネルモジュールを直接ロードして構築できます。
# 1. zramモジュールをカーネルにロード
sudo modprobe zram num_devices=1
# 2. 仮想デバイス /dev/zram0 に zstd 圧縮アルゴリズムを指定
echo zstd | sudo tee /sys/block/zram0/comp_algorithm
# 3. 仮想Swapサイズとして4GB(非圧縮時サイズ)を割り当て
echo 4G | sudo tee /sys/block/zram0/disksize
# 4. ブロックデバイスをSwap形式でフォーマット
sudo mkswap -U clear /dev/zram0
# 5. 高い優先度(priority 100)を指定して有効化
sudo swapon -p 100 /dev/zram0
ZRAMの稼働状態を確認・モニタリングする
ZRAMを有効化した後は、zramctlコマンドで直感的に稼働状況を確認できます。
zramctl
ターミナル上の実際の出力例:
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 zstd 4G 1.2G 420.5M 460.8M 4 [SWAP]
各カラムの意味: DATAは元のデータ量(1.2GB)を示し、圧縮後はわずか420.5Mに収まっています。メモリ管理オーバーヘッドを含めても、ZRAMが実際に消費しているRAMは460.8M(TOTAL列)のみであり、約2.8:1という高い圧縮率を達成しています。続いて、おなじみのコマンドでシステム全体のSwap割り当て状況を確認してみましょう。
swapon --show
free -h
sysctlによるSwappinessパラメータの最適化
デフォルトのLinux設定では、RAMが限界近くまで圧迫されるまでスワップアウトを行いません(標準値は通常vm.swappiness=60)。しかしZRAMはRAM上に存在しマイクロ秒単位でアクセスできるため、使用頻度の低いメモリページを早期にスワップアウトさせ、空いた物理メモリをPage Cacheに回す戦略が極めて有効です。
# 現在の値を確認
cat /proc/sys/vm/swappiness
# カーネルが積極的にZRAMを活用するよう swappiness を 100 に引き上げ
sudo sysctl vm.swappiness=100
リブート後も設定を永続化させるため、/etc/sysctl.confの末尾に次の2行を追加します。
vm.swappiness=100
vm.page-cluster=0
ここで指定したvm.page-cluster=0は、ページを$2^3 = 8$ページ(32KB)単位でクラスタリングして処理するのではなく、1ページ単位(4KB)で個別に読み込み・圧縮するようカーネルに指示するパラメータです。この小さなチューニングにより、圧縮Swapにおける不要なI/Oレイテンシを徹底的に排除できます。
