/note/tech

Linux 7.0カーネルにおけるPostgreSQL性能劣化問題、その後の動向

■ 1. 発端

  • Linux 7.0開発カーネル上でPostgreSQLのスループットが約半減する性能劣化が報告された
  • 報告者はAWSエンジニアのサルヴァトーレ・ディピエトロ(2026年4月3日 LKMLへ投稿)
  • テスト環境: AWS EC2 m8g.24xlarge(Graviton4 / 96 vCPU)、PostgreSQL 17
  • pgbench(simple-update / 1024クライアント / 96スレッド / 1200秒)の計測結果:
    • Linux 7.0(PREEMPT_LAZY): 約50,751 TPS
    • 問題コミットをリバート後(PREEMPT_NONE相当): 約98,565 TPS(約1.94倍)
  • perfプロファイリングにより CPU時間の55%がPostgreSQLのユーザースペーススピンロック(s_lock)で消費されていることが判明

■ 2. 当初議論された原因

  • Linux 7.0 rc1で導入されたカーネルスケジューラの変更が根本原因
  • IntelのカーネルエンジニアPeter Zijlstraによるコミットがプリエンプションモードを制限
  • 従来はサーバー向けに「PREEMPT_NONE」(プリエンプションなし・スループット最大化優先)が存在した
  • Linux 7.0でx86・ARM64等の主要アーキテクチャからPREEMPT_NONEが廃止され「FULL」と「LAZY」の2モードのみに変更
  • 廃止の目的はカーネルの簡素化とPREEMPT_RT(リアルタイム対応)への統合推進
  • PostgreSQLはプロセスモデルを採用し共有メモリへのアクセスにユーザースペーススピンロックを多用する
  • PREEMPT_LAZY環境ではロック保持中にタスクが中断される頻度が上がり他のプロセスが無駄にスピンし続ける

■ 3. 真の原因とAndres Freundの分析

  • Andres FreundがLKMLスレッドを調査した結果、真の原因はスピンロック機構そのものではなくスピンロック保持中に発生するTLBミスとマイナーページフォルトであると判明
  • 問題の連鎖:
    • Huge Pages未使用時、PostgreSQLの共有メモリは標準4KBページで管理される
    • 100GB超のバッファプールでは各ページへの初回アクセス時にマイナーフォルトが発生する
    • マイナーフォルトがスピンロック保持中に起きるとスレッドが失速し待機中の全スレッドがスピンし続ける
    • PREEMPT_LAZYは失速したロック保持者をスケジュールアウトすることがあり事態をさらに悪化させる
    • 根本問題は最初からページフォルトにあり、プリエンプションモードではない
  • Huge Pages有効化により新旧どちらのカーネルでもスループットが同等(約185k TPS)になることが確認された

■ 4. 4KBページとHuge Pagesの比較

  • ページサイズ:
    • 通常ページ: 4 KB
    • Huge Pages: 2 MB(x86)/ 1 GB(gigantic)
  • 100GBバッファに必要なTLBエントリ数:
    • 通常ページ: 約2,600万
    • Huge Pages: 約5万
  • TLBミス頻度:
    • 通常ページ: 高
    • Huge Pages: 大幅に低下
  • スピンロック保持中のフォルト:
    • 通常ページ: 発生しやすい
    • Huge Pages: ほぼ発生しない

■ 5. Huge PagesとTHPの区別

  • Transparent Huge Pages(THP)と明示的なHuge Pagesは別物
  • THPはLinuxカーネルが自動でHuge Pagesを割り当てようとする機能でスワップ対象になり得る
  • PostgreSQLには明示的に設定するHuge Pagesが推奨される
  • PostgreSQL公式ドキュメントもTHPについて「一部のユーザー・一部のLinuxバージョンで性能劣化が知られており現在は非推奨」と明記している

■ 6. 具体的な設定手順

  • OSレベル(明示的なHuge Pages予約):
    • shared_buffersに必要なHuge Pagesページ数を計算する(例: 32GB ÷ 2MB = 16384ページ + 余裕分)
    • sudo sysctl -w vm.nr_hugepages=16500 で設定し /etc/sysctl.conf に永続化する
    • /proc/meminfo の HugePages_Total / HugePages_Free / Hugepagesize で割り当てを確認する
  • PostgreSQL設定(postgresql.conf):
    • huge_pages = on: 確実に使用(取得失敗時は起動拒否)
    • huge_pages = try: 取得失敗時は通常ページで起動(デフォルト)
    • PostgreSQL 15以降は SHOW huge_pages_status で実際の使用状況を確認可能
  • THP無効化(競合防止):
    • 明示的Huge Pages使用前にTHPを無効化しなければカーネルがTHPで共有メモリをマッピングし性能が不安定になる
    • /sys/kernel/mm/transparent_hugepage/enabled および defragnever を書き込む

■ 7. 影響が限定的なケースとまとめ

  • 影響が出やすい条件:
    • Huge Pages未設定
    • shared_buffersが数十GB以上の大規模構成
    • コールドスタート直後(ウォームアップ期間)
    • 高コアCPU(96vCPUなど)での高並列ワークロード
  • Huge PagesまたはTHPを使用していればこのリグレッションはほぼ心配不要という見解がPostgreSQLメーリングリストでまとめられている
  • 問題の本質は「PREEMPT_LAZYが悪い」ではなく「Huge Pages未使用環境でのTLBミスがスピンロックと組み合わさった時にPREEMPT_LAZYによって悪化する」という複合的なもの
  • Huge Pagesを正しく設定すればLinux 7.0でも従来と同等の性能が得られることが実証されている

関連: