【実務・中級編】 プロセス配列 (ProcArray) – PostgreSQL

PostgreSQLの「影の支配者」:ProcArrayとMVCCの深い関係

やあ。今日も元気にクエリを投げてるかな?

PostgreSQLを使っていて、「なぜこのクエリは他のトランザクションの更新が見えないのか?」とか「VACUUMがなぜ終わらないんだ?」なんて悩んだことはないだろうか。

実は、そうしたPostgreSQLの「見え方」や「掃除のタイミング」を裏で一手に引き受けている、非常に重要な共有メモリ構造がある。それが`ProcArray`(プロセス配列)だ。

今回は、PostgreSQLの心臓部の一つであるこのProcArrayについて、現場でトラブルシューティングをする際に役立つ視点から深掘りしてみよう。

—

ProcArrayって、結局なんなの?

一言で言えば、「今、この瞬間にシステム内で生きているトランザクションの全名簿」だ。

PostgreSQLは共有メモリ上に「今、どのプロセス(バックエンド)が、どのトランザクションID(XID)で動いているか」を管理する巨大な配列を持っている。これがProcArrayだ。

MVCC(多版同時実行制御)において、あるデータが見えるか見えないかを判定するとき、PostgreSQLはこんな思考プロセスを辿る。

1. 「このデータはトランザクションID 100で書かれたものだ。」
2. 「じゃあ、今動いている連中(ProcArray)の中に、100より小さいIDや、まだコミットしていないIDはいるかな?」
3. 「もし誰もいなければ、このデータは公開していいよね。」

この「今動いている連中」を爆速で確認するための仕組みが、ProcArrayというわけだ。

—

なぜProcArrayが「ボトルネック」になり得るのか

ここからが現場の話だ。ProcArrayは共有メモリ上にあるから、当然ながら「ロック」が必要になる。

新しいトランザクションが始まるたびにProcArrayに名前を書き込み、終わるたびに消す。この時、`ProcArrayLock`という排他制御がかかるんだ。

もし、Webアプリから大量の接続が同時にトランザクションを開始・終了させようとすると、この`ProcArrayLock`の奪い合いが発生する。これを「ProcArrayLockのコンテンション(競合)」と呼ぶ。

現場で遭遇する兆候

  • CPU使用率はそこまで高くないのに、なぜかスループットが伸びない。
  • `pg_stat_activity`で見ると、多くのプロセスが `LWLock: ProcArrayLock` で待機している。

もし君のDBでこれが見えたら、まずは「接続数が多すぎないか?」を疑うべきだ。コネクションプーラー(pgBouncerなど)を導入して、DBへの物理接続数を絞るのが鉄板の解決策になる。

—

実際に中身を覗いてみよう

PostgreSQLのソースコード(`src/include/storage/procarray.h` あたり)を覗くと、`ProcArrayStruct`という構造体が見つかる。

/ 抜粋:概念的なイメージ /
typedef struct {
int numProcs; / 現在アクティブなプロセス数 /
int pgprocnos; / プロセス番号の配列 /
TransactionId latestCompletedXid; / 最後に終わったXID /
/ …などなど /
} ProcArrayStruct;

実務でこの構造を直接いじることはないけれど、`pg_stat_activity` を叩くとき、裏ではこの配列をスキャンして現在の状態を整形してくれているんだと意識するだけで、見え方が変わるはずだ。

例えば、「なぜVACUUMが古い行を消せないのか?」という問いに対しても、ProcArrayが答えを持っている。

— 今、一番古いトランザクションはどれだ?
SELECT pid, xact_start, now() – xact_start AS duration
FROM pg_stat_activity
WHERE state != ‘idle’
ORDER BY xact_start ASC LIMIT 1;

もしここで、数時間前のトランザクションが残っていたら、それがProcArrayに鎮座し続けている「見えない壁」だ。そのトランザクションが消えない限り、PostgreSQLは「このトランザクションが古いデータを参照するかもしれない」と判断し、VACUUMによるデータ削除(掃除)を先送りにしてしまう。

—

先輩からのアドバイス

ProcArrayは、いわば「DBの呼吸」を管理している場所だ。

1. トランザクションは短く保て:
長すぎるトランザクションはProcArrayに長く居座ることになる。それはVACUUMを遅延させ、テーブル肥大化(Bloat)を招く最大の要因だ。
2. 無駄な接続を減らせ:
接続の作成・破棄はProcArrayへの書き込みを伴う。アプリのコネクションプーリング設定は、DBの健康状態に直結する。
3. 監視の解像度を上げろ:
`pg_stat_activity` を定期的に取得して、`xact_start` が古いプロセスを検知するアラートを仕込んでおくと、現場ではかなり重宝されるはずだ。

PostgreSQLは「見えない部分」が本当にうまくできている。ProcArrayのようなアーキテクチャを理解しておくと、トラブルが起きた時に「なぜ?」という問いに対して、慌てず冷静に原因を切り分けられるようになる。

今日からクエリを叩くとき、少しだけ「今、ProcArrayがどうなっているかな」と想像してみてくれ。きっと、今までとは違う景色が見えるはずだよ。

それじゃ、また現場で会おう!

コメント

タイトルとURLをコピーしました