Closed25

「モノリスからマイクロサービスへ」サマリ

ほげさんほげさん

1. 必要十分なマイクロサービス

  • 独立してデプロイ可能であること
  • ネットワークエンドポイントを介して連携する
    • データベースはサービス境界の内側に隠蔽される
  • 特定の技術に依存しない

デプロイをオーケストレーションするのはモノリスより大変なので、それは減らそう

3層構造のアーキテクチャで担当チームが違う場合に、よくそれは発生する

「システムを設計する組織は、その構造をそっくりまねた構造の設計を生み出してしまう。」コンウェイの法則

技術の凝集度を高くするやり方だったが、ビジネスの凝集度を高くするやり方に変化する必要がでてきた


独立デプロイにより自身の領域に集中できるし、プロセスや技術が他に依存しなくなる


バックエンドにだけ焦点を当てフロントエンドをモノリスのままにしているケースをよく見るが、独立デプロイのためにはそうではいけない


実現に特定の技術は必要ない

k8s, docker, aws 等々は必須ではなく、ただネットワークで繋がれば良い

( 特に ) 新しい技術はコストが高いので、釣り合うか判断すべし


従来の開発部門とビジネス部門が分かれる形に対し、ビジネスで分離して編成する考え方は、昨今のスクラムやドメイン駆動開発とも親和性がある


モノリスとは、一緒にデプロイする必要がある範囲であり、さらに以下に分類される

  • 単一プロセスのモノリス
    • 1つのモジュールで全てを賄う、一般にイメージする形
    • 単一モノリスのサブセットとしてモジュラーモノリスという形がある
      • 複数のモジュールで構成されるが、デプロイは連携が必要
      • データベースは共有している
      • マイクロサービスに多少近いが、データベースでモジュールの独立性が失われている
      • データベースを多少分離しているケースもある
  • 分散モノリス
    • 複数サービスで構成されるが、システム全体を一括でデプロイする必要がある
    • 大抵、分散システムと単一モノリスの欠点を持ち、どちらの利点も不十分
    • 情報隠蔽やビジネスでの凝集が十分できていない場合に、これを招きやすい
  • サードパーティ製のブラックボックスシステム
    • 給与、人事、顧客管理などの自分たちではコードを変更できないモジュールを含む

モノリスの課題は関係者が増えやすいので変更やデプロイのたびに混乱すること
所有権や意思決定が曖昧だから
これをデリバリー衝突と呼ぶ

利点もあり、デプロイ自体はシンプルである
また監視、エラー対応、結合テストが容易であり、コードの再利用もしやすい

モノリスはレガシーの様な悪い単語ではなく、今でも有効な選択肢である

ほげさんほげさん

結合

実装結合

一般的な例としてデータベースの共有が挙げられる

注文
↓
注文テーブル ←  レコメンデーション

データベースを隠蔽すれば改善される

注文 ←  注文 API ←  レコメンデーション
↓
注文テーブル

取り決めたインターフェースを壊さなければ、注文はレコメンデーションに断らずデータベースの修正とデプロイが行える

一時的結合

分散環境での同期呼び出しが主要な課題

倉庫 → 同期的 HTTP → 注文 → 同期的 HTTP → 顧客

全て起動し連携する必要があり、その瞬間は一時的に結合している

キャッシュを使ったり非同期処理にすれば改善される

デプロイ時結合

ある変更をデプロイするためにシステム全体をデプロイする必要がある場合

デプロイはリスクなので小さくすべし

ドメイン結合

複数サービスで用いられる情報を全て共有している例が挙げられる

注文処理 → 倉庫 → 顧客
         ↑
     注文詳細
       - 顧客 ID
       - 金額
       - 支払い情報
       - 商品

倉庫では顧客 ID と商品が分かれば良いので、それを別のドメインとして再定義する

注文処理 → 倉庫 → 顧客
         ↑
     配送指示
       - 顧客 ID
       - 商品
ほげさんほげさん

2. 移行を計画する

  • マイクロサービスは目的ではない
  • 既存のアーキテクチャでは達成できないことを実現するために用いる

マイクロサービスを選択する理由

  • チームの自立性を高める
    • 小さく、権限があること
    • チームの裁量、コードベースの所有権、決定権を持つ人を検討すること
  • 市場投入を早める
    • デプロイに関与する意思決定が少ないこと
    • 発案からデプロイまでのデリバリー全ステップを観察し、問題点を探すこと
  • スケーリングの費用対効果を高める
    • 負荷の集中する箇所だけスケーリングできること
    • モノリスのコピーを稼働させることで費用対効果は悪くても性能改善ができる
  • 堅牢にする
    • システム全体がダウンしない様にできる
    • モノリスのコピーを別の場所で稼働させるという方法も
  • 開発者の数を増やす
    • 作業を相互に作用しない作業に分割するには、デリバリー衝突を減らすこと
    • 規模に応じてモジュラーモノリスも
  • 新しい技術を試す
    • 新たな技術の利点を取り入れやすく、問題を影響を制限しやすい
    • 例えば JVM は複数の言語をホストできる

マイクロサービスがふさわしくないケース

  • ドメインが不明瞭
    • サービス境界を見誤ると大変な目にあう
  • スタートアップ
    • 探索に従いビジョンも変わる可能性がある場合は、ドメインの変化も大きい
  • 顧客のインストールさせるソフトウェア
    • msi から kubernetes に変わったら無理だろう
  • 理由がない
    • 意味と価値がない

課題が移行の背後にある核であり、それと副次的な成果は切り離すこと

スケールのためであれば水平スケーリングで解決するかもしれないが、新言語は試せなくなる

明確な順位づけをしておくことで意思決定が簡単になる


可逆的な判断と不可逆的な判断がある ( ジェフ・ベゾス )

当然不可逆的な判断には熟慮と協議を伴うべきだが、全てを不可逆的な決定と勘違いしないこと

実際はどちらかに断定されるというより、横軸と度合いで考えた方がわかりやすい
程度は決定を覆したい時の影響から判断できる


ドメインモデリングを用いて境界づけられたコンテキストを探し、そこからサービスの境界を定義する

技術者と非技術者がドメインイベントを定義し、集約にし、境界づけられたコンテキストにする
イベント駆動のシステム設計ではなく、モデルの共通理解を行うことが目的である

境界づけられたコンテキストと依存の方向が見えると、着手するコンテキストが妥当か判断できる
一般に非依存が多いコンテキスト ( 通知 ) は困難だが、非依存がないコンテキスト ( 請求書 ) は着手しやすい

請求書 → 注文管理 → 在庫管理
 ↓        ↓
通知 ←  発送

ただしこれは論理ビューであることに留意する
実際の実装がこう行われているとは限らない

うまくいっているのか

  • 定期的なチェックポイントを設ける
    • 導入の目的とビジネスの方向がまだ一致しているか
    • 進捗は
  • 定量的な測定
    • デリバリー短縮が目的なら、デプロイ数や障害数を測定するとか
  • 定性的な測定
    • 楽しいか、サポートは適切か
  • サンクコストの錯誤を回避する
    • 先行投資しすぎた時にそれを取りやめないことを指す
ほげさんほげさん

3. モノリスを分解する

モノリスが変更できるのかまず確認する

できない場合も手はある

大抵モノリスは機能や技術で凝集されているので、境界づけられたコンテキストを探してモジュラーモノリスを目指す

モノリスを見つめるのは精神的に辛いが、いきなりモノリスの全リライトをしない

以下にパターンを整理する
いずれも次をよく留意する

  • デプロイとリリースを分け、デプロイは積極的に行う
  • パターンの案と例なので長短を考えて使うこと

ストラングラーアプリケーション

移行対象を決め

既存の呼び出し
  ↓
+--モノリス--+
|    ↓    |
| 移行対象 |
+---------+

コピーか再実装で切り出し

既存の呼び出し
  ↓
+--モノリス--+   +--サービス--+
|    ↓    |    |    ↓    |
| 移行対象 |     | 移行対象 |
+---------+    +---------+

リダイレクトする

                  リダイレクト
                    ↓
+--モノリス--+   +--サービス--+
|    ↓    |    |    ↓    |
| 移行対象 |     | 移行対象 |
+---------+    +---------+

移行対象を消すのは安定稼働を確認してからが望ましい
データベースについては後述

既存のシステムに変更を入れずに行えるので、衝突が起きないしブラックボックスシステムでも有効

抽出を大きさで作業量とリスクが変わるので留意する

抽出したいモジュールがモノリスの内部で呼ばれるものの場合、単純なリダイレクトでは置き換えられないので適用しづらい

プロキシを工夫することでデプロイ単位やプロトコルを調整できる
ただしプロキシを作り込みすぎて共有しすぎると独立デプロイから遠下がるので注意
プロキシを構築するかサービスが吸収するかをよく考える


移行と変更が同時に行われる場合、モノリスに残した機能にロールバックすると機能 ( もしくはバグ修正 ) がロストする


UI 合成

  • ページ合成
    • 全てをチームが所有している場合は、ページごとに切り替える方法が適している
  • ウィジェット合成
    • 小さく検証するには、ページ単位ではなくページ内の1機能で検証する
  • マイクロフロントエンド

抽象化によるブランチ

先述の抽出したい機能がモノリスの内側にある場合

昨日コードへの変更と開発者への混乱が発生する
一般にブランチを分けて開発するが、ブランチが長く生きるほどマージの問題は大きくなる

それを避けるにはどうしたら良いか

現状

   請求    給与                |
     ↓     ↓                  |
    既存の通知                  |

機能の抽象を作る

   請求    給与                |
     ↓     ↓                  |
    既存の通知 → 通知の抽象      |

抽象を使う
これは段階的に行える

   請求             給与     |
     ↓              ↓       |
    既存の通知 → 通知の抽象    |
               請求    給与   |
                ↓     ↓      |
    既存の通知 → 通知の抽象     |

新たな実装をする

               請求    給与   |
                ↓     ↓      |
    既存の通知 → 通知の抽象     |
                             |
                新たな通知     →   通知サービス
                             |

実装を切り替える
フィーチャーフラグなどを用いると実現しやすい

               請求    給与   |
                ↓     ↓      |
    既存の通知   通知の抽象     |
                   ↑         |
                新たな通知     →   通知サービス
                             |

古い機能を削除する
フィーチャーフラグも適切に消す
抽象を消すかは効果次第

               請求    給与   |
                ↓     ↓      |
                通知の抽象     |
                   ↑         |
                新たな通知     →   通知サービス
                             |
               請求    給与   |
                ↓     ↓      |
                新たな通知     →   通知サービス
                             |
ほげさんほげさん

同時実行

新旧両方を呼び出して結果を検証しつつも、しばらくは旧い方を本物のデータとする

非機能要件の検証も行う

両方にリクエストを送り、両方のデータベースから結果を抽出し毎日スクリプトで比較する

移行するものが通知サービスの様な場合は、片方はモックにして結果を貯めるに留める

ほげさんほげさん

デコレーティングコラボレーター

モノリス内部の挙動をトリガーにするモジュールを移行したいが、モノリスを変更できないとき

プロキシがモノリスを介さずサービスを起動する

↓   ↑
プロキシ → サービス
↓   ↑
モノリス

ただしモノリスの結果がサービスを起動する情報を満たせていない場合、サービスからモノリスの呼び出しやモノリスの変更が必要になる

この仕組み自体が複雑になりすぎない様に留意すること

ほげさんほげさん

変更データキャプチャ

リクエストではなくデータベースの変更を傍受する

既存のレスポンスが Success のみなど、デコレーティングコラボレーターには必要な情報が揃わないときなど

モノリス
  ↓
モノリス DB  →  サービス

実現方法はいくつか考えられる

  • データベーストリガー
  • トランザクションログ監視
  • 差分コピー
ほげさんほげさん

4. データベースを分割する

共有データベース

既存のモノリスがこうなっていることが多い

どのサービスがどのテーブルを扱うかが見えづらい

静的なマスターテーブルとサービスのインターフェースにする ( 後述 ) くらいしか適切な用途はない

データベースビュー

データベースを変更したいが、アクセスの変更が極めて困難な場合

共有データベースを緩和できる

            システム
              ↓
システム  →  データベース
              ↑
            システム

ビューを作る

           システム
             ↓
システム  →  公開ビュー  ←  データベース
             ↑
           システム

既存の構成を維持すれば、データベースは変更できる

ほげさんほげさん

データベースをラップするサービス

データベースを薄いラッパーの裏に隠し、データベースへの依存をサービスへの依存にする

            システム
              ↓
システム  →  データベース
              ↑
            システム

ラッパー呼び出しに変更する

           システム
             ↓
システム  →  サービス  →  データベース
             ↑
           システム
ほげさんほげさん

サービスのインターフェイスとしてのデータベース

共有データベースが適切な方法のひとつ

大量データの参照を行ったり Tableau の様に SQL エンドポイントを必要とする場合に用いる

ただし公開用とサービス境界の内側で分離することを留意

そのふたつを同期させるマッピングエンジンを用意しなければならない

これはビューより柔軟で例えばデータベースエンジン自体を分けることすら可能だが、当然ビューよりコストがかかるし同期の遅延等も検討しなければならない

ほげさんほげさん

所有権について

集約を公開するモノリス

モノリスをデータベースのラッパーの様にするのではなく、集約の参照や変更を外部に公開する

請求書サービスのためのエンドポイントから、従業員サービスの境界を見つけて抽出している

モノリスが従業員サービスを必要とし続ける場合、モノリスの呼び出しも変える

請求書サービス
    ↓
モノリス
  従業員 API
  従業員状態
    ↓
モノリスデータベース
  従業員テーブル
モノリス
    ↓
従業員サービス
  従業員 API
  従業員状態
    ↓
モノリスデータベース
  従業員テーブル
ほげさんほげさん

データ所有権の変更

サービスの抽出は下がデータベースはモノリスに残している

モノリス    請求書サービス
  ↓          ↓
モノリスデータベース
  請求書テーブル

データの管理がモノリスではふさわしくない場合、データベースを抽出したサービスに渡し、モノリスはそちらを呼ぶ様に変更する

モノリス  →  請求書サービス
           ↓
請求書データベース
  請求書テーブル

ただしこのデータベース分離は大変なことが多いので、参照に限られるならビューを作るという形もある

モノリス                請求書サービス
  ↓                      ↓
モノリスデータベース    請求書データベース
  請求書ビュー     ←    請求書テーブル
ほげさんほげさん

データ同期について
ストラングラーパターンの利点は切り戻しやすいことだが、並行に稼働するコピーにデータ同期が必要だと問題がある

            プロキシ
    ↓                 ↓
モノリス               請求書サービス
  請求機能

モノリスデータベース  ←  請求書データベース

アプリケーションでのデータ同期

まずデータを一括で同期する

システム
  ↓
既存データベース  →  エクスポート  →  新データベース

この間システムは止められなかったので、完了時点で差分が発生している
変更データキャプチャを用いて差分を埋める

システム
  ↓
既存データベース  →  新データベース

同期のために書き込みを増やすが、読み込みは変えない

            システム
    ↓ R/W            ↓ W
既存データベース  →  新データベース

確認が十分できたら、読み込みを切り替える
切り戻しに備え書き込みはし続ける

            システム
    ↓ W              ↓ R/W
既存データベース  →  新データベース
ほげさんほげさん

トレーサー書き込み

真の情報源を段階的に移行する

真の情報源はひとつにしたいが、実際は切り替えリスクが高い

同期を頑張ることで段階リリースを可能にしリスクを分散する

              モノリス
             ↓     ↓
モノリスデータベース    請求書データベース
  基本データ ( W )    基本データ ( R/W )
  送付先 ( R/W )
  支払い ( R/W )
              モノリス
             ↓     ↓
モノリスデータベース    請求書データベース
  基本データ ( W )    基本データ ( R/W )
  送付先 ( W )       送付先 ( R/W )
  支払い ( R/W )
              モノリス
             ↓     ↓
モノリスデータベース    請求書データベース
  基本データ ( W )    基本データ ( R/W )
  送付先 ( W )       送付先 ( R/W )
  支払い ( W )       支払い ( R/W )

データ同期は、片方に書き込みをして同期を取るか、両方に書き込むことになる

ほげさんほげさん

データベースを分割する

データベースにも接合部を見つけると良い

物理的なデータベースに論理的に複数のスキーマがある
ここでは論理的な分離を考える
これが進めば次に物理的な分離を行いやすい

 サービス A   →  サービス B
     ↓            ↓
+------------------------+
| スキーマ A     スキーマ B |
+------------------------+
 サービス A   →  サービス B
     ↓            ↓
+----------+ +----------+
| スキーマ A | | スキーマ B |
+----------+ +----------+

論理的分解は独立性を高め情報の隠蔽を容易にする

物理的分解は堅牢性を向上させリソース競合を排除する、またスループットやレイテンシーも改善する


分離する順番はいくつかある

データベース → コード

  • 👎 スキーマが分かれていると select 一発で参照できなくなり呼び出し回数が増えやすい
  • 👎 トランザクション整合性を保つのが難しい
  • 👎 モノリスの単一デプロイが改善されないので、短期的メリットはあまりない
  • 👍 スキーマの分離に問題が出た場合に利用者に影響を与えずにサービスで微調整しやすい

特にパフォーマンスやデータの一貫性を懸念している場合に用いる

コード → データベース

  • 👍 アプリケーションから分離することで、必要なデータを理解しやすい
  • 👍 独立デプロイの恩恵が早く受けられる
  • 👎 当然共有データベースの課題は最後まで解決しない

新しいデータの扱いを切り出したサービスに機能追加する場合はうまく機能する
新しいサービスはモノリスの既存スキーマと自分の新スキーマを両方使えば良いからだ

一括

  • 👎 とても大変であり、また決断してから結果が出るまでに時間がかかる

行うべきではない

ほげさんほげさん

テーブルの分割

1スキーマに複数の業務が混じっている場合、まずはテーブルを列で分割するのが自然

   サービス A    サービス B
    ↓     ↓      ↓
| 列 A , 列 B , 列 C |
   サービス A      サービス B
    ↓     ↓        ↓
| 列 A , 列 B |   | 列 C |

が、例えばある列を複数サービスが更新している場合もある

その場合はそれを扱うのがふさわしいサービスに更新を一任する

ユーザの支払い状況の列を、顧客管理と経理が更新している

       顧客管理  経理
               ↘︎  ↓
| 列 A , 列 B , 列 C |

ユーザの支払いについては顧客管理のサービス境界に入れ、経理は利用停止を顧客管理に依頼する形にする

       顧客管理  ←  経理
               ↘︎   
| 列 A , 列 B , 列 C |

どこのドメインか検討して決めること

ほげさんほげさん

外部キーのコードへの移動

外部キー制約が定義されたテーブルを異なるスキーマに分解したい場合、どうするべきか

モノリスは結合を含む select クエリ1回で参照している

カタログ            経理
  ↓                ↓
アルバム  ←  ( FK ) 元帳

これをアプリケーションで結合するために、経理はカタログにアルバム詳細を問い合わせなければならない

カタログ  ←  経理
  ↓         ↓
アルバム     元帳

当然性能が劣化するので、よく計測して判断する必要がある


外部キー制約がなくなっているので、アルバム定義を削除できてしまう

不整合にどう備えるべきか

  • 👎 削除前にチェックする
    • あるアルバム定義を消して良いか、都度経理に確認する
      • これは実質の逆依存が発生してしまっている
      • 削除中に経理にそのアルバムが必要になる可能性を抑えるため、ロック等が必要
      • アルバムを用いるサービスが増えるほど困難になる
    • この選択肢は検討しないべき
  • 👍 削除する
    • 削除は行う
    • 経理からの問い合わせが失敗した場合にレポートに「不明なアルバム」と表示させたりする
      • 404 ではなくて 401 を用いたりすると、発生時に不整合を追跡しやすくなる
    • 発展させ、削除のイベントをサブスクライブするという方法もある
      • その場合は、検知した経理が該当アルバムをローカルキャッシュしたりできる
  • 🤔 削除しない
    • 矛盾を生まない1つの方法として、これが考えられる
    • もしくは論理削除
      • 列のフラグで管理したり、削除済みテーブルに移動したりする
    • いずれの場合も、経理から参照し続けられること
    • 販売していない古いアルバムも扱い続けるため、アプリケーションが複雑になる可能性がある

テーブルを分割するべきかはよくよくドメインを用いて検討すること

共有静的データ

メモふっとんだ...

最後のまとめで再整理...

ほげさんほげさん

トランザクション

ACID おさらい

  • Atomicity
    • トランザクション内の操作が、全て完了か全て失敗のどちらかであること
  • Consistency
    • 変更をしても、データベースが有効で一貫性のあること
  • Isolation
    • 複数のトランザクションが干渉することなく同時に動作すること
  • Durability
    • トランザクションが完了すると、システム障害でもデータが損失しないこと

Atomicity に注目する


       モノリス
      ↙︎      ↘︎
テーブル A    テーブル B

2つのテーブルを操作していたモノリスを2つのサービスに分けたら、Atomicity が保証されなくなったことを受け入れなければならない

サービス A    サービス B
   ↓           ↓
テーブル A    テーブル B

分散トランザクションを検討する

2フェーズコミット

  • 投票フェーズでコーディネーターが全ワーカーに変更が可能か確認をする
    • 全ワーカーが同意すればコミットフェーズに進む
    • 1ワーカーでも違反すれば、操作全体を中断する
  • コミットフェーズで全ワーカーに更新処理を行わせる

ただしこれは次の課題を含んでいる

  • それぞれのコミットがいつ完了するかはわからないので、一時的に不整合の瞬間があり、これは Isolation に違反している
  • コミットフェーズであるワーカーが応答しない場合は、他全ワーカーがロールバックしなければならない
  • 処理に時間を要するワーカーに合わせて、全てのワーカーがロックを維持しなければならない

いろいろと大変なので、推奨しない

ほげさんほげさん

サーガ

リソースを長時間ロックしない様に考えられたアルゴリズム

こういうフローで考える

注文処理
  ↓
在庫チェックと予約  →  倉庫サービス
  ↓
支払いを受領  →  支払いサービス
  ↓
ポイントを付与  →  ポイントサービス
  ↓
梱包して配送  →  倉庫サービス
  ↓
完了

後方回復と前方回復が考えられている


後方回復は、一部が失敗した場合に他のサービスに取り消しをリクエストする

ここでの取り消しはデータベースのロールバックとは異なることに留意する
データベースのロールバックはレコードが完全になかったことになるが、ここでは打ち消した事実が残ったりするし、一度送ったメールは取り消せない ( 2 通目の訂正メールを送るかもしれない )

注文処理
  ↓
在庫チェックと予約  →  倉庫サービス ( OK )
  ↓
支払いを受領  →  支払いサービス ( OK )
  ↓
ポイントを付与  →  ポイントサービス ( OK )
  ↓
梱包して配送  →  倉庫サービス ( ERROR )
  ↓
完了


注文処理取り消し
  ↓
予約の削除  →  倉庫サービス
  ↓
返金  →  支払いサービス
  ↓
ポイント取消  →  ポイントサービス
  ↓
完了

また、一番失敗する可能性の高いものを最初に行うことで、ロールバックの範囲を制御することができる


前方回復は、失敗したポイントから処理を継続する

注文処理
  ↓
在庫チェックと予約  →  倉庫サービス ( OK )
  ↓
支払いを受領  →  支払いサービス ( OK )
  ↓
ポイントを付与  →  ポイントサービス ( OK )
  ↓
梱包して配送  →  倉庫サービス ( ERROR )
  ↓
完了


注文処理継続
  ↓
梱包して配送  →  倉庫サービス ( OK )
  ↓
完了

サーガの実装には 2 つのスタイルがある

オーケストレーションベースのサーガと、コレオグラフィベースのサーガ


オーケストレーションベースのサーガは、中央コーディネーター ( オーケストレーター ) が順序を定義し必要なアクションを実行する

ポイント
 ↑
注文  →  支払い
 ↓
倉庫
  • 👍 オーケストレーターを見るとプロセスがどう動作するか理解しやすい
  • 👎 オーケストレーターは全てのサービスを把握する必要があり、結合が強い

コレオグラフィベースのサーガは、複数のサービスでサーガの運用責任を分散させる

サービスは自分のやるべきことを完了したらイベントを発行する

発行されたイベントは全サービスが検知でき、必要に応じていくつかのサービスがそれを契機に動作する

開始              出荷イベント
  ↓ 0                ↑ 8
発注イベント    ← 1    倉庫     ー↘︎ 7
                     ↓ 2
支払い    3 →    在庫受領イベント        ポイント付与イベント
  ↓ 4                               ↗︎ 6
支払い確認イベント      ← 5       ポイント
  • 👍 どのサービスも他のサービスを知らない、イベントを受信した時に何をすべきかのみ知っている
  • 👎 全体フローを把握するのが困難

相関 ID を生成してサーガの発行するイベントに入れておき、それを収集するサービスを作れば、注文の状態を把握するビューが作れたりプロセスが把握しやすくなったりする

ほげさんほげさん

5. 成長の痛み

サービスが増えるに伴い色々な問題が発生する

所有権のスケール

  • 強い所有
    • 全てのサービスに開発者が割り当てられている
    • 他の開発者が変更する場合は変更を伝える
    • 変更を許すかは担当開発者が決定する
  • 弱い所有
    • 多数のサービスに開発者が割り当てられている
    • 誰でも直接変更できる
  • 共同所有
    • 開発者の割り当てはされていない
    • 誰でも変更できる

少数で開発していれば共同所有でうまく機能するが、開発者が急に増えたりすると共同所有のままではうまくいかない

共同所有では「何が正しいか、どう行うべきか」を全員が把握してかつそれに則っているという期待を軸にしているため

開発規模に合わせて適切な所有方を考えるのが望ましい

破壊的変更

あるサービスが他のサービスに公開する機能を契約と良い、データフォーマットにとどまらず期待する動作等も含む

サービスに変更を加える時に、この契約を破ってはならない

複数のサービスを同時にリリースしようとしているときは、契約が破壊される兆候かもしれない

対応するには次に留意すると良い

  1. 偶発的な破壊を排除する
  2. 破壊する前に避けられないか考える
  3. 破壊する場合は移行期間を儲ける

レポーティング

モノリスでは 1 データベース上で大掛かりな結合をしてレポートを作るのが主流だが、マイクロサービスだとそれが困難

ステークホルダーは SQL を使って単一データベースから取得できることを期待していることもある
これはそれまでその様なツールやプロセスに投資しているという背景もあるかもしれない

対策のうち最も簡単なのは、サービスのデータベースとレポートのデータベースを分離すること

請求サービス    →    レポートデータベース    ←    注文サービス
      ↓                  ↑                    ↓
請求データベース        レポートツール          注文データベース

監視とトラブルシューティング

モノリスと違い、単一サービスだけ障害を起こしたり、たった 1 プロセスが CPU 使用率 100% になっているかもしれない時に、それを適切に検知し適切な対応を決められるかが難しい

備える方法をいくつか挙げる

  • ログ集約
    • 分散しているログを中央へと送り、検索可能にする
    • 通常最も簡単であり、最初に行っておくと良いことである
  • トレーシング
    • 一連のフローを把握できないと、問題の分析は難しい
    • 相関 ID を用いる ( 払い出しは API ゲートウェイやサービスメッシュで行うのが一般的 )
  • 本番でのテスト
    • 環境や性能の変化が起きる可能性もあるため
    • 本番でダミーユーザでテスト行う準備をする
ほげさんほげさん

開発者体験

1 開発機で 10 や 20 のプロセスを起動できないため、手元でシステム全体を実行できなくなってくる

手元のビルドがどんどん大変になってきてしまう

一般には開発するわけではないサービスはスタブ化するかどこかのインスタンスを利用することになる

ローカル環境で開発しつつも他サービスの呼び出しをリモートクラスタにプロキシしてくれるサービスもある

検知と対策には定期的なフィードバックを行うこと

あまりにも多くのものを実行している

モノリスを手動でデプロイ・設定・保守できていたとしても、複数サービスでこれを行うのは難しい

理想的にはサービスごとに異なる理想的な状態を、開発者が自分でプロビジョニングして自動的に維持できるツールが必要になる

一般的には kubernetes を用いるが、これの導入をありきにしてはいけない

マイクロサービス化自体と同様に、問題をよく分析してから取り入れること

エンドツーエンドテスト

マイクロサービスではスコープが非常に大きくなり、全サービスをテストシナリオに合わせて適切に設定する必要がある

またサービスインスタンスの不正によるテストエラー等が起きる可能性も上がり、問題分析にも時間が掛かる様になる

またサービスを正しく把握できていない場合に「念の為」の様なテストケースが増え、実行時間が増大することがある

対処するにはいくつか方法がある

範囲を制限する

あまりチームをまたぐテストを書かずに、テストの所有権をサービスの所有権と近づけること

それによりシナリオが適切か判断しやすくなるし、実行責任の所在もはっきりする

コンシューマー駆動契約

サービスの利用者の視点で動作の期待を仕様にする

これはサービスの理解にも役に立つ

自動リリース修正とプログレッシブデリバリー

一部ユーザに新機能を公開し、リリース継続か切り戻しかを定義によって判断し自動で次のリリースを行う仕組みを用意しておく

問題が発生しない様だけではなく、発生した後どうするかを考えることでシステムが堅牢になることもある

ただこれはテストの代替になるものではないので注意

継続的なフィードバックサイクルの改善

テストを増やすのは当然として、全体を見てテストを削除することもできる必要がある

迅速な実施と安全のバランスを取ることが肝要

ほげさんほげさん

全体最適と局所最適

例えば 3 チームが自身の経験から Oracle, MongoDB, PostgreSQL を採用することにしたとする

会社としては似た機能を持つ 3 つのデータベースのためにスキルを構築しライセンス料も払うことになるが、この決定は誰が下せるのだろうか

自分たちの意思決定が可逆的か不可逆的かを考え、不可逆的なほど境界外の人と決定することが大事になる
例えばチーム代表の技術責任者で横断的に検討するとか

なんにせよチーム間のコミュニケーションなしに気付ける問題ではない

堅牢性と回復性

例えばネットワークタイムアウトやパケットロスの様な、モノリスでは起きない障害がどんどん増える

まずは 2 点を考える

  • サービスの呼び出しに関して、呼び出しがどう失敗するかをわかっているか
  • サービスの呼び出しに関して、失敗した場合に何をすべきかわかっているか

これに答えられれば、一時結合を避けるために非同期通信をしたり、サーキットブレーカーを導入させたり、と言った案が検討し始められる

孤児サービス

自分たちがどんなサービスを持っているのか、どこにあり誰が所有しているのか、正確に把握することに苦労する

長く時が経つにつれ忘れてしまったり、会社を離れてしまったりして、サーバの用途がわからなくなったりソースコードが見つけられなくなったりしてしまう

社内レジストリを作ると改善することがある
メタデータをクロールしてシステム全体の分析を行ったりしやすくなる

6. おわりに

  1. 合理的な意思決定をするために適切な情報を集め、真似ではなく自分の問題に対し判断すること、そのあとの変更を受け入れる姿勢を持つこと
  2. 技術やプラクティスは段階的に導入すること

ビッグバンリライトをしても、保証されるのはビッグバンだけ by 訳者

このスクラップは2021/03/21にクローズされました