Richard Rodger氏の講演"Surviving Microservices"より - メッセージ,パターンマッチング,障害

ベルリンで開催されたmicroXchg 2016カンファレンスでRichard Rodger氏は,“Surviving Microservices”と題したプレゼンテーションを行なった。マイクロサービスアーキテクチャの安定稼働を維持したいと望む開発者を対象とした,実践的なガイドだ。講演で議論されたおもなテーマは,メッセージ思考システムのメリット,サービス間コミュニケーションにおけるパターンマッチングの使用,障害時の対処,Seneca.jsマイクロサービスフレームワークの紹介などだ。

InfoQは先日,nearFormの共同設立者でCTOのRodger氏と席を共にする機会を得て,この講演のテーマについてさらに詳しく聞くことができた。話題はさらに,Seneca.jsフレームワークを開発した動機(およびマイクロサービスプラットフォームの現在の状況における位置付け)や分散システムにおける効果的な障害処理,メッセージ指向コミュニケーションを使用することのメリットにも及んでいる。

Rodger氏はいくつかの興味深い洞察を披露してくれた - マイクロサービスには,特に分散システムとして実装された場合に“禁断の障害モード”がいくつか存在すること。マイクロサービスの価値は,オブジェクト指向言語に感化された不要な複雑性ではなく,疎結合なコンポーネント性にこそあるということ。さらには,マイクロサービスコミュニティにおける“標準”的な実装を追い求めることによって,私たちは,かつてのSOA(サービス指向アーキテクチャ)で犯した過ちを繰り返そうとしているのではないか,という指摘だ。

InfoQ: こんにちはRichardさん,InfoQのために時間を頂いてありがとうございます。microXchgでの講演について,InfoQ読者に概要を説明して頂けますか?

Rodger: マイクロサービスは黄金の槌(golden hammer)ではありません!ソフトウェアエンジニアリングのプラクティスにおける新たな進歩が,“根拠のない熱狂”によって希薄になってしまうことが少なくないのは,私たちの業界の不幸な特徴と言えます。実際にマイクロサービスを利用してみれば,他のすべての分散システムと同じように,禁断の障害モードとでも呼ぶべきものの存在に気付くはずです。私たちも実際にそうでした。ですから,私たちの体験についてお話したいと思います。

まず指摘しておかなくてはならないのは,これも私たちの経験からなのですが,このような障害モードの存在が,必ずしもマイクロサービスアーキテクチャの重要なアドバンテージを損なうものではない,という点です。私たちがマイクロサービスを使うのは,それが技術的負債を緩和することで,私たちの行動を迅速にしてくれるからです。継続的デリバリやスケールアップが,はるかに簡単に実現できるようになります。さらにマイクロサービスは,開発者にとって,開発をより楽しめるものです。新機能をローカルで検証するために,システム全体が仕上がってくるのを待つ必要はありません – 関連するサービスだけを再起動すればよいのです。コード-ビルド-テストという開発サイクルが,分単位ではなく,秒単位で計測できるようになります。

講演では小さなデモンストレーションシステムを使って,サービスが,もっと重要なのはサービス間のメッセージが,どのように作用するのかを説明します。メッセージは,同期(応答を待機する)と非同期(待機しない)の2つに分類されます。また,メッセージに応答するサービスも,コンシューマ(単独で動作する)とオブザーバ(複数のサービスがメッセージに対応可能)に分類されます。つまりメッセージ処理には,4種類あるということです。それぞれの処理には,どのような障害が発生するのでしょうか。講演ではこの処理モデルを使って,それぞれの障害モードについて,さらにそれらを軽減する方法について議論します。

マイクロサービスアーキテクチャは,これまでのモノシリックアーキテクチャのように決定論的ではないので,従来の生存本能(優れたテスト,検証済みのスキーマ,プロジェクト管理理論)はあまり効果がありません。障害を日常的な出来事として受け入れられるような,新たな考え方を取り入れる必要があります。障害を許容するシステムを構築するのです。この点はアーキテクチャの全レベルに影響するので,講演では,それぞれの障害モードのコンテキストでこれらを検証します。

InfoQ: カンファレンスでは,マイクロサービスフレームワークのSeneca.jsを紹介していましたが,このようなツールキットを開発した動機について,詳しく教えて頂けますか?

Rodger: 実はこのフレームワークは,私たち(nearForm.com)が2010年から使っていたものなのです!この2年でオープンソースコミュニティとして立ち上がり,順調に成長して,素晴らしいものになりました。このフレームワークは元々,ビジネスロジック機能を構成するための手段として始まっています。どういう意味かって?そうですね,適切なソフトウェアコンポーネントを開発するというのは,実はとても難しいことなのです。ありとあらゆる設定が可能であったり,すべてのケースをカバーする巨大なAPIを提供したりしても,実際には役に立ちません。マスタするのに時間が掛かり過ぎる上に,バグの入り込む余地がたくさんあるからです。UNIXのパイプのようなコンポーネントモデルが実用的なのは,“構成(Composition)”が可能だからです。これは,小さなものを組み上げて大きなものを構築するという,極めてシンプルなアイデアです。

これが簡単にできるトリックは,その小さなものが互いに”スナップ”できるという点にあります。そのためには統一された,シンプルなインターフェースモデルが欠かせません。現在のオブジェクト指向言語では複雑過ぎます。機能が豊富過ぎることがかえって仇になっているのです。オブジェクトがコンポーネントモデルとしてうまく行かないのはこのためです。Gang-of-Fourの書籍“デザインパターン”は,オブジェクトがコンポーネントモデルとして失敗している,その決定的証拠といえます。一体なぜ,安全な設計パターンのセットなどが必要なのでしょう?古い方法に対して自由にプラグインできるということが,コンポーネント構造の本質だったのではないのでしょうか!

私は現在,コンポーネントのコミュニケーション媒体としてスキーマフリーのJSONドキュメントを使用した,メッセージ指向のコミュニケーションを実践しています。そこで出くわしたのが,アイデンティティに関する問題でした。コンポーネントAがコンポーネントBを使用するためには,Bのことを知っていなくてはなりません。Bのアイデンティティを知る必要があるのです。コンポーネント間の結合は無数にありますから,これは大きな問題です。オブジェクト指向の世界では,メソッドを呼び出すオブジェクトへの参照を持つ必要があります。大規模システムの複雑性とスケールでこれを実践するには,依存関係逆転(Dependency Inversion)のような最新技術をキャッチアップできなくてはなりません。

私のアプローチは違います。パターンマッチングを使うのです。コンポーネントがメッセージを必要としているのであれば,それは一目で分かります。必要なメッセージにマッチするパターンを(JSON構造のテンプレートとして)用意してください。それをトップレベルのプロパティのリテラル値と照合すればよいのです – ごく単純なことで十分なのだ,と分かって頂けると思います。システム全体を構築するには,これで十分過ぎるくらいです。コンポーネントモデルとして,構成を簡単なものにしてくれます。Senecaには現在,ほとんどのデータベースやメッセージバス,サービスレジストリなど,あらゆる種類のものに接続可能な200を越えるプラグインがありますから,それをベースとしてユーザアカウントやプロジェクト,さらにはハイパーカードデータモデルなどのビジネスロジックコンポーネントを構築することができます。

モデルが完成すれば,次のステップは明白です – それをネットワーク全体にスケールアップするのです。そのために私たちは,トランスポート独立の原則を採用することにしました。Senecaマイクロサービスは,メッセージをAからBに渡す手段についてまったく関与しません。HTTP RESTでも,メッセージバスでも,あるいはプッシュ/サブスクライブでも,何でも構わないのです。サービスの観点から言えば,メッセージを取得し,送信するだけです。それがすべてなのです。さらにそれらのメッセージは,ネットワークの不安定さの影響を前提としています。

Senecaはメッセージをローカルであると仮定していません - 障害の発生を前提としているのです。例えば,メッセージの繰り返しやループの対策として,内部的にサーキットブレーカが用意されています。ネットワークトポロジをサービス実装から完全に分離することによって,驚くべき柔軟性を実現します。例えば,関数コールをトランスポート機構として使用するだけで,Senecaマイクロサービスをモノリスに変えることさえ可能なのです!この方法はローカル開発や,とりあえずモノリスで立ち上げてみたい場合にはとても便利です。総じてSenecaは,マイクロサービスを展開する方法に前提を設けていない,ということになります。

InfoQ: ここ最近,Go(言語)やJavaScript言語の世界を中心に,マイクロサービスフレームワークが急増しているようですが,これについてはどう思われますか?明確な勝者が現れるでしょうか,あるいは,現在の状況が標準が生まれる推進力となるのでしょうか?

Rodger: 標準はまったく必要ではありません。SOAアーキテクチャの過ちを繰り返してはならないのです。マイクロサービスは標準を必要としていません。考えてみてください。例えばSenecaで,純粋なHTTP RESTマイクロサービスと通信したいとしましょう。それにはSenecaのメッセージをHTTPリクエストに変換する,ちょっとしたコードのライブラリがあれば十分です。他の場合には,また別の変換コードを用意すればよいことです。Akkaなどのフレームワークや,あるいはKafkaを共通のメッセージログとして使用するアーキテクチャであっても,これと同じことが言えます。メッセージ変換はロケット工学ではありません。ただし,Postelの法則 – “受け入れるものには寛容であること,発するものには厳格であること” – を適用することは当然必要です。厳格なスキーマを持たないことが重要な理由がここにあります – 厳格なスキーマは,アーキテクチャのメリットを損なうものだからなのです。

互いに干渉しないこと – それこそがマイクロサービスの哲学です。パーティには誰でも大歓迎なのです。

InfoQ: 講演の中で,マイクロサービスの障害発生の方法について(“分散コンピューティングの8つの誤謬”を参照しながら)説明していましたが,それにまつわるホラーストーリや,可能ならばその問題に対処した方法について教えて頂けますか?

Rodger: それは話せません!私たちがnodezoo.comなどサンプルシステムを開発しているのは,クライアントが最高レベルの機密性を望んでいるからなのです。私たちは彼らを戦略的イニシアティブで支援しています。競争市場においては非常に大きな意味があるのです。これは非常に難しい部分で,稼働中のシステムについて直接話すことのできる,NetflixやUberのような消費者志向の企業を本当にうらやましく思います。私たちにはそれができないのです。

ですからここでは,一般論としてのメモリリークについてお話しましょう。誰にでもある問題ですし,システムを運用している人たちにとって,たくさんの眠れない夜を過ごすことになる原因でもあります。その上,運用段階に入る前に見つけ出すのが非常に困難です。ユニットテストのカバレッジを100%にしても役に立ちません。最悪のシナリオでは,数分毎に障害を起こすシステムの再起動をずっと続けることになります。そこから発生する応答遅延は,ユーザエクスペリエンスを悲惨なものにするでしょう。システムは正確にはダウンしていませんが,立ち上がっているとも言えません。そして間違いなく,損害を抱えることになります。

マイクロサービスを使えば,この問題を覆すことができます。サービスを長時間稼働するものと仮定せずに,頻繁に,すぐに終了するように設計するのです。高負荷のロードにサービスするためには,インスタンスをたくさん立ち上げます。1時間を半減期にするとよいでしょう。ただし,ランダム性の採用が非常に大切です – システム全体に障害を引き起こすような,意図的なフィードバックの導入を回避することができるからです。半減期というアイデアは物理学から拝借したもので, 例えば100のサービスで開始して,1時間後には(細かい点を追求しなければ,最大寿命として)50個が残る,という意味です。こうすれば,メモリリークに対する耐性は大幅に向上します。コードにメモリリークがあったとしても,それが顕在化するほど個々のサービスが長時間動作しないので,ほとんど問題にならないのです。

サーバのメモリリークに関しては,マイクロサービスのアーキテクチャも一役買ってくれます。問題のあるサービスのインスタンスをたくさんスピンアップするのです。モノリスのインスタンスをいくつも起動することに比べれば,これははるかに低コストです。半減期の短いインスタンスをたくさん稼働しておくことで,問題解決のための時間が確保できます。通常は当面の策として,問題がないと分かっている旧バージョンのサービスを再度デプロイして,リークのある新バージョンを停止していきます。開発した新機能は失われますが,システムは安定するので,問題点を調査する時間的余裕が得られます。もっとも,マイクロサービスは段階的展開による継続的デリバリが容易なプラクティスですから,こういった状況はいずれにせよ,システム全体に影響が及ぶ前に気付くことができるでしょう。

この件に関しては,以前githubにいたZach Holman氏による,ソフトウェアのデプロイメントについての素晴らしい記事があります。記事自体はモノリスの観点で書かれていますが,マイクロサービスならばこのアイデアをずっと簡単に実装できるということは,すぐに理解できるでしょう。具体的に言えば,マイクロサービスの場合には,デプロイメント状況をマップするための機能フラグは必要ではありません。

InfoQ: microXchgの講演では,メッセージ指向システムのメリットを強調していましたが,その根拠について,もう少し詳しく説明してください。また,マイクロサービスの通信として,このスタイルを(RESTやRPCと比較検討した上で)採用したいと考えている人たちへのアドバイスもお願いできますか?

Rodger: それがマイクロサービスアーキテクチャと呼ばれること自体が残念ですね。メッゼーシは第一級市民であり,非常に重要なのです。ここでは,私たちがマイクロサービスシステムを構築する方法について紹介しましょう。私たちは稼働中のシステムを毎週デリバリしています。これがイテレーション単位です。各イテレーションの内容は,追加したサービスのリストと,削除したサービスのリストとして記述することができます。マイクロサービスアーキテクチャは非常にダイナミックなので,ビジネス要件にも素早く対応可能です。

ビジネス要件を確認したならば,ビジネスアクティビティの識別にそれらを使用します。 これらアクティビティが,メッセージのインタラクションに相当します。メッセージは単一の場合も,メッセージのチェーンの場合もあります。これでメッセージのリストができました。次はこれらのメッセージを論理グループにまとめます。このグループがサービスになります。ですから,まずはメッセージから着手して,そこからサービスを導出してください。

最初にすべてのメッセージを定義する必要はありません。誤ったメッセージを受信した場合でも,効率的にシステムからそれらを除く方法が常にあります。窮地に追い込まれるようなことは決してありません。

この処理を行なうには,パターンマッチングが重要です。これによって変換や,マルチバージョンのデプロイメントが可能になります。パターンのリストを作成できれば,それがドメイン言語になるのです。

InfoQ: 今日は素晴らしいお話を聞くことができました。他に何か,InfoQ読者に伝えておきたいことがありますか?

Rodger: それではお言葉に甘えて。私は今,‘The Tao of Microservices’という本を執筆中です。過去数年間にわたって開発を続けてきた同種のシステムに関する私たちの経験を,詳しく解説しています。現在はManningから早期リリースが公開されています。

マイクロサービスに関する議論は,Twitterの@rjrodgerなどでも行なっています。

Richard Rodger氏のmicroXchgでの講演 “Surviving Microservices”のビデオが,カンファレンスのYouTubeチャネルで公開されている。