PostgreSQL 17の新機能 - パフォーマンスと使いやすさが向上

2025.12.10

公式ドキュメント

この記事の要点

• SQL/JSON標準(JSON_TABLE等)の本格導入でJSONと関係モデルの両立が強化
• インクリメンタルバックアップでバックアップのコスト・時間を大幅圧縮
• VACUUMの高速化、I/O統計の刷新、BRINインデックス改善でエンジン効率化

PostgreSQL 17の概要

PostgreSQL 17は、JSONBのSQL/JSON標準準拠、インクリメンタルバックアップ、大幅なパフォーマンス改善を含むメジャーリリースです。オープンソースデータベースのデファクトスタンダードとして、エンタープライズから個人開発まで幅広く使われているPostgreSQLは、このリリースで「JSONと関係モデルの両立」「運用の省力化」「大規模データ処理」のすべてで前進しました。

背景 - なぜPostgreSQL 17が重要か

ここ数年のデータベーストレンドは、半構造化データ(JSON)と強い型システムの共存、クラウド時代のバックアップ/リストア戦略、そして解析系ワークロードの高速化に向かっています。PostgreSQL 17はまさにこのニーズに応えるリリースで、次の三本柱で進化しました。

  • SQL/JSON標準準拠: JSON_TABLEJSON_VALUEJSON_QUERYなどISO SQL標準の関数群を本格導入
  • 運用の抜本改善: pg_basebackupによるインクリメンタルバックアップでコスト・時間を圧縮
  • エンジン効率化: VACUUMの高速化、I/O統計の刷新、BRINインデックスの改善

これにより「PostgreSQLならドキュメントDBと専用OLAPの代替も一定までカバーできる」という立ち位置がさらに強化されました。

SQL/JSON標準のサポート

JSON_TABLE

JSONデータをリレーショナルテーブルとして扱えます。

SELECT *
FROM JSON_TABLE(
  '[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]',
  '$[*]'
  COLUMNS (
    id INT PATH '$.id',
    name TEXT PATH '$.name'
  )
) AS users;

-- 結果:
-- id | name
-- ---+------
--  1 | Alice
--  2 | Bob

JSON_QUERY / JSON_VALUE

-- JSON_VALUE: スカラー値を取得
SELECT JSON_VALUE(data, '$.user.name') as name
FROM events;

-- JSON_QUERY: JSON値(配列・オブジェクト)を取得
SELECT JSON_QUERY(data, '$.user.tags' WITH WRAPPER) as tags
FROM events;

JSON_EXISTS

-- 条件に一致するJSONが存在するかチェック
SELECT *
FROM products
WHERE JSON_EXISTS(attributes, '$.colors[*] ? (@ == "red")');

ネストパスとエラーハンドリング

JSON_TABLEはネストしたパスにも対応しており、親子関係を1つのクエリで平坦化できます。

SELECT t.*
FROM orders o,
JSON_TABLE(
  o.payload,
  '$' COLUMNS (
    order_id INT PATH '$.id',
    customer TEXT PATH '$.customer.name',
    NESTED PATH '$.items[*]' COLUMNS (
      sku TEXT PATH '$.sku',
      qty INT PATH '$.quantity',
      price NUMERIC(10,2) PATH '$.price'
    )
  )
) AS t;

エラー時の振る舞いはON ERROR句で制御できます。

SELECT JSON_VALUE(
  data,
  '$.price'
  RETURNING NUMERIC
  DEFAULT 0 ON EMPTY
  DEFAULT -1 ON ERROR
) FROM events;

インクリメンタルバックアップ

前回のバックアップからの変更分のみをバックアップできます。

# フルバックアップを取得
pg_basebackup -D /backup/base --checkpoint=fast

# インクリメンタルバックアップを取得
pg_basebackup -D /backup/incr1 \
  --incremental=/backup/base/backup_manifest

# 復元時にマージ
pg_combinebackup /backup/base /backup/incr1 -o /restore

利点

項目フルバックアップインクリメンタル
時間長い短い
容量大きい小さい
復元簡単マージが必要

運用シナリオ例

日次でフル、時間ごとにインクリメンタルを取得する構成。

#!/usr/bin/env bash
set -euo pipefail

BASE=/backup/base
INCR_DIR=/backup/incr
DATE=$(date +%Y%m%d_%H%M)

if [ ! -d "$BASE" ]; then
  pg_basebackup -D "$BASE" --checkpoint=fast --wal-method=stream
else
  pg_basebackup -D "$INCR_DIR/$DATE" \
    --incremental="$BASE/backup_manifest" \
    --checkpoint=fast
fi

リストアは時系列順にマージ。

pg_combinebackup \
  /backup/base \
  /backup/incr/20251210_0100 \
  /backup/incr/20251210_0200 \
  -o /restore/target

パフォーマンス改善

VACUUMの高速化

PostgreSQL 16 vs 17:

  • 大規模テーブルのVACUUM: 最大20倍高速化
  • メモリ使用量: 最大20倍削減

PostgreSQL 17ではVACUUMの内部で、dead tupleのIDを保持するデータ構造がradix treeベースに刷新され、メモリ効率と走査性能が大きく改善しました。特に数億行を超えるテーブルの自動VACUUMで効果が顕著です。

I/O最適化

-- 新しいI/O統計
SELECT * FROM pg_stat_io;

-- 結果にはバックエンドタイプごとのI/O統計が含まれる
-- ワーカー種別ごとの読み書き傾向を可視化
SELECT backend_type, object, context,
       reads, writes, extends, evictions
FROM pg_stat_io
ORDER BY writes DESC
LIMIT 10;

インデックスの改善

-- BRIN(Block Range Index)の改善
CREATE INDEX ON events USING BRIN (created_at)
WITH (pages_per_range = 32);

-- より効率的なマルチカラムBRIN
CREATE INDEX ON sensor_data USING BRIN (tenant_id, recorded_at);

B-treeも重複値の多い列での探索が改善され、IN句の大量展開や= ANY(array)がより高速になりました。

新しい機能

MERGE文の拡張

MERGE INTO inventory t
USING new_items s
ON t.product_id = s.product_id
WHEN MATCHED THEN
  UPDATE SET quantity = t.quantity + s.quantity
WHEN NOT MATCHED THEN
  INSERT (product_id, quantity)
  VALUES (s.product_id, s.quantity)
RETURNING *;  -- PostgreSQL 17で追加

WHEN NOT MATCHED BY SOURCEもサポートされ、削除系のアップサートが1文で書けるようになりました。

MERGE INTO inventory t
USING current_snapshot s ON t.product_id = s.product_id
WHEN MATCHED THEN
  UPDATE SET quantity = s.quantity
WHEN NOT MATCHED BY TARGET THEN
  INSERT (product_id, quantity) VALUES (s.product_id, s.quantity)
WHEN NOT MATCHED BY SOURCE THEN
  DELETE;

部分的な集計プッシュダウン

-- パーティションテーブルでの集計が高速化
SELECT date_trunc('month', created_at), COUNT(*)
FROM events  -- パーティション化されたテーブル
GROUP BY 1;

新しいシステムビュー

-- 実行中のクエリの進捗
SELECT * FROM pg_stat_progress_copy;
SELECT * FROM pg_stat_progress_basebackup;

論理レプリケーションの改善

フェイルオーバースロット

-- スタンバイサーバーでレプリケーションスロットを同期
ALTER SUBSCRIPTION my_sub
SET (failover = true);

サブスクライバーでの競合解決

-- 競合発生時の動作を設定
ALTER SUBSCRIPTION my_sub
SET (disable_on_error = false);

pg_createsubscriberによる物理→論理変換

物理レプリカを論理レプリケーションのサブスクライバーへ変換できるツールが追加されました。これにより巨大DBのコピーを高速に作成し、移行・分割に活用できます。

pg_createsubscriber \
  -D /var/lib/postgresql/17/replica \
  -P "host=primary port=5432 dbname=app user=repl" \
  -d app \
  -n my_subscription

実践的なサンプルコード

Node.jsからpgドライバで利用

// db.ts
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30_000,
});

export async function upsertInventory(
  items: Array<{ productId: number; quantity: number }>,
) {
  const sql = `
    MERGE INTO inventory t
    USING (SELECT unnest($1::int[]) AS product_id,
                  unnest($2::int[]) AS quantity) s
    ON t.product_id = s.product_id
    WHEN MATCHED THEN
      UPDATE SET quantity = t.quantity + s.quantity
    WHEN NOT MATCHED THEN
      INSERT (product_id, quantity) VALUES (s.product_id, s.quantity)
    RETURNING *;
  `;

  const ids = items.map((i) => i.productId);
  const qty = items.map((i) => i.quantity);
  const { rows } = await pool.query(sql, [ids, qty]);
  return rows;
}

JSON_TABLEとORMの併用

const result = await pool.query(`
  SELECT j.order_id, j.customer, j.sku, j.qty
  FROM orders o,
  JSON_TABLE(o.payload, '$'
    COLUMNS (
      order_id INT PATH '$.id',
      customer TEXT PATH '$.customer.name',
      NESTED PATH '$.items[*]' COLUMNS (
        sku TEXT PATH '$.sku',
        qty INT PATH '$.quantity'
      )
    )
  ) j
  WHERE o.created_at > $1
`, [since]);

バージョン比較表

機能PostgreSQL 15PostgreSQL 16PostgreSQL 17
SQL/JSON標準関数一部一部主要関数を完備
インクリメンタルバックアップなしなしあり
MERGE RETURNING不可不可
pg_stat_ioなしあり拡張
VACUUM高速化従来改善大幅改善
論理レプリフェイルオーバー手動手動宣言的
物理→論理変換手動手動pg_createsubscriber

競合DBとの簡易比較

観点PostgreSQL 17MySQL 8.4MongoDB 7
リレーショナル整合性強力強力弱(スキーマレス)
JSON操作SQL/JSON標準JSON関数群ネイティブ
トランザクション強力強力レプリカセットで対応
拡張性拡張モジュール豊富限定的プラグイン
ライセンスPostgreSQLライセンスGPL/商用SSPL

ベストプラクティス

1. JSONBは「スキーマレス部分」に限定

頻繁にクエリ条件となるフィールドは通常の列に昇格し、GINインデックスを貼る対象を絞りましょう。

CREATE INDEX idx_events_payload_gin
  ON events USING GIN (payload jsonb_path_ops);

2. autovacuum設定の見直し

PostgreSQL 17の高速VACUUMを活かすため、maintenance_work_memを十分に確保(例: 1GB以上)し、大規模テーブルにはautovacuum_vacuum_scale_factorを個別に設定。

ALTER TABLE events SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_analyze_scale_factor = 0.01
);

3. バックアップは世代管理

インクリメンタルバックアップは世代管理を誤るとマージ不能になります。backup_manifestファイルを厳密に追跡し、保持ポリシーをスクリプトで制御しましょう。

4. EXPLAIN (ANALYZE, BUFFERS, WAL)の活用

PostgreSQL 17ではEXPLAIN出力がさらに詳細になり、pg_stat_ioと組み合わせることでI/Oボトルネックの特定が容易になります。

5. レプリケーションスロットの監視

SELECT slot_name, active, restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag
FROM pg_replication_slots;

注意点・落とし穴

  • 拡張モジュール非互換: メジャーアップグレードでは拡張機能(pg_cron、TimescaleDBなど)の対応バージョンを先に確認
  • pg_basebackup --incrementalは9.5以前形式非対応: summarize_wal設定をONにしておく必要あり
  • MERGE文のデッドロック: 高並行のUPSERTではMERGEよりもINSERT ... ON CONFLICTの方が安定する場合あり
  • JSON_TABLEのエラーハンドリング: ON ERROR NULLを明示しないと想定外のエラーで失敗する
  • 論理レプリケーションのスキーマ変更: DDLは自動複製されないため、両側で適用順序を管理

導入・移行手順

# アップグレード前のチェック
pg_upgrade --check \
  -b /usr/lib/postgresql/16/bin \
  -B /usr/lib/postgresql/17/bin \
  -d /var/lib/postgresql/16/main \
  -D /var/lib/postgresql/17/main

# pg_upgradeでのアップグレード
pg_upgrade \
  -b /usr/lib/postgresql/16/bin \
  -B /usr/lib/postgresql/17/bin \
  -d /var/lib/postgresql/16/main \
  -D /var/lib/postgresql/17/main

推奨手順

  1. ステージング環境でpg_upgrade --check
  2. 拡張モジュールの17対応状況を確認(不足があればPR待ち or 代替検討)
  3. バックアップを取得(フル + WALアーカイブ)
  4. メンテナンスウィンドウでpg_upgrade --link(高速)
  5. ANALYZEで統計情報を再生成
  6. pg_stat_statementsをリセットして新バージョンでの傾向をモニタ
-- アップグレード後に必ず実行
VACUUM ANALYZE;

パフォーマンス/ベンチマーク

公表されているワークロードから、PostgreSQL 17で改善が大きい領域は次のとおりです。

ワークロードPG16比
大規模テーブルのVACUUM最大20倍高速
VACUUMのメモリ使用量最大20倍削減
パーティションGROUP BY大幅改善
高並行の書き込み若干改善
WALスループット10-20%改善

実際の数値はハードウェアとワークロード次第のため、自環境でpgbenchHammerDBを使った検証を推奨します。

pgbench -i -s 100 appdb
pgbench -c 32 -j 4 -T 300 -P 10 appdb

FAQ

Q1: PostgreSQL 17のインクリメンタルバックアップは、pgBackRestの代わりになりますか?

A: 基本機能は内蔵されましたが、世代管理・暗号化・遠隔保存・並列処理などの運用機能はpgBackRestやBarmanの方が豊富です。小規模ならpg_basebackupで十分、中〜大規模なら専用ツールの併用を推奨します。

Q2: JSONBから通常カラムに戻すべきタイミングは?

A: そのフィールドが(1) WHERE句で頻繁に使われる、(2) 型制約をかけたい、(3) 統計情報で選択率を正確に扱いたい、のいずれかに該当するなら通常カラムへ切り出しましょう。

Q3: VACUUM改善の恩恵を受けるには設定変更が必要?

A: 基本的には自動で効きますが、maintenance_work_memを大きめに設定するとより効果的です。従来の上限16MBを撤廃しているため、1GB程度まで割り当てて問題ありません。

Q4: MERGEとINSERT ... ON CONFLICT、どちらを使うべき?

A: 複数のアクション(挿入・更新・削除)を1文で扱いたい場合はMERGE、単純な重複回避UPSERTにはINSERT ... ON CONFLICTがシンプルで高速です。

Q5: 拡張機能の対応は?

A: 主要なものはPostgreSQL 17リリースから数週間〜数か月で対応が進みます。TimescaleDB、Citus、pgvectorなど主要拡張は対応済み。本番導入前に各拡張のChangelogを確認してください。

まとめ

PostgreSQL 17は、SQL/JSON標準への対応、インクリメンタルバックアップ、大幅なパフォーマンス改善により、エンタープライズでの利用がさらに容易になりました。特にJSONデータを扱うアプリケーションでは、新しいJSON関数が大きな恩恵をもたらします。

  • 開発者にとっての嬉しさ: SQL/JSON標準関数でアプリ側のJSON操作が簡素化
  • DBAにとっての嬉しさ: VACUUM/バックアップ/レプリの運用負荷が軽減
  • 経営にとっての嬉しさ: 解析系とOLTPを同じDBで回せる範囲が広がり、TCO削減に寄与

バージョンアップはpg_upgradeで比較的簡単に実施できるので、ステージングで検証のうえ積極的に採用を検討しましょう。

参考リンク

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

メールで無料相談する
← 一覧に戻る