PostgreSQL「disk quota exceeded」の原因と対処
- 作成日 2025.08.28
- 更新日 2025.10.06
- PostgreSQL
- PostgreSQL
概要。OS/ストレージのクォータ(ユーザ/グループ/プロジェクト/ボリューム制限)に到達すると、PostgreSQL がファイルの拡張・作成・書き込み時に「disk quota exceeded(EDQUOT)」で失敗する。発生条件、切り分け手順、即時の圧力低減、クォータ緩和・設計変更、データ削減・移設、監視と予防までを実務目線で整理。ログの典型文言と貼って使えるコマンド/SQL付き。
- 1. 発生条件(何が起きているか)
- 2. まず行う安全確保(圧力を下げてエラー頻発を止める)
- 3. どのマウント/誰のクォータに当たっているか特定
- 4. どこが太っているか(PostgreSQL 側の可視化テンプレ)
- 5. 一時ファイル/実行中クエリの圧力を特定
- 6. 権限者向け:OS クォータの緩和/解除(代表例)
- 7. 即効で空ける:ログ/バックアップ/キャッシュの整理
- 8. データ削減:安全にサイズを落とす実務手順
- 9. クォータを回避:別ディスクへ逃がす(テーブルスペース移設)
- 10. WAL・ログ・一時の分離(同一クォータに載せない)
- 11. コンテナ/Kubernetes/クラウドでの特有ポイント
- 12. 監視と予防の設計(再発させない)
- 13. よくある原因別の決め手(早見表)
- 14. 現場でそのまま流せる点検スクリプト(Bash)
- 15. WAL/スロット起因ならここも確認(補助 SQL)
- 16. 最後に(運用の勘どころ)
発生条件(何が起きているか)
・ファイルシステムのクォータが PGDATA やテーブルスペースの所有者に対して上限到達
・ユーザ/グループ/プロジェクト(XFS)/クラウドボリュームの割当サイズ制限に抵触
・拡張が必要な場面(テーブル/インデックス拡張、WAL/一時ファイル/ソート・ハッシュ、VACUUM/REINDEX/チェックポイント書込み)で失敗
・ログ/バックアップを同じクォータ対象ディスクに置いて肥大
・典型ログ例
ERROR: could not extend file "base/16384/2619": Disk quota exceeded
HINT: Check free disk space.
DETAIL: Failed on block 123456 of relation base/16384/2619.
-- または
could not write to file "pg_wal/000000010000000A000000FF": Disk quota exceededまず行う安全確保(圧力を下げてエラー頻発を止める)
・更新系トラフィックを抑制(アプリの一時的な書込み停止、長大トランザクション終了)
・重いバッチ/分析/メンテを一時停止(VACUUM FULL/REINDEX/大規模 CTAS は保留)
・pg_wal やデータファイルを手動削除しない(破損リスク大)
どのマウント/誰のクォータに当たっているか特定
-- PostgreSQL から主要パスを把握
SHOW data_directory;
SELECT spcname, pg_tablespace_location(oid) AS location FROM pg_tablespace;
-- OS からディスク/マウント確認
df -h
mount | egrep 'xfs|ext4|btrfs|zfs'
# クォータ対象と所有者を確認(postgres ユーザ前提)
id postgres
sudo -u postgres quota -s # ユーザクォータ
sudo repquota -a # システム全体
sudo xfs_quota -x -c "report -h" /mountpoint # XFS プロジェクト/ユーザ/グループ
どこが太っているか(PostgreSQL 側の可視化テンプレ)
-- データベース/テーブル/インデックスのサイズ上位
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database ORDER BY pg_database_size(datname) DESC;
SELECT schemaname||'.'||relname AS rel,
pg_size_pretty(pg_total_relation_size(relid)) AS total,
pg_size_pretty(pg_relation_size(relid)) AS tbl,
pg_size_pretty(pg_indexes_size(relid)) AS idx
FROM pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 30;
-- テーブルスペース単位の使用量
SELECT spcname,
pg_size_pretty(SUM(pg_total_relation_size(c.oid))) AS total
FROM pg_class c
JOIN pg_tablespace t ON c.reltablespace = t.oid OR (c.reltablespace=0 AND t.spcname='pg_default')
GROUP BY spcname ORDER BY SUM(pg_total_relation_size(c.oid)) DESC;
一時ファイル/実行中クエリの圧力を特定
-- 実行中の長時間トランザクション
SELECT pid, usename, xact_start, now()-xact_start AS age, query
FROM pg_stat_activity WHERE xact_start IS NOT NULL
ORDER BY xact_start;
-- 一時ファイルログを有効化して発生源を掴む(要リロード)
ALTER SYSTEM SET log_temp_files = '10MB';
SELECT pg_reload_conf();
-- 以後、PostgreSQL ログの "temporary file" を確認
権限者向け:OS クォータの緩和/解除(代表例)
# ext4 のユーザ/グループクォータを編集(例:postgres を 500GB に)
sudo edquota -u postgres
# もしくは setquota(ソフト/ハードを MB 単位で設定)
sudo setquota -u postgres 0 524288000 0 0 /mountpoint
# XFS のプロジェクトクォータ(/etc/projects, /etc/projid を設定済みとする)
sudo xfs_quota -x -c 'report -h' /mountpoint
sudo xfs_quota -x -c 'limit -p bsoft=450g bhard=500g pgdata' /mountpoint
# 変更後、クォータの反映と確認
sudo quotaon -av
sudo repquota -a
・マウントオプションに usrquota/grpquota/prjquota が無い場合は再マウントが必要
・クラウドボリュームはサイズ拡張後にファイルシステム拡張(resize2fs/xfs_growfs)
即効で空ける:ログ/バックアップ/キャッシュの整理
# PostgreSQL ログの強制ローテート(ディストリによりパス調整)
sudo logrotate -f /etc/logrotate.conf
# 同一マウントに置いていた .dump/.gz/バックアップを外部へ退避
sudo du -sh /var/lib/postgresql/*.dump* 2>/dev/null
sudo mv /var/lib/postgresql/*.dump* /mnt/backup/
# 緊急用の「予約ファイル」を平時から用意 → 満杯時に削除して数 GB 確保
sudo fallocate -l 2G /var/lib/postgresql/.reserve
sudo rm -f /var/lib/postgresql/.reserve
データ削減:安全にサイズを落とす実務手順
-- 1) 長期トランザクションを解消(VACUUM/トランザクションID 再利用阻害の元)
-- 2) 使用されていないインデックスを洗い出し削除(pg_stat_user_indexes)
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan NULLS FIRST, relname;
-- 3) REINDEX/REINDEX CONCURRENTLY で断片化を縮小
REINDEX TABLE CONCURRENTLY my_schema.my_table;
-- 4) 大規模縮小は pg_repack やパーティション化再設計を検討
-- 5) 古いデータのアーカイブ/削除(業務要件に沿って)
DELETE FROM my_table WHERE created_at < now() - interval '365 days';
クォータを回避:別ディスクへ逃がす(テーブルスペース移設)
-- 別マウントにテーブルスペースを作成(空き/クォータ余裕のある場所)
CREATE TABLESPACE ts_large LOCATION '/mnt/large_pg/ts_large';
-- 重いテーブルとインデックスを移動(無停止で段階的に)
ALTER TABLE my_schema.big_table SET TABLESPACE ts_large;
ALTER INDEX my_schema.big_table_idx SET TABLESPACE ts_large;
-- 新規オブジェクトのデフォルト先を切替(セッション単位/DB 単位で)
ALTER DATABASE mydb SET default_tablespace = 'ts_large';
WAL・ログ・一時の分離(同一クォータに載せない)
-- ログ出力先の分離(別マウントへ)
ALTER SYSTEM SET log_directory = '/var/log/postgresql-db1';
SELECT pg_reload_conf();
-- 一時領域(temp_tablespaces)の分離
CREATE TABLESPACE ts_temp LOCATION '/mnt/fast_tmp/ts_temp';
ALTER SYSTEM SET temp_tablespaces = 'ts_temp';
-- WAL アーカイブ先も別ボリュームへ
ALTER SYSTEM SET archive_mode = 'on';
ALTER SYSTEM SET archive_command = 'rsync -a %p /mnt/pgarchive/%f';
コンテナ/Kubernetes/クラウドでの特有ポイント
・コンテナ writable layer のクォータ到達(overlay2)を避け、必ず PVC/ボリュームへ永続化
・PVC 容量を拡張後、ファイルシステムも拡張(xfs_growfs/resize2fs)
・emptyDir.sizeLimit/ephemeral-storage の制限超過を監視
・クラウドスナップショット/バックアップが同一ボリュームを圧迫していないか確認
監視と予防の設計(再発させない)
・ディスク使用率に加えて「クォータ使用率」を監視(repquota/xfs_quota のメトリクス化)
・pg_wal 容量、アーカイブ失敗、長時間トランザクション、log_temp_files 発生回数を可視化
・定期 REINDEX/pg_repack とメンテ後のサイズ差分を記録
・ログ/バックアップ/一時は別ボリュームを原則に
・非常用予約ファイルを 1~5GB 用意し、運用手順に組み込む
よくある原因別の決め手(早見表)
・ユーザクォータ上限 → edquota/setquota で上限引き上げ、あるいは所有者変更/テーブルスペース移設
・XFS プロジェクトクォータ → xfs_quota limit -p で調整、設計上は PGDATA とログを別プロジェクトへ
・同一ボリュームにログ/バックアップ → 出力先分離+ローテート短縮
・一時ファイル爆発 → インデックス追加、クエリ見直し、work_mem 微調整、temp_tablespaces 分離
・テーブル/索引膨張 → 長期トランザクション排除→REINDEX/pg_repack/パーティション化
現場でそのまま流せる点検スクリプト(Bash)
#!/usr/bin/env bash
set -euo pipefail
echo "== Paths =="; sudo -u postgres psql -Atc "SHOW data_directory; SELECT spcname||':'||coalesce(pg_tablespace_location(oid),'[default]') FROM pg_tablespace;"
echo; echo "== Disk & Quota =="; df -h; df -i; sudo repquota -a || true
echo; echo "== XFS quota (if any) =="; mount | grep xfs && sudo xfs_quota -x -c "report -h" / || true
echo; echo "== Top dirs under PGDATA =="; PGDATA=$(sudo -u postgres psql -Atc "SHOW data_directory;"); du -xhd1 "$PGDATA" | sort -h
WAL/スロット起因ならここも確認(補助 SQL)
SELECT * FROM pg_stat_archiver;
SELECT slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
-- 不要な非アクティブスロットは削除(要確認)
-- SELECT pg_drop_replication_slot('unused_slot');最後に(運用の勘どころ)
・「disk quota exceeded」は ENOSPC と違い、空き容量があってもクォータに当たれば発生する
・短期は圧力低減とログ/バックアップ整理、長期はクォータ設計見直しとデータ/テーブルスペース分離
・削減と移設でしのぎつつ、根本は「上限の適正化」と「用途ごとのボリューム分離」で再発を断つ
-
前の記事
PostgreSQL「ERROR: invalid byte sequence for encoding」の原因と対処 2025.08.27
-
次の記事
kotlin Listの要素を次の値とPairに変換する 2025.08.28
コメントを書く