SQLリレーショナルデータのMongoDBへの移行:ステップバイステップガイド

この包括的なステップバイステップガイドで、SQLリレーショナルデータをMongoDBへ移行する方法を学びましょう。必須の計画、エンベディングやリファレンシングといったスキーマ設計戦略、データ抽出、変換技術、そしてMongoDBへのデータロードなど、従来のスキーマを効率的なMongoDBドキュメント構造に変換するためのベストプラクティスをご紹介します。このチュートリアルは、NoSQLデータベースへのスムーズで成功裏な移行のための実用的な例と実践的なアドバイスを提供します。

SQLリレーショナルデータをMongoDBに移行するためのステップバイステップガイド

SQLリレーショナルデータをMongoDBに移行することは、単なるテーブルのコピーではありません。難しいのは、どのリレーションシップを埋め込みドキュメントにすべきか、どれを参照のままにすべきか、そしてアプリケーションが新しい形状をどのようにクエリするかを決定することです。

リレーショナルスキーマは、多くの場合、テーブル間で正規化されています。MongoDBのドキュメントモデルは、一緒に読み取られる関連データが一緒に保存されるときに最も効果的に機能します。このガイドでは、すべてのSQLテーブルが1つのMongoDBコレクションになることを前提とせずに、計画、変換、ロード、検証、およびアプリケーションの変更について説明します。

核となる違いの理解:リレーショナルモデルとドキュメントモデル

移行プロセスに飛び込む前に、概念的な違いを把握することが不可欠です。

  • リレーショナルモデル: データは事前定義されたスキーマを持つテーブルに格納されます。リレーションシップは外部キーを介して管理され、関連データを取得するにはJOIN操作が必要です。正規化が重要な原則です。
  • ドキュメントモデル(MongoDB): データは柔軟なJSONライクなドキュメントに格納されます。ドキュメントはさまざまな構造を持つことができます。関連データは、単一のドキュメント内に埋め込む(非正規化)か、アプリケーションレベルのJOINまたはMongoDBの$lookup集約ステージを使用して参照することができます。

このデータモデリングの違いは、MongoDBのコレクションとドキュメントの設計方法に直接影響します。

フェーズ1:計画とスキーマ設計

これは最も重要なフェーズです。適切に設計されたMongoDBスキーマは、その利点を活用するための鍵です。目標は、SQLテーブルを直接変換するのではなく、アプリケーションのアクセスパターンに基づいてデータをモデル化することです。

1. アプリケーションのアクセスパターンを分析する

  • 読み取りが多い操作と書き込みが多い操作を特定する: データはどのくらいの頻度で読み取られ、通常どのようにクエリされるか?どのフィールドが最も頻繁に一緒に取得されるか?
  • 一般的なクエリパスを特定する: SQLアプリケーションで最も頻繁なSELECTステートメントは何か?通常どのテーブルが結合されるか?
  • データリレーションシップを理解する: エンティティはどのように関連しているか?これらは1対1、1対多、または多対多のリレーションシップか?

2. 非正規化戦略を選択する

MongoDBの強みは、関連データを埋め込む能力にあります。次の戦略を検討してください。

  • 埋め込み(非正規化): 最も一般的なアプローチ。リレーションシップが1対多の場合、またはデータが頻繁に一緒にアクセスされる場合に、親ドキュメント内にドキュメントまたはドキュメントの配列を埋め込みます。これにより、JOINの必要性が減ります。
    • 例: 別々のordersテーブルとorder_itemsテーブルを持つ代わりに、orderドキュメント内にorder_itemsを配列として埋め込むことができます。
  • 参照: 埋め込みによりドキュメントが過度に大きくなる場合、またはデータが独立してアクセスされる場合に使用します。外部キーと同様に、関連ドキュメントの_idを保存し、アプリケーションレベルのJOINを実行するか、MongoDBの$lookupを使用します。
    • 例: usersコレクションとpostsコレクション。投稿には作成者のuser_idが保存される場合があります。その後、$lookupを使用して、投稿を取得するときに作成者の詳細を取得できます。

3. MongoDBのコレクションとドキュメントを設計する

アクセスパターンと非正規化戦略に基づいて、コレクションを設計します。良い出発点は、SQLテーブルをMongoDBコレクションにマッピングすることです。次に、どの関連データを埋め込むべきか、どれを参照すべきかを決定します。

SQLスキーマの例:

-- Customersテーブル
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    FirstName VARCHAR(50),
    LastName VARCHAR(50),
    Email VARCHAR(100)
);

-- Ordersテーブル
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderDate DATE,
    TotalAmount DECIMAL(10, 2),
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

-- OrderItemsテーブル
CREATE TABLE OrderItems (
    OrderItemID INT PRIMARY KEY,
    OrderID INT,
    ProductID INT,
    Quantity INT,
    Price DECIMAL(10, 2),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);

MongoDBドキュメント設計オプション:

  • オプションA:顧客に注文を埋め込む(顧客の注文数が管理可能で、注文が顧客と一緒に頻繁に表示される場合):

    {
      "_id": ObjectId("..."),
      "customer_id": 1,
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]",
      "orders": [
        {
          "order_id": 101,
          "order_date": ISODate("2023-10-26T00:00:00Z"),
          "total_amount": 50.00,
          "items": [
            { "product_id": 1, "quantity": 2, "price": 25.00 },
            { "product_id": 3, "quantity": 1, "price": 0.00 }
          ]
        }
      ]
    }
    
  • オプションB:参照を使用した個別のコレクション(注文が多数ある場合、または独立して頻繁にクエリされる場合)。

    Customersコレクション:

    {
      "_id": ObjectId("..."),
      "customer_id": 1,
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]"
    }
    

    Ordersコレクション:

    {
      "_id": ObjectId("..."),
      "order_id": 101,
      "customer_id": 1,
      "order_date": ISODate("2023-10-26T00:00:00Z"),
      "total_amount": 50.00,
      "items": [
        { "product_id": 1, "quantity": 2, "price": 25.00 },
        { "product_id": 3, "quantity": 1, "price": 0.00 }
      ]
    }
    

ドキュメントサイズに関する考慮事項: MongoDBにはドキュメントサイズの制限(16MB)があります。この制限を超える可能性のある過度に大きな配列を埋め込まないでください。配列が無制限に大きくなる場合は、別のコレクションに分割することを検討してください。

フェーズ2:データ抽出と変換

ターゲットスキーマが設計されたら、SQLデータベースからデータを抽出し、新しいドキュメント形式に変換する必要があります。

1. SQLからデータを抽出する

標準のSQLクエリを使用して、必要なデータを選択します。このデータをCSVやJSONなどの形式にエクスポートできます。

  • SQLクライアントの使用: ほとんどのSQLデータベースツール(DBeaver、SQL Developer、pgAdminなど)では、クエリ結果をCSVまたはJSONにエクスポートできます。
  • スクリプティング: スクリプト(Python、Node.jsなど)を作成してSQLデータベースに接続し、クエリを実行してデータを取得します。

2. データを変換する

ここで、設計したスキーマを実装します。コードを作成するか、ツールを使用して以下を実行する必要があります。

  • 関連レコードをグループ化する: たとえば、特定のOrderに属するすべてのOrderItemsを収集します。
  • データを再構築する: リレーショナル行をネストされたJSONドキュメントに変換します。
  • データ型を処理する: データ型がMongoDBと互換性があることを確認します(例:日付、数値、文字列)。

Pythonを使用した例:

CustomersOrdersOrderItemsをCSVファイルにエクスポートしたと仮定します。

import pandas as pd
import json
from bson import ObjectId

# CSVファイルからデータをロード(同じディレクトリにあると仮定)
customers_df = pd.read_csv('customers.csv')
orders_df = pd.read_csv('orders.csv')
order_items_df = pd.read_csv('order_items.csv')

# --- データ変換ロジック ---

# 操作を容易にするためにDataFrameを辞書に変換
customers_list = customers_df.to_dict('records')
orders_list = orders_df.to_dict('records')
order_items_list = order_items_df.to_dict('records')

# クイックルックアップのために注文と注文アイテムのマッピングを作成
orders_by_customer = {}
for order in orders_list:
    customer_id = order['CustomerID']
    if customer_id not in orders_by_customer:
        orders_by_customer[customer_id] = []
    orders_by_customer[customer_id].append(order)

order_items_by_order = {}
for item in order_items_list:
    order_id = item['OrderID']
    if order_id not in order_items_by_order:
        order_items_by_order[order_id] = []
    order_items_by_order[order_id].append(item)

# --- MongoDBドキュメントの構築(オプションA:顧客に注文を埋め込む) ---
mongo_documents = []

for customer in customers_list:
    mongo_doc = {
        "_id": ObjectId(), # MongoDBは自動的に_idを生成しますが、必要に応じてマッピングできます
        "customer_id": customer['CustomerID'],
        "first_name": customer['FirstName'],
        "last_name": customer['LastName'],
        "email": customer['Email'],
        "orders": []
    }

    customer_id = customer['CustomerID']
    if customer_id in orders_by_customer:
        for order in orders_by_customer[customer_id]:
            order_doc = {
                "order_id": order['OrderID'],
                "order_date": order['OrderDate'], # 正しい日付形式を確認
                "total_amount": order['TotalAmount'],
                "items": []
            }

            order_id = order['OrderID']
            if order_id in order_items_by_order:
                for item in order_items_by_order[order_id]:
                    order_doc['items'].append({
                        "product_id": item['ProductID'],
                        "quantity": item['Quantity'],
                        "price": item['Price']
                    })
            mongo_doc['orders'].append(order_doc)

    mongo_documents.append(mongo_doc)

# これで 'mongo_documents' はMongoDBに挿入する準備ができた辞書のリストになります
# print(json.dumps(mongo_documents[0], indent=2, default=str)) # 最初のドキュメントをJSONで出力

# オプションB(個別のコレクション)の場合、各コレクションのリストを作成します:
# customers_mongo = [{'customer_id': c['CustomerID'], ...} for c in customers_list]
# orders_mongo = [{'order_id': o['OrderID'], 'customer_id': o['CustomerID'], ...} for o in orders_list]

# インポート用にJSONに保存(オプション)
# with open('mongo_customer_data.json', 'w') as f:
#     json.dump(mongo_documents, f, indent=2, default=str)

3. 変換のためのツール

  • カスタムスクリプト: PythonとPandas、Node.jsとcsv-parsermysql/pgなどのライブラリは、複雑な変換に強力です。
  • ETLツール: Apache NiFi、Talend、AWS Glueなどのツールは、SQLからMongoDBへの移行を含む複雑なデータパイプラインを調整できます。
  • データベース移行プラットフォーム: 一部の商用ETLおよびCDCツールは、リレーショナルソースをMongoDBに同期できます。ツールを中心に計画する前に、特定のSQLデータベースとMongoDBターゲットのコネクタサポートを確認してください。

フェーズ3:MongoDBへのデータロード

データが変換されたら、MongoDBインスタンスにロードできます。

1. MongoDBに接続する

MongoDBシェル(mongosh)またはMongoDBドライバー(プログラミング言語用)を使用してデータベースに接続します。

2. 変換されたデータをインポートする

  • mongoimportの使用: 変換されたデータをJSONファイルにエクスポートした場合は、mongoimportを使用できます:

    # データがmongo_customer_data.jsonにあり、'customers'コレクションにインポートすると仮定
    mongoimport --db your_database_name --collection customers --file mongo_customer_data.json --jsonArray
    
    • --jsonArray:JSONファイルにドキュメントの配列が含まれている場合にこのフラグを使用します。
  • MongoDBドライバーの使用: プログラミング言語でデータ構造を生成した場合(Pythonのmongo_documentsリストなど)、それらを直接挿入できます:

    pymongoを使用したPythonの例:

    from pymongo import MongoClient
    
    # 以前のPythonスクリプトから'mongo_documents'リストが定義されていると仮定
    client = MongoClient('mongodb://localhost:27017/')
    db = client['your_database_name']
    customers_collection = db['customers']
    
    # 変換されたドキュメントを挿入
    if mongo_documents:
        insert_result = customers_collection.insert_many(mongo_documents)
        print(f"{len(insert_result.inserted_ids)}件のドキュメントを挿入しました。")
    else:
        print("挿入するドキュメントがありません。")
    
    client.close()
    

3. データの整合性を検証する

ロード後、MongoDBでクエリを実行して、データが正しくインポートされ、期待と一致していることを確認します。

// 例:'customers'コレクションのドキュメント数をカウント
use your_database_name;
print(db.customers.countDocuments());

// 例:特定の顧客を検索し、埋め込まれた注文を確認
db.customers.findOne({ "customer_id": 1 })

フェーズ4:アプリケーションのリファクタリング

これは間違いなく最も時間のかかるフェーズです。アプリケーションコードを更新して、SQLの代わりにMongoDBと対話する必要があります。

  • データベース接続の更新: 接続文字列とライブラリを変更します。
  • クエリの書き換え: SQLクエリを、選択したドライバーのAPIを使用したMongoDBクエリ言語に置き換えます。
  • データアクセス層の調整: ORMまたはデータアクセス層をMongoDBドキュメントで動作するように変更します。
  • MongoDB機能の活用: 該当する場合は、柔軟なスキーマ、集約フレームワーク、地理空間クエリなどの機能を活用するようにアプリケーションを適応させます。

ベストプラクティスとヒント

  • 小さく始める: 可能であれば、まずデータのサブセットまたは重要度の低いアプリケーションを移行して経験を積みます。
  • スキーマ設計を反復する: 最初のMongoDBスキーマは完璧ではないかもしれません。パフォーマンステストとアプリケーションのフィードバックに基づいて、反復して改良する準備をしてください。
  • 賢くインデックスを作成する: SQLと同様に、MongoDBでもインデックスはパフォーマンスにとって重要です。クエリパターンを特定し、適切なインデックスを作成します。
  • パフォーマンスを監視する: MongoDBデプロイメントのパフォーマンスボトルネックを継続的に監視し、必要に応じてクエリとスキーマを最適化します。
  • 段階的な移行を検討する: 大規模なデータベースの場合は、最終的なカットオーバーを実行する前に、SQLからMongoDBへの変更をほぼリアルタイムで同期する段階的な移行戦略を検討します。

まとめ

最も安全なSQLからMongoDBへの移行は、テーブル名ではなくアクセスパターンから始まります。1つの重要なワークフローをモデル化し、小さなデータスライスを変換し、MongoDBにロードし、カウントとサンプルドキュメントを検証してから、その形状に合わせてアプリケーションコードを更新し、その後移行を拡大します。