BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル マイクロサービスにおけるデータ管理

マイクロサービスにおけるデータ管理

ブックマーク

原文(投稿日:2018/04/23)へのリンク

Stitch Fixのエンジニアリング担当VP、Randy Shoup氏によるQCon San Francisco 2017のプレゼンテーションより。

 

Stitch Fixでエンジニアリング担当VPを務めているRandy Shoupです。マイクロサービスにおけるデータ管理について、自分の経験から学んだことをお伝えします。

Stitch Fixは米国の衣料品小売業者で、テクノロジとデータサイエンスを活用して、顧客が好みの衣服を見つける手助けをしています。Stitch Fixの前は、放浪する「CTO as a service」として、企業がテクノロジやその状況について検討する手助けをしていました。

その前は、GoogleでGoogle App Engineのエンジニアリング担当ディレクターを務めていました。Google App Engineというのは、GoogleのPaaSで、HerokuやCloud Foundryのようなものです。さらにその前は、約6年半、eBayでチーフエンジニアを務め、何世代かの検索インフラの構築をチームとして手助けをしました。もし皆さんがeBayを訪れて、気に入ったものを見つけられていたなら、私のチームはいい仕事をしたということです。もしそうでなかったなら、誰のせいなのか、もうお分かりですね。

モノリスをマイクロサービスに分解することから得られた学びとテクニックをお伝えするため、Stitch Fixについて少しお話させてください。Stitch Fixは標準的な衣料品小売業者とは逆のことをします。オンラインで買い物をしたり、自分でお店に行ったりするのではなく、専門家があなたのためにそれをやってくれるとしたらどうでしょうか?

まず、60から70の質問からなる詳細なスタイルプロフィールを記入してもらいます。おそらく20分から30分かかるでしょう。サイズ、身長、体重、好みのスタイル、腕を見せたいか、お尻を隠したいか、... 非常に詳細で個人的なことを質問します。なぜだと思いますか? あなたの人生で、あなたに合った服の選び方を知っている人は、あなたについて知っているはずです。だから、こうしてはっきりと質問し、データサイエンスを活用して、これを実現しています。クライアントには、玄関先まで5つの商品を届けます。全国3,500名のスタイリストのうちの一人が手作業で選んだものです。あなたは気に入った商品をキープして代金を支払い、残りを無料で返品します。

裏では、人間とマシンとの間で、様々なことが行われています。マシン側では、毎晩すべての在庫を調べて、クライアント一人ひとりに対して、予測される購入可能性を計算します。つまり、Randyにこのシャツを送ったときに彼がキープする条件付き確率です。Randyがこのシャツをキープする可能性は72%、このパンツは54%、この靴は47%のようなものだと考えてください。この部屋にいる皆さん一人ひとり、値は違ってくるでしょう。そのパーセンテージを計算するため、1つに重ね合わせた機械学習モデルを持っています。これにより顧客ごとにパーソナライズされたアルゴリズムによる推薦を構築し、スタイリストに送られます。

基本的に、スタイリストはあなたのために買い物をします。あなたの代わりに5つの商品を選ぶとき、アルゴリズムによる推薦を見ながら、ボックスに何を入れるか考えます。

衣装一式をまとめるには、人間が必要です。今のところ、機械にはできません。「夕方にある結婚式のためにマンハッタンに行くので、それに合ったものを送ってください」という要求に答えることもあります。どうすべきか、機械にはわかりませんが、人間は機械が知らないことを知っています。

この手のことをやるには膨大なデータが必要になります。私が特に興味深く思っているのは、Stitch Fixではデータサイエンスとエンジニアリングの比率が1対1であることです。私が仕事をしているチームには、ソフトウェアエンジニアが100人以上、データサイエンスに取り組むデータサイエンティストとアルゴリズム開発者がおよそ80人います。私の知る限り、この比率は業界で独特です。これほど1対1に近い比率の会社は他に知りません。

では、データサイエンティストたちと何をしているのでしょうか? 賢明な方々なら、それが報われるとわかるでしょう。

私たちは同じテクニックを衣服の仕入れにも適用しています。アルゴリズムによる推薦により、バイヤーは「よし、次のシーズンは白いデニムをもっと仕入れよう」「コールドショルダーが不足しそうだ」「次はカプリパンツが流行するぞ」といったことがわかるのです。

どの倉庫に何を保管するか、という在庫管理にもデータ分析を使っています。物流と配送業者の選択を最適化することで、私たちにとって最小のコストで、顧客の希望日に商品が届くようにしています。そして、需要予測といったスタンダードなこともしています。

私たちがやっているのは物理的なビジネスです。物理的に衣服を仕入れ、倉庫に格納し、皆さんに出荷します。eBayやGoogle、その他多くのバーチャルなビジネスとは違って、需要の見込みが外れても、需要が見込みの倍あっても、歓迎すべきことではありません。半数の顧客にしか商品を提供できないことを意味しており、大失敗です。クライアントの数が倍になれば、倉庫、スタイリスト、従業員なども倍にしなくてはなりません。こうしたことを正しく行うことは、私たちにとって非常に重要なことです。

繰り返しになりますが、一般的なモデルは、人が得意なことには人を、機械が得意なことには機械を使うということです。

この規模のシステムを設計する場合、たくさんの目標があります。開発チームが独立して早足で前進し続けるようにする必要があります。私たちはこれを「feature velocity」と呼んでいます。私たちにはスケーラビリティが必要です。ビジネスが成長するにつれて、インフラストラクチャを成長させる必要があるためです。コンポーネントが負荷に対してスケールする、かかる需要に対してスケールする必要があります。また、コンポーネントにはレジリエンスが必要です。障害は隔離され、インフラに伝播しないようにする必要があります。

こうした要件を抱えたハイパフォーマンスな組織は、いくつかやっていることがあります。The DevOps Handbookにおいて、Gene Kim氏とNicole Forsgren氏らは、パフォーマンスの高い組織と低い組織の違いを調べています。組織のパフォーマンスが高ければ高いほど、すばやく動き、安定しています。スピードと安定性のどちらかを選ぶ必要はありません。両方を兼ね備えることができるのです。

よりパフォーマンスの高い組織は、1ヶ月に1度ではなく、1日に何度もデプロイします。ソースのコミットからデプロイまでの待ち時間は、1時間未満です。他の組織だと1週間かかるかもしれません。これがスピードです。

安定性に関して、ハイパフォーマンスな組織は障害から1時間で復旧します。パフォーマンスの低い組織では、おそらく1日かかるでしょう。障害の発生率も低くなっています。ハイパフォーマンスな組織では、デプロイしてうまくいかずにロールバックを必要とする頻度はゼロに近くなりますが、遅い組織だと、頻繁にする必要があるでしょう。

スピードと安定性だけではありません。技術的な指標だけではありません。収益、市場シェア、生産性といったビジネス目標を上回る可能性も、ハイパフォーマンスな組織の方が2.5倍高いのです。そのため、これはエンジニアだけでなく、ビジネスの人たちにとっても重要なことです。

マイクロサービスへの進化

私が「CTO as a service」の仕事をしていたとき、よくこう聞かれました。「RandyはGoogleとeBayで働いていたんですよね、どうやっていたか教えてくれませんか」

私は「教えるのは約束するけど、その通りやらないことを約束してくださいね」と答えます。そして、GoogleとeBayの秘訣を隠しておきたい訳でなく、Googleのようなエンジニア15,000名のチームと会議室に集まっているスタートアップの5名のチームとでは、抱えている問題が違うためです、と言います。人数にして3桁の違いがあり、企業が異なれば、規模が異なれば、解決策も異なるのです。

とはいえ、名前を耳にしたことがある企業がどうやってマイクロサービスへと進化していったのか、話をするのは大好きです。彼らはマイクロサービスから始めたのではなく、時間をかけてそこへ進化していきました。

eBay

eBayは現在、5回目のインフラストラクチャの完全な書き直しをしています。eBayは1995年、モノリシックなPerlアプリケーションとして始まりました。創業者がWebと呼ばれるものをいじり、Labor Dayの週末3日間で最終的にeBayになるものを構築しました。

第二世代は、モノリシックなC++アプリケーションでした。ひどいと、1つのDLLで340万行ものコードになりました。クラスあたりのメソッド数は、コンパイラの限界である16,000に達していました。モノリスを抱えている人はたくさんいると思いますが、これよりひどいものはほとんどないと思います。

第三世代はJavaで描き直されました。ただし、マイクロサービスと呼べるものではなく、ミニアプリケーションでした。彼らはサイトを、220個のJavaアプリケーションにしました。検索部分に1つ、購入部分に1つという具合に、220個のアプリケーションにしたのです。eBayの現在のインスタンスは、多言語のマイクロサービスの集合という特徴をかなり備えています。

Twitter

Twitterも同様の進化を遂げており、およそ第三世代にあたります。まずRailsアプリケーションとして始まり、これにはMonorailというニックネームが付けられていました。第二世代はフロントエンドをJavaScriptにして、バックエンドをScalaで書かれたサービスに抜き出しました。Twitterはアーリーアダプターであったためです。現在のTwitterは、多言語によるマイクロサービスの集合だと言えるでしょう。

Amazon.com

Amazon.comも同様の進化を遂げていますが、世代は明確ではありません。モノリシックなC++およびPerlアプリケーションとして始まりましたが、まだ製品ページにその痕跡を見ることができます。時々Amazon.comのURLで目にする"obidos"は、オリジナルのAmazon.comアプリケーションのコードネームです。Obidosはアマゾンにあるブラジルの地名なので、その名前が付けられたわけです。

2000年から2005年にかけて、Amazon.comはサービス指向アーキテクチャに書き直されました。サービスの多くはJavaとScalaで書かれました。その期間、Amazon.comはビジネスとして特にそれほどうまくはいっていませんでした。しかし、Jeff Bezosは信念を守り、サービス指向アーキテクチャですべてを再構築するよう、全社員を強制(もしくは強く推奨)しました。そして現在、Amazon.comを多言語のマイクロサービスの集合に分類しても構わないでしょう。

これらのお話すべてに共通するパターンがあります。どれもマイクロサービスから始まったものではありません。しかし、ある規模(おそらく私たちの0.1パーセントしか到達しないであろう規模)を過ぎると、いずれも最終的にマイクロサービスと呼べるものになっています。

マイクロサービスから始まったものはありません。ある規模を過ぎると、どれも最終的にマイクロサービスになるのです。

初期の技術判断によって後悔することにならないよう、私はこう言いたいです。皆さんはおそらく過度な設計をしています。

なぜそう言うのか?

1995年のeBayの競合やAmazon.comの競合を想像してください。この会社は、製品/市場フィット、ビジネスモデル、人々がお金を支払ってくれるものを見つける代わりに、5年で必要になるであろう分散システムを構築してきました。その会社について耳にしたことがないのには訳があるのです。

繰り返しになりますが、あなたのいるビジネス、あなたのチームのサイズについて考えてください。小さなスタートアップにいるなら、Amazon.com、Google、Netflixのソリューションは必ずしもあなたに合ったソリューションではないのです。

マイクロサービス

私が好きなのは、コードの行数ではなくインターフェイスのスコープとして、マイクロサービスの「マイクロ」を定義することです。

マイクロサービスは、1つの目的とシンプルで明確なインターフェイスを持ち、モジュールとして独立しています。注目すべき、影響を調べるべき重要なことは、Amazon.comが見つけたように、効果的なマイクロサービスは隔離された永続性を備えているということです。すなわち、マイクロサービスは他のサービスとデータを共有しているべきではありません。

マイクロサービスが期待通りにビジネスロジックを実行し、不変性を保証するためには、裏でだれかがデータを読み書きできてはいけません。eBayはこれを別の方法で見つけました。2008年、eBayは非常に賢い人たちと多大な労力をかけて、サービスレイヤーを構築したのですが、うまくいきませんでした。サービスは非常にうまく構築され、インターフェイスは非常にうまく直行していましたが、彼らはその検討に多大な時間を費やしました。ところが、サービスレイヤーの下には、アプリケーションから直接利用できる巨大な共有データベースがあり、自分の仕事をするのにサービスレイヤーを使う必要はありませんでした。そのため、だれもサービスレイヤーを使わなかったのです。

Stitch Fixも、自らの道を歩んでいます。モノリシックなアプリケーションは作りませんでしたが、私たちのモノリス問題は、モノリシックなデータベースを作ったことでした。

私たちはモノリシックなデータベースを分解し、そこからサービスを抽出しています。ただし、残しておきたいものもいくつかあります。

図1は、私たちの状況を簡単に示したものです。実際はもっと多くのアプリがあるのですが、1枚の画像に収まるだけにしています。

図1: Stitch Fixのモノリシックな共有データベース

基本的に、Stitch Fixにとって関心あるものをすべて含んだ共有データベースがあります。ここには、クライアント、出荷するボックス、ボックスに入れた商品、スタイルやSKUといった在庫に関するメタデータ、倉庫に関する情報、およそ175の様々なテーブルが含まれています。そして、70、80ほどのアプリケーションとサービスが、同じデータベースを使って、各自の仕事をしています。問題はここにあります。共有データベースはそれらチームの結合点になっており、独立ではなく相互依存を引き起こします。これは単一障害点であり、パフォーマンスボトルネックです。

そこで、アプリケーションとサービスを共有データベースから分離することにしました。これは多くの作業を伴います。

図2: 共有データベースの分割

図2は、共有データベースを分割するステップを示しています。Aが出発点です。実際の図はボックスとラインであふれるため、ここでは3つのテーブルと2つのアプリケーションだけ考えることにします。この例の場合、最初にやろうとするのは、クライアント情報を表すサービスを作ることです (B)。これはマイクロサービスの1つであり、明確なインターフェイスを持ちます。サービスを作成する前に、サービスのコンシューマとインターフェイスについて議論しました。

次に、共有データベースからテーブルを読み出す代わりに、サービスから情報を読み出すように、アプリケーションを変更します (C)。もっとも難しいのは、このラインを動かすことです。軽く考えている訳でなく、その難しさを図で表現するのは容易ではありません。このように変更することで、呼び出し側はデータベースに直接接続することはなく、代わりにサービスを経由することになります。それから、共有データベースにあるテーブルを、そのマイクロサービスにのみ関連づけられた独立したプライベートデータベースに動かします (D)。多くの大変な作業を伴いますが、これがパターンです。

次に、商品情報に対して同じことを行います。商品サービスを作成し、テーブルの代わりにそのサービスを使うように、アプリケーションを変更します (E)。それからテーブルを抽出し、そのサービスのプライベートデータベースにします。SKUやスタイルにも同じことをしていきます (F)。クライアントサービス(client-service)とコアクライアント(core client)データベースのペアのように、最終的に、それぞれのマイクロサービスの境界は、アプリケーションとデータベースの両方を囲むものになります (F)。

図のように、私たちはモノリシックなデータベースをすべて分解し、それぞれのマイクロサービスに永続性を持たせました。しかし、モノリシックなデータベースについて気に入っているところはたくさんありました。それらをあきらめたくはありません。異なるサービスとアプリケーション間で簡単にデータを共有できること、異なるテーブルの結合が簡単にできること、トランザクションといったものです。私たちは、複数のエンティティにまたがる操作を1つのアトミックな単位として実行したいのです。これらはすべて、モノリシックなデータベースによくあるものです。

イベント

各種データベース機能には、移行の次の部分へと進めるもの、進めないものがありますが、進めないものにも回避策はあります。その話に入る前に、皆さんご存知でしょうが、それほど正しく理解できていないアーキテクチャ上の構成要素、すなわちイベント、に触れておきます。Wikipediaではイベントを、状態の重大な変化、何か関心のあることが発生したということ、と定義しています。

従来の3層システムは、ユーザーやクライアントが使用するプレゼンテーション層、ステートレスなビジネスロジックを表現するアプリケーション層、リレーショナルデータベースによってサポートされる永続化層で構成されます。しかし、アーキテクチャとして、状態変化、すなわちイベントと呼ばれるもの、を表現する基本構成要素が欠けています。通常、イベントは非同期なので、誰もまだリッスンしていないイベントが生成されます。システム内の1つのコンシューマだけがリッスンすることもあれば、多くのコンシューマがサブスクライブしようとすることもあります。

私たちはイベントを、アーキテクチャに置ける第1級構成要素に昇格させ、マイクロサービスにそれを適用しています。

マイクロサービスのインターフェイスには、玄関が含まれていますよね。当然、同期のリクエストとレスポンスは含まれているでしょう。これは通常HTTPで、JSONかもしれませんし、gRPCやそういったものかもしれませんが、明らかにアクセスポイントを含んでいます。一方、あまり明らかではありませんが、皆さんにも納得してもらいたいものがあります。サービスが生成するすべてのイベント、サービスが消費するすべてのイベント、そのサービスにデータを出し入れする別の方法も、インターフェイスに含まれていることです。分析のためにサービスからバルクで読み出せること、アップロードのためにサービスにバルクで書き込めること、これらはみな、サービスのインターフェイスの一部です。簡単に言うと、私が言っているのは、サービスのインターフェイスには、データを出し入れする仕組みが含まれている、ということです。

道具箱にイベントが加わったので、共有データ、結合、トランザクションといった問題を解決するツールとして、イベントを使い始めています。イベントは共有データの問題に目を向けさせます。モノリシックなデータベースでは、共有データを活用するのは簡単です。アプリケーションを共有テーブルに向ければ、それでうまくいきます。しかし、マイクロサービスの世界では、共有データはどこにいくのでしょうか?

これには、いくつかの選択肢があります しかし、まずは議論に使うツール、あるいはフレーズを与えることにします。原則あるいはフレーズは、「single system of record」です。システムで関心のある顧客、商品、ボックスがあるなら、そのデータの基準である「system of record」となるサービスが1つだけ存在する必要があります。その顧客を所有する、その商品を所有する、そのボックスを所有するサービスは、システムにおいて1つだけである必要があります。顧客/商品/などの表現は、あちこちに(もちろんStitch Fixに)ありますが、システムにおける他のコピーはすべて、その「system of record」の読み出し専用で信頼できないキャッシュである必要があります。

読み出し専用で信頼できない、ということをよく考えましょう。別のところで顧客レコードを変更してはいけませんし、それを他のシステムに残そうと思ってはいけません。データを変更したければ、その「system of record」にアクセスする必要があります。それは顧客が何をしているか、ミリ秒精度で教えてくれる唯一の場所です。

これが「system of record」の考え方です。このアプローチをデータ共有に用いるとき、様々なテクニックがあります。最初に、もっとも自明かつ簡単なものは、その「system of record」から同期検索することです。

Stitch Fixのフルフィルメントサービスについて考えてみましょう。私たちは顧客の実際の住所に商品を出荷します。顧客データは顧客サービスが所有しています。顧客データの1つに顧客の住所があります。ひとつのソリューションは、フルフィルメントサービスが顧客サービスを呼び出して、住所を検索することです。このアプローチはどこも悪いところはありません。まったく正当な方法です。しかし、これがうまくいかないときもあります。おそらく、あらゆるものが顧客サービスと結合することは望んでいないでしょう。また、フルフィルメントサービスなどが頻繁に顧客サービスにアクセスすると、パフォーマンスが低下するでしょう。

もうひとつのソリューションは、非同期イベントとローカルキャッシュとの組み合わせに関係しています。引き続き、顧客サービスは顧客の表現を所有しますが、顧客データ(例えば顧客の住所)に変更があったとき、顧客サービスはアップデートイベントを送ります。フルフィルメントサービスはそのイベントをリッスンし、住所変更があると、その住所をローカルにキャッシュします。それから、フルフィルメントセンターは素早くボックスを発送します。

フルフィルメントサービス内のキャッシュには、他にもすぐれた特性があります。顧客サービスが住所変更の履歴を保持していない場合、フルフィルメントサービスで覚えておくことができます。これは大規模に起こります。注文開始から出荷時点までの間、顧客は住所を変更するかもしれません。私たちは適切な場所に確実に発送したいのです。

結合

モノリシックなデータベースでテーブルを結合するのは実に簡単です。SQL文のFROMに別のテーブルを追加するだけです、すべてうまくいきます。あらゆるものが1つの巨大でモノリシックなテーブルにある場合、これはうまく機能します。しかし、AとBが別のサービスの場合、SQL文ではうまくいきません。データが複数のマイクロサービスに分割されていると、概念的に結合するのは非常に困難です。

私たちには常にアーキテクチャ上の選択肢があり、結合の扱い方にも複数のやり方があります。最初の選択肢は、クライアントで結合することです。AとBで関心があるものは何でも結合できます。具体例として、注文履歴の生成を想像しましょう。発送されたボックスの履歴を見ようと、顧客がStitch Fixにアクセスしたとき、どうすればこのページを提供できるでしょうか。現在の顧客の情報(おそらく名前、住所、発送した件数など)を取得するため、注文履歴ページは顧客サービスを呼び出すことができます。それから注文サービスにアクセスして、その顧客の全注文の詳細を取得することができます。顧客サービスからある特定の顧客を取得し、注文サービスでその顧客にマッチする注文をクエリします。

これは基本的に、1つのサービスから全データを取得できないWebページすべてで使われているパターンです。繰り返しになりますが、これはこの問題に対する完全に正当なソリューションです。Stitch Fixではこのパターンをずっと使っていますし、あなたのアプリケーションでも至るところで使っていると思います。

ところが、パフォーマンスのためか信頼性のためか、古いサービスに頻繁に問い合わせていたりして、これがうまくいかない場合を想像してみましょう。

第二のアプローチは、データベース用語で「ビューのマテリアライズ」と呼ばれることを行うサービスを作成することです。商品フィードバックサービスを作成しているとしましょう。Stitch Fixでは、私たちがボックスを発送すると、お客さんは送ったものの一部をキープし、一部を返品します。私たちはその理由を知りたいと思っています。また、どの商品が返品されたのか、キープされたのか、覚えておきたいと思っています。これらを覚えるのに、商品フィードバックサービスを使いたいのです。おそらく特定のシャツで1,000あるいは10,000の品物があり、出荷するたびに、そのシャツに関する全顧客のフィードバックを覚えておきたいと思っています。これに私たちが抱えるであろう数万倍の在庫をかけてみましょう。

これをやるために、商品サービスを用意します。このサービスは、このシャツに関するメタデータを表現します。商品フィードバックサービスは、商品サービスからのイベント、新商品や売り切れ商品など、関心のあるメタデータの変更をリッスンします。また、注文サービスからのイベントもリッスンします。注文に関するすべてのフィードバックは、イベントを生成します。つまり、1つのボックスで5つの商品を送ったら、おそらく5つのイベントを生成することになるでしょう。消費フィードバックサービスはこれらのイベントをリッスンし、その結合をマテリアライズします。言い換えると、すべての商品に関して得られるフィードバックを、すべて1つのキャッシュに覚えます。もっと洒落た言い方をすると、商品と注文の非正規化結合を独自のローカルストレージに保持するということです。

よくある多くのシステムはずっとこうしたことをしてきており、そうしていると考えることすらありません。例えば、エンタープライズグレード(すなわち有償の)データベースシステムには、マテリアライズドビューという概念があります。これはOracleにも、SQL Serverにもあり、多くのエンタープライズクラスのデータベースには、ビューをマテリアライズするという概念があります。

多くのNoSQLシステムはこのように動いています。Dynamoにインスパイアされたデータストア、AmazonのDynamoDB、Cassandra、React、Voldemort、NoSQL由来のものはすべて、前もってそうすることを強制しています。リレーショナルデータベースは、簡単な書き込みに最適化されています。私たちは個々のレコードあるいは個々のテーブルに書き込み、読み出し側では、それらをまとめます。これに対し、多くのNoSQLシステムはその逆です。ストアするテーブルは、すでに求めていたクエリです。書き込み時に個々のサブテーブルに書き込む代わりに、読み出したいストアドクエリすべてに5回書き込みます。すべてのNoSQLシステムは、こうしたマテリアライズされた結合を前もってすることを強制します。

私たちがほぼ確実に使っている検索エンジンはすべて、特定のエンティティを別のエンティティで結合するという形式をとっています。地球上のすべての分析システムは、データの様々な部分を多数結合します。なぜなら、それが分析システムだからです。

このテクニックがもう少し馴染みのあるように聞こえるとよいのですが。

トランザクション

リレーショナルデータベースですばらしいのは、トランザクションの概念です。リレーショナルデータベースでは、1つのトランザクションがACID特性、すなわち原子性、一貫性、独立性、永続性を実現しています。モノリシックなデータベースではこれを実現できます。トランザクションは、私たちのシステムのデータベースに持たせたい機能の1つです。複数のエンティティ間でトランザクションを持つのは簡単です。私たちのSQL文では、トランザクションを開始し、挿入と更新を実行してから、コミットします。すべてが起こるか、まったく起こらないかのどちらかになります。

サービス間でデータを分離すると、トランザクションは困難になります。「困難」を「不可能」と言い換えてもよいでしょう。それが不可能だとどうしてわかるのでしょうか? 2フェーズコミットなど、データベースコミュニティには分散トランザクションに対する既知のテクニックがありますが、実際には誰もそんなことをしていません。その証拠に、分散トランザクションを実装しているクラウドサービスは、地球上に存在しません。なぜでしょうか? それはスケーラビリティを殺すためです。

そのため、トランザクションを持つことはできません。ですが、できることはあります。A、B、Cを1つの単位としてまとめて更新するか、まったくしないか、というトランザクションを、1つのSagaにするのです。Sagaを作成するために、トランザクションを個々のアトミックなイベントのステートマシンとしてモデル化します。図3を見るとわかりやすいかもしれません。Aを更新、Bを更新、Cを更新というのを、1つのワークフローとして再実装します。AはBサービスによって消費されるイベントを生成します。Bサービスはそれで何かして、Cサービスで消費されるイベントを生成します。ステートマシンの最後で、A、B、Cがすべて更新された最終状態になります。

図3: ワークフローとSaga

さて、どこかおかしくなったとしましょう。逆順に補正操作を適用することで、ロールバックできます。Cでやったことをやり直し、1つ以上のイベントを生成し、それから、Bサービスでやったことをやり直し、1つ以上のイベントを生成し、それから、Aでやったことをやり直します。これがSagaのコアとなる概念です。その背後には、非常に多くの詳細があります。Sagaについてもっと知りたければ、Chris Richardson氏のQConプレゼンテーション、「 Data Consistency in Microservices Using Sagas」を強くお勧めします。

ビューのマテリアライズと同様、私たちが日々使っているシステムの多くは、まさにこのようにして動いています。決済処理システムについて考えてみましょう。クレジットカードで支払いたい場合、1つの作業で、口座からお金が引き出されて、魔法のようにウォレットに入るようにしたいでしょう。しかし、実際にはそんなことは起こりません。裏では、決済処理業者や各種銀行、こうした金融マジックに関わる事柄が多数あります。

トランザクションを使うときの標準的な例として、Justinの口座からいくらか引き落として、それをRandyの口座に加えたいと思うでしょう。このように動いている金融システムはありません。代わりに、すべての金融システムはこれをワークフローとして実行しています。最初に、お金がJustinの口座から引き出され、数日間銀行にあります。それは望んでいる以上に銀行にあるのですが、最終的には私の口座に入ります。

別の例として、経費承認について考えてみましょう。おそらく皆さんは、カンファレンス後に経費承認を得る必要があるでしょう。これは即座に完了するものではありません。あなたは経費申請をマネージャに提出します。マネージャがそれを承認すると、さらに上司に回され、その上司が承認し、... と上がっていきます。それから、決済処理ワークフローのあと経費の償還が行われ、最終的に、あなたの口座かポケットにお金が入ります。これらを1つの単位にしたいでしょうが、実際にはワークフローとして発生します。複数ステップのワークフローはすべてこのようになります。

もしあなたがリビングでコードを書いているなら、最後に、IDEでリターンを打ったら、即座にコードがプロダクションにデプロイされると何が起こるか考えてみましょう。だれもそんなことはしません。これはアトミックなトランザクションではありませんし、そうであるべきでもありません。継続的デリバリパイプラインでは、コミットすると、たくさんのことを実行し、うまくいけば最終結果として、プロダクションにデプロイされます。これはまさにハイパフォーマンスな組織がやっていることです。自動的に起こることはありません。繰り返しになりますが、これはステートマシンです。あるステップが起こってから、次のステップが起こり、そのまた次のステップが起こります。もし途中でうまくいかなければ、取り消します。これは馴染みがあるはずです。私たちが日々使っているものは、このように振舞います。つまり、私たちが構築するサービスでこのテクニックを使っても、何もおかしくはないのです。

まとめると、私たちはアーキテクチャ道具箱のツールとして、イベントの使い方について見てきました。イベントを使って、システムの各種コンポーネント間でデータを共有する方法を紹介しました。また、イベントを使って、結合の実装を助ける方法について考えました。そして、トランザクションの実行を助ける方法について考えました。

著者について

Randy Shoup氏はシリコンバレー25年のベテラン。小さなスタートアップから、中規模なところ、eBayやGoogleまで、様々な企業で、シニア技術リーダーおよび幹部として働いてきた。現在はサンフランシスコのStitch Fixで、エンジニアリング担当VPを務める。特に、文化・技術・組織の結合に情熱を抱く。

Thomas Betts氏はIHS Markitの主任ソフトウェアエンジニア。プロフェッショナルなソフトウェア開発に20年の経験を持つ。彼のフォーカスは、常に、顧客を喜ばせるソフトウェアソリューションを提供すること。小売、金融、ヘルスケア、防衛、旅行などを含む、様々な業界で仕事をしてきた。妻と息子とともにデンバーに在住。ハイキングや美しいコロラド探索が大好き。

この記事に星をつける

おすすめ度
スタイル

BT