Skip to main content
© 2024 NTT DATA Group Corporation
© 2024 NTT DATA Group Corporation
第47回 PostgreSQLアンカンファレンス@オンライン
pg_stat_activityの
不可解な観測結果の謎
2024年7月1日(月)
NTTデータグループ 藤井雅雄
© 2024 NTT DATA Group Corporation 2
自己紹介
藤井 雅雄
Database Technical Lead @ NTTデータグループ
データベース研究開発
PostgreSQL 技術支援
PostgreSQLコミッタ
レプリケーション
WAL圧縮
バックアップ進捗確認
pg_bigm(全文検索モジュール) コミッタ
fujii_masao
MasaoFujii
© 2024 NTT DATA Group Corporation 3
本講演について
講演資料は、後日、NTTデータグループのSlideShareアカウント上で公開予定です。
https://www.slideshare.net/nttdata-tech
© 2024 NTT DATA Group Corporation 4
pg_stat_activityの
不可解な観測結果の謎
© 2024 NTT DATA Group Corporation 5
不可解な観測結果
定期的にpg_stat_activityを監視して、1時間以上かかっているロングトランザクションを検出。
SELECT * FROM pg_stat_activity
WHERE current_timestamp – xact_start > '1 hour'::interval;
ある時、下記の結果が急に観測され、ロングトランザクションとして検出。
ただし、観測とほぼ同じタイミングで、この"ロングトランザクション"のバックエンドは終了。
-[ RECORD 1 ]----+--------------------------------
xact_start | 2日前のタイムスタンプ
query_start | 2日前のタイムスタンプ
state | idle
query | COMMIT;
© 2024 NTT DATA Group Corporation 6
不可解な3つのポイント
その1: state が idle なのに xact_start に NULL以外 がセットされているのはなぜなのか?
• idle状態のバックエンドでは、トランザクションを実行していないため、xact_start は NULL のはず。
• xact_start が NULL以外 にセットされて、バックエンドがトランザクションを開始済の場合は、state は active
や idle in transaction などになるはず。
その2: 「xact_start = 2日前」のレコードが急に観測されたのはなぜなのか?
• 本当に2日前から開始されたトランザクションであれば、2日経過する前にロングトランザクションとして検出されてい
たはず。
その3: 観測とほぼ同じタイミングで"ロングトランザクション"のバックエンドが終了したのはなぜなのか?
• 今回はロングトランザクションを強制的に終了させる処理をしているわけではない。
© 2024 NTT DATA Group Corporation 7
state と xact_start の基本的な遷移
項番 処理 処理中の state と xact_start の変更 state xact_start
① 次のクエリの受信待ち state を idle に変更 idle NULL
② BEGINの受信と実行
state を active に変更 active NULL
xact_start を現在時刻に変更 active ②の時刻
③ 次のクエリの受信待ち state を idle in transaction に変更 idle in transaction ②の時刻
④ クエリの受信と実行 state を active に変更 active ②の時刻
⑤ 次のクエリの受信待ち state を idle in transaction に変更 idle in transaction ②の時刻
⑥ COMMITの受信と実行
state を active に変更 active ②の時刻
xact_start を NULL に変更 active NULL
⑦ 次のクエリの受信待ち state を idle に変更 idle NULL
state = idle で xact_start = NULL以外 となることは基本的にない。
© 2024 NTT DATA Group Corporation 8
コミュニティでの報告
コミュニティでも同様の問題は報告されているが、その発生条件や原因は謎のまま。
BUG #10123: Weird entries in pg_stat_activity
https://www.postgresql.org/message-id/flat/20140424101827.2714.39486%40wrigleys.postgresql.org
Open transaction with 'idle' (not 'idle in transaction') status
https://www.postgresql.org/message-id/flat/CAL_0b1s33-SD56GUCMpjXTrEnyViY2WHxS894dmOnGeoHiSMBA%40mail.gmail.com
© 2024 NTT DATA Group Corporation 9
発生条件や原因を解説する前に
面白い解析ネタなので、自分で解析したい人は、
一旦アンカンファレンスから退席して、10分後ぐらいに戻ってきてもらえれば!
© 2024 NTT DATA Group Corporation 10
state が idle なのに xact_start が NULL以外 となる少なくとも3つのケース
ロングトランザクションを検出するクエリを0.1秒間隔で実行しながら、
リグレッションテスト(make installcheck)を走らせると、少なくとも以下3つのケースにおいて、
pg_stat_activityで「state = idle」かつ「xact_start = NULL以外」と観測されることを確認。
1. 'D' (Describe) message
2. ProcessCatchupInterrupt()
3. RemoveTempRelationsCallback()
© 2024 NTT DATA Group Corporation 11
1. 'D' (Describe) message
プリペアド文やポータルの情報を取得したいときに、クライアントがPostgreSQL側に発行する処理要求メッセージ
• libpqでは、PQdescribePrepared()やPQdescribePortal()を呼ぶと、'D' (Describe) messageが発行される
トランザクションを開始していない状態で、PQdescribePrepared()を呼ぶなどして、'D' (Describe) messageの処理を
PostgreSQLに要求すると、バックエンド側は idle 状態のままで xact_start にメッセージ受信時のタイムスタンプをセットする。
ただし、以下2点が今回の観測結果と異なる。
• xact_start は、急に2日前のタイムスタンプではなく、メッセージ受信時のタイムスタンプとなる
• 'D' (Describe) messageの処理後もバックエンドは勝手に終了しない
項番 処理 処理中の state と xact_start の変更 state xact_start
① 次のクエリの受信待ち state を idle に変更 idle NULL
②
'D' (Describe)
messageの受信と実行
state を active に変更 active NULL
xact_start を現在時刻に変更 idle ②の時刻
'D' (Describe) messageを処理
する関数のひとつである
exec_describe_statement_me
ssage()では、stateをactiveにセッ
トするコードを呼んでいない。これがバ
グなのかどうかは不明
© 2024 NTT DATA Group Corporation 12
2. ProcessCatchupInterrupt()
無効化メッセージを受信して処理する関数
トランザクションを開始していない状態で、CatchupInterruptの割り込みが発生して、ProcessCatchupInterrupt()が呼ば
れて無効化メッセージを受信・処理すると、バックエンド側は idle 状態のままで xact_start に前回のクエリ実行時のタイムスタン
プをセットする。
項番 処理 処理中の state と xact_start の変更 state xact_start
①
次のクエリの受信待ち state を idle に変更 idle NULL
【割り込み】
CatchupInterruptの受信と処理
(ProcessCatchupInterrupt())
xact_start を前回クエリの時刻に変更 idle 前回クエリの時刻
xact_start を NULL に変更 idle NULL
次のクエリの受信待ちの再開 idle NULL
② BEGINの受信と実行
state を active に変更 active NULL
xact_start を現在時刻に変更 active ②の時刻
• バックエンドは、クエリ実行を効率化するために必要なカタログ情報をpg_classなどから取得し、その情報をローカルメモリにキャッシュする
• 例えばDDLでテーブルに列が追加されるなどしてカタログ情報が変更されると、バックエンドは、ローカルメモリ内のカタログ情報のキャッシュを
無効化して、再度pg_classなどから取得してキャッシュする必要がある
• 無効化メッセージは、このカタログ情報のキャッシュの無効化を要求するメッセージ
例えば、前回のクエリ実行が2日前の場合 (idle状態のまま
2日間経過している場合)、xact_start は急に2日前のタイ
ムスタンプとなる
© 2024 NTT DATA Group Corporation 13
2. ProcessCatchupInterrupt()
pg_stat_activityで「state = idle」かつ「xact_start = 2日前」は観測できるが、
ProcessCatchupInterrupt()の処理後もバックエンドは勝手に終了しない点が今回の観測結果と異なる。
2024-06-12 07:49:14 JST [client backend] BACKTRACE:
2 postgres 0x00000001084b4a82 pgstat_report_xact_timestamp + 210
3 postgres 0x0000000107fcaa32 StartTransaction + 882
4 postgres 0x0000000107fca581 StartTransactionCommand + 65
5 postgres 0x000000010845b502 ProcessCatchupInterrupt + 290
6 postgres 0x0000000108487b1d ProcessClientReadInterrupt + 109
7 postgres 0x0000000108268edb secure_read + 27
8 postgres 0x0000000108275869 pq_recvbuf + 201
9 postgres 0x0000000108275755 pq_getbyte + 85
10 postgres 0x000000010848f03b SocketBackend + 43
11 postgres 0x000000010848bff2 ReadCommand + 34
12 postgres 0x000000010848b5a7 PostgresMain + 2087
13 postgres 0x000000010839cce5 BackendRun + 85
14 postgres 0x000000010839c273 BackendStartup + 547
15 postgres 0x000000010839b06c ServerLoop + 716
16 postgres 0x0000000108398701 PostmasterMain + 6241
17 postgres 0x00000001082792ca main + 762
18 dyld 0x00007ff812ec6366 start + 1942
(参考) idle状態のバックエンドが、ProcessCatchupInterrupt()を呼んで、xact_startを変更するバックトレース
© 2024 NTT DATA Group Corporation 14
3. RemoveTempRelationsCallback()
バックエンドの終了時に、そのバックエンドが作成した一時リレーションを削除する関数
トランザクションを開始していない状態で、一時リレーションを作成したバックエンドを終了させると、終了処理のコールバック関数とし
てRemoveTempRelationsCallback()が呼び出され、バックエンド側は idle 状態のままで xact_start に前回のクエリ実行
時のタイムスタンプがセットされる。
項番 処理 処理中の state と xact_start の変更 state xact_start
・・・ (CREATE TEMP TABLEなどで一時リレーションを作成)
① 次のクエリの受信待ち state を idle に変更 idle NULL
②
終了要求の受信
とバックエンドの
終了処理
・・・ idle NULL
RemoveTempRel
ationsCallback()
xact_start を前回クエリの時刻に変更 idle 前回クエリの時刻
xact_start を NULL に変更 idle NULL
・・・ 例えば、前回のクエリ実行が2日前の場合 (idle状態のまま
2日間経過している場合)、xact_start は急に2日前のタイ
ムスタンプとなる
© 2024 NTT DATA Group Corporation 15
3. RemoveTempRelationsCallback()
pg_stat_activityで「state = idle」かつ「xact_start = 2日前」を観測でき、
RemoveTempRelationsCallback()の処理後にバックエンドは終了しているため、今回の観測結果のとおり。
2024-06-11 22:01:07 JST [client backend][55626] BACKTRACE:
2 postgres 0x0000000110134912 pgstat_report_xact_timestamp + 194
3 postgres 0x000000010fc4aa72 StartTransaction + 882
4 postgres 0x000000010fc4a5c1 StartTransactionCommand + 65
5 postgres 0x000000010fcafd16 RemoveTempRelationsCallback + 38
6 postgres 0x00000001100c9a1e shmem_exit + 222
7 postgres 0x00000001100c9857 proc_exit_prepare + 119
8 postgres 0x00000001100c9763 proc_exit + 19
9 postgres 0x000000011010ba6b PostgresMain + 3643
10 postgres 0x000000011001cba5 BackendRun + 85
11 postgres 0x000000011001c133 BackendStartup + 547
12 postgres 0x000000011001af2c ServerLoop + 716
13 postgres 0x00000001100185c1 PostmasterMain + 6241
14 postgres 0x000000010fef918a main + 762
15 dyld 0x00007ff812ec6366 start + 1942
(参考) idle状態のバックエンドが、終了時にRemoveTempRelationsCallback()を呼んで、xact_startを変更するバックトレース
© 2024 NTT DATA Group Corporation 16
まとめ
pg_stat_activityでは、少なくとも3つのケースで
1. 'D' (Describe) message ・・・ プリペアド文やポータルの情報を取得
2. ProcessCatchupInterrupt() ・・・ 割り込みによる無効化メッセージの処理
3. RemoveTempRelationsCallback() ・・・ バックエンド終了時の一時リレーションの削除
一見不可解な観測結果となる可能性がある。
・ state が idle なのに xact_start が NULL以外 になる
・ xact_start が急に古いタイムスタンプになる
・ 観測とほぼ同じタイミングで上記の state と xact_start のバックエンドが終了する
© 2024 NTT DATA Group Corporation 17
まとめ
pg_stat_activityからロングトランザクションを検出する場合、xact_startのチェックだけでは誤検出の可能性があるため、
state が idle でないことも確認する方がよい
SELECT * FROM pg_stat_activity
WHERE current_timestamp – xact_start > '1 hour'::interval AND state <> 'idle';
この条件追加により、ProcessCatchupInterrupt()などの3つのケースを
検出できなくなるが、これらはロングトランザクションとして検出すべきものでは
ない(例えば、VACUUMを長時間阻害するものではない)ため、問題ない
記載されている会社名、商品名、又はサービス名は、
各社の登録商標又は商標です。