PostgreSQLは「とりあえず動かす」のは簡単だが,本番で安定・高速に運用するとなると話は別だ.デフォルト設定のまま放置し,ある日「遅い」「ディスクが膨らむ」「接続が詰まる」に直面する個人開発者は多い.

これらの問題は,メモリ設定・インデックス・VACUUM・接続管理という勘所を押さえれば,その大半を未然に防げる.逆に知らないと,原因不明の劣化に延々と悩まされる.

この記事は,VPSでPostgreSQLを運用する個人開発者,「動くけど遅い」を脱したいエンジニアに向けている.設定チューニングからクエリ最適化,肥大化対策,バックアップまでを実務目線で解説する.

読み終えたとき,あなたは自分のDBの設定を適切に調整し,遅いクエリを特定して直し,肥大化を防ぐ運用ができるようになっている.

なぜPostgreSQL運用の知識が効くのか

DBはアプリのパフォーマンスのボトルネックになりやすい場所だ.アプリのコードをいくら最適化しても,DBが遅ければ全体が遅い.そしてDBの遅さの多くは,設定とクエリで解決できる.

PostgreSQLのデフォルト設定は非常に控えめ(小さなマシンでも動くよう保守的)に作られている.VPSのメモリを活かす設定にするだけで,体感が変わることも多い.

またVACUUMという独自の仕組みを理解しないと,更新の多いテーブルが肥大化し,徐々に遅くなる.これはPostgreSQL特有の落とし穴だ.

DB運用の知識は,「動く」を「速く・安定して動き続ける」に変える.これは個人開発のサービスの質を直接左右する.

postgresql.confの勘所 ― まず触るべきパラメータ

PostgreSQLのチューニングは,まずメモリ関連の主要パラメータから始める.VPSの搭載メモリに応じて,控えめなデフォルトを引き上げる.

パラメータ役割目安
shared_buffersDB専用のキャッシュ領域搭載メモリの25%程度
effective_cache_sizeOSキャッシュ含む利用可能メモリの見積り搭載メモリの50〜75%
work_memソート/結合1回あたりの作業メモリ数MB〜(接続数に注意)
maintenance_work_memVACUUM/INDEX作成の作業メモリ数百MB
max_connections最大同時接続数アプリの実需に合わせ控えめに

work_mem接続数×クエリの複雑さの分だけ消費される点に注意が要る.大きくしすぎると,多接続時にメモリ不足を招く.「全体のメモリ ÷ 想定接続数」を意識して決める.設定変更後はSELECT pg_reload_conf();または再起動で反映する.

メモリチューニングの実際 ― 数値の決め方

数値の決定は,VPSの搭載メモリと用途から逆算する.たとえばメモリ4GBのVPSでPostgreSQL中心の用途なら,おおよその出発点は次のようになる.

メモリ4GB VPSでの設定例

# postgresql.conf
shared_buffers = 1GB              # 4GBの約25%
effective_cache_size = 3GB        # 4GBの約75%
work_mem = 16MB                   # 接続数を考慮して控えめに
maintenance_work_mem = 256MB
max_connections = 50              # 実需に合わせる

重要なのは「アプリ・OS・他のサービスとメモリを分け合っている」意識だ.DBにメモリを振りすぎると,アプリやOSが圧迫されてかえって不安定になる.まず控えめに設定し,実測しながら調整するのが安全だ.設定支援ツール(PGTune等)の出力を出発点にするのも良い.

インデックス設計 ― 遅いクエリの最大の特効薬

クエリ高速化の最も効く一手が適切なインデックスだ.インデックスが無いと,DBはテーブル全体を走査(シーケンシャルスキャン)するため,データが増えるほど線形に遅くなる.

インデックスの作成

-- よく検索する列にインデックス
CREATE INDEX idx_users_email ON users(email);
-- 複合インデックス(WHERE + ORDER BYの組み合わせ)
CREATE INDEX idx_posts_user_created ON posts(user_id, created_at DESC);
-- 条件付き(部分)インデックス
CREATE INDEX idx_active ON users(last_login) WHERE active = true;

ただしインデックスは万能ではない.書き込み時のコストが増え,ディスクも消費する.「頻繁に検索・結合・ソートに使う列」に絞って張るのが鉄則だ.使われないインデックスはむしろ害になる.

EXPLAINで「実際に何が起きているか」を見る

クエリが速いか遅いかは推測せず,EXPLAIN ANALYZEで実行計画を見るSeq Scan(全件走査)が出ていれば,インデックスが効いていない証拠だ.インデックスを張った後に再度実行し,Index Scanに変わり実行時間が減ることを確認する.

実行計画の確認

EXPLAIN ANALYZE SELECT * FROM posts WHERE user_id = 42 ORDER BY created_at DESC LIMIT 20;
-- Seq Scan が出たらインデックスを検討
-- Index Scan になり実行時間が減れば成功

VACUUM ― PostgreSQL特有の「掃除」を理解する

PostgreSQLは更新・削除時に,古い行を即座に消さず「不要(dead tuple)」としてマークする独特の方式(MVCC)を採る.この不要行を回収するのがVACUUMだ.これを怠るとテーブルが肥大化し,徐々に遅くなる.

通常はautovacuumが自動で実行するが,更新が非常に多いテーブルでは追いつかないことがある.肥大化(table bloat)の兆候があれば,autovacuumの設定を見直すか手動VACUUMを検討する.

VACUUMと肥大化の確認

-- 手動VACUUM(統計情報も更新)
VACUUM ANALYZE posts;
-- autovacuumが効いているか/dead tupleの量を確認
SELECT relname, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;

n_dead_tup(不要行の数)が多くlast_autovacuumが古いテーブルは要注意だ.VACUUM FULLは領域を完全に回収するがテーブルをロックするため,本番では慎重に(メンテ時間に)行う.通常運用はautovacuumに任せ,設定を適切に保つのが基本だ.

スロークエリの発見 ― 推測せず計測する

「どこが遅いか」は推測してはいけない.遅いクエリをログや統計から特定し,そこを直す.PostgreSQLには強力な計測手段がある.

スロークエリを記録・集計する

-- postgresql.conf: 一定時間超のクエリをログに記録
log_min_duration_statement = 500   # 500ms超を記録

-- pg_stat_statements拡張で重いクエリを集計
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT query, calls, mean_exec_time
FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;

log_min_duration_statementで「遅いクエリだけ」をログに残せる.さらにpg_stat_statements拡張を使えば,「平均実行時間が長い」「呼び出し回数が多い」クエリを集計でき,最も効果の大きい最適化対象が見つかる.計測なき最適化は時間の無駄だ.

接続管理 ― コネクションプールで詰まりを防ぐ

PostgreSQLは1接続ごとにプロセスを作るため,接続数が増えるとメモリを圧迫し,過剰になると詰まる.アプリが無制限に接続を張る設計は危険だ.

対策はコネクションプールだ.アプリ側のプール機能か,PgBouncerのような外部プーラーを挟み,限られた接続を使い回す.これにより少ない接続数で多数のリクエストを捌ける.

目安として,max_connectionsを闇雲に増やすより,適切なプールサイズ(CPUコア数の数倍程度)で使い回す方が安定する.「接続が足りない」と感じたら,まずプール設計を見直すのが正攻法だ.

バックアップ ― pg_dumpと、その先

DBは最も価値が高い資産だ.定期的な論理バックアップ(pg_dump)を必ず仕込む.そして「取れているか」だけでなく「戻せるか」を確認する.

pg_dumpによるバックアップとリストア

# バックアップ(圧縮)
pg_dump -U app -Fc mydb > /backup/mydb-$(date +%F).dump
# リストア(別DBで検証)
pg_restore -U app -d restore_test /backup/mydb-2026-07-10.dump
# 全DB一括(ロール等も含む)
pg_dumpall -U postgres > /backup/all.sql

個人開発ではpg_dumpの定期実行+外部保管で十分なことが多い.より高い要件(任意時点への復旧)が必要ならWALアーカイブによるPITR(Point-In-Time Recovery)を学ぶ段階だが,まずは日次ダンプとリストア検証を確実にすることが先決だ.

補論:DBの性能は「速いディスク」と「戻せる安心」で決まる

PostgreSQLの性能は,設定だけでなくディスクのI/O速度に大きく左右される.遅いストレージのVPSでは,どれだけチューニングしてもクエリやVACUUMが詰まる.そしてチューニングの試行錯誤には「失敗しても戻せる」環境が要る.

高速NVMe・50種類以上のOSテンプレートに対応した国内VPS─シン・VPS─ はNVMe SSDでDBの読み書き・VACUUM・インデックス作成が高速に進み,スナップショットで設定変更やバージョンアップの前に丸ごと退避できる.「postgresql.confを攻めて調整し,問題なら即戻す」という運用がしやすく,DBチューニングの学習・本番運用の両方に向く.

DBを使うサービスを独自ドメインで公開するなら,取り扱い400種類以上のドメイン取得サービス─ムームードメイン─ でドメインを取得しよう.DB自体は外部に公開せずlocalhost接続にし,アプリだけをHTTPSで公開するのが安全な構成だ.

よくある質問(FAQ)

Q1.まず何をチューニングすべき?

shared_buffersとeffective_cache_sizeをVPSのメモリに合わせて引き上げるのが最初の一手だ.デフォルトは極めて控えめなので,これだけで体感が変わることも多い.次にインデックスとスロークエリ対策へ進む.

Q2.インデックスは多いほど良い?

いいえ.インデックスは書き込みコストとディスクを増やす.「頻繁に検索・結合・ソートに使う列」に絞る.EXPLAIN ANALYZEで効果を確認し,使われないインデックスは削除する.

Q3.VACUUMは手動でやるべき?

通常はautovacuumに任せる.更新が非常に多くautovacuumが追いつかない場合のみ,設定見直しや手動VACUUMを検討する.VACUUM FULLはロックするので本番ではメンテ時間に限る.

Q4.遅いクエリの見つけ方は?

log_min_duration_statementで遅いクエリをログに記録し,pg_stat_statements拡張で重いクエリを集計する.推測せず計測してから最適化するのが鉄則だ.

Q5.接続がよく詰まる/足りない

max_connectionsを増やすより,コネクションプールを導入する.PgBouncer等で限られた接続を使い回せば,少ない接続数で多数のリクエストを捌け,メモリ圧迫も防げる.

Q6.PostgreSQLとMySQLどちらを選ぶ?

型の厳密さ・高度な機能・JSON活用ならPostgreSQL,枯れた情報量やWordPress連携ならMySQLが選ばれやすい.個人開発で迷ったらPostgreSQLで困ることは少ない.

まとめ ― 「動く」を「速く安定して動き続ける」へ

PostgreSQL運用の要点は,メモリ設定・インデックス・VACUUM・接続管理・バックアップだ.これらを押さえれば,「動くけど遅い」「徐々に肥大化する」といった問題を未然に防げる.

今日やるべきことは,shared_buffersをVPSのメモリに合わせて見直し,遅いと感じるクエリをEXPLAIN ANALYZEにかけること.ボトルネックは計測すれば必ず見える.

DBはサービスの心臓だ.NVMe SSDでスナップショットの取れるVPSがあれば,チューニングも安心して攻められる.