TL;DR: クイックフィックス
このエラーが表示される場合は、PostgreSQLサービスが実行されているものの、まだトラフィックを受け入れる準備ができていないことを意味します。通常、クラッシュや急な再起動の後にデータの整合性を確保するため、エンジンがWrite-Ahead Logs (WAL) のリプレイ(再適用)を行っています。
- しばらく待つ: ほとんどの場合、データベースが内部チェックを完了するまで数分待つだけで解決します。
- ログを監視する:
tail -f /var/log/postgresql/postgresql-15-main.logを実行して、リカバリの進行状況をリアルタイムで追跡します。 - 準備状態を確認する:
pg_isreadyユーティリティを使用して、接続失敗のログを増やすことなくステータスを確認します。
このエラーが発生する理由
PostgreSQLは起動時に保護用のリカバリモードに入ります。pg_wal ディレクトリ(古いバージョンでは pg_xlog)をスキャンし、メインのデータファイルに保存されていないコミット済みのトランザクションを探します。この安全策は、ハードリブート、停電、またはpostmasterプロセスが kill -9 で強制終了された後にトリガーされます。
データベースは「整合性のある状態」に達するまでロックされます。書き込み量が多い500GBのデータベースの場合、このプロセスに数分かかることがあります。Hot Standby レプリカを運用している場合も、スタンバイ側がプライマリから読み取り専用クエリを許可するのに十分なWALデータを受信するまで、このメッセージが表示されます。
ステップ1:起動の進行状況を監視する
完了までどのくらいかかるか推測するのではなく、ログを確認しましょう。ログには、リプレイ中の正確なWALの場所や、残りの作業量などの詳細情報が記載されています。
# Debian/Ubuntu系システム
sudo tail -f /var/log/postgresql/postgresql-main.log
# RHEL/CentOS系システム
sudo tail -f /var/lib/pgsql/data/log/postgresql.log
# Dockerコンテナ
docker logs -f <container_name>
以下の特定のログマーカーに注目してください:
LOG: database system was shut down at 2023-10-27 10:00:00 UTC
LOG: redo starts at 0/1A2B3C8
LOG: consistent recovery state reached at 0/1A2B4D0
LOG: database system is ready to accept connections
ステップ2:pg_isready を使用してヘルスチェックを行う
pg_isready ユーティリティを使用して、アプリケーションのクラッシュを回避しましょう。このツールは標準的なシェル終了コードを返すため、CI/CDパイプラインや起動スクリプトに最適です。ログを汚さずにデータベースのステータスをポーリングできます。
pg_isready -h localhost -p 5432
終了コードの意味は以下の通りです:
- 0: 準備完了。サーバーは接続を受け付けています。
- 1: ビジー。サーバーは接続を拒否しています(おそらくまだ起動中です)。
- 2: 応答なし。サーバーがダウンしているか、ネットワークの問題が発生しています。
ステップ3:スマートなリトライロジックを実装する
DockerやKubernetes環境では、アプリがデータベースよりも早く起動することがよくあります。コード側で、コンテナが起動した瞬間にデータベースが利用できない可能性があることを想定しておく必要があります。
このPythonの例では、基本的なバックオフ戦略を使用して、「起動中」フェーズを適切に処理しています。
import psycopg2
import time
def connect_with_retry():
retries = 15
while retries > 0:
try:
conn = psycopg2.connect("dbname=test user=postgres password=secret host=db")
return conn
except psycopg2.OperationalError as e:
if "starting up" in str(e):
print("Postgresはまだリカバリ中です。5秒後に再試行します...")
time.sleep(5)
retries -= 1
else:
raise e
raise Exception("タイムアウト: PostgreSQLに接続できませんでした。")
ステップ4:設定を調整してリカバリを高速化する
毎回データベースの起動に10分以上かかる場合は、設定によって過度なWALリプレイが強制されている可能性があります。PostgreSQLが「チェックポイント」を実行する頻度を調整することで、この時間を短縮できます。
postgresql.conf を開き、以下の設定を確認してください:
# チェックポイントの頻度を上げてリカバリ時間を短縮する
checkpoint_timeout = 5min
max_wal_size = 2GB
min_wal_size = 1GB
max_wal_size を増やすことで、データベースに余裕を持たせることができます。ただし、起動を速くしたい場合は、checkpoint_timeout が30分のような極端に長い時間に設定されていないことを確認してください。そうしないと、クラッシュ後にリプレイが必要なデータが膨大になってしまいます。
Docker/Kubernetesでのエラー対応
コンテナ化された環境では、このエラーが不要な再起動ループを引き起こすことがよくあります。docker-compose.yml で healthcheck を使用し、依存するサービスがデータベースの完全な稼働を待つように設定してください。
services:
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
最終確認
データベースが起動したと思ったら、以下の3つのステップで確認してください:
pg_isreadyをチェックし、終了コード0が返ってくることを確認する。- psql経由で簡単なクエリを実行する:
psql -U postgres -c "SELECT 1;"。 - ログに
database system is ready to accept connectionsというメッセージが出ているか確認する。
ログに動きがないままエラーが20分以上続く場合は、df -h でディスク容量を確認してください。ディスクがいっぱいになるとリカバリプロセスが無期限に停止し、データベースが起動フェーズで止まったままになります。

