「Linuxサーバーのメモリ使用量をfreeで確認したら、ほとんど使われているように見えた」
「でも実際のアプリは快調に動いている……これはなぜ?」
その答えがページキャッシュです。
Linuxはメモリに空きがある限り、ディスクから読んだデータをキャッシュとして積極的に使い続けます。
この記事では、ページキャッシュの仕組み・読み書きの流れ・メモリへの影響・チューニング方法まで解説します。
ページキャッシュとは何か
ページキャッシュとは、ディスク上のファイルデータをメモリ上に保持し、同じデータへの2回目以降のアクセスを高速化する仕組みです。
CPUからメモリへのアクセスが数十ナノ秒なのに対し、SSDでも数十〜数百マイクロ秒、HDDは数ミリ秒かかります。
この速度差は数百倍〜数万倍にも及びます。
ページキャッシュはその差を埋めるために、Linuxカーネルが透過的(アプリケーション側に意識させずに)提供しているディスクキャッシュです。
「ページ」という名称は、キャッシュの管理単位が仮想メモリのページ(通常4KB)であることに由来します。
ファイル全体をまとめてキャッシュするのではなく、inodeとファイル内オフセットをキーにしたページ単位でキャッシュが管理されています。
バッファキャッシュとの違い
古いLinux(カーネル2.4以前)には「ページキャッシュ」と「バッファキャッシュ」が別々に存在していました。
| キャッシュ | 役割 |
|---|---|
| ページキャッシュ | ファイルI/O(ファイルシステム経由のアクセス)を高速化 |
| バッファキャッシュ | ブロックデバイスへの直接I/Oを高速化 |
カーネル2.4以降は両者が統合され、ファイルデータはページキャッシュに一元管理されます。freeコマンドのbuff/cacheという表示は、その統合された領域を示しています。
現在もバッファキャッシュの名称は残っていますが、実体はページキャッシュを指し示しているだけで、データの二重保持はありません。
読み込み(read)の仕組み
ファイルのread()システムコールが発行されると、カーネルは以下の順序で処理します。
キャッシュヒットの場合(高速):
- カーネルがページキャッシュを検索する
- 対象ページがキャッシュに存在する(キャッシュヒット)
- メモリからデータをプロセスにコピーして完了
ディスクアクセスは発生しないため、非常に高速に完了します。
キャッシュミスの場合(低速):
- カーネルがページキャッシュを検索する
- 対象ページがキャッシュに存在しない(キャッシュミス)
- ディスクからデータを読み込む(I/O発生)
- 読み込んだデータをページキャッシュに追加する
- プロセスにデータをコピーして完了
2回目以降の同じページへのアクセスは、手順4で追加したキャッシュが使われるため高速になります。
リードアヘッド(先読み)
カーネルはファイルのアクセスパターンを監視し、連続したアクセスが続いていると判断すると、まだリクエストされていないページを先読み(readahead)してキャッシュに積みます。
これにより、シーケンシャル読み込みでは体感的にディスクI/Oの遅延がほぼなくなります。
書き込み(write)の仕組みとダーティページ
ファイルへのwrite()が発行されたとき、Linuxはライトバック(write-back)方式を採用しています。
プロセスはまずページキャッシュに書き込み、即座に処理を返します。
ディスクへの書き出しは後でカーネルがまとめて行います。
この方式により:
- アプリケーションはディスクの遅延を待たずに次の処理へ進める
- 同じページへの複数の書き込みをまとめてディスクに書き出せる(I/O回数を削減できる)
ダーティページとは
ページキャッシュに書き込まれたが、まだディスクに反映されていないページをダーティページ(dirty page)と呼びます。
ダーティページは/proc/vmstatで確認できます。
cat /proc/vmstat | grep -E "nr_dirty|nr_writeback"
出力例:
nr_dirty 1024
nr_writeback 0
nr_writeback_temp 0
nr_dirtyがダーティページの数、nr_writebackがディスクへ書き出し中のページ数です。
ライトバックのタイミング
カーネルのフラッシャースレッド(kworker/flush)が以下のいずれかの条件で起動し、ダーティページをディスクに書き出します。
- ダーティページがメモリ全体の一定割合(
dirty_background_ratio、デフォルト10%)を超えたとき → バックグラウンドで書き出し開始 - ダーティページがより高い閾値(
dirty_ratio、デフォルト20%)を超えたとき → プロセス自身がブロックされて書き出し - ダーティページが一定時間(
dirty_expire_centisecs、デフォルト3000 = 30秒)以上残っているとき - プロセスが
sync()・fsync()・fdatasync()を呼んだとき
ページキャッシュの追い出し(eviction)
メモリが不足してくると、カーネルはページキャッシュを解放して空きを作ります。
この仕組みをページ回収(page reclaim)と呼びます。
Linuxは2リストLRU戦略(Two-List LRU)でページを管理しています。
| リスト | 状態 | 追い出し対象 |
|---|---|---|
| アクティブリスト | 最近アクセスされたホットなページ | 対象外 |
| インアクティブリスト | アクセス頻度の低いコールドなページ | 対象 |
ページは最初インアクティブリストに置かれ、再度アクセスされるとアクティブリストに昇格します。
アクティブリストが大きくなりすぎると、古いページをインアクティブリストに降格させます。
追い出し時はインアクティブリストの先頭(最も古いページ)から回収されます。
単純なLRU(Least Recently Used)だと「大きなファイルを一度だけ読んだだけで、よく使うファイルのキャッシュが全部追い出される」問題が起きます。
2リスト方式は2回アクセスされたページをアクティブリストに昇格させることで、この問題を軽減しています。
ページキャッシュの確認方法
freeコマンド
Linuxのスペック確認コマンドでも紹介しているfreeコマンドで概要を確認できます。
free -h
出力例:
total used free shared buff/cache available
Mem: 15G 3.2G 1.1G 256M 11G 11G
Swap: 2.0G 0B 2.0G
buff/cacheがページキャッシュ(とバッファキャッシュ)の使用量です。availableはアプリケーションが実際に使えるメモリで、freeよりはるかに大きくなるのが通常です。
キャッシュはメモリが必要になると自動で解放されるため、buff/cacheが大きくても問題ありません。
/proc/meminfoで詳細確認
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Cached|Buffers"
出力例:
MemTotal: 16384000 kB
MemFree: 1024000 kB
MemAvailable: 11264000 kB
Buffers: 204800 kB
Cached: 10240000 kB
Cachedがページキャッシュ、Buffersがバッファキャッシュ(メタデータ等)の使用量です。
vmstatでI/Oとの関係を観察
vmstat 1
出力のbi(blocks in)が増えていればディスクから読み込み中、bo(blocks out)が増えていればダーティページをディスクへ書き出し中です。
ページキャッシュが効いているときはbiがほぼ0になります。
Direct I/Oとの違い
通常のファイルI/O(バッファードI/O)は必ずページキャッシュを経由します。
一方、O_DIRECTフラグを指定したDirect I/Oはページキャッシュをバイパスして直接ディスクにアクセスします。
データベース(MySQLのInnoDBなど)は独自のキャッシュ(バッファプール)を持つため、カーネルのページキャッシュと二重にキャッシュされることを避けるためにDirect I/Oを使うケースがあります。
ページキャッシュのチューニング
dirty系パラメータ
Linuxのハードリミット・ソフトリミット記事でも触れているとおり、カーネルのリソース管理はsysctlで調整できます。
ページキャッシュのwrite-back動作を制御する主なパラメータは以下のとおりです。
| パラメータ | デフォルト | 意味 |
|---|---|---|
vm.dirty_background_ratio | 10(%) | バックグラウンドでの書き出し開始の閾値。メモリ全体に対するダーティページの割合 |
vm.dirty_ratio | 20(%) | プロセスをブロックして書き出しを強制する閾値 |
vm.dirty_expire_centisecs | 3000(30秒) | ダーティページをディスクに書き出すまでの最大待機時間(1/100秒単位) |
vm.dirty_writeback_centisecs | 500(5秒) | フラッシャースレッドの起動間隔(1/100秒単位) |
現在の値の確認:
sysctl -a | grep dirty
書き込み遅延を減らしたい場合(データ安全性を重視)
# ダーティページを早めにディスクへ書き出す
sudo sysctl -w vm.dirty_background_ratio=5
sudo sysctl -w vm.dirty_ratio=10
電源障害時のデータロスリスクが減りますが、ディスクへの書き込み頻度が上がります。
書き込みスループットを上げたい場合
# メモリ上にダーティページを多く溜めてまとめて書き出す
sudo sysctl -w vm.dirty_background_ratio=20
sudo sysctl -w vm.dirty_ratio=40
スループットは上がりますが、障害時にメモリ上の未書き出しデータが失われるリスクが増えます。
データが失われても問題ないキャッシュサーバーやログ集積サーバーでは有効な場合があります。
恒久的に設定する方法
# /etc/sysctl.confに追記して永続化
echo "vm.dirty_background_ratio=5" | sudo tee -a /etc/sysctl.conf
echo "vm.dirty_ratio=10" | sudo tee -a /etc/sysctl.conf
# 設定をすぐ反映
sudo sysctl -p
ページキャッシュの強制解放
dentryキャッシュの記事でも触れたとおり、drop_cachesでページキャッシュを強制解放できます。
# 書き込みを先に確定させる
sync
# 1:ページキャッシュのみ解放
echo 1 | sudo tee /proc/sys/vm/drop_caches
# 3:ページキャッシュ・dentry・inodeキャッシュをまとめて解放
echo 3 | sudo tee /proc/sys/vm/drop_caches
注意: 解放直後はすべてのファイルアクセスがディスクから読み直しになるため、一時的にパフォーマンスが大幅に低下します。
ベンチマーク前のキャッシュクリアなど、明確な目的がある場合にのみ使用してください。
通常の運用では実行する必要はありません。
まとめ
ページキャッシュはLinuxがファイルI/Oを高速化するための中核的な仕組みです。
ポイントを整理します。
- ページキャッシュはファイルデータをメモリ上に保持し、2回目以降のアクセスをディスクI/Oなしで完結させる
- Linuxは空きメモリをどんどんページキャッシュに使う。
freeのbuff/cacheが大きくても問題ない - 書き込みはライトバック方式で、まずページキャッシュに書かれ(ダーティページ)、後でディスクに書き出される
- ダーティページの書き出しタイミングは
dirty_background_ratio・dirty_ratio・dirty_expire_centisecsで制御できる - ページの追い出しは2リストLRU方式で管理される
- Direct I/Oはページキャッシュをバイパスしてディスクに直接アクセスする
サーバー運用でメモリが逼迫している場合は、まずfreeと/proc/meminfoでページキャッシュの使用量を確認してみてください。
ストレージ容量の確認方法についてはLinuxのストレージ容量を確認する全方法も参考にしてください。
参考情報源:
- Linux Kernel documentation「Documentation for /proc/sys/vm/」 (kernel.org)
- naoyaのはてなダイアリー「Linux のページキャッシュ」
- gihyo.jp「第52回 Linuxカーネルのコンテナ機能 ― cgroupを使ったI/O制限」 (2023年)
- Baeldung「How to Configure File System Caching in Linux」 (2024年)
- The Linux Kernel documentation「Filesystem Management」
- lonesysadmin.net「Better Linux Disk Caching & Performance with vm.dirty_ratio & vm.dirty_background_ratio」

コメント