PostgreSQL「no space left on device」の原因と対処
- 作成日 2025.08.26
- 更新日 2025.10.06
- PostgreSQL
- PostgreSQL
概要。OS が書き込み先デバイスの空きブロックまたは inode を返せなくなると、PostgreSQL はファイル作成・拡張・書き込み時に「no space left on device(ENOSPC)」で失敗する。増えがちな場所は pg_wal(WAL)、ログ出力先、テンポラリ、テーブルスペース、バックアップ置き場。発生条件の整理、原因別の切り分け、緊急確保手順と恒久対策、監視ポイントまでを一気にまとめる。
- 1. 発生条件(どんな時に出るか)
- 2. まず最初の安全確認(書き込み圧力を下げる)
- 3. OS レベルの現状把握クイックコマンド
- 4. PostgreSQL から見える危険サイン
- 5. 緊急対応:数百 MB~数 GB を素早く空ける
- 6. WAL(pg_wal)が膨らむ原因と対処
- 7. テンポラリ肥大(ソート/ハッシュ/集計)の抑制
- 8. テーブル・インデックスの膨張対策(空き確保と恒久策)
- 9. ログ/バックアップの置き場所を分離(同居は詰まりの元)
- 10. pg_wal の移設(停止メンテでのみ実施)
- 11. コンテナ/Kubernetes/クラウド特有の詰まり
- 12. inode 枯渇や quota 超過の見分け方
- 13. 監視・予防(再発させない)
- 14. 原因別トラブルシュートの決め手一覧
- 15. 現場でそのまま使える点検スクリプト(Bash)
- 16. WAL 保持の具体値を把握(SQL ひな形)
- 17. テンポラリ多発クエリの可視化(設定と読み方)
- 18. 最後に(運用の勘所)
発生条件(どんな時に出るか)
・PGDATA 配下やテーブルスペースのマウントで「空きブロック」または「inode」が尽きた
・コンテナや VM の割り当てストレージ上限に到達(overlay2、LVM、クラウドのボリューム制限)
・WAL の過剰蓄積(レプリケーションスロット、長時間停止したスタンバイ、wal_keep_size 等)
・巨大な並べ替え/ハッシュでテンポラリが爆発(work_mem 不足、索引無しの重いクエリ)
・ログやバックアップ、ダンプを同一ディスクに置いて肥大
・inode 枯渇(小さなファイルを大量生成)や quota 超過でも同様に ENOSPC になる
まず最初の安全確認(書き込み圧力を下げる)
・アプリからの更新を抑える(読み取り系のみ許可)。長大トランザクションを終了させる
・自動バキュームや重いバッチが動いていれば一時停止を検討
・クラッシュ回避のため「pg_wal を絶対に手で削除しない」。復旧不能になる
OS レベルの現状把握クイックコマンド
# ディスクと inode の空き
df -h
df -i
# どのディレクトリが増えているか(PGDATA 直下)
du -xhd1 "$PGDATA" | sort -h
# WAL/ログ/テンポラリの代表格
du -sh "$PGDATA/pg_wal" 2>/dev/null
du -sch "$PGDATA"/log/* 2>/dev/null | tail -1
find "$PGDATA" -type d -name "pgsql_tmp" -print -exec du -sh {} \;
# 最近巨大化したファイルの特定(24 時間以内に 100MB 超)
find "$PGDATA" -xdev -type f -size +100M -mtime -1 -lsPostgreSQL から見える危険サイン
-- WAL とアーカイバの状況
SELECT * FROM pg_stat_archiver;
SELECT slot_name, plugin, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots;
-- 長時間トランザクション(VACUUM/不要WAL保持の元凶)
SELECT pid, usename, state, xact_start, now()-xact_start AS xact_age, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;
-- データ/指数の肥大状況
SELECT relkind, schemaname||'.'||relname AS rel,
pg_size_pretty(pg_total_relation_size(relid)) AS total,
pg_size_pretty(pg_relation_size(relid)) AS table,
pg_size_pretty(pg_indexes_size(relid)) AS indexes
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;
-- 設定の重要どころ
SELECT name, setting, unit
FROM pg_settings
WHERE name IN ('data_directory','log_directory','temp_tablespaces',
'wal_keep_size','max_wal_size','archive_mode','archive_command',
'work_mem','maintenance_work_mem','log_temp_files');緊急対応:数百 MB~数 GB を素早く空ける
・アプリ/OS のログを圧縮・削除(logrotate を即時実行)。PostgreSQL のログ出力先が別マウントならそちらを優先
・PGDATA 配下に紛れ込んだバックアップや古い .dump/.gz を移動
・巨大な core dump、不要キャッシュ(/var/cache、パッケージキャッシュ)を清掃
・平常時は「非常用の予約ファイル」を用意しておき、満杯時に消して即時確保
# logrotate を手動起動(ディストリによりコマンドは異なる)
sudo logrotate -f /etc/logrotate.conf
# 非常用 1GB 予約ファイル(平常時に作成しておく)
sudo fallocate -l 1G /var/lib/postgresql/.reserve
# ディスクが詰まったら即削除
sudo rm -f /var/lib/postgresql/.reserveWAL(pg_wal)が膨らむ原因と対処
・スタンバイ停止やネットワーク断で送れない → 復旧後に追いつくまで増える
・レプリケーションスロットが非アクティブで残存 → restart_lsn 以前の WAL を保持
・wal_keep_size が過大 → 物理レプリカ用の保持が大きすぎる
・アーカイブ先が詰まり archiver が失敗 → pg_wal が再利用できない
-- 使っていないレプリケーションスロットを削除(要確認)
SELECT slot_name, active FROM pg_replication_slots WHERE active = false;
SELECT pg_drop_replication_slot('unused_slot_name');
-- wal_keep_size を縮小(再起動/リロードが必要な場合あり)
ALTER SYSTEM SET wal_keep_size = '256MB'; -- 例
SELECT pg_reload_conf();
-- アーカイブ失敗が続くならアーカイブ先の空き/到達性を回復
SELECT * FROM pg_stat_archiver; -- failed_count, last_failed_wal を確認テンポラリ肥大(ソート/ハッシュ/集計)の抑制
・索引の追加、クエリ計画の見直しでテンポラリ発生自体を減らす
・work_mem を適切化(大き過ぎは逆に同時並行で危険)。ログで可視化
-- 10MB 超のテンポラリ発生をログへ
ALTER SYSTEM SET log_temp_files = '10MB';
SELECT pg_reload_conf();
-- 一時ディレクトリ/テーブルスペースの使用量確認
du -sh "$PGDATA"/base/*/pgsql_tmp 2>/dev/null
-- 可能なら temp_tablespaces を別ディスクに
ALTER SYSTEM SET temp_tablespaces = 'ts_temp'; -- 事前に作成したテーブルスペース名テーブル・インデックスの膨張対策(空き確保と恒久策)
・長期トランザクションを解消してから VACUUM を実行
・重い表には並列 vacuumdb、断続的に REINDEX。ダウンタイム無し縮小が必要なら pg_repack を検討
-- 全データベースを並列バキューム(要メンテ時間)
vacuumdb -j4 -d postgres -U postgres -v --all
-- 断片化がひどい索引を再構成
REINDEX TABLE CONCURRENTLY my_schema.my_table;
-- 代表的なサイズ可視化
SELECT schemaname, relname,
pg_size_pretty(pg_total_relation_size(relid)) AS total
FROM pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 30;ログ/バックアップの置き場所を分離(同居は詰まりの元)
・log_directory を別マウントへ。アーカイブ先、バックアップ先も別ディスクか外部ストレージへ
・同一マウントに置く場合は容量監視とローテーションを厳格化
-- ログの出力先を変更(ディレクトリは事前作成・権限調整)
ALTER SYSTEM SET log_directory = '/var/log/postgresql-db1';
SELECT pg_reload_conf();
-- WAL アーカイブ先も別ボリューム/オブジェクトストレージへ
ALTER SYSTEM SET archive_mode = 'on';
ALTER SYSTEM SET archive_command = 'rsync -a %p backuphost:/pgarchive/%f';pg_wal の移設(停止メンテでのみ実施)
・ディスクを増やせない場合の最終手段。必ずサーバ停止→安全に移動→シンボリックリンク
・運用中に pg_wal を触らない
# 1) PostgreSQL 停止
sudo systemctl stop postgresql
# 2) 新マウントへ移動
sudo rsync -a --delete "$PGDATA/pg_wal/" /mnt/big/pg_wal/
# 3) 元を退避しシンボリックリンク作成
sudo mv "$PGDATA/pg_wal" "$PGDATA/pg_wal.bak"
sudo ln -s /mnt/big/pg_wal "$PGDATA/pg_wal"
# 4) パーミッション確認後、起動
sudo chown -h postgres:postgres "$PGDATA/pg_wal"
sudo systemctl start postgresqlコンテナ/Kubernetes/クラウド特有の詰まり
・コンテナの writable layer 容量不足 → データは必ず PVC/ボリュームへ。ログも stdout へ出し集中ローテート
・PVC 容量のオンライン拡張とファイルシステム拡張(resize2fs/xfs_growfs)
・スナップショットやバックアップジョブが同一ボリュームを専有していないかを確認
inode 枯渇や quota 超過の見分け方
・df -i で IUse% が 100% なら inode 不足。小さなファイル群(ログや一時ファイル)が原因
・ユーザ/グループ quota が設定されている環境では repquota、quota コマンドで上限超過を確認・調整
監視・予防(再発させない)
・ディスク使用率/残り inode/pg_wal 容量/アーカイブ失敗回数の監視
・長時間トランザクションのしきい値監視(xact_age)
・log_temp_files と auto_explain でテンポラリ多発クエリを洗い出し
・定期的に vacuumdb と REINDEX、メンテ後のサイズ差分を記録
・非常用予約ファイルを 1~5GB 用意し、満杯時に即削除できる体制を整える
原因別トラブルシュートの決め手一覧
・pg_wal が巨大 → レプリケーションスロット/スタンバイ停止/アーカイブ失敗を確認、不要スロット削除・復旧
・ログだらけ → logrotate 即時実行、保管日数短縮、出力先分離
・テンポラリだらけ → log_temp_files で発生源特定、索引/クエリ改善、temp_tablespaces 分離
・テーブル/索引肥大 → 長期トランザクション解消→VACUUM/REINDEX/pg_repack
・inode 枯渇 → 小ファイル群の整理、ローテーション設定の見直し
・コンテナ層満杯 → 永続ボリュームへ誘導、レイヤ肥大を避ける
現場でそのまま使える点検スクリプト(Bash)
#!/usr/bin/env bash
set -e
echo "== Disk blocks & inodes =="
df -h; echo; df -i
echo; echo "== Top dirs under PGDATA =="; du -xhd1 "$PGDATA" | sort -h
echo; echo "== pg_wal size =="; du -sh "$PGDATA/pg_wal" 2>/dev/null || true
echo; echo "== log dir size =="; du -sh "$PGDATA"/log 2>/dev/null || true
echo; echo "== temp dirs =="; find "$PGDATA" -type d -name pgsql_tmp -exec du -sh {} \; 2>/dev/nullWAL 保持の具体値を把握(SQL ひな形)
WITH slots AS (
SELECT slot_name, active, restart_lsn
FROM pg_replication_slots
),
prog AS (
SELECT pg_current_wal_lsn() AS cur
)
SELECT s.slot_name, s.active,
pg_size_pretty(pg_wal_lsn_diff(p.cur, s.restart_lsn)) AS retained_from_slot
FROM slots s CROSS JOIN prog p
ORDER BY pg_wal_lsn_diff(p.cur, s.restart_lsn) DESC;テンポラリ多発クエリの可視化(設定と読み方)
-- 10MB 超のテンポラリをログする(恒久化は postgresql.conf へ)
ALTER SYSTEM SET log_temp_files = '10MB';
SELECT pg_reload_conf();
-- 以降、ログ(csvlog)から "temporary file" を grep
grep "temporary file" /var/log/postgresql/*.csv | sort | tail -n 50最後に(運用の勘所)
・「pg_wal を手で消さない」「稼働中の PGDATA を直接いじらない」が大前提
・短期はログ/一時/バックアップの分離とローテート、長期はレプリケーションスロット管理・クエリ/索引改善・定期メンテ
・ディスク拡張は最も確実な恒久策。拡張後も監視しきい値と非常用予約ファイルでワンミスを防ぐ
-
前の記事
PostgreSQLでの『column reference is ambiguous』の原因と対処法 2025.08.25
-
次の記事
PostgreSQL「ERROR: invalid byte sequence for encoding」の原因と対処 2025.08.27
コメントを書く