「モノリスからマイクロサービスへ」サマリのサマリ
「モノリスからマイクロサービスへ」サマリ をさらにサマライズしたもの
絵をテキストアートからちょっと整えて、文書もさらに絞った
いつでも参照できる自分のためのちょっと細かく書いてある辞書として使う
絵の色は基本的にこう
- 緑:移行先とか作成・変更するもの
- 赤:移行元とか問題のあるもの
- 灰:移行中に必要になるもの
イメージ的には GitHub の差分カラー
画像がちょっと大きくて見通しが悪いけど、50 枚数直すのも大変だし、困ったらcmd -
して倍率変えて見るか...
これはあくまで章にアタリをつけたり一度読んだことを思い出したり、とにかく本を読むためのまとめ
h1 と h2 を抽出
-
1. 必要十分なマイクロサービス
- デプロイ
- 凝集度
- 判断
- モノリス
- 結合
-
2. 移行を計画する
- 選択する理由
- ふさわしくないケース
- 定期的にチェックする
-
3. モノリスを分解する
- パターン: ストラングラーアプリケーション
- パターン: 抽象化によるブランチ
- パターン: 同時実行
- パターン: デコレーティングコレボレーター
- パターン: 変更データキャプチャ
-
4. データベースを分割する
- いくつかのパターン
- パターン: 共有データベース
- パターン: データベースビュー
- パターン: データベースをラップするサービス
- パターン: サービスのインターフェイスとしてのデータベース
- 所有権
- パターン: 集約を公開するモノリス
- パターン: データ所有権の変更
- データ同期
- パターン: アプリケーションでのデータ同期
- パターン: トレーサー書き込み
- データベースを分割する
- 分離する順番
- パターン: テーブルの分割
- パターン: 外部キーのコードへの移動
- 共有静的データ
- トランザクション
- ACID おさらい
- 2フェーズコミット
- サーガ
- いくつかのパターン
-
5. 成長の痛み
- パターン: 所有権のスケール
- パターン: 破壊的変更
- パターン: レポーティング
- パターン: 監視とトラブルシューティング
- パターン: 開発者体験
- パターン: あまりにも多くのものを実行している
- パターン エンドツーエンドテスト
- パターン: 全体最適と局所最適
- パターン: 堅牢性と回復性
- パターン: 孤児サービス
- 6. おわりに
1. 必要十分なマイクロサービス
概要
デプロイ
大事なのは独立してデプロイできること
- そうするとそれぞれの領域に集中できる
- 開発プロセスや技術が他に依存しなくなる
- データベースはサービス境界の内側に隠蔽しよう
マイクロサービスの実現は特定の技術に依存しない
ただネットワークで繋がれば良い
凝集度
「システムを設計する組織は、その構造をそっくりまねた構造の設計を生み出してしまう。」
コンウェイの法則
技術やレイヤーで凝集度を高くするやり方が多かったけど、ビジネスで凝集度を高めるやり方に変化していこう
判断
Kubernetes, Docker, AWS 等々は必須ではない
特に新しい技術はコストが高いので、釣り合うかよく判断しよう
そもそもマイクロサービスにするかも、問題がちゃんと解決できるか考えて推進しよう
モノリス
一緒にデプロイする必要がある範囲のこと
さらに以下に分類する
単一プロセスのモノリス
- 1 モジュールの、よくイメージする形
- n モジュール 1 DB のモジュラーモノリスという形も
分散モノリス
- n サービスで構成されるがデプロイは一括で行う必要がある形
- 情報隠蔽とビジネスでの凝集ができてないと陥りやすい
サードパーティ製のブラックボックスシステム
- 自分たちではコードを変更できないモジュール
モノリスは悪い単語ではない
以下の様な良い点もあり、今でも有効な選択肢である
- デプロイがシンプル
- 監視しやすい
- エラー対応がしやすい
- 結合テストがしやすい
- コードの再利用がしやすい
結合
いくつか結合のされかたがある
実装結合
データベースを共有している
API 経由に切り替えれば改善可能
取り決めたインターフェースを壊さなければ、レコメンドに断らずに注文テーブルを変更・デプロイできる
一時的結合
分散環境での同期呼び出しをしている間は、一時的に結合している
キャッシュや非同期処理で改善可能
ドメイン結合
他のサービスへ全ての情報を共有している
必要な情報を整理して別のドメインとして再定義すると改善する
2. 移行を計画する
マイクロサービスは目的ではない
選択する理由
- ⏱️ デプロイ衝突がないと、すぐリリースできる
- 💰 スケーリングが適切な範囲だけできる
- ✅ システム全体がダウンしないので、堅牢になる
- 🖥️ チームが小さく権限も十分だと、自立性が高まる
- 🖥️ 新しい技術を局所的に試しやすい
💡 トレードオフスライダーとかをちゃんと定めておきたい
「ある範囲だけスケーリングしたいのでサービスを切り出したい」
「機能追加 > コスト だから今は取り急ぎ広範囲をスケーリングしちゃおう」
みたいな
ふさわしくないケース
- ドメインが不明瞭
- ビジョンが変わる可能性がある模索段階
- 顧客にインストールさせる
定期的にチェックする
- 導入の目的
- 進捗
- 定量的に
- 定性的に
3. モノリスを分解する
モノリスから一部サービスを切り出す方法について
パターン: ストラングラーアプリケーション
そっくり別の場所に移して通信経路を変える
- 👍 既存システムを変更しなくて良い
- 👍 プロキシ次第でいろいろな調整ができる
- 🤔 プロキシを作り込みすぎると独立デプロイしづらくなる
- 👎 移行と変更が同時に発生すると辛い
パターン: 抽象化によるブランチ
移行したいものがモノリスの内側にある場合
- 👍 段階を踏めるポイントが多い
- 抽象を使う
- 実装を切り替える
- 削除する
- 💡 フィーチャーフラグとかを使って上手に切り替える
パターン: 同時実行
新旧両方を呼び出しつつ比較を行う
- 👍 非機能要件も比較検証しやすい
- 💡 移行対象が通知の様な場合は、片方をモックにする
パターン: デコレーティングコレボレーター
モノリスを変更できないとき
- 👍 モノリスを変更せずにすむ
- 🤔 この機能自体を複雑にしない
- 👎 3 の応答に情報が足りない場合、新機能からモノリスに問い合わせるかモノリスの変更が必要
パターン: 変更データキャプチャ
リクエストではなくデータベースの変更を傍受する
- 👍 レスポンスが不足していてデコレーティングコラボレーターが使えない場合に役に立つ
- 💡 実現方法はいくつか考えられる
- データベーストリガー
- トランザクションログ監視
- 差分コピー
4. データベースを分割する
特に困難なデータベースをどうするか
いくつかのパターン
パターン: 共有データベース
複数のサービスがデータベースを共用している
- 👎 データの管理者が不明瞭である
- 💡 役に立つのは静的なマスターテーブルくらい
パターン: データベースビュー
データベースではなくビューを公開する
- 👍 既存のシステムは変更が不要
- 👍 ビューを壊さない限り、データベースの変更が可能
パターン: データベースをラップするサービス
ビューではなくサービスを公開する
- 👍 既存のシステムが変更できる場合、サービスは洗礼された応答を返すことができる
- 👍 ビューとは異なりサービスを経由して書き込むことも可能
- 💡 サービスが隠蔽したデータベースを改善する足がかりになる
パターン: サービスのインターフェイスとしてのデータベース
共有データベースの適切な形の一つ
- 👍 大量データの参照や SQL エンドポイントを必要とするツールに用いる
- 💡 極論データベースエンジンすら分けることが可能だが、性能やコストに留意
- 👎 なんらかのマッピングの実装が必要
所有権
隠蔽の次に所有権を移す
パターン: 集約を公開するモノリス
公開される状態変更ルールの所有権を移す
- 👍 単なるデータベースアクセスのラッパーからロジックを保持したサービスに繰り上がる
- 💡 公開しているインターフェースからサービス境界を見つけやすい
パターン: データ所有権の変更
テーブルの所有権を移す
- 👍 モノリスの API 経由でデータベースを使い続けるのと違い、データの所有もサービスで行う
データ同期
実際に分離する際のアプローチ
例えばストラングラーパターンの利点に切り戻しが容易なことが挙げられるが、そのためにはデータを同期していないといけない
パターン: アプリケーションでのデータ同期
データベースエンジンの検証や切り戻しに備える
- 👍 機能と非機能の検証がしやすく、切り戻しも行いやすい
- 💡 サービス停止を回避できる
- 🤔 別の段階移行や別のサービスと読み書きが複雑に関係する場合は、困難なので避けた方が良い
パターン: トレーサー書き込み
真の情報源を段階的に移行する
データベースを分割する
データベースにも接合部を見つけると良い
物理的なデータベースに論理的な複数のスキーマが構成される
ここでは論理的な分離を考える
これが進めば次に物理的な分離を行いやすい
論理的な分離は独立性を高め情報の隠蔽を容易にする
物理的な分離は堅牢性を向上させリソース競合を排除する
分離する順番
データベース → コード
- 👍 スキーマの分離に問題が出た場合に利用者に影響を与えずにサービスで微調整しやすい
- 👎 スキーマが分かれていると select 一発で参照できなくなり呼び出し回数が増えやすい
- 👎 トランザクション整合性を保つのが難しい
- 👎 モノリスの単一デプロイが改善されないので、短期的メリットはあまりない
特にパフォーマンスやデータの一貫性を懸念している場合に用いる
コード → データベース
- 👍 アプリケーションから分離することで、必要なデータを理解しやすい
- 👍 独立デプロイの恩恵が早く受けられる
- 👎 当然共有データベースの課題は最後まで解決しない
今後の機能追加で用意するデータを扱う場合、アプリケーションが分離されているとうまくはまる
新しいサービスはモノリスの既存スキーマと自分の新スキーマを両方使えば良いからだ
同時
- 👎 とても大変であり、また決断してから結果が出るまでに時間がかかる
行うべきではない
パターン: テーブルの分割
ひとつのテーブルのデータをサービス境界をまたいで分離する
- 💡 データを扱うのにふさわしいドメインを検討して分離する
パターン: 外部キーのコードへの移動
外部キー制約が定義されたテーブルを異なるスキーマに分離する
- 💡 性能が劣化するのでよく計測する
データの一貫性について
外部キー制約がなくなったので不整合が発生する可能性がある
アルバムID を削除したい場合にどうするべきか
削除前にチェックする
経理にアルバムを消して良いか都度確認する
- 👎 これでは実施の逆依存ができている
- 👎 削除中に経理でそのアルバムが必要になる可能性があり、ロック等を考慮する必要がある
- 👎 他にアルバムを参照するサービスが増えるほど困難になる
検討するべきではない
円滑に削除する
消したことに備える
- 💡 経理は問い合わせに失敗したら「不明なアルバム」と表示する様に備える
- 👍 カタログは 404 ではなく 401 を用いるとなお良い
- 💡 削除イベントをサブスクライブするという方法もある
- 👍 経理が削除に気づけるのでキャッシュをしたりできる
削除しない
矛盾を許さない
- 👍 不整合が発生しない
- 💡 削除しない、もしくは論理削除する、いずれにせよ経理から参照できること
- 🤔 販売してない古いアルバムを維持し続けるため、アプリケーションが複雑になる可能性がある
共有静的データ
まれにしか変更されない重要なデータの管理について
国コードテーブルを例に考える
パターン: 複製する
- 💡 データの更新頻度を考える ( 最後の国追加は 2011 )
- 💡 テーブルごとに同期が取れていなかったとして、それが問題になるかどうか考える
- 👎 実際は他の選択肢の方が良い面が多い
パターン: 専用の参照スキーマ
- 👍 重複やサービス間の不整合は発生しない
- 👎 共有スキーマの課題は解決していない
- 💡 このスキーマを更新するのは困難なので、安定している
- 🤔 物理的に同じデータベースエンジンである必要があり、単一障害点の懸念が消えない
パターン: 静的データライブラリ
- 🤔 技術スタックが混在している場合は、全てのサービスが利用できないかもしれない
- 🤔 全てのサービスが同じバージョンでビルドしているかは保証されない
- 👍 バージョン差異を許容するなら優れた選択肢である
- 👍 特にどのサービスがどのバージョンに依存しているかが把握しやすい
パターン: 静的データサービス
- 👍 テーブルの変更が利用サービスに影響しづらい
- 👍 全てのサービスが同じバージョンを用いていることが保証される
- 💡 コストについて検討すること
- 🤔 マイクロサービスを作ることにハードルが高い組織だと導入しづらい
トランザクション
ACID おさらい
- Atomicity
- トランザクション内の操作が、全て完了か全て失敗のどちらかであること
- Consistency
- 変更をしても、データベースが有効で一貫性のあること
- Isolation
- 複数のトランザクションが干渉することなく同時に動作すること
- Durability
- トランザクションが完了すると、システム障害でもデータが損失しないこと
ここでは Atomicity に注目する
ふたつのテーブルの操作をしていたモノリスをふたつのサービスに分けたら、Atomicity が保証されなくなることを受け入れる
ここから分散トランザクションを検討する
2フェーズコミット
- 投票フェーズでコーディネーターが全ワーカーに変更が可能か確認する
- 全ワーカーが同意すればコミットフェーズに進む
- ひとつでも違反すれば、操作全体を中断する
- コミットフェーズで全ワーカーに更新処理を行わせる
ただし、以下の課題がありいろいろ大変なので推奨しない
- それぞれのコミットがいつ完了するかわからないので一時的に不整合が発生し、Isolation に違反する
- コミットフェーズであるワーカーが応答しなかった場合、全ワーカーがロールバックする必要がある
- 処理に時間を要するワーカーに合わせて、全てのワーカーがロックを維持する必要がある
サーガ
リソースを長時間ロックしない様に考えられたアルゴリズム
方針として前方回復と後方回復が、実装としてオーケストレーションベースとコレオグラフィベースが考えられる
以下のフローを例にとる
後方回復
一部が失敗した場合に他のサービスに取り消しをリクエストする
- 💡 ここでの取り消しはデータベースのロールバックではない
- レコードがなかったことにはならない
- 打ち消した事実が残るとか
- 訂正のメールを送るとか
- レコードがなかったことにはならない
- 💡 失敗する可能性の高いものを先に行うことで、ロールバック範囲を制御できる
- 倉庫で発送ができたらポイントを付与する、とか
前方回復
失敗したポイントから処理を継続する
- 💡 サービスに冪等性が備わっていれば、どこまで成功したかを考慮せず全てを再実行することができる
- はずだけど、前方回復についての記載がほぼなかった...
オーケストレーションベースのサーガ
中央コーディネーター(オーケストレーター)が順序を定義し必要な処理を実行する
- 👍 オーケストレーターを見るとプロセスがどう動作するか理解しやすい
- 👎 オーケストレーターは全てのサービスを把握する必要があり、結合が強い
コレオグラフィベースベースのサーガ
複数のサービスで運用責任を分散させる
- サービスは自分のやるべきことを完了したらイベントを発行する
- 発行されたイベントは全サービスが検知できる
- イベントを検知したサービスは、必要に応じて動作する
- 👍 どのサービスも他のサービスを知らない
- イベントを受信した時に何をすべきかのみ知っている
- 👎 全体フローを把握するのが困難
- 💡 相関 ID を生成してイベントに入れておき収集すると良い
- 注文の状態を把握するビューが作りやすい
- プロセスが把握しやすい
5. 成長の痛み
サービスの増加に伴ういろいろな問題について
パターン: 所有権のスケール
所有のしかた | 開発者の担当 | 変更 |
---|---|---|
強い所有 | 全サービスで決まっている | 他の開発者は変更を申し出る 変更を許すかは担当者が決定する |
弱い所有 | 多数のサービスで決まっている | 誰でも直接変更する |
共同所有 | 決まっていない | 誰でも変更できる |
少数であれば共同所有で問題ないが、開発者が急に増えるとうまくいかない
共同所有は何が正しくどう行うべきかを全員が把握していて、かつそれを守っていることが暗黙の期待であるから
パターン: 破壊的変更
あるサービスが他のサービスに公開する機能を契約という
契約とはデータフォーマットにとどまらず、期待する挙動等も含む
サービスに変更を加える時にこの契約を破ってはならないが、複数のサービスを同時にリリースしたいときは契約を破壊する兆候かもしれない
パターン: レポーティング
モノリスでは 1 データベース上で大掛かりな結合をしてレポートを作るが、マイクロサービスではそれが困難である
ステークホルダーが SQL を使って単一データベースから取得できることを期待していたり、これまでそういうツールやプロセスに投資している背景もあるかもしれない
対策のうち最も簡単なのは、レポートのデータベースを分離することである
パターン: 監視とトラブルシューティング
モノリスと異なり、単一のサービスが障害を起こしたり 1 プロセスだけが CPU 使用率 100% になってしまう時に、それを正しく検知しなければならない
また、単純な全停止ではなく適切な部分停止や復旧を判断しなければならない
以下の様な方法で備える
- ログ収集
- 分散しているログを中央で検索可能にする
- 通常、最も簡単
- トレーシング
- 一連のフローを解析できる様にするため
- 相関 ID を用いる
- API ゲートウェイやサービスメッシュで払い出すのが一般的
- 本番でのテスト
- 環境や性能の変化が起きる可能性もあるため
- 本番でダミーユーザテストを行える様にする
パターン: 開発者体験
1 開発機で 10 や 20 のプロセスを起動するのが困難なため、手元でシステム全体を実行できなくなる
一般には、開発するわけではないサービスはスタブ化したり、ネットワーク上のインスタンスを利用することになる
ローカル環境で開発しつつもサービス呼び出しをリモートクラスタにしてくれるプロキシサービスもある
定期的なフィードバックで検知すること
パターン: あまりにも多くのものを実行している
モノリスなら手動でデプロイ・設定・保守を行えるが、複数サービスでは難しい
理想的には、サービスごとに異なる理想の状態を開発者が自分でプロビジョニングし自動的に維持するツールが必要である
一般には Kubernetes を用いるが、マイクロサービス化と同様にこれの導入を目的にしてはならない
パターン エンドツーエンドテスト
スコープが非常に大きくなるので、テストシナリオの設定が困難
また、一部のサービスが不正でテストが失敗する可能性も上がり問題分析が困難になったり、サービスを正しく把握できないことで念の為のテストケースが増えたりしやすい
いくつか対処方法がある
- 範囲を制限する
- チームをまたぐテストを減らし、テストの所有権をサービスの所有権と近づける
- コンシューマー駆動契約
- サービスの利用者の視点で動作の期待を仕様にする
- 自動リリース修正とプログレッシブデリバリー
- 一部ユーザに新機能を公開し、リリース継続か切り戻しかを定義により自動で判断すし、自動で次のリリースを行う
- 問題が発生したことを考えシステムを堅牢にする
- これはテストではないので注意
- 継続的なフィードバックサイクルの改善
- テストを減らすことも必要
- 迅速な実施と安全のバランスを取る
パターン: 全体最適と局所最適
例えば 3 チームが Oracle, MongoDB, PostgreSQL を採用したとする
会社としては似た 3 つのスキルとライセンスに投資することになるが、この決定は誰が下すのか
決定が可逆的か不可逆的か考え、不可逆的なほど境界外と共に決定すること
パターン: 堅牢性と回復性
ネットワークタイムアウトやパケットロスの様な、モノリスではおきない障害が増える
まず次の 2 点を考える
- サービスの呼び出しに関して、呼び出しがどう失敗するかをわかっているか
- サービスの呼び出しに関して、失敗した場合に何をすべきかわかっているか
これを元に、一時結合を避ける非同期通信を行ったり、サーキットブレーカーを導入したり、と検討を始める
パターン: 孤児サービス
自分たちがどんなサービスを持っているのか、どこにあり誰が所有しているのか、正確に把握するのが困難になる
社内レジストリを作ると改善できるし、メタデータをクロールしてシステムの分析を行ったりしやすくなる
6. おわりに
- 合理的な意思決定をするために適切な情報を集める
- 真似ではなく自分の問題に対し判断すること
- そのあとの変更を受け入れる姿勢を持つこと
- 技術やプラクティスは段階的に導入すること
ビッグバンリライトをしても、保証されるのはビッグバンだけ by 訳者