精読「モノリスからマイクロサービスへ (2)」
モノリスからマイクロサービスへ ―モノリスを進化させる実践移行ガイド
従来のモノリシックアーキテクチャを現代のビジネスニーズに適応させるための実践的な移行手法を網羅した一冊です。本書では、マイクロサービスの基本的な概念や利点だけでなく、移行プロジェクトで直面する課題とその解決方法を具体例を交えながら解説しています。
また、技術的なアプローチだけでなく、組織的・文化的な側面にも焦点を当て、チーム全体でのスムーズな移行を支援します。実務で活用できる戦略やツール、ステップバイステップのガイドを提供しているため、モノリスに課題を感じている開発者やアーキテクト、プロジェクトリーダーに最適です。
これからのシステム開発に必要なスキルと知識を身につけ、進化するシステムの未来を切り拓きたい方にぜひおすすめしたい一冊です。
関連記事
モノリスを分割する
3章では、モノリスからマイクロサービスへの段階的な移行方法が解説されています。重要なポイントは以下の通りです:
- モノリスの現実を受け入れる:ビッグバンリライトを避け、既存システムを活用しながら移行を進める。
- 段階的移行:移行は小さなステップで行い、プロセスから学びつつ調整する。
- 既存システムとの連携:モノリスとマイクロサービスが共存できるようにする。
段階的に進め、リスクを最小化しながら移行を進めることが推奨されている。
モノリスを変更すべきかどうか
モノリスの変更が可能かどうかを検討することが重要。以下のポイントが挙げられる。
- 変更可能なモノリス:コードを変更できる場合、さまざまな移行パターンを活用できるが、外部ベンダー製品や技術的制約がある場合は難しいこともある。
- 切り貼りか再実装か?:既存コードをそのまま移行するか、再実装するかはケースバイケース。移行初期はコードをコピーしてモノリス内に残すことで、選択肢を広げる。
- リファクタリング:既存のコードがビジネスドメインに沿って整理されていない場合、コードをドメイン駆動設計に基づいて整理する方法を考える。『レガシーコード改善ガイド』などのリファクタリング技術が有効。
- モジュラーモノリス:モノリスを複数のモジュールに分割し、マイクロサービスに移行する前に、モジュラーモノリスにすることでリスクを回避する。
- 段階的なリライト:機能を一度に再実装せず、少しずつリライトして提供するアプローチが推奨される。
移行パターン
マイクロサービスへの移行で使われるパターンとテクニックを紹介する。これらのパターンは一概に「良い」とは限らないため、それぞれの長所と短所を理解することが重要。
パターン:ストラングラーアプリケーション
この手法は、Martin Fowlerによって提案され、イチジクの木の成長過程に例えられている。イチジクが木の上の枝に種を蒔き、その根が下りていくように、新しいシステムが既存のシステムを包み込みながら成長し、最終的には古いシステムを置き換えるというもの。このパターンを使うことで、システムの段階的な移行が可能になり、途中で中断や完全な停止をすることもできる。
使い方
ストラングラーパターンは、モノリシックシステムからマイクロサービスへの移行に特に有効。新しい機能を新しいマイクロサービスとして実装し、その後、モノリスからの呼び出しを新しいマイクロサービスにリダイレクトする方法。この移行は段階的に行われ、各ステップでリスクを最小限に抑えることができる。
ストラングラーパターンの実装ステップ
-
移行する部分の特定
移行したいシステムの部分を特定し、最初に取り組むべき機能を選定します。 -
新しいマイクロサービスに実装
移行対象の機能を新しいマイクロサービスとして実装します。 -
モノリスから新しいサービスへのリダイレクト
モノリスからの呼び出しを新しいマイクロサービスにリダイレクトします。
このパターンの大きな特徴は、移行途中で本番環境に変更がデプロイされても、まだ新しい機能が実際に使用されていないため、リスクを最小限に保ちながら新しいシステムの構築が可能であること。
使いどころ
ストラングラーパターンは、既存システムに手を加えることなく新しい機能を移行できるため、特に他の開発者が作業しているモノリスや、サードパーティ製のソフトウェア、SaaSサービスといったブラックボックスシステムに適している。
-
単純な機能の移行
移行対象の機能がモノリスの外部に明確に存在する場合(たとえば在庫管理など)は、ストラングラーパターンを使うことで、比較的簡単に移行ができる。 -
複雑な機能の移行
移行対象の機能が他のモノリスの機能と深く統合されている場合(例えばユーザー通知など)、移行は難しくなるため、抽象化などの追加的なテクニックを使う必要がある場合がある。
HTTPリバースプロキシ
HTTPプロトコルには、リクエストの傍受やリダイレクトを透過的に行える機能があるため、HTTPインターフェイスを持つ既存のモノリスに対してストラングラーパターンを適用する場合、リバースプロキシを使うことで段階的な移行が可能。
-
プロキシの差し込み
最初に、既存のモノリスと上流システムの間にプロキシを挿入する。この段階では、プロキシはリクエストをそのまま通過させ、システムの影響を最小限に抑える。 -
機能の移行
次に、新しいマイクロサービスを実装し、移行した機能を新しいサービスとして稼働させる。この段階は複数のサブステップに分けて進めることが可能。
ストラングラーパターンを使うことで、既存のシステムに手を加えることなく、新しいシステムへの移行を段階的に行い、リスクを最小限に抑えつつ、スムーズに移行を進めることができる。
このセクションでは、ストラングラーパターンを活用して、モノリシックなシステムからマイクロサービスへの移行を進めるための方法に関するいくつかのアプローチが紹介されています。特に、通信プロトコルの変換やメッセージングを通じて、徐々に移行する方法に焦点を当てています。
例: FTPの移行
Homegate社の例では、FTPベースのファイルアップロードを、REST APIを使用した新しいシステムに移行する際、プロトコルを変更することなく、既存のFTPアップロードを傍受し、変換して新しいAPIに送信するアダプターを使った。これにより、顧客に影響を与えることなく、バックエンドでの変更が進められた。
メッセージングの傍受とリダイレクト
-
コンテンツベースのルーティング:
- メッセージブローカーを介して受け取ったメッセージを傍受し、特定のメッセージをフィルタリングして新しいサービスにリダイレクトする方法。この方法では、メッセージングの複雑さが増すことがあり、注意が必要。
-
選択的消費:
- モノリスと新しいサービスが同じキューを共有し、各サービスが関心のあるメッセージだけを処理する方法。これにより、サービス間で共有のキューを使いながらも、適切にメッセージをフィルタリングできる。
これらのアプローチは、サービスの移行を段階的に行うための有効な手段であり、既存システムに影響を与えずに新しいマイクロサービスに移行するために役立つ。しかし、プロキシ層やメッセージングの処理に関しては、注意深く管理し、過剰な複雑さを避けることが重要。
機能を移行しながら振る舞いを変える
システムをマイクロサービスに移行する際、機能変更がロールバックを難しくするリスクになる。移行中に新しい機能や修正を加えると、古いシステムとの整合性が取れなくなり、問題が再発する可能性があります。そのため、移行中は変更を控え、移行が完了するまで新しい機能や修正を遅らせるべき。
パターン:UI合成
これまで紹介したテクニックは、主にサーバー側の段階的移行をサポートしてきたが、ユーザーインターフェイスも部分的にモノリスや新しいマイクロサービスアーキテクチャから機能を統合する重要な手段となる。
ページ合成の例
ガーディアン紙のオンライン版移行では、バーティカルごとに移行を進め、最初に旅行領域の新しいページを公開。ユーザーは新しいページにアクセスする際のみ新しいデザインを体験。数年後のシステム変更時には、CDNを用いて新しいルーティングルールを実装。
オーストラリアREAグループも、商業用と住宅用物件のリスティングを別々に扱い、それぞれに異なるブランディングを採用してページベースで合成。
ウィジェット合成の例
旅行領域における最初の移行では、特定のウィジェット(旅行先トップ10)を新システムで作成し、ApacheのEdge-Side Includes(ESI)を用いて埋め込んだ。最近は、ブラウザ側で多くの合成が行われることが一般的。
Orbitzでは、検索フォームや地図などをウィジェットに分割し、マイクロサービス化する過程で、モジュールごとの移行を実施。
マイクロフロントエンドの例
Spotifyは、iOSやAndroidなどのアプリでUIをコンポーネント化し、サーバー側でビューを動的に変更できる技術を使用。これにより、アプリの新バージョンを提出せずにユーザーインターフェイスの更新が可能となり、迅速なフィーチャー展開が実現している。
パターン:抽象化によるブランチ
概要
「抽象化によるブランチ」パターンは、コードベースを変更する際に、他の開発者への影響を最小限に抑えつつ、段階的に機能の置き換えを進める方法です。このパターンでは、モノリスの内部でブランチを作成せず、抽象化を使って新しい実装と古い実装を共存させます。これにより、システムが変更される過程でのリスクを軽減し、最終的に新しい実装を切り替えることができます。
背景と課題
通知機能のような既存のシステム内で深く組み込まれている機能を新しいサービスに移行する場合、既存システムに大きな変更を加えなければならない可能性があります。変更を加えることで他の開発者に混乱を招くため、段階的な変更が求められます。しかし、ソースコードのブランチで作業を行うと、長期間ブランチが分岐してしまい、統合時に問題が発生することがあります。
解決策
「抽象化によるブランチ」は、コードベースに直接影響を与えることなく、抽象化を使って異なる実装を並行して使う方法です。これにより、段階的な変更を行いながら、最終的に新しい実装へ移行できます。ブランチの長期化を避け、他の開発者の作業にも配慮することができます。
使い方
このパターンは5つのステップで進める。
-
抽象を作成する
置き換える機能の抽象を作り、既存のコードから通知機能の呼び出しを抽象化する。これにより、通知メカニズム(例えば、電子メールやSMS)に依存しない形で、コードを変更する準備が整う。 -
既存クライアントの変更
抽象を作成した後、その抽象を使用するように既存のクライアントコードをリファクタリングする。ここでは、大きな影響を与えずにコードの変更を小さく行う。 -
新たな実装を作成
新しい通知機能(新しいマイクロサービスなど)を実装する。この新しい実装は、最初はまだ有効にされていないが、コードベースには新旧2つの実装が共存する。 -
実装を切り替える
新しい実装が正常に動作することを確認した後、抽象を切り替えて新しい実装を有効にする。この切り替えを行うために、フィーチャーフラグを使用することが一般的。 -
後始末
古い実装が使われなくなったら、それを削除する。さらに、不要になったフィーチャーフラグや抽象ポイントも削除して、コードをシンプルに保つ。
↓
↓
↓
抽象化によるブランチより
フォールバック機構として
新しい実装がうまく動作しない場合、古い実装に戻すことができる方法もある。これには、「抽象化によるブランチ」のバリエーションである「実行時検証」を使うことができる。この方法では、新しい実装が失敗した場合に古い実装に切り替える機能を追加することができるが、システムの複雑さが増すため、データの一貫性やシステム動作の理解が難しくなる可能性がある。
結論
「抽象化によるブランチ」は、既存のシステムの変更を最小限に抑えつつ、段階的に新しい機能へ移行するための強力なパターン。この方法を使うことで、システムの運用を続けながら、新しいマイクロサービスや機能の導入を進めることができる。
パターン:同時実行
このセクションは、マイクロサービスにおける「同時実行パターン」を中心に、新しい実装と既存の実装を比較しながらテストを行う手法について解説しています。
同時実行パターン
新しい実装と既存の実装を同時に実行し、その結果を比較することで、機能やパフォーマンスが期待通りに動作するかを確認する。新しいシステムが導入される際に、旧システムと並行して動作させ、結果やパフォーマンスの違いをモニタリングする。
例: クレジットデリバティブの価格比較
- 銀行が新しい計算プラットフォームを導入する際に、旧システムと新システムを並行して動作させ、毎日の結果を比較することで、不一致を発見し、どちらが正しいかを確認した。
- 結果: 最終的に新しいシステムが正しい結果を提供し、旧システムは引退した。
例: Homegate社のリスティング
- 新しいマイクロサービスと既存のモノリスを並行して実行し、顧客がリストのインポートを行う際、両方のシステムが正しく動作しているかを確認した。
検証テクニック
- 同時実行では、機能的な結果だけでなく、非機能的な側面(例: レイテンシー、タイムアウト)も検証する。
- 新しいマイクロサービスが適時に応答し、許容される失敗率で動作していることを確認する。
スパイを使う
- スパイ: 特定の機能が実行されたかどうかを検証するために使用されるテストパターン。例えば、通知機能が正しく動作したかどうかを確認するために使用する。
GitHub Scientist
- Scientistライブラリ: 同じ機能に対して複数の実装を並行して実行し、結果を比較するためのRubyライブラリ。これを使うことで、新しい実装が期待通りに動作するかを確認できる。
このパターンは、マイクロサービスのリリース時に、問題のリスクを減らし、移行プロセスをスムーズに進めるために非常に有効。
パターン:デコレーティングコラボレーター
この「デコレーティングコラボレーター」パターンは、モノリス内部の挙動を変更せずに、新しいマイクロサービスを追加する方法として有効。モノリス自体のコードを変更せずに、外部のサービスを呼び出すことができる。具体的には、プロキシを使ってモノリスの注文処理を傍受し、その結果に基づいてポイントサービスを呼び出す方法を取る。プロキシは、外部サービスの呼び出し結果を適切に顧客に返す役割を担う。
使用ケース
Music Corpの例では、顧客が注文する際にポイントが付与される仕組みを追加するために、注文処理のモノリスコードを変更せずにポイントサービスを呼び出す仕組みを実装している。このプロキシがポイントを付与するためには、注文価格などの詳細情報を外部のマイクロサービスから取得する必要がある。
チャレンジと課題
- プロキシがスマートすぎると、プロキシ自体がマイクロサービスとなり、呼び出しを増やすたびに複雑さが増す。
- モノリスから十分な情報を得られない場合は、モノリスを呼び出して追加のデータを抽出する必要があるかもしれない。これによりシステムの負荷や循環依存関係の問題が生じる可能性もある。
適用可能な場面
- モノリスから受け取るリクエストやレスポンスに必要な情報が含まれている場合、このパターンはシンプルでエレガントなアプローチ。
- もし、より多くの情報が必要な場合、実装が複雑になり、結合度が高くなってしまうので慎重に考える必要がある。
このパターンを適用する際は、必要な情報をモノリスからうまく取得できるかを確認し、過度な複雑化を避けるように設計を行うことが大切。
パターン:変更データキャプチャ
変更データキャプチャ (CDC) は、モノリスにおけるデータの変更を反応的に処理するための手法。このパターンの重要な特徴は、モノリスの呼び出しを傍受して動作させるのではなく、モノリス内部のデータストアで行われた変更を追跡して、それに基づいてアクションを起こす点。
例: ポイントカードの発行
例えば、顧客がサインアップした際にポイントカードを印刷する機能を追加したい場合、モノリスの登録処理を経て顧客データは登録されるが、ポイントカードの発行に必要な詳細な情報は、即時にモノリスが提供するものではない。従って、情報を追加で取得する必要がありますが、これをAPI経由で行うのは複雑。
その代替策として、変更データキャプチャを使う方法が考えられる。顧客が登録されると、ポイント会員テーブルに対する挿入イベントがトリガーされ、そこで新しいポイントカード印刷サービスを呼び出す仕組み。これにより、ポイント会員が追加されるタイミングでポイントカードの印刷がトリガーされるようになる。
変更データキャプチャを実装する方法
CDCの実装方法にはいくつかのアプローチがあり、それぞれに異なるトレードオフがある。
-
データベーストリガー: 多くのリレーショナルデータベースでは、特定のデータ変更時にカスタムアクションを発火させるデータベーストリガーをサポートしている。この方法は非常にシンプルであり、追加のインフラや新技術を導入せずに済むというメリットがある。しかし、トリガーが過度に増えると、システムの可視性が低下し、メンテナンスが難しくなるというデメリットもある。
-
トランザクションログ監視: より洗練された方法として、トランザクションログを監視し、そのログから変更内容を抽出して別の処理に繋げる手法がある。この方法は、システム外で動作するツールを使用してトランザクションログを監視するため、データベース本体の負荷が軽減され、実行効率も良好。
-
バッチによる差分コピー: もっとシンプルで手間が少ない方法として、定期的にデータベースをスキャンして変更されたデータをコピーする方法がある。しかし、この方法ではリアルタイム性に欠ける可能性があり、変更のタイミングを正確に反映するのが難しいことがある。
使いどころ
変更データキャプチャは、特にデータを複製する必要がある場合に有効。また、モノリスからマイクロサービスに移行する際、システムの境界でイベントを傍受できない場合などに役立つ。しかし、実装には注意が必要であり、トリガーの過剰使用や、トランザクションログに依存するツールがシステムに過剰な複雑さをもたらす可能性もある。
この章のまとめ
この章では、既存のモノリシックなコードベースを段階的にマイクロサービスに移行するための多様なアプローチやテクニックが紹介された。これらのテクニックは、単独で使用することは少なく、通常は複数の手法を組み合わせて適用することになる。重要なのは、どの方法が最適かを状況に応じて見極め、選択すること。
データベースを分割する
データベースの分割は、マイクロサービス移行の鍵だが、多くの課題を伴う。本章では、データ同期やスキーマ分解などの問題を整理し、解決のためのパターンを探る。まずは、共有データベースの課題とその対処法を見ていく。
パターン:共有データベース
1つのデータベースを複数サービスで共有することは、情報隠蔽の欠如やビジネスロジックの分散など、多くの問題を引き起こす。結果として、スキーマの安全な変更範囲が不明確になり、一貫性の確保が難しくなる。また、データの管理責任が曖昧になり、状態マシンの適切な実装が妨げられる場合もある。
対処パターン
- 各マイクロサービスに独自のデータベースを持たせる
- データベースビューを利用する
- データベースをラップするサービスを導入する
適用場面
共有データベースが有効な場合は以下の通りです:
-
読み取り専用の静的参照データ
例:通貨コードや郵便番号テーブルのような安定したスキーマ。 -
定義済みエンドポイントとしてのデータベース公開
複数のサービスが利用するために設計されたインターフェイスを持つデータベース。
これらのケース以外では、データベースの分割が推奨される。
しかし、できない!
理想は新しいサービスごとに独立したスキーマを持たせることですが、既存のモノリシックなシステムから始める場合、すぐにそれを実現するのは難しい場合があります。以下に対応策を整理しました。
理解しておくべきこと
-
スキーマとデータベースの違い
- スキーマ:論理的に分離されたテーブルの集合
- データベース:スキーマをホストするエンジン
図4-2のように、1つのデータベースエンジンで複数のスキーマをホストできる。
(この章では「データベース」という言葉を「論理的に分離されたスキーマ」として扱う)
-
NoSQLの特性
DynamoDBのようにスキーマが明示されないデータベースでは、ロールベースのアクセス制御などの手段で適切な分離を考える必要がある。
対処方法
- すぐにスキーマを分割できない場合、以下のようなパターンを活用する
- データベースビューを利用する
- データベースをラップするサービスを設ける
- 必要に応じてスキーマの分割を段階的に進める
困難への対応
-
課題に正面から向き合う
すぐに解決できない問題であっても、チームと共有し、可能な範囲で対策を始めることが重要。最初から完璧を目指す必要はない。 -
成長と改善
時間が経つにつれ、スキルや経験が増すことで、解決が難しいと感じていた問題にも取り組みやすくなる。
「問題を認識し、前進を続けること」が重要。
パターン:データベースビュー
ビューを活用することで、複数のサービスが単一のデータソースにアクセスする際の懸念(データスキーマ変更、アクセス制御など)を緩和できる。
パブリックな契約としてのデータベース
-
背景:
投資銀行向けシステムでデータベーススループットの向上を目指したが、複数の外部システムが同一認証情報で直接アクセスしており、どのアプリケーションがどのデータを使っているか不明。 -
問題:
- スキーマ変更が難しく、データベース構造が事実上「パブリック契約」になっていた。
- 外部システムの大半がアクティブなメンテナンスを受けていない状態。
-
教訓:
各アクターに独立した認証情報を与えることで、アクセス制御や問題特定が容易になる。
提示用のビュー
-
ソリューション:
古いスキーマのように見えるビューを提供し、クライアント側での影響を最小限にしつつ基盤スキーマを自由に変更可能に。 -
例:
ポイントサービスの例では、顧客テーブルの情報を制限し、顧客IDとポイント会員IDのマッピングだけを公開するビューを作成。
制限事項
-
ビューの制限:
- 読み取り専用である場合が多く、更新操作には不向き。
- 全てのデータベースがビューをサポートしているわけではない。
-
技術的な制約:
ソーススキーマとビューが同じデータベースエンジンに存在する必要があり、デプロイ時に結合度が高くなる。
所有権
ビューの更新責任は、基礎スキーマを管理するチームが負うべき。ビューは他のサービスインターフェイスと同等に扱い、継続的なメンテナンスを確実に行う。
使いどころ
- 適用シナリオ: モノリシックなスキーマの分解が現実的でない場合に有効。
- 理想: 可能であれば、ビューに依存せず適切なスキーマ分解を進めるべき。
重要なポイント
- ビューのメリット: データ共有の柔軟性を高めつつ、基礎スキーマの独立性を保つ。
- トレードオフ: パフォーマンスや管理の複雑さ、ビューが長期的に維持される必要性。
このパターンは、既存のモノリシックスキーマを保ちながら、徐々にマイクロサービス化へ移行するための現実的なステップとして有効。しかし、将来的にはビューを減らし、独立したサービス間のインターフェイスを構築することが望ましい。
パターン:データベースをラップするサービス
このパターンは、データベースの複雑さをラッピングサービスの背後に隠すことで、データベースへの依存を軽減し、他のアプリケーションが独立して動作できるようにするもの。
背景
- 課題の発端: 大規模システムでデータベースに権限ロジックを集中的に実装した結果、負荷が限界に達していた。将来的に性能やスケール面で問題が悪化する懸念があった。
- 解決の初手: 既存のスキーマに変更を加えることを避け、他のチームが権限スキーマに依存し続けるのを防ぐための計画が必要だった。
解決方法
-
ラッピングサービスの導入:
- データベースへのアクセスをサービス経由に限定し、APIを通じて利用者に安定したインターフェースを提供。
- 新しいデータや振る舞いが既存のスキーマに追加されることを防止。
-
段階的な改善:
- 短期的に問題を悪化させない環境を整え、時間をかけてスキーマの問題を解消する。
- 新しい権限サービスを導入し、データベースの負荷を軽減。
パターンの利点
- 依存関係の移動: データベースの依存をサービスAPIに移し、データアクセスの責任範囲を明確化。
- 段階的改善: 基盤の変更が難しい場合でも、API層を設けることで、後にスキーマを分解する時間を確保。
- 柔軟性: データベースビューよりも自由にデータの投影や書き込みが可能。
- テスト容易性: テストやスタブの実装が容易になる。
適用例
- データベース構造を直接変更するのが難しい場合。
- サービスとスキーマの所有権を同一のチームが持つ場合。
- 上流コンシューマーにとって、データベースの変更による影響を最小化する必要がある場合。
注意点
- コンシューマーがDBアクセスからAPI呼び出しに移行する際、一定の変更が求められる。
- 問題の本質的な解決ではなく、段階的改善の手段であることを理解する必要がある。
このパターンは、技術的負債を抱えた状況でも、段階的にシステム改善を進める上で有用なアプローチといえる。
モノリス分解パターンより
パターン:サービスのインターフェースとしてのデータベース
クライアントが読み取り専用データベースにアクセスできるようにする。ユースケースとしては、大量データの参照やアドホッククエリの実行、外部ツール(例: Tableau)の利用がある。サービス内部のデータベースと外部公開用データベースを分離することで、セキュリティや柔軟性を確保する。
実現手法
-
マッピングエンジン:
- 内部データベースと外部公開データベースの間で抽象化レイヤーとして機能。
- データ変更を検出し、外部データベースに反映する。
- 遅延が発生する可能性があるため、クライアントにはデータの最終更新日時を提供するのが推奨される。
-
変更データキャプチャ (CDC):
- DebeziumのようなCDCツールを使用することで、データ変更をリアルタイムに外部データベースへ反映可能。
- バッチ処理に比べ、最新データの提供に優れる。
-
技術選択の柔軟性:
- 内部データベースと外部データベースは異なる技術スタックを選択可能(例: Cassandra → SQL)。
比較
-
データベースビューとの違い:
- データベースビュー: 特定の技術スタックに依存。
- 本パターン: 技術選択の自由度が高い反面、管理コストが増加。
適用シナリオ
適している場合:
- 大量の読み取り専用データが必要なケース(例: レポート生成)。
- 外部ツールやデータウェアハウスへのデータ統合。
注意点:
外部データベースを最新状態に保つための運用コストや実装の複雑さを過小評価しないこと。
このパターンは、レポーティングやアドホッククエリ対応において特に有効だが、設計と運用のバランスを考慮することが重要。シンプルな要件にはデータベースビューから始め、必要に応じて進化させる方法も検討する価値がある。
所有権を移す
集約を公開するモノリス
- モノリスがAPIやイベントを介して必要なデータを公開。
- モノリスが引き続きビジネスロジックと状態遷移を所有。
- 例: 従業員データをモノリスから公開し、請求書サービスがアクセス。
データ所有権の変更
- データをモノリスから新しいサービスへ移行し、新サービスが管理。
- モノリスも新サービスのAPI経由でデータを利用。
- 例: 請求書データを請求書サービスへ移行。
注意点
- API経由のデータ共有は直接アクセスより長期的に有益。
- データ移行には外部キーやトランザクションの解消が必要。
モノリス分解パターンより
データ同期
ストラングラーパターンを使って新しいサービスに移行する場合、モノリスと新サービス間でデータ同期の問題が生じる可能性がある。データの一貫性を保つには、以下の選択肢が考えられる。
-
モノリスからの直接データ読み取り
新サービスがモノリスのデータを直接参照する方法。一時的に共有データベースを使用するが、長期利用は避けるべき。 -
バッチ処理でデータをコピー
データを新サービスに移行後、トラフィックを流す。ただし、モノリスの機能使用時にデータ整合性が失われる懸念あり。 -
両方のデータベースを同期
モノリスまたは新サービスが両方のデータベースを更新する方法。ただし、慎重な設計が必要。
データ同期の方法は、移行のスムーズさと長期的な運用の負担を考慮して選ぶ必要がある。
パターン:アプリケーションでのデータ同期
データ移行は複雑であり、特に高価値のデータを扱う場合、その重要性が増す。デンマーク市民の診療記録を統合するプロジェクトでは、MySQLからRiakへの移行を通じて、スケーラビリティと回復性を向上させた。以下のステップで移行が行われた。
-
ステップ1:データを一括で同期する
バッチインポートで既存データを新しいデータベース(Riak)にコピー。ただし、データのスナップショットを使用したため、移行後の同期処理が必要だった。 -
ステップ2:同期のために書き込み、古いスキーマから読み取る
アプリケーションを更新して、データをMySQLとRiakの両方に書き込みながら、読み取りはMySQLのみで行った。この段階でRiakの動作検証が行われた。 -
ステップ3:同期のために書き込み、新しいスキーマから読み取る
Riakを真の情報源として使用開始。同時にMySQLへの書き込みも維持し、問題が発生した場合にフォールバック可能な状態を確保した。 -
安全な移行完了
新しいシステムが安定した後、古いデータベース(MySQL)を削除。
このパターンの活用場面
- モノリシックシステムのスキーマ分割前のデータ同期。
- マイクロサービス移行時のデータ同期を保証しつつ、迅速なロールバックが必要な場合。
注意点
- 同期パターンは、モノリスとマイクロサービスが同じデータを共有する状況では複雑さが増す。同期エラーがシステム全体の問題を引き起こす可能性があるため、利用には慎重な検討が必要。
パターン:トレーサー書き込み
トレーサー書き込みパターンの核心は、データの移行を段階的に行うことであり、複数の「真の情報源」を一時的に存在させながら、システムをスムーズに移行させること。これにより、システムの切り替えにおけるリスクや影響を最小限に抑えることができる。このパターンを実装することで、モノリスから新しいマイクロサービスへのデータ移行が円滑に進み、システム全体の一貫性が保たれる。
具体的な実装方法としては、まず新しいサービスが提供するデータインターフェースを活用し、既存システムが引き続きデータを記録しながら、新しいサービスにもデータを書き込む仕組みを整える。最終的には、全てのコンシューマーが新しいサービスを使用するようになることを目指す。
データ同期の方法としては、以下の選択肢がある
- 一方のソースに書き込む:書き込み後、他方の真の情報源に同期。
- 両方のソースに書き込む:書き込みを両方の情報源に行う。
- どちらかのソースに書き込む:システム間で双方向にデータを同期。
データの同期が行われる間、必ずしも完全な整合性が保たれていない可能性があるため、同期が適切に機能しているかを常に監視し、問題が発生しないように調整を行うことが重要。
データベースを分割する
データベースの分割方法について、特に論理的分割と物理的分割の違いに焦点を当てる。
-
論理的分割は、異なるサービスが同じデータベースエンジン内で異なるスキーマを使用する方法。この方法は、変更を独立して行いやすく、データの隠蔽を助けます。
-
物理的分割は、各サービスが異なるデータベースエンジンを使用する方法で、システムの堅牢性向上やリソース競合を排除し、スループットやレイテンシーを改善できる。しかし、物理的分割にはコストや管理の負担が増す可能性があり、単一障害点を回避するための高耐障害性クラスタを構築するには、多大な努力やコストがかかる。
さらに、データベースビューを公開する場合には、複数のスキーマが同じデータベースエンジン上にある必要がある場合もある。このように、データベース分割の選択肢は、システムの目的やコスト、運用上の制約によって慎重に決める必要がある。
この内容を踏まえて、データベースの分割をどう進めるか、実際のプロジェクトにどのように適用するかを考慮することが重要。
データベースとコードのどちらを最初に分割するか
このセクションでは、マイクロサービスにおけるデータベースとコードの分割の順番についての選択肢とそれぞれの利点・欠点が議論されています。要点は以下の通り。
- データベースを最初に分割する
-
利点:
- データベースが分割されることで、パフォーマンスやトランザクション整合性の問題を早期に発見できる可能性がある。
- 初期段階で問題が明確になり、長期的な見返りが期待できる。
- データベースのスキーマ分割を進めることで、後々のコード分割がスムーズになる。
-
欠点:
- 短期的な利益は得られにくい。モノリスのコードはまだデプロイされており、データベース分割だけでは解決できない問題が残る。
- 分割後のパフォーマンスやデータ一貫性問題に対処するため、さらなる調整が必要になることが多い。
-
使用時の注意:
- データベースの変更は、コード変更に比べて難易度が高いため、適切なツール(例: FlywayDB)を使用することが推奨されている。
- コードを最初に分割する
-
利点:
- コードの分割により、新しいサービスが必要とするデータが明確になり、コードベースの分割がしやすくなる。
- より短期的に効果を感じやすく、データベース分割の実行前に実際に分割後のシステムを動かして確認できる。
-
欠点:
- 最終的にデータベース分割が遅れる可能性があるため、システム全体の結合度が高くなりやすい。
- 初期段階でコードとデータベースの整合性が保たれないことがある。
- 両方を一度に分割する
-
利点:
- より包括的なアプローチとして、最初から完全に分離されたサービス構造を目指せる。
-
欠点:
- より複雑でリスクが高く、一度に多くの要素を変更するため、問題が発生した場合に調整が難しい。
選択肢の使い分け
- データベースを最初に分割するのは、特にパフォーマンスやトランザクション整合性に強い懸念がある場合や、将来のスムーズなサービス分割を見越した場合に有効。
- コードを最初に分割するのは、短期的に新しいサービスを動かしてその結果を確認したい場合に適している。
- 両方を一度に分割するのは、特に大規模な移行計画であり、リスクを取って一度に全てを解決したい場合に適用できる。
これらの選択肢をチームやプロジェクトの状況に応じて柔軟に選ぶことが重要。
スキーマ分割の例
リレーショナルデータベースとNoSQLデータベースにおけるスキーマ分割について。
リレーショナルデータベースはテーブル間の関係性が強いため、スキーマ分割には整合性の維持が求められる。一方、NoSQLは柔軟性が高く、スキーマ変更に制約は少ないが、データの一貫性やクエリ効率に課題がある。スキーマ分割はスケーラビリティ向上に役立ちますが、システム全体への影響を慎重に考慮する必要がある。
パターン:テーブルの分割
複数のサービス境界をまたぐテーブルを分割するパターンを説明している。商品テーブルをカタログと倉庫の2つに分ける例が挙げられ、最終的には別々のデータベースに移動する。また、顧客テーブルの「ステータス」列の更新が複数のサービスにまたがる場合、どのサービスがデータを所有するかを判断する必要がある。分割後、データの一貫性が失われる可能性があるため、慎重に進める必要がある。
パターン:外部キーのコードへの移動
モノリスでカタログ情報はアルバムテーブルに保存され、元帳テーブルで参照されている。カタログ機能と経理機能を分割した場合、経理サービスがアルバム情報を取得できなくなる。代わりに、経理サービスはSKUリストを元帳テーブルから取得し、カタログサービスに問い合わせてアルバム情報を取得する。これにより、レポート作成時に遅延が増加する可能性がある。
また、異なるサービスでデータを管理すると、不整合が生じる可能性がある。削除時には、レコードの参照がないかを確認する、削除を許可しない、あるいは通知で処理する方法がある。
トランザクション
データベースの分割で重要な問題の一つは、トランザクションの管理。通常、ACIDトランザクションは、データの整合性と一貫性を保証するが、複数のデータベース間での操作では、原子性が保てなくなる。この問題に対処する方法の一つとして、2フェーズコミット(2PC)がある。これは、複数のデータベースで変更を一貫して行うための方法だが、ロックの調整や、通信遅延による不整合など、いくつかの課題を伴う。
サーガ
サーガは、長時間のトランザクションを複数の独立した短いトランザクションに分割し、リソースのロックを避けながら、マイクロサービスアーキテクチャで調整を行う手法。ACID原則の「原子性」には欠けるが、各トランザクションは独立して原子性を持つ。障害時には後方回復(ロールバック)や前方回復(再試行)を用い、補償トランザクションでロールバックする。サーガは2つの実装スタイル(オーケストレーションとコレオグラフィ)で構築できる。
この章のまとめ
この章では、システムの分解においてサービスの境界を見つけることの重要性と、そのプロセスが段階的であることを強調している。サービス分割に伴う課題があるものの、これを恐れる必要はなく、段階的に進めることで柔軟に対応できるとしている。次の章では、モノリスの分解時に直面するさまざまな問題と、それに対処するためのアイデアが紹介されることが予告されている。
Discussion