適切なデータベースの選択:実際のプロジェクトにおけるリレーショナル vs NoSQL
実際の要件に基づいて、PostgreSQL、MongoDB、Firebase、Redis、その他のデータベースから選択するための実用的な意思決定フレームワーク。

「それは状況による」というのが、あらゆるデータベースの質問に対する正直な答えですが、同時に何の役にも立ちません。プロジェクトを開始し、データベースを選択する必要があるとき、トレードオフのリストよりも具体的なものが必要です。データの形状、クエリパターン、チームの専門知識、スケーリングの期待、運用上の複雑さといった実際の要件を考慮した意思決定フレームワークが必要です。
私はPostgreSQL、MongoDB、Firebase/Firestore、Redis、SQLiteをさまざまな本番プロジェクトで使用してきました。それぞれの選択はその文脈において正しかったですが、いくつか間違っていて移行を余儀なくされたものもありました。これらの決定の背後にあるパターンは、あらゆるベンチマーク比較よりも有用です。
意思決定フレームワーク
個々のデータベースを見る前に、プロジェクトについて次の5つの質問に答えてください。
1. データの形状はどのようなものですか?
これは「リレーショナル vs ドキュメント」の話ではありません。データエンティティが互いにどのように関連しているかについてです。
高度にリレーショナル: ユーザーは注文を持ち、注文はアイテムを持ち、アイテムはカテゴリに属し、カテゴリは階層を持ちます。データモデルを描いて、多くの接続を持つグラフのように見える場合、強力なリレーショナル機能が必要です。
ドキュメント指向: 各エンティティは比較的自己完結型です。ブログ投稿には、タイトル、本文、タグ、著者情報が含まれます。エンティティタイプ間で結合する必要はほとんどありません。
キーバリュー: キーで保存および取得する必要があります。セッションデータ、設定、機能フラグなど。
時系列: イベント、メトリクス、ログ。書き込みが多く、追記のみで、時間範囲でクエリされます。
2. クエリパターンはどのようなものですか?
既知のクエリ: アプリケーションが実行するクエリを正確に把握しています。Eコマース:「ユーザーの注文を取得」、「カテゴリ別に製品を検索」、「今月の総収益を計算」。既知のクエリは、インデックスとクエリプランで最適化できるリレーショナルデータベースに適しています。
アドホッククエリ: ユーザーは予測できない方法で検索、フィルタリング、集計を行うことができます。分析ダッシュボード、検索機能、レポートツールなど。これらは柔軟なクエリエンジンに適しています。
シンプルなルックアップ: ほとんどの読み取りは「IDでドキュメントを取得」です。ドキュメントデータベースとキーバリューストアはここで優れています。
3. 一貫性の要件は何ですか?
強力な一貫性: 銀行業務、在庫管理など、古いデータを読み取ることが実際の問題を引き起こすあらゆるもの。ACIDトランザクションを備えたリレーショナルデータベースが安全な選択肢です。
結果整合性: ソーシャルフィード、分析、キャッシュ。パフォーマンスと可用性のために、わずかに古いデータを読み取ることを許容できます。
4. スケーリングの軌跡はどのようなものですか?
垂直スケーリングで十分: ほとんどのアプリケーション。データベースが成長の余地がある単一のマシンに収まる場合、どのデータベースでも機能します。最新のサーバー上のPostgreSQLは、何百万もの行を楽々と処理します。
水平スケーリングが必要: 複数のマシンにデータを分散させる必要があります。これはほとんどの開発者が考えるよりも稀ですが、必要な場合、データベースの選択は制約されます。
5. チームは何を知っていますか?
この要素は過小評価されがちです。PostgreSQLの専門家チームは、MongoDBがデータモデルにより適していると理論的に考えられる場合でも、PostgreSQLを使用する方が生産性が高くなります。新しいデータベースを学習するコスト(慣れないエラーのデバッグ、運用上のベストプラクティスの学習、パフォーマンス特性の理解)は現実的で重要です。
PostgreSQL:デフォルトの選択肢
迷ったらPostgreSQLを選びましょう。これはバックエンドエンジニアの間で議論の余地のない意見であり、コンセンサスです。PostgreSQLは、リレーショナルデータ、JSONドキュメント、全文検索、地理空間クエリ、時系列データを処理できます。これらのいずれにおいても最高ではありませんが、ほとんどのアプリケーションで単一のデータベースとして機能するのに十分な性能を持っています。
強み
ACIDトランザクション。 在庫を差し引いて注文を作成したり、口座間で送金したりするなど、複数のテーブルをアトミックに更新する必要がある場合、PostgreSQLは正確性を保証します。
豊富なクエリ機能。 ウィンドウ関数、CTE(共通テーブル式)、再帰クエリ、ラテラルジョイン。SQLは単なるSELECT * FROMではなく、データをアプリケーション層に移動することなく複雑な分析を表現できる強力なクエリ言語です。
-- Find each user's most recent order with running total
WITH ranked_orders AS (
SELECT
user_id,
order_id,
total,
created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) as rn,
SUM(total) OVER (PARTITION BY user_id ORDER BY created_at) as running_total
FROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;
JSONBカラム。 データの一部が真にスキーマレスである場合(ユーザー設定、動的フォーム応答、サードパーティのWebhookペイロードなど)、JSONBとして保存し、完全なインデックスサポートでクエリできます。
-- Store and query semi-structured data
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
attributes JSONB DEFAULT '{}'
);
-- Index a specific JSON path
CREATE INDEX idx_products_color ON products USING GIN ((attributes->'color'));
-- Query JSON data
SELECT name FROM products
WHERE attributes->>'color' = 'blue'
AND (attributes->>'weight')::numeric < 10;
拡張機能エコシステム。 地理空間データ用のPostGIS、あいまいテキスト検索用のpg_trgm、時系列データ用のTimescaleDB、AI埋め込み用のpgvectorなど。拡張機能モデルにより、PostgreSQLは主要なデータベースを置き換えることなく、特殊なワークロードに適応できます。
PostgreSQLが適切な選択ではない場合
- 水平スケーリングを伴う大量の書き込みスループット。 PostgreSQLは垂直方向にはうまくスケーリングしますが(より大きなマシン)、水平シャーディングは複雑です。毎秒数百万のイベントを数十台のマシンに書き込む必要がある場合は、専用のソリューションを検討してください。
- スキーマ変更が毎日発生する迅速なプロトタイピング。 スタートアップの初期段階でデータモデルが毎週根本的に変更される場合、スキーマレスなオプションと比較して、移行のオーバーヘッドが開発速度を低下させる可能性があります。
- チームにSQL経験が全くなく、 プロジェクトのタイムラインが学習を許さない場合。これは稀ですが、起こり得ます。
MongoDB:ドキュメントが理にかなっている場合
MongoDBは、必要以上に批判されることがあります。初期のマーケティング(「スキーマレス!ウェブスケール!」)は非現実的な期待を生み出し、多くの開発者がPostgreSQLの方が適していたであろうユースケースでMongoDBを使用しました。しかし、MongoDBが適切な選択である真のシナリオも存在します。
MongoDBが優れている場合
コンテンツ管理システム。 各コンテンツは異なる構造を持っています。記事は動画とは異なるフィールドを持ち、動画はポッドキャストとは異なるフィールドを持ちます。MongoDBでは、これらはすべて同じコレクション内の異なる形状のドキュメントです。PostgreSQLでは、多くのNULL許容カラムを持つワイドテーブルを作成するか、JSONカラムを使用するか(この時点でPostgreSQLでMongoDBのパラダイムを使用していることになります)、または複雑な結合を伴うタイプごとのテーブルを作成することになります。
イベントソーシングとロギング。 ほとんどが書き込みと読み取りで、めったに更新されず、結合されることのないドキュメントで高い書き込みスループットを実現します。
埋め込みドキュメントは結合を減らします。 注文が常にそのアイテムを必要とし、アイテムが注文なしでアクセスされることがない場合、注文ドキュメント内にアイテムを埋め込むことで、最も一般的なクエリが2つのテーブル間の結合ではなく、単一のドキュメントルックアップになります。
// MongoDB document with embedded sub-documents
{
_id: ObjectId("..."),
customer: {
name: "John Doe",
email: "john@example.com"
},
items: [
{ product: "Widget", quantity: 2, price: 9.99 },
{ product: "Gadget", quantity: 1, price: 24.99 }
],
total: 44.97,
status: "shipped",
createdAt: ISODate("2026-03-01T10:00:00Z")
}
水平スケーリングはファーストクラスの機能です。 MongoDBのシャーディングは組み込みで、十分に文書化されています。本当に多くのノードにデータを分散させる必要がある場合(高スループットで数億のドキュメント)、MongoDBはPostgreSQLよりも優雅にこれを処理します。
MongoDBが間違っている場合
- 高度にリレーショナルなデータ。 すべてのクエリで
$lookupアグリゲーション(MongoDBの結合バージョン)を記述している場合、間違ったデータベースを選択しています。 - 頻繁にコレクション間のトランザクションが必要な場合。 MongoDBはマルチドキュメントトランザクションをサポートしていますが、それらはレイテンシと複雑さを追加します。コア操作がコレクション間のアトミック性を必要とする場合、PostgreSQLのトランザクションモデルの方が自然です。
- 実際にスキーマが必要な場合。 「スキーマレス」は自由な響きですが、データベースから読み取るすべてのコードが暗黙的にスキーマであることを認識するまではそう聞こえます。データベースによって強制されるスキーマがないと、検証はアプリケーション層に移動され、そこで間違いを犯しやすくなり、一貫して強制するのが難しくなります。
FirebaseとFirestore:迅速な開発
Firebase Realtime DatabaseとFirestoreは特定のニッチに対応します。それは、データモデリングの純粋さよりも開発速度が重要であり、バックエンドが複雑なビジネスロジックエンジンではなく、主にデータ永続化と同期のレイヤーであるアプリケーションです。
Firebaseが輝くとき
リアルタイム同期を備えたモバイルファーストアプリ。 FirebaseのSDKは、オフラインキャッシュ、リアルタイムリスナー、競合解決をすぐに処理します。これをPostgreSQLでゼロから構築するには、WebSocketレイヤー、キャッシュ戦略、およびかなりのカスタムコードが必要です。
バックエンドエンジニアがいない小規模チーム。 Firebaseは、データベースサーバーの管理、APIレイヤーの構築、認証の実装、ファイルストレージの処理の必要性を排除します。MVPを構築する2人チームにとって、この運用オーバーヘッドの削減は、製品を出荷できるかどうかの違いになるでしょう。
プロトタイピングと検証。 2週間で実際のユーザーとアイデアをテストする必要がある場合、Firebaseを使用するとクライアントアプリケーションに完全に集中できます。アイデアが検証されたら、後でより伝統的なバックエンドに移行できます。
Firebaseの制限事項
Firestoreのクエリ制約。 インデックスが作成されていないフィールドではクエリを実行できません。複数のフィールドで不等号フィルターを実行できません。全文検索はできません。これらの制限により、積極的に非正規化を行い、場合によってはコレクション間でデータを重複させる必要があります。
// Firestore: You CAN'T do this
db.collection('products')
.where('price', '>', 10)
.where('rating', '>', 4)
.orderBy('name') // Error: needs composite index on price + rating + name
// Firestore: You CAN do this (with a composite index)
db.collection('products')
.where('price', '>', 10)
.where('rating', '>', 4)
.orderBy('price') // Must order by a field used in inequality
ベンダーロックイン。 データモデル、セキュリティルール、クエリパターンはすべてFirebase固有です。Firebaseからの移行は、移行ではなく書き換えになります。
コストの予測不能性。 Firebaseはドキュメントの読み取り/書き込みごとに課金されます。最適化されていないクエリや、大規模なコレクションに対するリアルタイムリスナーは、驚くべき請求額を生成する可能性があります。単一の誤設定されたリスナーが、専用のPostgreSQLサーバーの年間費用よりも高くなったプロジェクトを見たことがあります。
限られたサーバーサイドロジック。 Cloud Functionsは一部のサーバーサイド処理を処理できますが、複雑なビジネスロジック(多段階トランザクション、データ集計、バックグラウンド処理)には、創造的な回避策か、いずれにせよ別のバックエンドが必要です。
意思決定:Firebase vs 従来のバックエンド
| 要素 | Firebase | PostgreSQL + API |
|---|---|---|
| MVPまでの時間 | 数日 | 数週間 |
| 運用オーバーヘッド | ほぼゼロ | 中程度 |
| クエリの柔軟性 | 制限あり | 完全なSQL |
| スケール時のコスト | 予測不能 | 予測可能 |
| ベンダーロックイン | 高い | 低い |
| オフラインサポート | 組み込み | 構築が必要 |
| 複雑なビジネスロジック | 困難 | 自然 |
| 必要なチーム | フロントエンドのみ | フロントエンド + バックエンド |
Redis:キャッシュ、キューなど
Redisはほとんどのアプリケーションにとって主要なデータベースではありません(永続化モジュールを備えたRedisはその役割を果たすことができますが)。特定の課題に優れている高性能なデータ構造ストアです。
キャッシュ
最も一般的なRedisのユースケース。高価なデータベースクエリ、API応答、または計算結果をキャッシュします。
import redis
import json
r = redis.Redis(host='localhost', port=6379, db=0)
def get_user_profile(user_id: str):
# Check cache first
cached = r.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# Cache miss — query database
profile = db.query("SELECT * FROM users WHERE id = %s", user_id)
# Cache for 5 minutes
r.setex(f"user:{user_id}", 300, json.dumps(profile))
return profile
セッションストレージ
TTL(time-to-live)サポートを備えたRedisのキーバリューモデルは、セッションデータに自然に適合します。データベースバックエンドのセッションよりも高速で、ステートフルなアプリケーション向けのJWTベースのソリューションよりもシンプルです。
レート制限
def is_rate_limited(user_id: str, limit: int = 100, window: int = 60) -> bool:
key = f"rate:{user_id}"
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current > limit
ジョブキュー
Redisのリストとストリームは優れたジョブキューになります。Bull(Node.js)、Celery(Python)、Sidekiq(Ruby)などのライブラリは、Redisをメッセージブローカーとして使用しています。
Redisを使用しない方が良い場合
- 唯一のデータベースとして。 Redisはデフォルトでインメモリです。永続化(RDBスナップショットまたはAOFログ)を使用しても、失うわけにはいかないデータのために設計されていません。
- 複雑なクエリの場合。 Redisのデータ構造(文字列、リスト、セット、ソート済みセット、ハッシュ)は強力ですが、データベースのようにクエリ可能ではありません。任意の条件ではなく、キーでデータにアクセスします。
- キャッシュの問題がない場合。 キャッシュを必要としないスタックにRedisを追加すると、メリットなしに運用上の複雑さが増します。5msかかるPostgreSQLクエリはキャッシュする必要はありません。
複数のデータベースの使用
中程度の複雑さを持つほとんどの本番アプリケーションは、複数のデータベースを使用します。重要なのは、各データベースが最も得意とすることに利用することであり、1つのデータベースですべてを処理しようとしないことです。
ウェブアプリケーションの一般的なパターン:
- PostgreSQL:コアビジネスデータ(ユーザー、注文、製品)用。
- Redis:キャッシュ、セッション、レート制限用。
- S3(または同等品):ファイルストレージ(画像、ドキュメント、バックアップ)用。
リアルタイムアプリケーションの場合:
- PostgreSQL:永続データと複雑なクエリ用。
- Redis:pub/subとキャッシュ用。
- Firebase/Supabase:モバイルクライアントへのリアルタイム同期用。
統合パターン
複数のデータベースを使用する場合、明確な所有権を確立します。各データには1つの信頼できる情報源があり、他のシステムはそのデータのキャッシュまたはビューです。
PostgreSQL (source of truth)
├── Redis (read cache, expires after TTL)
├── Elasticsearch (search index, synced via change data capture)
└── Firebase (mobile sync, updated via webhooks)
同じデータの所有者であると両方が考える2つのデータベースを持つことは絶対に避けてください。その道は、デバッグがほぼ不可能な一貫性の悪夢につながります。
移行に関する考慮事項
間違ったデータベースを選択したと気づいた場合でも、移行は可能ですが費用がかかります。以下に実用的な考慮事項を示します。
MongoDBからPostgreSQLへの移行は、私がこれまで見てきた中で最も一般的な移行です。通常の理由は、アプリケーションがドキュメントクエリの範囲を超えて成長し、複雑な結合、トランザクション、または集計が必要になったためです。この移行には、リレーショナルスキーマの設計、変換スクリプトの作成、およびアプリケーション内のすべてのデータベースクエリの更新が含まれます。中程度の複雑さのアプリケーションの場合、2〜4週間を見積もってください。
PostgreSQLからMongoDBへの移行は稀ですが、アプリケーションのデータモデルが主にドキュメント指向になった場合に発生します。この移行は機械的にはよりシンプルですが(テーブルをドキュメントにフラット化する)、すべてのクエリを再考し、トランザクション保証を失う必要があります。
FirebaseからPostgreSQLへの移行は最も困難な移行です。これは単なるデータベースの変更ではなく、アーキテクチャの変更だからです。APIレイヤーを構築し、認証を実装し、リアルタイムリスナーをWebSocketまたはポーリングに置き換え、オフライン同期を処理する必要があります。これは移行というよりも書き換えに近いものです。
最良の移行は、回避する移行です。 事前にデータモデルについてもう1日考えてみてください。同様のアプリケーションを構築した経験のある人に相談してください。最初に適切なデータベースを選択するコストは、後で移行するコストよりも常に低いです。
コスト分析
スケール時のデータベースコストは、特にマネージドサービスの場合、驚くほど高くなることがあります。
| サービス | 無料枠 | 小規模本番環境 | 中規模本番環境 |
|---|---|---|---|
| Supabase (PostgreSQL) | 500MB、2プロジェクト | 約$25/月 (8GB、2コア) | 約$100/月 (32GB、4コア) |
| Neon (PostgreSQL) | 0.5GBストレージ | 約$19/月 | 約$69/月 |
| MongoDB Atlas | 512MB共有 | 約$57/月 (M10専用) | 約$200/月 (M30) |
| Firebase Firestore | 1GBストレージ、5万回読み取り/日 | 約$25-100/月 (大きく変動) | $100-1000/月 (クエリに依存) |
| Redis Cloud | 30MB | 約$7/月 (250MB) | 約$60/月 (1GB) |
| PlanetScale (MySQL) | 5GB、10億行読み取り/月 | 約$39/月 | 約$99/月 |
Firebaseのコストの予測不能性は強調されるべきです。数ヶ月間は月額30ドル未満だったプロジェクトが、ドキュメントの読み取りを増加させる機能リリース後に300ドルに急増するのを見てきました。PostgreSQLやMongoDBでは、コストはマシンのサイズと相関するため、予測可能です。
私の実際の意思決定プロセス
新しいプロジェクトを開始するとき、意思決定は通常次のようになります。
- 特定の理由がない限り、PostgreSQLをデフォルトとする。
- アプリケーションにキャッシュの必要性、レート制限、またはジョブキューがある場合は、Redisを追加する。
- アプリケーションがリアルタイム要件を持つモバイルファーストであり、チームが小規模である場合は、Firebase/Supabaseを検討する。
- データモデルが真にドキュメント指向であり、リレーショナル要件が最小限である場合は、MongoDBを検討する。
- 特定のワークロードが必要とする場合にのみ、専門的なデータベース(Elasticsearch、TimescaleDBなど)を追加する。
このフレームワークは、レストラン管理プラットフォームからモバイルヘルスアプリケーションまで、さまざまなプロジェクトで私に役立ってきました。重要な洞察は、データベースはインフラストラクチャであり、アプリケーションのニーズに応えるべきであり、アーキテクチャを主導すべきではないということです。機能する退屈な選択肢を選び、エンジニアリングの労力を、あなたを差別化するアプリケーションの部分に費やしましょう。