STEP 17:MongoDB実践

🍃 STEP 17: MongoDB実践

NoSQLの代表格・MongoDBの基本操作をマスターしよう

📋 このステップで学ぶこと
  • MongoDBの基本操作(CRUD)
  • コレクションとドキュメントの理解
  • 埋め込み vs 参照の判断基準
  • 実践例:ユーザープロファイル管理システム
  • MongoDBとRDBMSの使い分け

学習時間の目安: 2.5時間 | 前提知識: STEP 16(NoSQL基礎)の理解

🎯 1. MongoDBの基本構造

STEP 16からの続き:MongoDBを実際に使ってみよう

STEP 16でNoSQLの基礎を学びました。このSTEP 17では、NoSQLの代表格であるMongoDBを実際に操作して、CRUD(作成・読み取り・更新・削除)操作をマスターしましょう。MongoDBはドキュメント型データベースであり、JSON形式でデータを柔軟に管理できます。

MongoDBの用語対比

RDBMS(MySQL等) MongoDB 説明
データベース データベース データの大きな入れ物
テーブル コレクション データをまとめる単位
レコード(行) ドキュメント 1件のデータ
カラム(列) フィールド データの項目
主キー _id 一意識別子(自動生成)
JOIN 埋め込み or $lookup データの関連付け

ドキュメントとは?

MongoDBでは、データをJSON形式(厳密にはBSON)で保存します。

// ユーザー情報のドキュメント例 { “_id”: ObjectId(“507f1f77bcf86cd799439011”), // 自動生成される一意ID “name”: “山田太郎”, “email”: “yamada@example.com”, “age”: 28, “address”: { “prefecture”: “東京都”, “city”: “渋谷区” }, “hobbies”: [“読書”, “プログラミング”, “旅行”], “created_at”: ISODate(“2024-01-15T10:30:00Z”) }
🎯 ドキュメントの特徴
  • 柔軟なスキーマ:フィールドの数や型が違ってもOK
  • 入れ子構造:オブジェクトや配列を含められる
  • 動的追加:後から新しいフィールドを追加できる
  • 自己完結:関連データを1つのドキュメントに含められる

🔧 2. MongoDB基本操作(CRUD)

MongoDB Compassのインストール

📥 MongoDB Compass(GUIツール)

MongoDBを視覚的に操作できる公式ツールです。

  1. MongoDB公式サイト(mongodb.com)にアクセス
  2. 「MongoDB Compass」をダウンロード
  3. インストール後、起動
  4. 「New Connection」で接続(ローカルの場合:mongodb://localhost:27017)

データベースとコレクションの作成

// データベース「my_app」を使用(なければ自動作成) use my_app // コレクション「users」を作成 db.createCollection(“users”) // コレクション一覧を表示 show collections

Create(作成)- insertOne / insertMany

1件のドキュメントを挿入

// 1人のユーザーを追加 db.users.insertOne({ name: “佐藤花子”, email: “sato@example.com”, age: 25, hobbies: [“料理”, “ヨガ”] }) // 実行結果 { “acknowledged”: true, “insertedId”: ObjectId(“…”) // 自動生成されたID }

複数のドキュメントを一括挿入

// 複数ユーザーを一度に追加 db.users.insertMany([ { name: “田中一郎”, email: “tanaka@example.com”, age: 30, hobbies: [“ゲーム”, “映画鑑賞”] }, { name: “鈴木美咲”, email: “suzuki@example.com”, age: 22, hobbies: [“音楽”, “ダンス”], premium: true // フィールドは自由に追加可能 } ])

Read(読み取り)- find / findOne

全件取得・条件検索

// 全ユーザーを取得 db.users.find() // 年齢が25歳のユーザーを検索 db.users.find({ age: 25 }) // 年齢が25歳以上のユーザーを検索 db.users.find({ age: { $gte: 25 } }) // 名前に「田中」を含むユーザーを検索 db.users.find({ name: /田中/ }) // 趣味に「料理」が含まれるユーザーを検索 db.users.find({ hobbies: “料理” })

特定のフィールドだけ取得(プロジェクション)

// nameとemailだけ取得(_idは自動的に含まれる) db.users.find({}, { name: 1, email: 1 }) // _idを除外してnameとemailだけ取得 db.users.find({}, { name: 1, email: 1, _id: 0 })

Update(更新)- updateOne / updateMany

1件を更新

// 佐藤花子の年齢を26に更新 db.users.updateOne( { name: “佐藤花子” }, // 検索条件 { $set: { age: 26 } } // 更新内容 ) // フィールドを追加 db.users.updateOne( { name: “佐藤花子” }, { $set: { city: “大阪市” } } ) // 配列に要素を追加 db.users.updateOne( { name: “佐藤花子” }, { $push: { hobbies: “旅行” } } )

複数件を更新

// 全ユーザーにstatus: “active”を追加 db.users.updateMany( {}, // 全件対象(条件なし) { $set: { status: “active” } } )

Delete(削除)- deleteOne / deleteMany

// 田中一郎を削除 db.users.deleteOne({ name: “田中一郎” }) // 年齢が20未満のユーザーを全て削除 db.users.deleteMany({ age: { $lt: 20 } })
⚠️ 削除時の注意

MongoDBにはトランザクションのロールバック機能が限定的です。deleteMany({})は全件削除してしまうので、実行前に必ず確認しましょう!

🔗 3. 埋め込み vs 参照の判断

埋め込み(Embedding)方式

関連データを1つのドキュメント内に含める方法です。

// ブログ記事に著者情報を埋め込む例 { “_id”: ObjectId(“…”), “title”: “MongoDBの使い方”, “content”: “MongoDBは…”, “author”: { “name”: “山田太郎”, “email”: “yamada@example.com” }, “comments”: [ { “user”: “佐藤花子”, “text”: “参考になりました!” }, { “user”: “田中一郎”, “text”: “わかりやすいです” } ] }
✅ 埋め込みのメリット
  • 高速:1回の読み取りで全データ取得
  • シンプル:JOINが不要
  • アトミック:1つのドキュメント内で完結
❌ 埋め込みのデメリット
  • 重複:同じデータが複数箇所に存在
  • サイズ制限:1ドキュメント16MB以下
  • 更新コスト:埋め込んだデータの更新が大変

参照(Referencing)方式

関連データを別のコレクションに保存し、IDで参照する方法です。

// usersコレクション { “_id”: ObjectId(“64a1b2c3d4e5f6789”), “name”: “山田太郎”, “email”: “yamada@example.com” } // postsコレクション { “_id”: ObjectId(“…”), “title”: “MongoDBの使い方”, “content”: “MongoDBは…”, “author_id”: ObjectId(“64a1b2c3d4e5f6789”) // 参照 }

判断基準フローチャート

🤔 埋め込み vs 参照の判断基準

以下の場合は「埋め込み」を選択

  • 1対少数(1対1〜1対数件)の関係
  • 関連データが一緒に読まれることが多い
  • 関連データがめったに更新されない
  • データサイズが小さい(合計16MB以下)

以下の場合は「参照」を選択

  • 1対多数(1対数百件以上)の関係
  • 関連データが頻繁に更新される
  • 関連データが独立して読まれることが多い
  • データサイズが大きい
  • 多対多の関係

実例で理解する

ケース 推奨 理由
ブログ記事 + 著者情報 埋め込み 著者情報は記事と一緒に読まれ、めったに変わらない
ブログ記事 + コメント(数件) 埋め込み コメントは記事と一緒に表示される
ブログ記事 + コメント(数千件) 参照 コメント数が多すぎて16MB超える可能性
ユーザー + 注文履歴 参照 注文履歴は増え続け、独立して参照される
商品 + カテゴリ 参照 カテゴリは複数商品で共有され、変更される

💼 4. 実践例:ユーザープロファイル管理システム

要件定義

📋 システム要件
  • ユーザーの基本情報(名前、メール、パスワード)
  • プロフィール情報(自己紹介、アバター画像URL、SNSリンク)
  • スキル情報(プログラミング言語、レベル)
  • 投稿履歴(タイトル、内容、投稿日時)
  • フォロー・フォロワー関係

設計方針

【埋め込みにするもの】 ✅ プロフィール情報 → ユーザーと一緒に読まれる ✅ スキル情報 → 数件程度で、ユーザーと密接 【参照にするもの】 ✅ 投稿履歴 → 独立して表示、数が多い ✅ フォロー関係 → 多対多の関係

コレクション設計

usersコレクション

// ユーザー情報(埋め込み方式) { “_id”: ObjectId(“64a1b2c3d4e5f6789”), “username”: “yamada_taro”, “email”: “yamada@example.com”, “password_hash”: “hashed_password_here”, // プロフィール情報を埋め込み “profile”: { “display_name”: “山田太郎”, “bio”: “プログラマーです。MongoDBを勉強中!”, “avatar_url”: “https://example.com/avatar/yamada.jpg”, “social”: { “twitter”: “@yamada_taro”, “github”: “yamada-taro” } }, // スキル情報を埋め込み “skills”: [ { “name”: “JavaScript”, “level”: “上級” }, { “name”: “Python”, “level”: “中級” }, { “name”: “MongoDB”, “level”: “初級” } ], “created_at”: ISODate(“2024-01-01T00:00:00Z”), “updated_at”: ISODate(“2024-01-15T10:00:00Z”) }

postsコレクション(参照方式)

{ “_id”: ObjectId(“…”), “title”: “MongoDBの勉強記録”, “content”: “今日はMongoDBのCRUD操作を学びました…”, “author_id”: ObjectId(“64a1b2c3d4e5f6789”), // 参照 “tags”: [“MongoDB”, “学習記録”], “likes_count”: 15, “created_at”: ISODate(“2024-01-15T09:00:00Z”) }

followsコレクション(参照方式)

{ “_id”: ObjectId(“…”), “follower_id”: ObjectId(“64a1b2c3d4e5f6789”), // フォローする人 “following_id”: ObjectId(“64b2c3d4e5f678901”), // フォローされる人 “created_at”: ISODate(“2024-01-10T08:00:00Z”) }

サンプルクエリ

ユーザーの投稿一覧を取得($lookup使用)

// ユーザー情報と投稿を結合 db.users.aggregate([ { $match: { username: “yamada_taro” } }, { $lookup: { from: “posts”, localField: “_id”, foreignField: “author_id”, as: “user_posts” } } ])

⚡ 5. パフォーマンス最適化

インデックスの作成

// ユーザー名にインデックス(一意制約付き) db.users.createIndex({ username: 1 }, { unique: true }) // メールアドレスにインデックス db.users.createIndex({ email: 1 }, { unique: true }) // 投稿の作成日時に降順インデックス(新着順取得に便利) db.posts.createIndex({ created_at: -1 }) // author_idにインデックス(特定ユーザーの投稿検索に便利) db.posts.createIndex({ author_id: 1 }) // タグ検索用インデックス db.posts.createIndex({ tags: 1 })
💡 インデックス設計のコツ
  • 検索条件によく使うフィールドにインデックス
  • ソートに使うフィールドにインデックス
  • 一意性が必要ならunique: trueを指定
  • インデックスが多すぎると書き込み速度が低下するので注意

🔄 6. MongoDBとRDBMSの使い分け

観点 MongoDBを選ぶ RDBMSを選ぶ
データ構造 柔軟なスキーマが必要、頻繁に変更される 固定的なスキーマ、明確な関係性
読み取り 高速な読み取りが最重要、1つのドキュメントで完結 複雑なJOINが必要、トランザクション重視
スケーラビリティ 水平スケーリング(サーバー追加で拡張) 垂直スケーリング(サーバー性能向上)
データ整合性 結果整合性でOK 強い整合性が必須
トランザクション 簡易的でOK ACIDが必須
📱 MongoDBが向いているケース
  • コンテンツ管理(ブログ、ニュースサイト)
  • ユーザープロファイル(SNS、マッチングアプリ)
  • IoTデータ(センサーデータの蓄積)
  • ログ管理(アクセスログ、エラーログ)
  • カタログ(商品情報、属性が多様)
💳 RDBMSが向いているケース
  • 金融システム(銀行、決済)
  • 在庫管理(ECサイトの在庫)
  • 会計システム(経理、給与計算)
  • 予約システム(ホテル、航空券)
  • 複雑なレポート(多数のJOIN必要)

📝 7. STEP 17 のまとめ

✅ このステップで学んだこと
  • MongoDBの基本操作(CRUD)をマスター
  • コレクションドキュメントの理解
  • 埋め込み vs 参照の判断基準
  • 実践的なユーザープロファイル管理システムの設計
  • MongoDBとRDBMSの使い分け
💡 重要ポイント
  • MongoDBは柔軟性スピードが強み
  • 埋め込みと参照を適切に使い分けることが設計の鍵
  • インデックスを使ってパフォーマンスを最適化
  • 用途に応じてRDBMSと使い分ける(または併用)
🎯 次のステップへ

STEP 18からは、いよいよ総合プロジェクトに挑戦します!
これまで学んだ知識を総動員して、SNSシステムを設計しましょう。

📝 練習問題

問題 1 基礎

ブログシステムを想定して、以下のドキュメントを挿入するコマンドを書いてください。

  • タイトル:「MongoDBの魅力」
  • 内容:「MongoDBは柔軟性が高く…」
  • 著者:あなたの名前
  • タグ:[“MongoDB”, “NoSQL”, “Database”]
【解答】
db.posts.insertOne({ title: “MongoDBの魅力”, content: “MongoDBは柔軟性が高く…”, author: “あなたの名前”, tags: [“MongoDB”, “NoSQL”, “Database”], created_at: new Date() })
問題 2 基礎

タグに「MongoDB」が含まれる記事を全て検索するコマンドを書いてください。

【解答】
db.posts.find({ tags: “MongoDB” })

解説:配列フィールドに対する検索は、配列内の要素を指定するだけで、その要素を含むドキュメントが取得できます。

問題 3 標準

以下のケースで、埋め込みと参照のどちらを選ぶべきか理由とともに答えてください。

ケース:Amazonのような大規模ECサイトで、「商品」と「レビュー」の関係

【解答】

推奨:参照方式

理由:

  • 人気商品は数千〜数万件のレビューが付く可能性がある
  • 16MBの制限を超える可能性が高い
  • レビューは独立して表示されることが多い(レビュー一覧ページなど)
  • レビューは頻繁に追加される(書き込み性能重視)

設計例:

// productsコレクション { “_id”: ObjectId(“…”), “name”: “ノートPC”, “price”: 120000, “reviews_count”: 1523 // 件数だけ持つ } // reviewsコレクション { “_id”: ObjectId(“…”), “product_id”: ObjectId(“…”), // 参照 “user_id”: ObjectId(“…”), “rating”: 5, “comment”: “最高です!” }
問題 4 標準

ユーザーコレクションのusernameフィールドに、一意制約付きのインデックスを作成するコマンドを書いてください。

【解答】
db.users.createIndex({ username: 1 }, { unique: true })

解説:

  • { username: 1 }:昇順インデックス(-1なら降順)
  • { unique: true }:一意制約(重複不可)
  • 既に同じusernameが存在する場合、エラーが発生します
問題 5 応用

以下の要件を満たすTwitter風のコレクション設計をしてください。

  • ユーザー情報(名前、ユーザーID、プロフィール画像)
  • ツイート(本文、投稿日時、いいね数、リツイート数)
  • フォロー関係

埋め込みと参照を適切に使い分け、理由も説明してください。

【解答例】

1. usersコレクション(埋め込み)

{ “_id”: ObjectId(“…”), “username”: “yamada_taro”, “display_name”: “山田太郎”, “profile”: { “bio”: “プログラマー”, “avatar_url”: “https://…”, “location”: “東京” }, “followers_count”: 120, “following_count”: 80, “created_at”: ISODate(“…”) }

理由:プロフィール情報は少量でユーザーと一体なので埋め込み

2. tweetsコレクション(参照)

{ “_id”: ObjectId(“…”), “user_id”: ObjectId(“…”), // 参照 “text”: “MongoDBを勉強中!”, “likes_count”: 5, “retweets_count”: 2, “created_at”: ISODate(“…”) }

理由:ツイートは大量に増えるため、参照で分離

3. followsコレクション(参照)

{ “_id”: ObjectId(“…”), “follower_id”: ObjectId(“…”), “following_id”: ObjectId(“…”), “created_at”: ISODate(“…”) }

理由:多対多の関係なので、別コレクションで管理

問題 6 応用

以下のデータに対して、$lookupを使って結合クエリを書いてください。

要件:

usersコレクションとordersコレクションがあります。
ユーザー「yamada_taro」の全ての注文を、ユーザー情報とともに取得してください。

【解答】
db.users.aggregate([ // 1. 対象ユーザーを絞り込む { $match: { username: “yamada_taro” } }, // 2. ordersコレクションと結合 { $lookup: { from: “orders”, // 結合先コレクション localField: “_id”, // usersの_id foreignField: “user_id”, // ordersのuser_id as: “user_orders” // 結果を格納するフィールド名 } } ])

実行結果のイメージ:

{ “_id”: ObjectId(“…”), “username”: “yamada_taro”, “email”: “yamada@example.com”, “user_orders”: [ { “order_id”: “ORD001”, “amount”: 5000 }, { “order_id”: “ORD002”, “amount”: 3000 } ] }

ポイント:$lookupはRDBMSのJOINに相当する機能です。ただし、パフォーマンスに影響するため、頻繁に使う場合は埋め込みも検討しましょう。

❓ よくある質問

Q1: MongoDBでもトランザクションは使えますか?

はい、MongoDB 4.0以降で使えます。ただし、RDBMSほど強力ではありません。MongoDBのトランザクションは複数ドキュメントにまたがる操作で使用できますが、パフォーマンスへの影響があります。基本的には1つのドキュメント内で操作を完結させる設計(埋め込み)が推奨されます。金融取引など厳密なトランザクションが必要な場合は、RDBMSを使う方が安全です。

Q2: 埋め込みにしたデータを後から参照に変更できますか?

可能ですが、マイグレーション作業が必要です。埋め込みから参照への変更は、新しいコレクションを作成し、既存のドキュメントからデータを抽出して移行するスクリプトを書く必要があります。本番環境では慎重に行う必要があるため、最初の設計段階で適切な方式を選ぶことが重要です。データ量が増えた後の変更は大変なので、成長を見越した設計を心がけましょう。

Q3: MongoDBの16MB制限に引っかかりそうな場合どうすればいいですか?

参照方式に切り替えるか、GridFSを使います。ドキュメントサイズが16MBに近づく場合は、大きなデータ(配列やネストしたオブジェクト)を別のコレクションに分離して参照で管理します。画像やファイルなどのバイナリデータを扱う場合は、GridFSという仕組みを使ってファイルを分割保存できます。実務では16MBに達する前に設計を見直すのが一般的です。

Q4: MongoDBのインデックスはいくつまで作れますか?

制限はありますが、実務では5〜10個程度が目安です。技術的には1コレクションあたり64個までインデックスを作成できますが、インデックスが多いと書き込み速度が低下します。検索で頻繁に使うフィールドと、ソートに使うフィールドに絞ってインデックスを作成しましょう。explain()コマンドでクエリがインデックスを使っているか確認できます。

Q5: MongoDBとMySQLを同じプロジェクトで使っても大丈夫ですか?

問題ありません。むしろ推奨されるケースも多いです。STEP 16で学んだハイブリッド構成のように、MongoDBとMySQLを併用することは一般的です。例えば、注文・決済処理はMySQL(トランザクション重視)、商品カタログやユーザーレビューはMongoDB(柔軟性重視)という使い分けができます。ただし、複数のデータベースを管理する運用コストは増えるため、チームのスキルセットも考慮して判断しましょう。

Q6: MongoDBを学ぶのにMongo Shellとcompass、どちらがいいですか?

両方使えるようになることを推奨します。学習の初期段階ではMongoDB Compass(GUI)が視覚的で理解しやすいです。データの構造を確認したり、クエリ結果を見やすく表示できます。ある程度慣れてきたらMongo Shell(CLI)を使いましょう。スクリプトの作成や自動化にはShellが必須です。実務では状況に応じて使い分けます。

📝

学習メモ

データベース設計・データモデリング - Step 17

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE