16 ポイント 投稿者 GN⁺ 2026-05-04 | 11件のコメント | WhatsAppで共有
  • Linux 7.0で、従来サーバーのデフォルトだった PREEMPT_NONE プリエンプションモードが削除され、同一ハードウェア上でPostgreSQLのスループットが半減する深刻な性能退行が発生
  • AWSのエンジニアが96-vCPUのGraviton4マシンでpgbenchを実行した結果、Linux 6.xと比べてLinux 7.0では 1秒あたりのトランザクション数が98,565件から50,751件 に低下し、CPUの55%が単一のスピンロック関数で消費された
  • PostgreSQLの 共有バッファプール (shared buffer pool) へのアクセスを保護するスピンロックが、4KBメモリページのマイナーページフォールトと組み合わさることで、ロック保持中にスケジューラによるプリエンプトが起きると、待機中のすべてのバックエンドがCPUを浪費しながらスピンする
  • Huge Pages (2MBまたは1GB) を有効にすると、潜在的なページフォールト数が3,100万件から数万〜数百件へ減少し、退行現象が解消
  • カーネル側では Restartable Sequences (rseq) の採用が提案されたが、PostgreSQLコミュニティは、カーネルアップグレードによる性能低下そのものが「ユーザースペースを壊さない」という原則に反するとの立場

問題の現象

  • AWSのエンジニア Salvatore Dipietro が 96-vCPU Graviton4 プロセッサでpgbenchを実行し、scale factor 8,470(約8億4,700万行テーブル)、1,024クライアント、96スレッド構成の高並列負荷テストを実施
  • Linux 6.xでは 98,565 TPS、Linux 7.0では 50,751 TPS と、スループットがほぼ半減
  • perf プロファイリングの結果、CPU時間の 55.60%が s_lock 関数 の内部で消費されていた
    • 呼び出し経路: StartReadBufferGetVictimBufferStrategyGetBuffers_lock

プリエンプション (Preemption) とは

  • OSスケジューラが実行中のスレッドを中断し、別のスレッドへCPUを渡す決定が プリエンプション
  • Linux 7.0以前には3つの選択肢が存在
    • PREEMPT_NONE: スレッドが自発的にCPUを譲るまで(syscall、I/Oブロック、sleep)ほとんど中断しない。従来のサーバー向けデフォルトで、コンテキストスイッチが少なくスループットが高い
    • PREEMPT_FULL: 安全なほぼすべての地点で実行中のスレッドを中断可能。応答時間は短くなるが、コンテキストスイッチのオーバーヘッドが増える。従来のデスクトップ向けデフォルト
    • PREEMPT_LAZY: Linux 6.12で導入された折衷案で、自然な境界を待ちながら必要に応じてプリエンプトを許可する。PREEMPT_NONEのスループット特性に近づけるよう設計
  • Linux 7.0では PREEMPT_NONEが最新CPUアーキテクチャで削除 され、PREEMPT_FULLとPREEMPT_LAZYだけが残った
    • PREEMPT_LAZYはほとんどのサーバーソフトウェアでは代替として機能するが、PostgreSQLでは 致命的な差 が生じた

PostgreSQLのメモリ管理

  • PostgreSQLは固定サイズの データページ (デフォルト8KB)を基本的な保存単位として使用し、テーブル行、B-treeインデックスノード、メタデータなどをすべてこのページに保存する
  • ディスク読み込みを減らすため、共有バッファプール (shared buffer pool) という大規模な共有メモリ領域に最近読み込んだデータページをキャッシュする
  • クライアントが接続すると専用の バックエンドプロセス が生成され、バッファプールにないページはディスクから読み込んだうえで、空きバッファまたは追い出し可能なバッファを探す必要がある
    • このバッファ選択処理を担当する関数が StrategyGetBuffer

PostgreSQLのスピンロック

  • スピンロック は、ロック待ちの間にスリープせず、ループしながら継続的に確認するロック機構
    • 非常に短いクリティカルセクションでは、スレッドを眠らせて起こすコストよりスピンのほうが効率的
  • 中核となる前提: ロックを保持したスレッドがごく短時間で解放すること
  • StrategyGetBuffer はバッファ選択を保護するために 単一のグローバルスピンロック を使用
    • 96-vCPU、1,024クライアント環境では、すべてのバックエンドが同じロックを奪い合う

仮想メモリとTLB

  • すべてのプロセスは 仮想メモリアドレス を使い、ハードウェアが ページテーブル (多段ツリー構造)を通じて物理アドレスへ変換する
  • 毎回ページテーブルをたどると遅いため、CPUは最近の変換結果をキャッシュする TLB (Translation Lookaside Buffer) を備える
    • TLBヒット時は高速にアクセスでき、TLBミス 時にはページテーブルウォークが必要になって時間がかかる
  • Linuxは 遅延割り当て (lazy allocation) の原則を使い、仮想メモリ割り当て時には実際の物理ページを最初のアクセス時点でマッピングする
    • 初回アクセス時には マイナーページフォールト が発生し、カーネルが物理ページを割り当ててマッピングを保存するため、通常の読み書きより数マイクロ秒単位で遅い

4KBページの問題

  • ベンチマークでは shared_buffers を120GBに設定しており、4KBメモリページ基準では約 3,100万個のメモリページ、すなわち3,100万件の潜在的な初回アクセスページフォールトがある
  • 120GBの共有バッファプールを使う長時間ベンチマークでは、新しいメモリ領域が継続的にワーキングセットへ入ってくるため、ページフォールトは開始時だけでなく継続的に発生 する
  • StrategyGetBuffer 内でスピンロックを保持したまま共有メモリへアクセスした際、その領域がまだマッピングされていなければ マイナーページフォールトが発生 する
  • PREEMPT_NONE(Linux 7.0以前): バックエンドAがページフォールトハンドラに入っても、自発的な再スケジューリングポイントを避けるため、フォールト解決前にスケジュールアウトされる可能性は低い。待ち時間は想定より長くなるが、被害は限定的
  • PREEMPT_LAZY(Linux 7.0以降): スケジューラはページフォールトハンドラの内部でバックエンドAを プリエンプトして別プロセスをスケジュール できる。フォールト処理が完了しても、スケジューラが制御を戻すまで追加待ち時間 t が発生
    • この追加待ち時間は単なる t ではなく、現在スピン中のすべてのバックエンド数 × t のCPU浪費として増幅される
    • 96-vCPUで数百バックエンドという環境では、この乗数効果が致命的になり、結果としてCPUの56%が s_lock で消費された

Huge Pagesによる解決

  • shared_buffers 120GBを前提に、メモリページサイズ を変更すると潜在的なページフォールト数は劇的に減少する
    • 4KBページ: 約31,000,000件の潜在的ページフォールト
    • 2MB Huge Pages: 約61,440件
    • 1GB Huge Pages: 約120件
  • ページサイズ拡大はページフォールト数を減らすだけでなく、TLBプレッシャーも緩和 する。同じメモリをはるかに少ないTLBエントリでカバーできるため、TLBミスとページテーブルウォークが減る
  • StrategyGetBuffer がロック保持中にフォールトを起こさなくなり、ロック保持者はすぐに完了し、他のバックエンドはミリ秒ではなくマイクロ秒だけ待てばよくなる。退行現象は解消 する
  • PostgreSQLでの huge pages 設定は huge_pages パラメータで制御する
    • off, on, try(デフォルト値)の3つをサポート
    • try は huge pages が使えれば使い、使えなければ4KBへ静かにフォールバックするため、誤設定に気づけない危険がある
    • on に設定すると huge pages を使えない場合にPostgreSQLの起動が失敗し、問題を即座に認識できる
  • トレードオフ: huge pages は事前割り当て・予約方式のため、PostgreSQLがすべて使わなくても、そのメモリはシステムの他の部分で利用できない。ページの一部しか使わない場合は残りが無駄になる。大規模な shared_buffers を使う本番環境では、一般的に受け入れる価値のあるトレードオフといえる

今後の展開

  • このプリエンプション変更を設計したIntelのカーネルエンジニア Peter Zijlstra は、PostgreSQLが Restartable Sequences (rseq) を採用することを提案
    • rseq は、ユーザースペースコードが クリティカルセクション中のプリエンプトやマイグレーションの有無を検知 し、その区間を再実行できるようにするLinuxカーネル機能
    • PostgreSQLのスピンロック経路に rseq を適用すれば、プリエンプトされたロック保持者が待機中の全バックエンドを遅延させるシナリオを回避できる
  • PostgreSQLコミュニティの反応は否定的
    • Linux 7.0以前には無料で得られていた性能を取り戻すために、別のカーネル機能を採用しなければならない点は受け入れがたい
    • カーネルの長年の原則である 「ユーザースペースを壊さない」 (カーネルアップグレード前に正常動作していたソフトウェアは、アップグレード後も正常動作すべき)に反するという立場

11件のコメント

 
y15un 2026-05-04

これはどう考えてもタイトルが間違っています。

https://ja.news.hada.io/topic?id=28241#cid54772
カーネルのメインテイナーが postgres に対してかなり前から勧告してきた事案だというのだから、むしろ「Postgres が Linux 7.0 で遅くなる理由」が正しくて、7.0 が Postgres を壊したわけではありません。

カーネルが厳密には semver に従わないとしてもメジャーバージョンアップなのに、自業自得をこんなふうにフレーミングするんですか??

 
domuji6 2026-05-05

代替案として rseq が提示されてはいましたが、Linux 専用コードを導入しなければならないという点で、クロスプラットフォームを考慮する必要があるオープンソースプロジェクトの立場では、簡単には受け入れがたい提案だと思います。

メジャーバージョンアップで動作変更が起きるのは理解できますが、結果として 50% の性能低下が発生したのであれば、インフラを運用する立場としては、カーネルのアップグレード自体を慎重に見ざるを得ない気がします。

 
y15un 2026-05-06

わお、出張中にコメントを投稿してホテルに戻って見たら、お三方もご意見をくださっていました。ありがとうございます。

ご指摘いただいた観点も私としては十分理解できますが、それでも私はこれを postgres 側の tech debt と見ているので、結局は postgres が自ら解決すべき件だと考えています。(目先の性能のために hack などを使ってきて痛い目を見る、というのは spectre でもう十分な気がするので……)
結局この件は、しばらく様子を見るしかなさそうです。

良い一日を。 :)

 
ilsubyeega 2026-05-06

同意します。IntelのLinuxエンジニアが退職していくという話を時々見かけますが、既存の振る舞いをこのまま放置し続けると、いつかWindowsみたいになってしまうでしょう o,o..

 
xenoside 2026-05-05

すでにその実装は最適な性能のためにプラットフォームごとの専用アセンブリコードが大量に入っている部分なので、
特定のプラットフォーム専用コードが追加されるという理由でできない、というのは理由にならない気がしますね。
(Gemini に聞いて要約しました。)

 
nanashi222 2026-05-06

コードを見ましたが、それほどアセンブリコードだらけの関数でもありませんし、アセンブリコードが多いということが、特定プラットフォーム専用コードを追加しても問題になるという意味ではないと思います。ここで言うアセンブリコードというのは、アトミック演算関数(GCCのビルトイン __atomic__ 関数群)のことを指しているようですが、関数の中身を見る限り、Linux だけ特別にコードを追加できるようなものではないですね。

 
jjw9512151 2026-05-11

オープンソースですし、それを変更してテストするのも負担が大きいでしょうね……。ユーザーも多すぎますし。

 
hiseob 2026-05-05

たしかに昔、リーナス神が WE DO NOT BREAK USERSPACE! と言って叱りつけた喝!みたいなことがあったので、オプションを付けるべきでは、という気もしますが
だからといって、ユーザースペースでわざわざ頑なにスピンロックを使うと言うのも、ちょっと筋が通らない気がします
そんな感じですね

 
cafedead 2026-05-04

「ユーザースペースを壊すな」 vs 「ユーザースペースでスピンロックするな」

 
savvykang 2026-05-05

30年もの間、OSの機能を一部再発明しながら、誰もその限界や理由を文書化しようとしなかったというのは、ちょっと理解しがたいですね。30年前に独自の同期機構、独自のメモリ管理、独自のプロセスモデルまで作ったのには、明らかに合理的な理由があったはずですが。

 
hungryman 2026-05-05

AI時代なのに、わざわざああいう否定的な反応があるということは、アーキテクチャ的に向こう側で複雑にこんがらがっているんですか?