精読「マイクロサービスアーキテクチャ 第2版」(第二部 実装 - 第12章 レジリエンス)
マイクロサービスアーキテクチャ 第2版
マイクロサービスの設計、実装、運用に必要なベストプラクティスや最新技術を解説した、実践的なガイドブックです。これを読めば、マイクロサービスに関してそれっぽい会話もできますよ。
関連記事
レジリエンスとは
堅牢性
堅牢性(ロバストネス)は、システムが障害や混乱に対処できる能力。マイクロサービスでは、ホスト障害やネットワークタイムアウトに対応するため、代替ホストの起動や再試行が行われる。ソフトウェアだけでなく、人やプロセスの側面も重要。堅牢性向上には予測と対策が必要だが、システムが複雑化し新たな問題を引き起こす可能性もあるため、慎重に検討する必要がある。
回復性
回復性は、システムが障害から迅速に回復できる能力。完全に障害を排除することは不可能だが、事前に対策を取ることで回復能力を高めることができる。例えば、バックアップやプレイブックを用意し、システム停止時に適切な手順を踏むことで回復がスムーズになる。また、停止時の役割やコミュニケーション方法を事前に明確にし、緊急時に備えることが重要。
グレースフルな拡張性
グレースフルな拡張性は、予期しない事態に対する準備と対応力を高めること。想定外の事態が発生した場合、システムが脆弱になる可能性がある。システムの最適化が逆に脆弱性を高める場合もあり、特に自動化が進むことで、予期しない問題に対処できなくなることがある。そのため、グレースフルな拡張性を確保するためには、適切なスキルや経験を持つ人材を配置することが重要。
持続的な適応性
持続的な適応性を持つためには、現状に満足せず、未来の変化に備えて組織全体が適応し続ける必要がある。未来に対する不安や脆弱性を受け入れ、予期しない事態に柔軟に対応できる文化を育むことが重要。持続的な適応性を実現するためには、短期的な成果と長期的な適応性のバランスを取ることが必要で、組織の戦略や文化の中核に位置付けるべき。この適応性には、継続的な投資と学習が欠かせない。
そして、マイクロサービスアーキテクチャ
マイクロサービスアーキテクチャは堅牢性を実現する方法を提供するが、レジリエンスの実現にはそれだけでは不十分。広い意味でのレジリエンスは、ソフトウェア自体の特性だけでなく、システムを構築・運用する人々の特性にも関わる。
障害はどこにでもある
障害はどこにでもあり、特に大規模なシステムでは、故障が統計的に確実になる。ハードディスクやソフトウェアの障害、ネットワークの信頼性など、障害を完全に避けることはできない。しかし、重要なのは障害を防ぐための時間を減らし、グレースフルに対応する時間を増やすこと。多くの組織は障害を防ぐためのプロセスを導入していますが、実際には障害からの迅速な復旧を考えることが少ないことが驚きである。
例えば、Googleはサーバーの堅牢性を向上させるために、機器の障害が起きてもサービスが中断しないようなシステム設計を行っている。サーバのハードドライブは簡単に交換できるように設計され、システム全体の堅牢性を高めている。このように、大規模システムでは障害が避けられないことを前提に、障害に備えた設計や計画が重要。
どの程度が多すぎるのか
システム設計における非機能要件(可用性、耐久性、遅延など)は重要。
要点は以下の通り
-
要件の把握
システム設計にはユーザーのニーズに合った非機能要件の理解が重要。 -
スケーラビリティのバランス
過剰な機能を避け、実際のニーズに見合った設計が必要。 -
サービスごとの要件
サービスの可用性やデータの保持期間などは、サービスの性質に応じて設定する。 -
SLOの設定
サービスレベル目標(SLO)を設定し、システムの運用や設計をガイドする。
これらの要素を考慮することで、より適切なシステム設計が可能になる。
機能低下
マイクロサービスアーキテクチャにおける「機能低下」の重要。
要点は以下の通り
-
レジリエンスの重要性
システムの一部が停止しても、他の部分が機能し続けることが重要。特に複数のマイクロサービスを使用するシステムでは、サービスが停止しても全体が停止しないように工夫する必要がある。 -
機能低下の戦略
システムが部分的に停止した場合でも、サービスを完全に停止させず、機能を減らして運用を続ける方法を考える。例えば、ショッピングカートが使えない場合でも、商品一覧だけを表示するなどの対策が有効。 -
技術的・ビジネス的判断
技術的な選択肢だけでなく、ビジネス的な視点を踏まえた判断が求められる。どのサービスを停止し、どれを優先するかを決定するのは、技術だけでなくビジネスニーズに基づいて行われるべき。 -
障害対応の準備
障害時にスムーズに対応できるよう、予め「これが停止した場合どうするか」を考えておくことが重要。
これにより、サービスの中断があっても、ユーザーにとって許容範囲内で機能が提供され続けるように設計できる。
安定性パターン
あるプロジェクトで、オンライン広告システムの移行に関する問題が発生した。新システムへの移行中、旧システムとの統合を透過的に行うために「ストラングラーフィグパターン」を使用したが、広告システムの一部が遅延を引き起こし、その影響でシステム全体が停止する事態に陥った。
最も遅延が発生したのは、旧サービスの1つである広告システムで、これが連鎖的な障害を引き起こした。問題は、下流サービスが遅いためにリソース競合が発生し、システム全体の遅延が悪化したこと。解決策として、以下の3つの修正が行われた
- タイムアウト設定の適切化
- サービスを分離するための「バルクヘッド」パターンの実装
- 失敗しているシステムへのリクエストを回避するための「サーキットブレーカー」パターンの実装
この事例から、分散システムにおける遅延がいかに致命的であるか、そして適切な安全策を講じる重要性が示されている
タイムアウト
タイムアウト設定は分散システムで重要。過度に長いタイムアウトはシステム全体の速度低下を引き起こし、短すぎると正常な処理を失敗と判断する可能性がある。
AdvertCorpの例では、HTTPリクエストのタイムアウト設定に問題があった。リクエストを諦めるまでの時間が長すぎて、無駄にリクエストが繰り返されてしまう事態が発生した。改善策として、レスポンスをより迅速に期待できる目標を設定し、タイムアウトを1秒に短縮した。
タイムアウトの設定は、下流システムの通常の応答時間を基に決定し、必要に応じて調整する。また、操作全体にタイムアウトを設定し、残りの時間を下流に渡すことで無駄な待機を防ぐ。
再試行(リトライ)
再試行は、一時的なエラーに対処するため有効。例えば、HTTPステータスコード「503」や「504」では再試行が有効だが、「404」では意味がない。
再試行を行う際は、タイムアウトと再試行間隔を考慮し、全体のタイムアウトバジェットを守ることが重要。再試行の回数や待機時間も設定により調整する。
バルクヘッド
「バルクヘッド」は、システム内で障害の影響を最小化する手法。船の隔壁のように、システム内で問題が発生した場合でも、他の部分が影響を受けずに機能し続けるように設計する。これには、下流接続ごとに個別の接続プールを使用したり、システムを機能ごとに分割して関心事を分離することが含まれる。さらに、リソースの飽和を防ぐために「負荷制限」を導入し、場合によってはリクエストを拒否することもある。
サーキットブレーカー
サーキットブレーカーは、システムの過負荷や障害が他の部分に影響を与えるのを防ぐ仕組み。一定回数失敗が続くと、リクエストを即座に失敗させ、システムを守る。復旧後は自動で回復し、手動での管理も可能。
例えば、AdvertCorpでは、広告システムが遅延しても他の部分は正常に動作し続け、顧客に問題を伝えることができた。
分離性
サービス間の依存性が高くなるほど、あるサービスの問題が他のサービスに与える影響が大きくなる。サービスを分離することで、影響を最小化でき、調整の手間が減少し、チームの自律性も高まる。
ただし、物理的・論理的な分離にはコストがかかり、管理が複雑になる。分離による堅牢性向上とコスト・複雑さの増加のバランスを取ることが重要。
冗長性
冗長性を高めることで、コンポーネントの堅牢性が向上する。どの程度の冗長性が必要かは、コンポーネントの障害モード、影響、追加コストによって決まる。
冗長性は、負荷の増加に対応するためにも有用であり、スケーリングとの違いも考慮する必要があります。
ミドルウェア
メッセージブローカーは、リクエスト/レスポンスやイベントベースの対話を効率的に管理できるミドルウェア。その特性の一つに配信保証があり、ブローカーがメッセージの再試行やタイムアウトを処理する。これにより、ユーザーは詳細なエラーハンドリングを自分で行う必要がなくなる。
冪等性
冪等性(べきとうせい、idempotent)は、同じ操作が何度適用されても結果が変わらない性質を指す。例えば、顧客にポイントを付与する操作が冪等でない場合、同じリクエストを繰り返すとポイントが重複して付与される。これを冪等にするためには、リクエストに追加情報(例:特定の注文ID)を加えることで、同じ注文に対してポイントを一度だけ付与する。
冪等性は、エラーから回復する手段として有用で、例えばメッセージが処理済みかどうか不明な場合でも、再試行しても問題が生じない。また、イベントベースの連携でも便利で、同じメッセージが複数のワーカーによって処理されても影響がないようにする。
危険性の分散
システムのレジリエンスを高めるためには、サービスを複数のホストやデータセンター、アベイラビリティゾーンに分散させることが重要。これにより、単一の障害点による影響を避けられる。例えば、AWSではサービスを複数のアベイラビリティゾーンに分散させ、99.99%の稼働時間を目指すことが推奨される。また、サービスプロバイダーとのSLAを確認し、障害時の対応策を準備することも大切。
CAP定理
CAP定理は、分散システムにおいて「一貫性」「可用性」「分断耐性」の3つの特性を全て同時に満たすことができないことを示している。具体的には、障害が発生した場合、システムは2つの特性しか保証できない。
- 一貫性は、すべてのノードが同じデータを提供すること
- 可用性は、すべてのリクエストに応答すること
- 分断耐性は、ネットワーク分断が発生してもシステムが機能し続けること
例えば、2つのデータセンターで構成されるサービスがあり、ネットワーク障害によりデータベース間の同期が取れなくなる場合、どちらかのデータセンターで書き込まれたデータがもう一方に伝播しないことがある。多くのデータベースは、後で復旧できるようにキューイングをサポートしていますが、その間にデータの整合性が保たれないことがある。
一貫性を犠牲にする
一貫性を犠牲にする場合、システムは可用性と分断耐性を優先する。例えば、DC1でデータが変更された場合、DC2のデータベースではその変更が反映されない可能性がある。DC2の在庫ノードへのリクエストは古いデータを返すことになるが、システムは依然としてリクエストに対応可能であり、「可用性」は保たれている。これが「AP」システムの特徴。このような状況では、将来的にデータの再同期が必要となり、分断が長引けば再同期が難しくなる。
「結果整合性(eventually consistent)」とは、データが最終的には一貫性を持つようになるが、即時に更新が反映されるわけではないことを意味する。ユーザーは、短期間に古いデータが表示されることを受け入れる必要がある。
可用性を犠牲にする
可用性を犠牲にして一貫性を維持する場合、分断時にノード間でのデータ同期ができないため、リクエストへの応答を拒否せざるを得なくなる。この結果、システムは「CP」システム(Consistency, Partition tolerance)として機能する。データベースノード間のトランザクション読み取りやロック管理は非常に難しく、分散システムでは特に困難。複数ノードの一貫性を確保することは非常に難しく、独自に実装するのではなく、既製のデータストアやサービスを利用することが推奨される。
分断耐性を犠牲にするか
CAシステムは分散システムには存在しない。分断耐性を欠いたシステムはネットワーク上で動作できず、分散システムでは必須の特性だから。
APかCPか
APとCPシステムの選択は「状況次第」。
APシステムはスケーリングが簡単で、5分前のデータでも問題ない場合に適している。例えば在庫管理システムには向いている。
一方、CPシステムは分散一貫性を重視し、銀行などのシステムでは最新データが重要となるため、CPが適している。
この選択のトレードオフを理解することが重要です。
オール・オア・ナッシングではない
APとCPは、システム全体で選択する必要はない。例えば、MusicCorpのカタログでは古い情報でも問題ないためAPでよいですが、在庫サービスではCPを選ぶべき。サービスごとにCAP定理のトレードオフを考慮し、必要な一貫性や可用性を決める。例えば、ポイント残高サービスでは、表示される残高が古くても構わないが、一貫性を保つ必要がある場合はCPが適している。Cassandraのように、読み取り時に一貫性レベルを調整することで、トレードオフを細かく設定できる。CAP定理は克服するものではなく、異なる機能に対してAPとCPを使い分けること。
そして実世界
実世界の問題を扱うシステムでは、完全な一貫性を保つことは難しく、特に物理的な品目を反映した在庫システムなどでは、APシステムが適切な場合が多い。例えば、あるアルバムが99枚あると表示されていても、実際には床に落ちて壊れていることがある。この場合、APシステムにすることで、構築やスケーリングが容易になり、後でユーザーに通知して対応することができる。実世界の記録はすべてを正確に把握することが難しく、APシステムが現実的な選択になることが多い。CPシステムは複雑で、すべての問題を解決できるわけではない。
カオスエンジニアリング
カオスエンジニアリングは、システムが予期しない混乱に耐える能力を確信するために、実際の本番環境で実験を行う手法。Netflixがその実践者として有名だが、その目的はシステムの堅牢性や持続的な適応性を向上させること。カオスエンジニアリングの定義は曖昧であり、多くの人がその意味を「システム上でツールを実行して結果を確認すること」と捉えがちだが、より広い観点では「システム全体(ソフトウェア、ハードウェア、プロセス、文化など)のレジリエンスを試す」という考え方が重要。つまり、カオスエンジニアリングは単なるマシンの電源を切る実験ではなく、システム全体の信頼性を確立するための方法論として理解すべき。
ゲームデイ
「ゲームデイ」とは、システムやプロセスの準備状況をテストするために実施される訓練で、通常は計画的に行われるが、理想的には抜き打ちで開始される。この訓練では、現実的ながら架空の状況を模して、人やプロセスをテストすることができる。例えば、Googleではサーバ障害や大規模災害(地震など)のシミュレーションが行われ、システムの弱点を探ることができた。特に、ボブという社員への過度な依存をテストするためのゲームデイでは、ボブがチームを助けられない状況をシミュレーションし、チームが誤って本番環境でログインしてしまい、データを破壊するという教訓を得た事例もある。このような訓練を通じて、システムの問題を発見し、改善するための重要な学びが得られる。
本番環境実験
Netflixはその巨大な事業規模とAWSインフラを基盤にすることから、障害に対する耐性を重要視している。Netflixは、障害が発生する可能性を認識し、それに対処できるようなソフトウェアを開発する必要があることを理解した。そのため、実際に障害を引き起こすツールを使ってシステムをテストしている。
最も有名なツールが「Chaos Monkey」で、これは本番環境でランダムにマシンを停止させる。これにより、システムが実際にどのように障害に耐えるかを確認できる。また、Netflixは「Simian Army」と呼ばれる障害ボット群を使って、さらに高度なテストを行っている。例えば、Chaos GorillaはAWSのアベイラビリティゾーン全体を除外し、Latency Monkeyは遅いネットワーク接続をシミュレートする。
このような実際の本番環境で障害を発生させるテストは、システムが本当に堅牢であるかを試す究極の方法といえる。
堅牢性の先へ
カオスエンジニアリングは、アプリケーションの堅牢性を向上させる手段として有用。堅牢性は、システムが想定される問題にどの程度対応できるかを指す。Netflixは、特定の仮想マシンに依存しないよう、Chaos Monkeyを使って本番環境でテストし、システムが「想定される」問題に耐えられるようにした。しかし、カオスエンジニアリングはその適用範囲を広げ、システムのレジリエンスを継続的に確認するためのアプローチとして利用できる。
避難
問題が発生した際、復旧後には責任の追及が行われることがよくある。特に「根本原因分析(RCA)」の概念において、問題の原因を個人に帰すことが多いが、これは問題を深刻にし、学習の機会を奪う可能性がある。
例えば、オーストラリアの大手電話会社テルストラが通信障害を起こした際、会社は迅速に「担当者のヒューマンエラー」が原因と特定し、その責任を個人に負わせた。これにより、会社の内部で責任を転嫁する文化が強まり、失敗を恐れる環境が生まれた。このような文化では、社員が問題を報告しづらくなり、再発防止のための改善が進まないことが多い。
重要なのは、失敗から学び、改善するための「学習文化」を作ること。失敗を認めることができる安全な環境を作ることで、堅牢なシステムが生まれ、より良い組織環境が作られる。従って、問題が発生した後には、情報を最大限に活用し、再発防止に向けた取り組みを進めることが大切。
まとめ
この部分では、システムのレジリエンス(回復力)を高めるためには、ソフトウェアやインフラだけでなく、人、プロセス、組織も考慮する必要がある。レジリエンスを実現するための4つの概念(堅牢性、回復性、グレースフルな拡張性、持続的な適応性)について、これらを適切に選択し、継続的に学びながら改善していくことが重要
Discussion