RDBとの比較を通してMongoDBに入門する
はじめに
業務で MongoDB を初めて扱うことになり、この機会に理解を深めようと色々調査した内容を記事にしたいと思います。
RDB(リレーショナルデータベース)と比較することで、両者の違いも踏まえながら MongoDB について詳しくなることが目標です!
まず導入として NoSQL が作られた背景や歴史を知ることで、考え方を理解する手助けになります。
以下の記事がとてもわかりやすくまとめられておりましたので、よければご一読ください!
用語の対応関係
それでは基本的な用語のおさらいと、RDB と MongoDB の対応関係を表にしてみます。
(RDB の例はこれ以降 MySQL とします)
MySQL (RDB) | MongoDB (NoSQL ドキュメント型) | 意味・役割 |
---|---|---|
データベース (Database) | データベース (Database) | 複数のテーブル(コレクション)をまとめた、一番大きなデータの入れ物。プロジェクト単位で作成することが多いです。 |
テーブル (Table) | コレクション (Collection) | 関連するデータの集まり。MySQL では表形式、MongoDB ではドキュメントの集まりです。例えば「ユーザーテーブル」や「ユーザーコレクション」。 |
行 (Row) / レコード (Record) | ドキュメント (Document) | 一つのデータ単位。例えば、一人のユーザー情報、一つの商品情報など。MongoDB では JSON/BSON 形式のデータです。 |
列 (Column) / フィールド (Field) | フィールド (Field) | データの項目名。例えば「名前」「メールアドレス」「価格」など。MongoDB ではドキュメント内のキーと値のペアの「キー」部分。 |
主キー (Primary Key) | _id (オブジェクト ID) |
各行(ドキュメント)を一意に識別するための特別な ID。MongoDB では自動的に _id が生成されます。これは RDB のサロゲートキーの役割に似ています。 |
インデックス (Index) | インデックス (Index) | データの検索を速くするための「索引」。本の巻末にある索引と同じような働きをします。 |
JOIN (結合) | $lookup (アグリゲーション) |
複数のテーブル(コレクション)の情報を関連付けて取得する操作。MongoDB ではアグリゲーションパイプラインの一部として実現します。 |
スキーマ (Schema) | スキーマレス | データの構造定義。MySQL では事前に厳密に定義しますが、MongoDB は柔軟で、ドキュメントごとに構造が異なっても構いません。 |
正規化 (Normalization) | 非正規化 (Denormalization) / 埋め込み | MongoDB では関連データをドキュメントに埋め込む非正規化もよく行われます。 |
トランザクション (Transaction) | トランザクション (Transaction) | データの整合性を保つために重要です。MySQL(InnoDB)では強力にサポートされていますが、MongoDB も 2018 年からサポートを開始しました。 |
InnoDB | WiredTiger | (参考)代表的なストレージエンジン |
MongoDB (NoSQL ドキュメント型 DB)とは
-
MongoDB の基本とアーキテクチャ
-
ドキュメント型データベース: MongoDB は、データを ドキュメント という単位で管理します。ドキュメントは、JSON によく似た BSON (Binary JSON) という形式で、キーと値のペアで構成されます。柔軟な構造が特徴です。ユーザー情報のドキュメント例
{ "_id": ObjectId("6131c9cc9d1234f567f3fed3"), // MongoDBが自動生成するID "name": "MongoDB ユーザー", "email": "user@example.com", "age": 30, "tags": ["developer", "mongodb", "nosql"], // 配列も格納可能 "address": { // このようにドキュメント内に別のドキュメントを入れ子にできます (埋め込み) "street": "123 Main St", "city": "MongoCity" } }
- ドキュメントの集まりを コレクション と呼び、一つのデータベース内に複数のコレクションを格納できます。
-
ドキュメント型データベース: MongoDB は、データを ドキュメント という単位で管理します。ドキュメントは、JSON によく似た BSON (Binary JSON) という形式で、キーと値のペアで構成されます。柔軟な構造が特徴です。
-
スキーマレスと柔軟なデータモデル
-
MongoDB の最も大きな特徴の一つが スキーマレス であることです。これは、コレクション内の各ドキュメントが、それぞれ異なるフィールド(キー)や構造を持てる、という意味です。
-
例えば、同じ
users
コレクション内でも、あるユーザーはcompany
フィールドを持っているけれど、別のユーザーは持っていない、といったことが許容されます。 -
この柔軟性のおかげで、アプリケーション開発の途中で仕様変更があったり、新しい項目を追加したくなったりした場合にも、迅速に対応できます。特に、開発の初期段階でまだデータ構造が固まっていない時や、様々な種類のデータを扱いたい場合に、その真価を発揮します。
-
とはいえ、あまりに自由すぎるとデータの秩序がなくなってしまうことも。そこで MongoDB にはスキーマ検証という機能があり、特定のフィールドを必須にしたり、データ型をチェックしたりと、ある程度の一貫性を保つためのルールを設けることも可能です。
スキーマ検証:https://www.mongodb.com/ja-jp/docs/manual/replication/
-
-
主な機能の紹介
-
クエリ言語 (MQL - MongoDB Query Language): JSON ライクな直感的で分かりやすいクエリを使って、柔軟なデータ検索や更新ができます。豊富な演算子が用意されているのも魅力です。
-
例:
db.users.find({ age: { $gt: 25 }, tags: "developer" })
(年齢が 25 より大きく、タグに"developer"を含むユーザーを検索) -
レプリカセット (高可用性)
- 複数の MongoDB サーバー(ノード)間で、データのコピーを同期的に保持する仕組みです。1 台が書き込み処理を担当する プライマリ ノードとなり、他のノードは セカンダリ としてデータのコピーを持ち、プライマリの変更をリアルタイムで反映します。
- もしプライマリノードに障害が発生しても、セカリノードの中から自動的に新しいプライマリが選出(この仕組みを フェイルオーバー と言います)されるため、システム全体のダウンタイムを最小限に抑えられます(高可用性)。また、読み取り処理(Read)をセカンダリノードに分散させて、負荷を軽減することも可能です。
レプリカセット:https://www.mongodb.com/ja-jp/docs/manual/replication/
-
シャーディング (水平スケーリング)
- 膨大なデータ量やアクセス集中に対応するため、データを複数のサーバー(またはサーバー群である シャード)に分割して保存する技術です。これにより、システム全体で対応できるデータ量や処理能力を向上させます(水平スケーリング)。どのデータをどのシャードに配置するかは、あらかじめ決めておく シャードキー に基づいて自動で振り分けられます。
- サーバー 1 台では扱いきれないような大規模なデータや高い負荷も、サーバーの台数を増やすことでスケールアウトできるのが強みです。
このシャーディングの仕組みについては、以下の記事が図解付きで非常に分かりやすいので、ぜひ参考にしてみてください。
-
-
アグリゲーションフレームワーク (高機能なデータ集計)
-
RDB の
GROUP BY
や集計関数のように、データ集計や加工を行うための強力な機能です。複数の処理ステージをパイプラインのようにつなぎ合わせることで、複雑なデータ処理を MongoDB 内で完結させることができます。 -
代表的なステージ例:
$match
(絞り込み),$group
(グループ化),$project
(出力フィールドの整形),$sort
(並び替え),$lookup
(他コレクションとの JOIN のような操作) etc... -
これにより、アプリケーション側で行っていた複雑な処理をデータベースに任せられるため、コードがシンプルになったり、パフォーマンスが向上したりします。
より詳しい使い方は、こちらの記事が参考になりますので引用させていただきます!
-
// orders コレクションのドキュメント
// { _id: 1, customer_id: "A", product_id: "P101", quantity: 2 }
// products コレクションのドキュメント
// { _id: "P101", name: "テストビール", price: 1000 }
// $lookup を使ったアグリゲーション
db.orders.aggregate([
{
$lookup: {
from: "products", // 結合したいコレクション
localField: "product_id", // 自分(orders)の結合キー
foreignField: "_id", // 相手(products)の結合キー
as: "product_details" // 結合結果を格納する新しいフィールド名
}
}
])
// 結果のイメージ
/*
{
_id: 1,
customer_id: "A",
product_id: "P101",
quantity: 2,
product_details: [ // 結合されたドキュメントが配列で格納される
{ _id: "P101", name: "Super Widget", price: 1000 }
]
}
*/
-
MongoDB で User データはこんな感じで持つ:
usersコレクションのドキュメント例{ "_id": ObjectId("6831c9cc9d7948f791f3fed3"), "name": "MongoDB 花子", "email": "mongo.hanako.xxxxxxxx@example.com", "password": "hashed_password_string", "status": "inactive", "preferences": { "theme": "dark", "language": "en" }, "login_ips": [ "192.168.1.10", "10.0.0.5", "172.16.0.100" ], "created_at": ISODate("2025-06-02T..."), "updated_at": ISODate("2025-06-02T...") }
コレクション設計パターン
MongoDB の設計思想がどのように活かされるのか、例を考えてみます。
-
某巨大 EC サイト
- シナリオ: 数千万点の商品と数億人のユーザーを抱え、ピーク時には毎秒数万件のアクセスがある巨大 EC サイトを考えます。商品のスペックは多岐にわたり、リアルタイムでの在庫管理や、ユーザー毎のレコメンド表示が求められます。
-
設計方針:
-
商品カタログ (
products
): 商品の基本情報は一つのドキュメントにまとめます。ただし、際限なく増える商品レビューや Q&A は別コレクション (reviews
) に分離し、商品 ID で参照(Referencing)します。これにより、商品ドキュメントの肥大化を防ぎます。 -
在庫管理 (
inventory
): 在庫情報は更新頻度が非常に高いため、商品カタログとは別の専用コレクションで管理します。これにより、在庫確認や更新のトラフィックを分離し、書き込み性能を最大化します。 -
ユーザーと注文 (
users
,orders
): ユーザー情報と注文履歴も分離します。ユーザーのドキュメントに注文履歴をすべて埋め込むと無限に肥大化するため、orders
コレクションで管理し、ユーザー ID で参照します。
-
商品カタログ (
-
コレクション構成例:products コレクション
{ "_id": "product123", "name": "ゲーミングPC", "category_id": "cat_pc_gaming", "brand": "UltraPC", "price": 300000, "specs": { "cpu": "i99", "ram_gb": 1024, "storage_gb": 2000000 } }
reviews コレクション{ "_id": "reviewABC", "product_id": "product123", "user_id": "userXYZ", "rating": 5, "comment": "最高!" }
-
ポイント: データの特性(更新頻度、関連性、データサイズ)に応じて「埋め込み」と「参照」を使い分けるのが鍵です。
$lookup
を使い、必要な場面で商品とレビューを結合して表示します。この設計により、システムの各関心事を分離し、それぞれを独立してスケールさせることが可能になります。
-
大人気ソーシャルゲーム
- シナリオ: 数百万人のプレイヤーが同時にプレイするソーシャルゲーム。プレイヤーのレベル、所持アイテム、仮想通貨といったデータは常に変動し、リアルタイムでのランキング表示も必要です。
-
設計方針:
-
プレイヤーデータ (
players
): プレイヤー 1 人につき 1 ドキュメントを作成し、そのプレイヤーに関連するほとんどのデータ(ステータス、アイテム、通貨など)をこのドキュメント内に集約します。これにより、プレイヤーの情報を 1 回の読み書きで操作でき、アトミックな更新が保証されます。 -
ゲームログ (
game_logs
): 対戦履歴や行動ログは非常に速いペースで生成されるため、専用のコレクションに書き込みます。古いログを自動的に削除するために、TTL インデックスを利用するのが非常に効果的です。 -
ランキング (
leaderboards
): ランキングデータは、全プレイヤーから集計する必要があるため、アグリゲーションフレームワークを使って定期的にバッチ処理で生成し、専用のコレクションに保存します。これにより、ランキング表示のたびに重い集計処理が走るのを防ぎ、高速な読み取りを実現します。
-
プレイヤーデータ (
-
コレクション構成例:players コレション
{ "_id": "playerA", "username": "Gamer", "level": 99, "xp": 1234567, "virtual_currency": { "gold": 5000, "gems": 120 }, "inventory": [ // 所持アイテムは配列で埋め込み { "item_id": "sword001", "quantity": 1 }, { "item_id": "potion003", "quantity": 15 } ] }
leaderboards コレクション (集計結果){ "_id": "weekly_score_ranking", "updated_at": ISODate("..."), "scores": [ { "player_id": "playerX", "rank": 1, "score": 98765 }, { "player_id": "playerA", "rank": 2, "score": 98700 } ] }
- ポイント: プレイヤー中心のデータはドキュメントに集約して更新性能を高め、全体集計が必要なデータ(ランキング)やログは別コレクションに分離するのが定石です。ユースケースに応じてデータの持ち方を最適化することで、MongoDB はリアルタイム性が求められるアプリケーションの要求に応えることができます。
MongoDB のユースケース
これまでの設計パターンを踏まえると、MongoDB がどのような場面で有効か想像できそうです。
-
データ構造が多様で、変化し続けるサービス
-
EC サイトの例では、PC とアパレルでは商品のスペック(
specs
)が全く異なります。これらの異なる構造を持つデータを同じコレクション内で柔軟に管理できます。新しい種類の商品を追加する際も、DB のスキーマ変更に悩まされることはありません。このような柔軟性が、変化の速いビジネス要件に対応する上で強力な武器となりえます。
-
EC サイトの例では、PC とアパレルでは商品のスペック(
-
高い書き込み性能とスケーラビリティが求められる大規模サービス
- 人気ソーシャルゲームのように、数百万のプレイヤーが同時にアクセスし、常にデータを更新する状況を考えてみると汗が止まりません。プレイヤーデータを 1 ドキュメントに集約する設計は、高速な書き込みに最適化されています。データ量やアクセスが増えても、シャーディングによってサーバーを増やすことでスケールアウトできるのが MongoDB の大きな強みです。
-
専用の機能で特定の課題を解決したい場面
- MongoDB は単に柔軟なだけでなく、特定の課題を解決するための便利な機能も備えています。
例えばゲームのログのように、大量に生成され、一定期間で不要になるデータにはTTL インデックスが最適です。
- MongoDB は単に柔軟なだけでなく、特定の課題を解決するための便利な機能も備えています。
最後に
ここまでいただきありがとうございました!
まだまだ調べきれていない事が多く、例えばRDBとNoSQLを組み合わせて使う「ポリグロット・パーシステンス」のようなアーキテクチャなど、もっと深掘ると興味深い事がたくさん転がっていそうです。
RDB と NoSQL、それぞれの思想や得意なことを理解して、実務に活かしていきたいと思います!
Discussion