【PostgreSQL】PostgreSQLとMongoDB、JSONBはどこまでMongoDBの代わりになるか比較検証

PostgreSQL

こんにちは、かつコーチです。

これでPostgreSQL編は20本目、最終回です。

「MongoDBを検討していたけど、PostgreSQLのJSONBがあれば十分では」。

こういう相談を、案件のDB選定でよく受けます。

JSONB型(バイナリ形式で格納されるJSON型で、インデックスを効率的に張れる)は本連載の4本目で扱いましたが、今回はその応用として「JSONBはどこまでMongoDBの代替になるのか」を、実際のクエリを比較しながら検証します。

前提知識ありきの上級者向け記事として、両者のアーキテクチャの違いから、実務での使い分けまで踏み込みます。

アーキテクチャの違い

スキーマの扱い方

MongoDBはスキーマレス(ドキュメントごとに異なる構造を持てる)なデータベースとして設計されており、コレクション(テーブルに相当)内のドキュメントは、それぞれ自由な構造を持てます。

一方、PostgreSQLはスキーマフルなRDBMSであり、JSONB型もあくまで「1つのカラムの型」として、決まった構造のテーブルの中に存在します。

-- PostgreSQL: テーブル構造は固定、JSONBカラムの中身は柔軟
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    attributes JSONB
);

INSERT INTO products (name, attributes) VALUES
    ('ノートPC', '{"cpu": "Core i7", "ram_gb": 16}'),
    ('マウス', '{"color": "black", "wireless": true}');
// MongoDB: コレクション全体がスキーマレス
db.products.insertMany([
  { name: "ノートPC", cpu: "Core i7", ram_gb: 16 },
  { name: "マウス", color: "black", wireless: true }
]);

PostgreSQLは「固定カラム+柔軟なJSONBカラム」というハイブリッド構成が基本であり、MongoDBのようにドキュメント全体を自由な構造にするという発想とは根本的に異なります。

クエリ言語の違い

PostgreSQLはSQL、MongoDBはBSON(バイナリ形式のJSON)ベースの独自クエリAPIを使います。

-- PostgreSQL: JSONB内のcpuフィールドで検索
SELECT name, attributes->>'cpu' AS cpu
FROM products
WHERE attributes->>'cpu' = 'Core i7';
// MongoDB: 同等のクエリ
db.products.find(
  { cpu: "Core i7" },
  { name: 1, cpu: 1 }
);

シンプルな検索であれば大差ありませんが、複数テーブル(コレクション)を組み合わせる場面で違いが顕著になります。

JOINとインデックスの比較

リレーション(結合)の扱い

PostgreSQLはRDBMSなので、JOINによるテーブル結合がネイティブかつ高速に行えます。

-- PostgreSQL: 注文と商品情報をJOINで結合
SELECT o.id, o.ordered_at, p.name, p.attributes->>'cpu' AS cpu
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status = 'shipped';

MongoDBにも$lookupという結合に相当する機能がありますが、これはPostgreSQLのJOINほど高速に最適化されておらず、公式ドキュメントでも「複雑な結合を多用するならリレーショナルDBを検討すべき」という趣旨の案内がされています。

// MongoDB: $lookupによる結合(PostgreSQLのJOINより制約が多い)
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $lookup: {
      from: "order_items",
      localField: "_id",
      foreignField: "order_id",
      as: "items"
  }}
]);

「注文・商品・在庫のように、明確にリレーションがあるデータを多数組み合わせて扱う」という要件では、PostgreSQLのJOINの方が素直に書け、実行計画も最適化されやすいというのが実務での実感です。

JSONB検索用のインデックス

JSONBカラムに対しても、GINインデックス(本連載5本目で解説)を使えば効率的な検索が可能です。

-- JSONB全体に対するGINインデックス
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);

-- @>演算子(含む)を使った検索がインデックスを活用できる
SELECT name FROM products WHERE attributes @> '{"wireless": true}';
-- 実行計画を確認すると、Bitmap Index Scanが使われていることが分かる
EXPLAIN ANALYZE
SELECT name FROM products WHERE attributes @> '{"wireless": true}';
Bitmap Heap Scan on products  (cost=4.20..12.45 rows=3 width=36) (actual time=0.031..0.033 rows=2 loops=1)
  Recheck Cond: (attributes @> '{"wireless": true}'::jsonb)
  ->  Bitmap Index Scan on idx_products_attributes  (cost=0.00..4.20 rows=3 width=0) (actual time=0.021..0.021 rows=2 loops=1)
        Index Cond: (attributes @> '{"wireless": true}'::jsonb)

MongoDBの複合インデックスと比べると設定の考え方はやや異なりますが、「特定のキーに対する検索を高速化する」という目的自体はGINインデックスで十分に達成できます。

筆者が実際にJSONBカラム10万件のテーブルで検証したところ、GINインデックスなしではSeq Scan(全件走査)で数百ミリ秒かかっていた検索が、インデックス作成後は数ミリ秒まで短縮されました。

用途別の使い分け

JSONBで十分なケース

次のような要件であれば、JSONB型で十分にMongoDBの代替になります。

  • 商品の付加属性(カラー・サイズなど、商品カテゴリによって項目が変わる情報)のように、一部のカラムだけ柔軟にしたい
  • APIレスポンスやWebhookのペイロードを、そのまま保存しておきたい(監査ログなど)
  • 既存のRDBMS運用資産(バックアップ・監視・レプリケーション)を維持したまま、部分的に柔軟なスキーマを取り入れたい
-- Webhookペイロードをそのまま保存する用途はJSONBが最適
CREATE TABLE webhook_logs (
    id SERIAL PRIMARY KEY,
    received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    payload JSONB NOT NULL
);

MongoDBの方が向いているケース

一方で、次のような要件ではMongoDBに分があります。

  • ドキュメント構造そのものが頻繁かつ大幅に変化し、リレーショナルなテーブル設計に無理が生じる(例:CMSの記事本文のように、コンテンツタイプごとに全く違う構造を持つ)
  • 水平分散(シャーディング)を前提とした、巨大なドキュメントストアとして運用したい
  • チーム全体がドキュメント指向の設計・運用に習熟しており、RDBMSの正規化設計よりもスキーマレスの方が開発速度が上がる

比較表:判断基準の整理

観点PostgreSQL(JSONB)MongoDB
スキーマ設計固定カラム+部分的に柔軟全体がスキーマレス
リレーション(結合)得意(JOINがネイティブ)弱い($lookupは制約あり)
トランザクション標準で強力(ACID完全対応)4.0以降で対応、設計次第で制約あり
柔軟なデータ構造への追従一部カラムなら十分対応可全面的な柔軟性が強み
運用資産の流用既存RDBMS運用と統合しやすい別のミドルウェアとして新規運用が必要
学習コストSQLの知識で対応可能独自クエリAPI・集約パイプラインの学習が必要

実務での判断フロー

筆者の経験では、次の順番で検討するのが実践的です。

  1. まず「本当にドキュメント指向のDBが必要か」を疑う(多くの要件はRDBMS+JSONBで解決する)
  2. リレーション(JOIN)を多用する設計か確認する → 多用するならPostgreSQL優位
  3. トランザクション整合性が重要な業務データか確認する → 重要ならPostgreSQL優位
  4. それでもスキーマの変化が激しく、既存のRDBMS設計に無理が生じるならMongoDBを検討する

「新しい技術だから」という理由だけでMongoDBを選ぶのではなく、既存のPostgreSQL運用にJSONBを組み込む方が、トータルの運用コストを抑えられるケースが多いというのが、複数案件を見てきた実感です。

まとめ

この記事のポイント

  • MongoDBはスキーマレス、PostgreSQLは固定スキーマ+部分的に柔軟なJSONBというアーキテクチャの違いがある
  • JOINを多用するリレーショナルな設計は、PostgreSQLの方が高速かつ素直に書ける
  • GINインデックスを使えば、JSONBでも実務上十分な検索パフォーマンスが出せる
  • ドキュメント構造が全面的に頻繁に変化する用途以外は、PostgreSQL+JSONBで十分にカバーできることが多い

次に読むべき記事

  • JSONB型とGINインデックスで半構造化データを扱う
  • Row-Level Securityでマルチテナントのデータを守る
  • MongoDB編(近日公開予定)

これでPostgreSQL編(全20本)は完結です。

ここまでお読みいただき、ありがとうございました。

タグ: PostgreSQL, 上級者向け, 比較検証

タイトルとURLをコピーしました