Huge Pages とは何か、そしてなぜ Postgres にとって重要なのか。OS はメモリを 4KB のページ単位で払い出し、CPU は仮想アドレスから物理アドレスへの変換を小さなキャッシュ、すなわち TLB に保持しています。Huge Page とは、同じメモリをはるかに大きな単位(2MB や 1GB)で扱うもので、1 つのページテーブルエントリで最大 262,144 倍の範囲をカバーできます。
Postgres はページサイズに対して極めて敏感です。そのキャッシュである shared_buffers は 1 つの大きな共有メモリセグメントであり、すべてのバックエンドプロセスが独自のページテーブル経由でマッピングします。4KB ページでは、100GB のキャッシュを扱うために各バックエンドがおよそ 200MB のページテーブルエントリを必要とし、100 コネクションであればページテーブルだけで 20GB もの RAM を消費します。2MB の Huge Pages なら、これが数十 MB で済みます。さらに TLB が保持できる変換は数千個ほどで、4KB ページではキャッシュのうちわずか数 MB しかカバーできませんが、2MB ページなら 1 スロットあたりの到達範囲が 512 倍となり、ホットなワーキングセットが TLB に収まり、読み取りがページテーブルウォークで停止することがなくなります。
ClickHouse Managed Postgres では、すべてのサーバがバッファキャッシュを Huge Pages 上で動作させています。これを確実に成立させるには 3 つの要素が必要です。ページを早期に予約すること、それが確保できなければ起動を拒否すること、そして共有メモリセグメント全体が予約済みプールに正確に収まるように shared_buffers をサイジングすることです。

ページの予約
マシンメモリの 4 分の 1 を Huge Pages として予約します。これはベース設定における shared_buffers = RAM の 25% というルールと対応しています。予約は Postgres が起動する前に実行され、カーネルが連続した 2MB ブロックを見つけられるよう、まずキャッシュをフラッシュしてメモリをコンパクションします:
echo 'vm.nr_hugepages = ' > /etc/sysctl.d/10-hugepages.conf
sync
echo 3 > /proc/sys/vm/drop_caches
echo 1 > /proc/sys/vm/compact_memory
sysctl --system割り当ては /proc/meminfo の HugePages_Total に対して検証され、カーネルが確保できなかった場合はプロビジョニングが失敗します。Huge Pages はメモリが断片化していない状態でしか確実に予約できず、負荷がかかった後のマシンでは、同じリクエストがしばしば失敗します。
確保できなければ起動を拒否する
Postgres は huge_pages = 'on' に固定されており、ページが確保できなければ起動を拒否します。デフォルトの 'try' では、ログには何も残らないまま静かに 4KB ページへフォールバックし、上述のあらゆる特性が黙って消えてしまいます。起動時の失敗は診断可能ですが、静かなフォールバックは数ヶ月後にプロファイラで発見されるのが常です。
ランタイムでのチェックもあります。postmaster の /proc/<pid>/status には、そのアドレス空間のうちどれだけが hugetlb ページ上にあるかが記録されています。以下のデモ用マシンでは次のようになります:
$ grep HugetlbPages /proc//status
HugetlbPages: 700416 kBHuge Pages を使うはずのサーバで HugetlbPages: 0 となっている場合は、フォールバックが起きた証拠です。
shared_buffers をページに合わせてサイジングする
Postgres の共有メモリセグメントは shared_buffers よりも大きく、バッファディスクリプタ、WAL バッファ、ロックテーブルなども含みます。以下のデモ用マシンでは、shared_buffers = 32GB は 32.75GB のセグメントを生成し、およそ 750MB のオーバーヘッドがあります。shared_buffers だけで予約済みプールを埋めてしまうと、セグメント全体は収まらず、'on' は起動を拒否します。サイジングは 2 段階で行います。まず shared_buffers をプール全体に向け、次にサーバを起動せずにバイナリに完全なセグメントの実コストを問い合わせます:
postgres -D -C shared_memory_sizeその差分を shared_buffers から差し引き、8KB ブロック単位で切り下げて、結果を書き込みます:
# conf.d/002-hugepages.conf
huge_pages = 'on'
huge_page_size = 0 # use the system default, 2MB
shared_buffers = kBオーバーヘッドは仮定せず実測します。これはバージョンや設定によって変動するためであり、大きめのサイズで測定することで予約より数ページ少なくなる方向に誤差が出るようにしています。予約済みプールと残りのメモリは別々に会計されます。Huge Pages はカーネルのコミット制限にはカウントされないため、メモリオーバーコミットは残り 75% の RAM に対してチューニングします。
実機での確認
ページテーブルに関する主張を単一の EC2 マシンで検証しました。r7i.4xlarge(16 vCPU、128GB)、Postgres 16、shared_buffers = 32GB、そして 15.6GB の accounts テーブルを pg_prewarm でキャッシュに完全にロードした pgbench データセットを使用しました。各テストコネクションはそのテーブルのフルシーケンシャルスキャンを 1 回実行し、これによりバックエンドはキャッシュされたすべてのページに触れて完全なページテーブルマッピングを構築し、そのままコネクションを開いたままにします。カーネルの PageTables カウンタを /proc/meminfo からサンプリングし、25、50、100、200 コネクションの各段階で、4KB ページの場合と 2MB Huge Pages の場合の 2 通りを測定しました。同じマシン、同じデータ、同じアクセスパターン、変数はページサイズだけです。

4KB ページでは、ページテーブルはコネクションごとに 31.1MB ずつ増加し、計算(触れたキャッシュ 15.6GB / 4KB ページ × エントリあたり 8 バイト = 31.2MB)と一致します。そして 200 コネクションで 6.1GB に達します。15.6GB のデータの管理のためだけにカーネルメモリが消費されるわけです。2MB Huge Pages では、同じ 200 コネクションのコストは 111MB、1 コネクションあたり約 0.5MB です。
| コネクション数 | PageTables、4KB | PageTables、2MB Huge Pages |
|---|---|---|
| 0 | 48 MB | 5 MB |
| 25 | 789 MB | 18 MB |
| 50 | 1.53 GB | 31 MB |
| 100 | 3.06 GB | 58 MB |
| 200 | 6.12 GB | 111 MB |
huge_pages = 'on' の挙動も同様にはっきりと見えます。プールが予約されていない状態では、Postgres は起動を拒否し、その理由を明示します:
FATAL: could not map anonymous shared memory: Cannot allocate memory
HINT: This error usually means that PostgreSQL's request for a shared
memory segment exceeded available memory, swap space, or huge pages.スループットも変化します。メモリほど劇的ではありませんが、100 クライアントの select-only pgbench では、4KB ページで 373,083 TPS、2MB Huge Pages で 418,087 TPS となり、それ以外は同一の構成で 12% の差が出ました。構造的な変化はメモリにあります。コネクション数に線形にスケールするページテーブルと、平坦なままのページテーブルとの違いです。
まとめ
Huge Pages は shared_buffers をコネクションごとのページテーブル税から、平坦で変換フレンドリーなキャッシュへと変えます。断片化が起きる前にプールを予約すること、huge_pages = 'on' で動作させて壊れた設定は静かに劣化する代わりに起動時に失敗させること、そして postgres バイナリ自身が報告する数値でセグメントを予約サイズに合わせることこそが、新品のマシンだけでなく本番環境でも設定を維持するための鍵です。
ClickHouseが管理するPostgresを試す
ClickHouse と Postgres の組み合わせは、スケールするアプリケーションのための統合データスタックとしての地位を確立しています。ClickHouse Cloud で Managed Postgres が利用可能になったことで、このスタックは Day 1 から選択できる選択肢となりました。
Sign up


