- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — マイグレーションはデータのコピーではない
- モデルの違いの本質 — 正規化 vs ドキュメント/KV
- 方向別マイグレーション戦略
- データモデルの再設計
- 漸進的移行 — ストラングラーパターン
- 二重書き込みとCDC
- スキーマ進化 — スキーマレスの落とし穴
- 検証
- 補助ストアの同期 — ElasticSearch、Redis
- 一貫性のトレードオフ
- カットオーバー戦略 — いつ、どう切り替えるか
- 継続的照合 — マイグレーション後も
- マイグレーションツール — AWS DMSとCDCパイプライン
- 時系列・グラフなど特殊ストアへの移行
- 事例 — モノリシックRDBからドキュメント + 検索へ
- ポリグロットの運用負担
- よくある落とし穴
- チェックリスト
- おわりに
- 参考資料
はじめに — マイグレーションはデータのコピーではない
異種データマイグレーションを初めて担当すると「データをコピーする作業」だと考えがちです。PostgreSQLのテーブルをMongoDBのコレクションに移せばいいのでは、と。しかしすぐに気づきます。リレーショナルとドキュメントはデータを見る哲学そのものが異なり、その違いを無視すると「動くが遅く、一貫性が崩れ、運用が地獄の」システムができあがります。
異種マイグレーションの本質はデータモデルの再設計です。正規化された複数テーブルを一つのドキュメントにまとめるか、参照のまま残すか。強い一貫性を諦めて最終的一貫性を受け入れるか。こうした決定がコピー作業よりはるかに重要です。
本稿では、モデルの違いの本質から、方向別マイグレーション戦略、ストラングラーと二重書き込みとCDCによる漸進的移行、スキーマ進化の落とし穴、検証、補助ストア同期、一貫性のトレードオフまで扱います。
モデルの違いの本質 — 正規化 vs ドキュメント/KV
まず各モデルの考え方を整理します。
+----------------+------------------------+----------------------------+
| モデル | データ配置 | 最適ワークロード |
+----------------+------------------------+----------------------------+
| リレーショナル | 正規化、結合で合成 | 複雑なクエリ、トランザクション |
| ドキュメント | 非正規化、一つにまとめる | アグリゲート単位の読み書き |
| キーバリュー | キーで直接アクセス | キャッシュ、セッション、単純照会|
| ワイドカラム | パーティションキーで分散 | 大量書き込み、時系列 |
| 検索 (ES) | 転置インデックス | 全文検索、集計 |
+----------------+------------------------+----------------------------+
リレーショナルは「データを一度だけ保存し結合で組み合わせる」正規化の哲学に従います。一方ドキュメントは「一緒に読むデータは一緒に保存する」非正規化の哲学に従います。同じ「注文と注文明細」でも、RDBでは二つのテーブルに分けて結合しますが、MongoDBでは一つの注文ドキュメントの中に明細配列を埋め込むのが自然です。
この違いがマイグレーションのすべてを決めます。
方向別マイグレーション戦略
RDBからドキュメントへ
正規化されたテーブルをドキュメントへ移すとき、核心の問いは「埋め込むか、参照するか」です。
RDB:
orders(id, user_id, total)
order_items(id, order_id, product, qty)
MongoDB (埋め込み):
{
"_id": "...",
"user_id": "...",
"total": 50000,
"items": [
{ "product": "キーボード", "qty": 1 },
{ "product": "マウス", "qty": 2 }
]
}
埋め込みの基準は「一緒に読み、一緒に変わり、無限に大きくならない」です。注文明細は注文と一緒に読まれ、確定すればあまり変わらず、数が限られるので埋め込みが適します。逆に「あるユーザーの全注文」のように無限に増える関係は参照にするか別コレクションにします。
// 変換ロジック (擬似コード)
async function migrateOrder(order) {
const items = await pg.query(
"SELECT product, qty FROM order_items WHERE order_id = $1",
[order.id]
);
await mongo.collection("orders").insertOne({
user_id: order.user_id,
total: order.total,
items: items.rows,
});
}
ドキュメントからRDBへ
逆方向はより厄介です。スキーマレスのドキュメントにはフィールドがまちまちでありうるからです。あるドキュメントには phone があり、あるドキュメントにはない場合があります。RDBへ移すにはまず実際のスキーマを把握する必要があります。
1. スキーマ推論: すべてのドキュメントをスキャンしフィールドの存在率と型分布を集計
- phone: 73%存在、すべてstring
- tags: 41%存在、array of string
2. テーブル設計: 核心フィールドはカラムに、可変フィールドはJSONBカラムか別テーブル
3. 埋め込み配列 -> 子テーブルへ正規化
4. 欠落フィールドはNULL、型衝突は変換ルールを定義
PostgreSQLのJSONBカラムはこのマイグレーションの良い安全弁です。明確に正規化できるフィールドはカラムに抜き、曖昧またはまれなフィールドはJSONBに入れておけば、データ損失なく漸進的に正規化できます。
データモデルの再設計
マイグレーションはモデルを考え直す良い機会です。単に古い構造をそのまま移さず、新ストアの強みを生かす方向で再設計します。
- アクセスパターン優先: 「どのクエリが最も頻繁か」を先に決め、そのクエリが速くなるようモデルを組みます。ドキュメントDBは特にアクセスパターン中心の設計が重要です。
- 非正規化トレードオフ: 読み取り性能のためにデータを重複保存すると、書き込み時に複数箇所を更新する必要があります。読み取りが圧倒的に多いワークロードで有利です。
- アグリゲート境界: 一緒に変わり一緒に一貫性を保つべきデータを一つのアグリゲート(ドキュメント)にまとめます。ドメイン駆動設計のアグリゲート概念がそのまま適用されます。
漸進的移行 — ストラングラーパターン
大きなシステムを一度に作り直すビッグバンマイグレーションは危険です。代わりにストラングラーパターンを使います。新システムが古いシステムを少しずつ「絞め(strangle)」機能を引き継ぐ方式です。
[クライアント]
|
v
+-----------------+
| ルーティング層 | 機能別に新旧を分岐
+--------+--------+
| |
(旧機能) (移行済み機能)
| |
v v
[RDB] [MongoDB]
最初はすべてのリクエストが古いシステム(RDB)に行きますが、機能を一つずつ新システム(MongoDB)へ移しながらルーティングを変えます。ユーザープロフィールを先に移し、検証できたら注文を、その次に決済を移します。各ステップが小さいので問題が起きてもその機能だけ戻せばよいのです。
二重書き込みとCDC
漸進的移行の核心技術が二重書き込み(dual write)とCDC(Change Data Capture)です。
二重書き込み
移行期間中、アプリケーションが古いストアと新ストアの両方に書きます。
アプリケーション
|
+--> RDB (旧、依然として正解)
|
+--> MongoDB (新、検証中)
二重書き込みの落とし穴は二つの書き込みが原子的でないことです。RDBには書けたがMongoDBへの書き込みが失敗すると不整合が生じます。そこで二重書き込みは「検証段階」としてだけ使い、整合性は別途の照合作業で補強するか、より安全なCDCで置き換えるのが良いです。
CDC — 変更データキャプチャ
CDCはソースDBの変更ログ(PostgreSQLのWAL、MySQLのbinlog)を読み、変更をストリームとして流します。Debeziumが代表的です。
[RDB] --WAL--> [Debezium] --> [Kafka] --> [コンシューマ] --> [MongoDB]
ソース 変更キャプチャ ストリーム 変換 ターゲット
CDCの利点はアプリケーションコードに触れず、ソースのすべての変更を漏れなくターゲットに反映することです。初期スナップショットで既存データを移した後、CDCでその時点以降の変更を追いつけば、ソースを止めずにターゲットを同期できます。無停止マイグレーションの核心技法です。
1. スナップショット: ソースの現在データを丸ごとターゲットにコピー
2. CDC開始: スナップショット時点以降の変更をストリームで追いつく
3. 同期到達: ターゲットがソースをほぼリアルタイムで追従 (lag ~0)
4. カットオーバー: 書き込みをターゲットへ切り替え、ソースを廃棄
スキーマ進化 — スキーマレスの落とし穴
ドキュメントDBは「スキーマがない」とよく言われますが、正確には「スキーマがデータベースではなくアプリケーションコードにある」という意味です。スキーマは消えず、コードへ移るだけです。
その落とし穴は、時間が経つと同じコレクションの中に異なる形のドキュメントが混ざることです。
v1ドキュメント: { name, email }
v2ドキュメント: { name, email, phone }
v3ドキュメント: { fullName, email, phone } <- name -> fullName 変更
対応戦略は二つです。
- スキーマバージョンフィールド: 各ドキュメントに
schemaVersionを置き、読むときバージョンに応じて変換します(lazy migration)。読みながら漸進的に最新版へ更新します。 - 一括マイグレーションスクリプト: コレクション全体を巡回し古い形を新しい形へ変換します。大量ならバッチに分けて実行します。
スキーマレスだからと検証を諦めてはいけません。MongoDBのJSON Schema validationのように、DBレベルで最小限の制約をかけることが長期運用に役立ちます。
検証
異種マイグレーションの検証は単純な行数比較では不十分です。
- 行/ドキュメント数の一致: ソースとターゲットのレコード数が同じか確認します。最も基本ですが必須です。
- サンプル照合: ランダムにサンプルを抽出しフィールド単位で値を比較します。変換ロジックのバグを捕まえます。
- チェックサム/ハッシュ: 核心フィールドのハッシュを両側で計算して比較します。全数比較が難しいとき有用です。
- ビジネス不変条件: 「すべての注文の合計は明細合計と等しい」のようなドメインルールがターゲットでも成立するか検証します。
検証レベル:
L1 数量 : count(ソース) == count(ターゲット)
L2 サンプル : ランダム1%抽出後フィールド照合
L3 チェックサム: ソート後核心カラムのハッシュ比較
L4 不変条件 : ドメインルール(合計、参照整合性)を再検証
補助ストアの同期 — ElasticSearch、Redis
主ストアを移すと、そこから派生する補助ストア(検索インデックス、キャッシュ)も一緒に再構築する必要があります。
- ElasticSearch: 主ストアを真実の源(source of truth)とし、CDCやインデックスパイプラインで検索インデックスを再構築します。インデックスはいつでも主ストアから作り直せるので、マイグレーション後に丸ごと再インデックスするのがきれいです。
- Redis: キャッシュは一般に真実の源ではないので、マイグレーション後に空にして新しく満たします(cache warming)。キャッシュにしかないデータがあれば、先に主ストアへ永続化する必要があります。
[主ストアのマイグレーション完了]
|
+--> ElasticSearch全体再インデックス (主ストア基準)
|
+--> Redisを空にしてウォーミング (主ストア基準)
核心原則は「補助ストアは常に主ストアから再生成可能であるべき」です。そうすればマイグレーションが単純になります。
一貫性のトレードオフ
異種マイグレーションで最も難しい決定は一貫性レベルです。RDBの強い一貫性とACIDトランザクションに慣れたチームがドキュメント/分散ストアへ移ると、最終的一貫性を受け入れねばならない場合が多いです。
強い一貫性 (RDB) 最終的一貫性 (分散NoSQL)
---------------- -----------------------
書き込み即座に全員同じ値 しばらく後すべてのノードが収束
トランザクションで多行原子性 ドキュメント(アグリゲート)単位の原子性
結合で整合性を保証 アプリケーションが整合性を管理
スケーラビリティの限界 水平スケールが容易
転換時に点検する問いは次のとおりです。このデータに本当に強い一貫性が必要か。決済・在庫のように強い一貫性が必須な部分と、いいね数・閲覧数のように最終的一貫性で十分な部分を区別します。すべてに強い一貫性を要求するとNoSQLの利点を生かせず、すべてを最終的一貫性にするとお金が消えるバグが生じます。
カットオーバー戦略 — いつ、どう切り替えるか
マイグレーションの最も緊張する瞬間はカットオーバー、つまり真実の源を旧ストアから新ストアへ渡す瞬間です。カットオーバー方式はリスク度合いによっていくつかに分かれます。
+--------------------+------------------------------------------------+
| 方式 | 特徴 |
+--------------------+------------------------------------------------+
| ビッグバンカットオーバー| 一時点で全体切り替え。単純だがロールバックが困難 |
| 漸進(読み取り) | 読み取りを一部ずつ新ストアへ (カナリア) |
| 漸進(書き込み) | 書き込みを機能別に新ストアへ |
| シャドー読み取り | 新ストアを読むが結果は捨て旧と照合 |
+--------------------+------------------------------------------------+
最も安全なアプローチはシャドー読み取りから始めることです。新ストアからも読むが、ユーザーには旧ストアの結果を見せ、二つの結果をバックグラウンドで比較します。不一致が十分減ったら、そのとき読み取りを漸進的に新ストアへ渡します。書き込み切り替えは最後です。
1. シャドー読み取り: 新ストア読み取り結果を旧と照合 (ユーザーには旧を表示)
2. カナリア読み取り: トラフィック1% -> 10% -> 50% -> 100%を新ストアへ
3. 書き込み切り替え: 新ストアを真実の源に
4. 旧ストア廃棄: 一定期間読み取り専用バックアップで維持後に削除
各ステップの間に十分な観察期間を置くのが核心です。急がないことが最も速い道です。
継続的照合 — マイグレーション後も
カットオーバー後もしばらくは旧ストアと新ストアを並行運用し継続的に照合するのが安全です。二重書き込みを維持しつつ、定期的に二つのストアを比較するreconciliationジョブを回します。
[reconciliationジョブ (毎時)]
|
+-- 旧ストアと新ストアのレコードをキー基準で比較
|
+-- 不一致発見時: ログ + アラート + (自動) 新ストア修正
|
+-- 不一致率の推移をダッシュボードで観察
不一致率が0に収束し十分な期間安定したら、そのとき初めて旧ストアを廃棄します。この「重なる期間」がマイグレーションの安全を保証する保険です。旧ストアを早く消しすぎると、新ストアの隠れたバグが現れたとき戻る場所がありません。
マイグレーションツール — AWS DMSとCDCパイプライン
異種マイグレーションを手書きのスクリプトだけで行うのは困難です。専用ツールが役立ちます。
AWS DMS
AWS Database Migration Serviceは異種マイグレーションの代表的なマネージドツールです。ソースとターゲットの種類が違っても動作します(例: OracleからPostgreSQL、MySQLからDynamoDB)。核心概念は三つです。
+----------------+------------------------------------------------+
| 概念 | 役割 |
+----------------+------------------------------------------------+
| レプリケーション | マイグレーションを実際に実行するコンピュート |
| インスタンス | |
| エンドポイント | ソース/ターゲットDBの接続情報 |
| マイグレーションタスク | full load(全体コピー) + CDC(以降の変更追跡) |
+----------------+------------------------------------------------+
DMSのマイグレーションタスクは通常二段階で構成されます。full loadで既存データを丸ごと移した後、CDCでその時点以降の変更を追いつきます。異種の場合は型マッピング(ソースのNUMBERがターゲットの何になるか)を変換ルール(transformation rule)で明示する必要があります。
変換ルールの落とし穴
異種で最も厄介な部分が型とエンコーディングの変換です。
注意すべき変換:
- 数値精度: Oracle NUMBER(38) -> PostgreSQL numeric (精度を保存)
- 日付/タイムゾーン: タイムゾーン情報の喪失に注意 (timestamptzに統一)
- 文字エンコーディング: Latin-1 -> UTF-8変換時の文字化け
- NULL vs 空文字列: Oracleは''とNULLを同じに扱い、Postgresは異なる
- 大文字小文字: Oracle識別子は大文字デフォルト、Postgresは小文字デフォルト
これらの変換は自動ツールがほとんど処理しますが、エッジケースは必ず検証で捕まえる必要があります。特にNULLと空文字列の違いは静かにデータを壊す代表的な落とし穴です。
時系列・グラフなど特殊ストアへの移行
ドキュメント・検索以外にも、データ特性に合った特殊ストアへ移す場合があります。
- 時系列DB(TimescaleDB、InfluxDB): ログ・メトリクスのように時間軸が核心のデータを移すとき、時間ベースのパーティショニングとダウンサンプリングポリシーを一緒に設計します。古いデータは圧縮・ロールアップし、最近のデータだけ高解像度で残します。
- グラフDB(Neo4j): 関係が一級市民のデータ(ソーシャルグラフ、レコメンド)は、RDBの結合地獄からグラフへ移すとクエリが単純になります。マイグレーション時に外部キーをエッジに、行をノードに変換します。
RDBの関係 -> グラフ
users行 -> (:User) ノード
follows(a, b) -> (:User)-[:FOLLOWS]->(:User) エッジ
複雑なNホップ結合 -> 可変長パスクエリ (単純)
核心はデータの本質的な形に合うストアを選ぶことです。すべてをRDBに押し込まず、時系列は時系列DBへ、関係はグラフへ移せばそれぞれの強みを生かせます。
事例 — モノリシックRDBからドキュメント + 検索へ
仮想のEコマースチームの事例を整理します。
初期: PostgreSQL一つにすべて (ユーザー、商品、注文、検索はLIKEクエリ)
問題: 商品検索が遅い(LIKE %キーワード%)、商品カタログのスキーマが頻繁に変わる
(柔軟な属性が必要)、トラフィック増加で単一DBに負荷
戦略:
1. 商品カタログをMongoDBへ (柔軟な属性、ドキュメントモデルに適合)
2. 商品検索をElasticSearchへ (全文検索、ファセット)
3. 注文・決済はPostgreSQL維持 (強い一貫性が必要)
移行:
- CDC(Debezium)でPostgreSQL商品 -> MongoDB同期
- MongoDB -> ElasticSearchインデックスパイプライン
- ストラングラーで商品照会トラフィックを漸進的に転換
- 検証後カットオーバー、旧商品テーブルは読み取り専用で一定期間維持
核心はすべてを移さなかった点です。強い一貫性が必要な注文・決済はRDBに残し、柔軟性と検索が必要な部分だけを移しました。異種環境では「各データに合うストア」を選ぶポリグロット・パーシスタンスが正解の場合が多いです。
ポリグロットの運用負担
異種へ移すと各データに合うストアを使うポリグロット・パーシスタンスの利点を得ますが、運用負担も一緒に増えます。このトレードオフを率直に認める必要があります。
ポリグロットのコスト:
- 運用の複雑さ: DBごとにバックアップ・監視・チューニング・障害対応が異なる
- 一貫性の境界: ストア間のデータ同期/整合性管理が必要
- 認知負荷: チームが複数のデータモデルとクエリ言語を知る必要がある
- トランザクションの不在: ストアをまたぐ原子性がない
だから「移せる」と「移すべき」は違います。新ストアへ移して得る利益(性能、柔軟性、スケーラビリティ)が増える運用負担を明確に上回るときだけ移します。しばしば最良の答えは「PostgreSQLのJSONBで十分」か「今はRDBで耐え、本当のボトルネックが出たらそのとき移す」です。マイグレーションは手段であって目的ではありません。
判断基準を表に整理すると次のとおりです。
+---------------------------+--------------------------------------+
| こういうとき移す | こういうとき留まる |
+---------------------------+--------------------------------------+
| 現在のストアが明確なボトルネック| 性能の余裕がまだ十分 |
| データモデルが根本的に合わない | JSONBなどで吸収可能 |
| 新ストアの運用能力がある | チームが新ストアを運用した経験がない |
| 利益が運用負担を明確に上回る | 「最新技術だから」が唯一の理由 |
+---------------------------+--------------------------------------+
よくある落とし穴
- ビッグバンマイグレーション: 一度に全部移そうとしてロールバック不能に陥ります。ストラングラーで分割します。
- モデルをそのままコピー: RDBテーブルをドキュメントへ1:1コピーすると結合が必要な遅いドキュメントDBになります。アクセスパターン基準で再設計します。
- 二重書き込みの原子性の錯覚: 二つの書き込みは原子的ではありません。不整合補正メカニズムが必要です。
- 検証を行数だけで: 数量は合うが値が違う場合がよくあります。サンプル・チェックサム・不変条件まで検証します。
- 一貫性要求の過剰/過少: 部分ごとに必要な一貫性レベルを区別しないと性能を失うかデータが壊れます。
- 補助ストアの漏れ: 検索インデックスとキャッシュを忘れるとマイグレーション後に検索できないか古いデータがキャッシュに残ります。
チェックリスト
- アクセスパターンを先に定義しそれに合わせてモデルを再設計したか
- 埋め込み vs 参照の基準を明確にしたか
- ストラングラーで漸進的移行計画を立てたか
- CDCまたは二重書き込みで無停止同期経路を用意したか
- スキーマ進化戦略(バージョンフィールドまたは一括変換)を決めたか
- 数量・サンプル・チェックサム・不変条件の4段階検証を準備したか
- 補助ストア(検索・キャッシュ)の再構築計画があるか
- データごとに必要な一貫性レベルを区別したか
- カットオーバー失敗時のロールバック経路があるか
おわりに
異種データマイグレーションはデータを移す作業ではなく、データを再モデリングする作業です。リレーショナルの正規化思考からドキュメントのアグリゲート思考へ、強い一貫性から最終的一貫性への転換は、単なるツール交換ではなく設計哲学の転換です。ストラングラーで小さく分割し、CDCで無停止で移し、4段階で検証し、各データに合う一貫性を選ぶこと。この原則を守れば、異種マイグレーションは危険な賭けではなく制御された進化になります。
参考資料
- MongoDB Data Modelingドキュメント: https://www.mongodb.com/docs/manual/core/data-modeling-introduction/
- MongoDB Schema Validation: https://www.mongodb.com/docs/manual/core/schema-validation/
- Debezium (CDC): https://debezium.io/documentation/
- PostgreSQL論理レプリケーション(WAL): https://www.postgresql.org/docs/current/logical-replication.html
- Elasticsearch Reindex API: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html
- Redisドキュメント: https://redis.io/docs/latest/
- AWS DMSドキュメント: https://docs.aws.amazon.com/dms/
- TimescaleDBドキュメント: https://docs.timescale.com/
- Neo4jデータインポートドキュメント: https://neo4j.com/docs/getting-started/data-import/
- Strangler Figパターン(Martin Fowler): https://martinfowler.com/bliki/StranglerFigApplication.html