本番運用でつまずきやすい点を、原因→対策→確認コマンドの流れでまとめました。Aurora PostgreSQL でも同様に有効です。


1) パーティション数が多すぎる

症状: INSERT/プランニングが遅い、pg_class が膨張、オブジェクト管理が地獄。

指針

  • 日次切り: 直近 90–180 日程度に限定(古い分は月次へ再集約 or アーカイブ)
  • 月次切り: 数年保管までなら現実的(数百パーティション規模が上限目安)

チェック

SELECT relname, relkind, reltuples, relpages
FROM pg_class WHERE relname LIKE 'inference_logs%';

2) パーティション・プルーニングが効いていない

症状: すべての子テーブルにアクセスし、想定より遅い。

原因

  • WHERE 句がパーティションキーにマッチしていない(関数適用や型不一致)。
  • プラン時間/実行時間のプルーニング条件に届かない。

対策

  • パーティションキー(例: created_at)に 関数をかけないdate_trunc は述語側でなく引数側で整える。
  • バインド変数でも効くよう、PG11+の 実行時プルーニング を前提にする。

確認

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM inference_logs
WHERE created_at >= TIMESTAMPTZ '2025-09-01' AND created_at < TIMESTAMPTZ '2025-10-01';
-- → 触っているパーティション数を確認

3) UNIQUE/PRIMARY KEY が作れない/効かない

症状: 親テーブルにユニーク制約を作れない、または“パーティション単位でしか”一意にならない。

背景

  • グローバルインデックスがないため、一意制約はパーティションキーを必ず含める 必要がある。

対策例

-- OK: (id, created_at) を主キーにして created_at で RANGE 分割
ALTER TABLE inference_logs
  ADD CONSTRAINT inference_logs_pkey PRIMARY KEY (id, created_at);

4) 親に作ったインデックスが子に反映されない

症状: パーティションごとに手でインデックスを作っていてツラい。

正解パターン

  • 親に パーティション化インデックス を作成すると、以後作る子に自動適用される。
  • 既存の子には ATTACH PARTITION で後付け可能。

-- 親に定義(以後の子に自動適用)
CREATE INDEX ON inference_logs (user_id, created_at);

-- 既存の子に後付け
CREATE INDEX ON inference_logs_2025_09 (user_id, created_at);
ALTER INDEX inference_logs_user_id_created_at ATTACH PARTITION inference_logs_2025_09_user_id_created_at_idx;

5) デフォルト・パーティションの落とし穴

症状: 境界外データが全部 DEFAULT に溜まり、片肺運転に。

対策

  • DEFAULT安全弁 として最小限に。定期的に再配分するジョブを用意。
  • 可能なら 先行作成(次月/翌週)を徹底し、DEFAULT に落ちないようにする。

CREATE TABLE inference_logs_default
  PARTITION OF inference_logs DEFAULT;

6) 境界ミス(FROM/TO の半開区間)

症状: 9/30 の 23:59:59.999999+09:00 が想定外のパーティションに入る。

対策

  • RANGE は [FROM, TO) の半開区間。月初・月初で揃える。
  • タイムゾーンを明示(TIMESTAMPTZ)。

テンプレ

CREATE TABLE inference_logs_2025_09
  PARTITION OF inference_logs
  FOR VALUES FROM ('2025-09-01 00:00+09') TO ('2025-10-01 00:00+09');

7) 外部キー・参照整合のコスト

症状: 参照整合のチェックで遅い。

対策

  • 参照先/参照元も同じキーで 共同パーティショニング(co-partition)する。
  • 参照整合が不要なログ系は FK を設けない 設計も検討。

8) Autovacuum の“効きが悪い”

症状: 膨れたまま戻らない、統計が古い。

対策

  • パーティション 単位で autovacuum 設定を上書き。
  • 書き込みの多い区画は autovacuum_vacuum_scale_factor を小さめに。

ALTER TABLE inference_logs_2025_09 SET (
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_analyze_scale_factor = 0.02
);

9) COPY/バルク投入が遅い

原因: ルーティング・トリガのオーバーヘッド。

対策

  • 可能なら 単一パーティションに対して直接 COPY
  • 期間ごとにファイルを分け、対応する子へ投入。

\copy inference_logs_2025_09 (created_at, user_id, payload) FROM '2025-09.csv' CSV;

10) パーティション間の再配置・再圧縮

ユースケース: ホット (直近) は日次、コールドは月次へ寄せて台数削減。

パターン

  1. 新しい集約先(月次)を作る
  2. INSERT ... SELECT で移送(または CREATE TABLE AS
  3. 旧日次を DETACH PARTITIONDROP or アーカイブ

11) パーティションワイズ・ジョイン/集計を生かす

ポイント

  • 設定: enable_partitionwise_join = on, enable_partitionwise_aggregate = on
  • 同じキーで分割したテーブル同士は、子同士で並列に処理できる。

確認

SHOW enable_partitionwise_join;
SHOW enable_partitionwise_aggregate;

12) 誤ったキー選定

アンチパターン

  • 一様分布しないキー(例: model_version が数種類しかない)で RANGE 分割 → 偏り。
  • user_id のみで RANGE 分割 → 範囲クエリ(時間絞り)が効かない。

推奨

  • 時系列ワークロードは 時間 + サブキー(LIST/HASH 併用も検討)。

13) 監視を忘れる

最低限のメトリクス

  • パーティション数、最古/最新の境界、DEFAULT の件数
  • オートバキューム実行回数・所要時間、bloat 推定
  • 主要クエリの触れたパーティション数(pg_stat_statements + EXPLAIN サンプリング)

運用レシピ

A. 先行パーティション自動生成(例: 次月分を毎月25日に)

DO $$
DECLARE
  y int := EXTRACT(YEAR  FROM now());
  m int := EXTRACT(MONTH FROM now());
  next_first date := (date_trunc('month', now()) + interval '1 month')::date;
  next_next_first date := (date_trunc('month', now()) + interval '2 month')::date;
  part_name text := format('inference_logs_%s_%s', to_char(next_first,'YYYY'), to_char(next_first,'MM'));
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_class WHERE relname = part_name
  ) THEN
    EXECUTE format($$CREATE TABLE %I PARTITION OF inference_logs
                   FOR VALUES FROM (%L) TO (%L)$$,
                   part_name, next_first, next_next_first);
  END IF;
END$$;

B. プルーニングが効くかワンライナーチェック

EXPLAIN SELECT 1 FROM inference_logs
WHERE created_at >= '2025-09-01' AND created_at < '2025-10-01';
-- → "Partitioned Seq Scan" の "Partitions scanned" が少数か確認

C. アーカイブ&DROP(安全手順)

  1. 対象パーティションを 読み取り専用 に(任意)
  2. 外部へエクスポート(S3 など)
  3. 参照が無いことを確認
  4. ALTER TABLE ... DETACH PARTITION ...; → 必要なら DROP

まとめ(運用の勘所)

“自動化する”: 先行作成・監視・アーカイブのジョブ化で人手依存を排除。長期運用でも安定したログ処理基盤を維持できます。

“先に決める”: キー選定と保持方針(ホット/コールド)を最初に。

“測って直す”: EXPLAIN と実測でプルーニング確認、設定は子ごと最適化。

“増やしすぎない”: パーティション数は管理できる上限に抑える。

投稿者 kojiro777

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です