PostgreSQLでpg_stat_activityがactiveのまま終了しない原因と対処法

概要

今回はPostgreSQLでクエリがずっと終了せず「active」のまま残ってしまう問題の対処法を解説します!

PostgreSQLを運用していると、SQLが実行されたまま終了しない事象に遭遇することがあります。

原因はいろいろありますが、そのまま放置するとパフォーマンス低下や他のクエリのブロック(ロック待ち)を引き起こす可能性があるため、早急な原因特定と対処が必要です。

この記事では、それを安全に強制停止するための対処法(pg_cancel_backend / pg_terminate_backendなど)を解説します。

それではやっていきましょう(^^!

目次

1. 状況の確認

まずは対象のセッションがどのクエリを実行中で、どのくらい時間がかかっているのかを特定します。
以下のSQLを実行して状況を確認しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT 
pid,
usename,
state,
wait_event_type,
wait_event,
query_start,
now() - query_start AS duration,
query
FROM
pg_stat_activity
ORDER BY
duration DESC;

このクエリで、実行時間(duration)が長いものや、ロック待ちが発生しているもの(wait_event_typeがLockなど)を特定できます。

2. クエリの強制停止方法

原因となっているプロセス(pid)が特定できたら、そのプロセスを停止させます。
PostgreSQLには停止のための関数が2つ用意されており、リスクが低いものから使用していきます(^^b

ステップ1:pg_cancel_backend でクエリのみをキャンセル

まずは影響の少ない「pg_cancel_backend(pid)」を使用します。

1
2
-- 指定したPIDのクエリをキャンセルする
SELECT pg_cancel_backend([対象のPID]);

ここで指定する対象のPIDは、「1. 状況の確認」で取得したPIDです。

これは現在実行中のクエリ(処理)だけを中断し、データベースへの接続(セッション)自体は維持します。
一時的な負荷や単純な重いクエリであれば、これで解決することが多いです。

ステップ2:pg_terminate_backend でセッションごと強制切断する

「pg_cancel_backend」を実行しても反応がない場合は、より強力な 「pg_terminate_backend(pid)」を使用します。

1
2
-- 指定したPIDのプロセス(接続)を強制切断する
SELECT pg_terminate_backend([対象のPID]);

これはOSレベルのSIGTERMシグナルを送り、接続そのものを強制的に切断します。
実行中のトランザクションはすべてロールバックされるため、本番環境での実行には十分な注意が必要です。

ステップ3:PostgreSQL自体の再起動(安全なリセット)

pg_terminate_backend は対象のプロセスにOSレベルで SIGTERM シグナルを送りますが、I/O待ちなどでプロセスが完全にハングアップ状態になっていると、このシグナルを受け取れずに止まらないことがあります。

しかしプロセスが消えないからといって、対象のプロセスだけを kill -9 でOSから強制終了するのは危険です。
なぜなら、PostgreSQLはデータ破損を防ぐため、1つのプロセスが異常終了(SIGKILL)したことを検知すると、強制的に他のすべての正常な接続も切断し、クラッシュリカバリ(自動再起動)を始める可能性があるからです(- -;

なので、プロセスがどうしても消えない場合は、PostgreSQL サービス自体を「計画的に再起動」する方がリスクの少ない安全な対処法になります!

1
2
3
4
5
# systemctl が使える環境の場合
$ sudo systemctl restart postgresql

# または pg_ctl コマンドを使用
$ pg_ctl restart -D /パス/データディレクトリ

再起動をすれば他のセッションも一度切断されますが、クラッシュリカバリではなく「正しくシャットダウンしてから起動」の処理が走るため、データ破損リスクは最小限に抑えられます。

しかしシステムの一時的な停止には変わりないので、本番環境なら業務影響がない深夜帯を狙うなど、体制をきちんと整えてから実行する必要があります(^^;

また、コマンドについては各運用環境によって異なるので、手順書がある場合はそれに従いましょう(^^!

ステップ4:それでも再起動すらできない場合の最終手段(kill -9 / OS再起動)

対象プロセスがOSレベルで完全にゾンビ化していると、PostgreSQLの再起動コマンドすらタイムアウトして終わらなくなってしまうことがあります。

この場合はどうしようもないので対象プロセスのkill -9やOS(サーバー)自体の再起動を実施します。

1
2
# PostgreSQLの再起動もできない場合のみ、本当に最終手段として実行
$ kill -9 [対象のPID]

前述の通り、これを実行すると自動的に PostgreSQL 全体がクラッシュリカバリモードに入って一度ダウンする可能性があります。
「すでにデータベース全体がハングしており、どうしようもない場合」のみ実行してください(^^;

3. 「active」のまま終了しないときによくある原因

PostgreSQLでセッションが長時間active状態になる理由は、主に以下の4つが考えられます。

① 重いクエリの実行

インデックスが効いていないフルスキャンや、巨大なテーブル同士の複雑なJOINなど、純粋に処理時間がかかりすぎているケースです。
まずはそのクエリ自体が本当に重い処理なのかを確認してみましょう。

② ロック待ち(ブロック)とデッドロック

他のトランザクションが更新中の行やテーブルに対するロックを保持しており、終わるのを待機(Wait)している状態です。
複数セッションが互いにロックを待ち合う「デッドロック」に陥っている可能性があります。

③ クライアント(アプリ)側のハングアップ

アプリケーション側の処理が止まっていたり、ネットワーク切断などでコネクションだけがDB側に残ってしまっているケースです。

④ サーバリソースの不足

CPUやメモリ、ディスクI/Oが枯渇しており、通常ならすぐに終わるクエリも極端に遅くなっている可能性があります。

4. 「active」のまま終了しない事象の対応策

特定した原因をもとに、再発防止のための恒久対策を実施します。

問題が頻発すると心臓に悪いし、お客様にも迷惑をかけるので、根本的な原因を取り除くチューニングや運用見直しはやったほうが良いです。

  • クエリのチューニング:
    • EXPLAIN ANALYZE を使って実行計画を確認し、適切なインデックスを追加する、あるいはSQL自体を書き直す。
  • 定期的なメンテナンス:
    • VACUUMANALYZE が適切に実行されているか確認し、統計情報を最新化する。
  • タイムアウトの設定:
    • statement_timeout などを設定し、一定時間以上かかるクエリは自動でキャンセルされる仕組みを導入する。

締め

結構頻繁に起こる事象だとは思いますが、対処法を間違えると大きな障害になるリスクが高いため、運用にかかわる人間としては、気を遣わないといけません。

pg_stat_activity で長時間終わらないクエリを見つけた場合は、慌てずに上記の手順で対処しましょう。
以下にまとめを用意しておきますね!

  1. 状況確認(どのクエリか、どれだけ時間がかかっているか)
  2. 安全なキャンセル(まず pg_cancel_backend を試す)
  3. 強制切断(それでもダメなら pg_terminate_backend
  4. 全体再起動(それでも消えないなら、全体を安全に再起動する)
  5. 最終手段の検討(再起動もできないなら kill -9 やOS再起動を実施)

トラブル発生時にすぐ対応できるよう、これらの確認や停止の手順は運用マニュアルにまとめておく方が精神衛生上も安心できますね!

以上となります。
運用上発生する事象は多数ありますが、できる限りマニュアル化して、誰でも対応できるようにしておきましょう!
それではお疲れさまでした!