CAP定理を時間と空間に拡張する「落ちず、速く、信頼できるサービスをどう作るのか?」

Top / テクニカルノート / CAP定理を時間と空間に拡張する「落ちず、速く、信頼できるサービスをどう作るのか?」
この記事に関連するサービス、プロダクト
High Response Private Cloud 6Gv (HRPC) / InfiniCloud HV / Solaris SPARC Private Cloud

落ちず、速く、信頼できるサービスをどう作るのか?

オンプレミスでもクラウド上でも、システムを作る時に次の2つはどちらも「大切だ」と思うことでしょう。

  1. サーバの可用性(要求が正しく返ること。実ケースにおいては、落ちないサーバであること)
  2. サーバの一貫性(書いたデータが、順序正しく保存されること。実ケースにおいては、データが壊れずに保存されていくこと)

しかし分散システムにおいては、実際は「どちらか1つを優先するしかない」ことになります。

どういうことなのでしょうか?


データの一貫性を過度に重視することが可用性を低下させている

データストアをどのように設計するか?

分散システムを構築するにあたり、「データストアをどのように設計するか」は重要なテーマのひとつです。

残念ながら機械である以上サーバーはいつかは必ず壊れます。複数台のサーバーで構成されているシステムの内で、たった1台のサーバが故障するだけでサービス停止が発生したり(可用性の低下)、データが壊れること(一貫性の低下)によりサービス終了するわけにはいきません。

そこで、データは複数箇所に保存(分散システム)するべきだと考えられます。

しかし、複数のサーバーで共通のデータを持たねばならない場合、次の3つの望まれる性質のうち、2つしか同時に満たすことができません。これを『CAP定理』といいます。Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services (Gilbert & Lynch, 2002)

 

CAP
C:Consistency(一貫性)
すべてのサーバーで順序正しく統一されたデータを持っていること。
A:Availability(可用性)
そのデータの更新に対する高可用性。つまり、常に動いているということ。
P:Partition-tolerance(分断に対する許容)
サーバー同士が分断する状態がありえる、または分断が起きてしまった=許容された状態
※日本では一般に分断耐性と翻訳されることが多くありますが、分断耐性という言葉による混乱が多々見られるので、本ドキュメントでは「tolerance」を「許容」と訳しています。

簡単に説明するため、2つのサーバーに同じデータを保存したい場合を考えます。CAP定理では、『P-A』『P-C』『A-C』の3つのパターンのみが達成可能です。

P-A
サーバーに分断が発生したとき(P=分断が起きてしまった)、可用性(A)を考えて片方のサーバーにだけ書き込みをしてしまうと、2つのサーバーでは同じデータを持つことができません。そこで一貫性(C)が失われます。
P-C
同様にサーバーに分断が発生したとき(P=分断が起きてしまった)、一貫性(C)を考えて2つのサーバーで同じデータにするには、システムを一時停止するしかありません。つまり可用性(A)が失われます。
A-C
分断されていない状態(Pが起きていない)ときのみ、一貫性(C)も可用性(A)も得ることができます。
 

二つのサーバーでCAP定理を考える

例えば、遠隔の拠点(例として2つのデータセンター)にそれぞれサーバーが設置されている分散システムで、データの出し入れなどの協調動作する場合、短時間といえどこれらのサーバー間の通信が分断されることを避けることはできません。したがってシステムの全体設計として、分断(Partition)が起きることを許容(Tolerance)しなければならず、『P』は必須となります。CAP定理に従うと、結果として残りは『A』または『C』のどちらかを選ぶしかありません。

視点を変えて、近距離に設置したの二つのサーバーではどうでしょうか?

『P』を選ぶ必要がない、すなわち分断は必ず起きないと保証されるとき、『A』と『C』は同時に成立するように見えます。データの一貫性が保たれ可用性もある(動作が継続する)。これは理想的なシステムと考えられそうです。

そこで、ミラー化されたストレージサーバー・システムを想像してみましょう。ミラー化されたストレージサーバーでは、一方のストレージサーバーがダウンしてももう一方のストレージサーバーのみで動作を継続するため、一見するとデータ一貫性も可用性も保たれているように見えます。

 

xentec1-2.jpg

具体的に想像するため、ストレージサーバー『1号機』と『2号機』があり、同じデータを書き込むリクエストがあったと仮定します。

システムは『1号機』と『2号機』の両方に書き込み要求をし、その書き込みが完了したという「完了報告」(一般にAcknowledge/アクノリッジといいます)をもらってから、ようやく次のデータの読み書きに移ることができます。

こうしなければ一貫性(C)を保つことができません。

 
xen

ここでもし、『1号機』はすぐに書き込みを完了したが、『2号機』の書き込みが遅れた場合はどうなるでしょうか? 

この場合、『1号機』だけが最新の状態になり、『2号機』はどうなっているのかわかりません。つまりこの瞬間においては分断が発生(P)しているのです。

 

『A』『C』のどちらかを選ばなければならない

あなたがプログラマーだったとしたら、一方のストレージ応答が遅れた場合の処理をどう書きますか? おそらく、できることは次のどちらかか、両方の組み合わせしかないでしょう。

  • 『応答を待ち続ける』
    • この場合は、データの一貫性(C)を保つことを重視していることになります。ただし、『2号機』の書き込み完了を待つ間(Δt)、そのシステムは停止することになるため可用性(A)を失います。
  • 『応答を待たずに進む』
    • この場合は、『1号機』だけで動作を継続することから可用性(A)を重視していることになります。ただし、『1号機』と『2号機』のデータ整合性が崩れるため、一貫性(C)を失います。

つまり、近距離の二つのサーバーであったとしても、プログラマーは実務として分断が発生する前提で『A』『C』のどちらかを選ばなくてはなりません。

※この文書でのΔtは、ある時間t1から、ある時間t2までの間の、一定の時間Δt=t2-t1を意味しています。ここでは、とある一定の時間だと考えれば良いでしょう。

一貫性を保つことで可用性の低下(レイテンシの低下)が発生する

実際のストレージシステムにおいては、この遅延時間『Δt』はしばしば発生しています。たとえ同じハードウェア・同じソフトウェアのストレージシステムを用いたとしても必ず発生します。上の例では、『2号機』の書き込み遅延が終了するまでの時間(Δt)が分断が発生(P)している時間になります。

これは、実務においては、CAP定理自体を、マクロな時間軸の設計視点から、一定の短い時間(Δt)を考慮したミクロな時間軸の設計視点へ概念を拡張しなくてはならないことを意味しています。ほぼすべての分散システムにおいて、Δtのようなごく短時間の間に、分断『P』が発生したり消滅したりしているのです。したがって、分散システム上で処理を記述するプログラマーは、必ずどこかで『A』と『C』のどちらかを選ぶ必要があります。

現実的なミラーストレージをミクロ的な視点から見ると、書き込み遅延により分断が発生(P)した場合、基本的にはタイムアウトが発生するまでは書き込み完了を待ち続けます。すなわち待つ(停止する)ことでΔtの間の可用性(A)を捨て、一貫性(C)を保つのです。この「Δtの間待つ」という行為が多数発生しているシステムは、マクロ的な視点からは「全体的に遅いシステム」に見えることになります。

なお、タイムアウトが発生してしまった場合は、そのタイムアウトが発生した片系のシステムを切り離します。それにより一貫性(C)が捨てられ、可用性(A)が選択されて動作を継続します。

さて、多くの3-Tier型クラウドシステムでは、ハイパーバイザー層で共有ストレージ(SANストレージ)を利用し、この層で一貫性(C)を担保しようとします。ストレージは、複数のハイパーバイザーから届く様々なリクエストを、一貫性を保ちつつ処理するため、大小様々な遅延を発生させています。

これにより一貫性は保たれますが、マクロの視点からは上述のとおり「全体的に遅いシステム」に見えます。一貫性を重視しすぎることで、可用性の低下(≈レイテンシの低下)が発生してしまうのです。

つまり「データ書き込みに安全性があるストレージは、そうでないものに比べて遅い」ことになります。

コラム

ミラーストレージのような分散システムでは、片系ストレージでタイムアウトが発生することにより、その切り離しが発生します。このとき、一貫性(C)が失われ、代わりに可用性(A)を選択することになります。タイムアウトにより『P-C』のシステムが『P-A』のシステムへと入れ替わるのです。

これをマクロ視点から見た場合、仮にタイムアウトまでの時間を無視できるのであれば、ミラー化された冗長ストレージシステムは一貫性(C)ではなく、可用性(A)を重視したシステムといえます。

実際には、片系が切り離されデグレードとなった状態で、一端、分散システムから単一系になります。再度、リカバリー(リビルド、リシルバーなど)処理が完了すると、元の冗長された分散システムへと戻ります。

Δtという時間軸でみることにより、この「A」と「C」の選択は、レイテンシと一貫性のトレードオフを表しています。これはまさに「PACELCの定理」になります。PACELCは、「if Partition, then Availability or Consistency; Else, Latency or Consistency」を示しており、分断があるときはCAPと同じでも、平常時はレイテンシ(L)と一貫性(C)のトレードオフが常に存在しているというものです。

このように、分断と平常時の二軸で考えるとき、

PA/EL
分断時は可用性優先、平常時は低レイテンシ優先。CassandraやAWSのDynamoDBのデフォルト設定がこれにあたります。常に一貫性を犠牲にする代わりに、速さと落ちにくさを取るタイプです。PostgreSQLのデフォルトクラスタはこれに近く、平常時はプライマリコミットでレイテンシを保ちますが、フェイルオーバー時にデータの欠損が生じます。
PC/EC
分断時も平常時も一貫性優先。MariaDB Galera Cluster や、CephのRADOS、Google SpannerやHBaseがこれにあたります。PostgreSQLクラスタのsynchronous_commit = on + synchronous_standby_names設定でもこれに当たります。どんな状況でも一貫性を守る代わりに、可用性やレイテンシのコストを払います。
PA/EC
分断時は可用性優先だが、平常時は一貫性優先。MongoDBがこの分類にあたるとされています。
PC/EL
分断時は一貫性優先だが、平常時は低レイテンシ優先。理論的にはあり得ますが、実際のシステムではあまり見られない組み合わせです。

Consistency Tradeoffs in Modern Distributed Database System Design


レスポンスの速いサーバ(可用性の高いサーバ)はどう作るのか?

ここまでで、ある一定の時間Δt内に応答を返す必要があるならば、ある段階で「一貫性(C)を捨て、可用性(A)を重視する」しかないことがわかります。レスポンスが良いとはいうのは「ある一定の十分に短い時間Δt内にシステムが応答すること≈レイテンシが小さい」とも考えられます。これまでの例からわかるとおり、時間軸にCAP定理を拡張するならば、レスポンスの良い(≈レイテンシが小さい)サーバを作る為には、データ一貫性をある程度、捨てなくてはならないことを意味します。

今まで上げたとおり「データ一貫性(C)はどの程度必要か?」を考える必要があります。

そこで今度は時間軸ではなく、空間(エリア)をイメージします。

一般に、書き込んだデータを失う(一貫性を失う)ことは、大問題です。しかし、サービスを役割ごとに分割することで、「あるサーバーにはデータ一貫性が必要」だが「あるサーバーは数時間に一度の更新しか発生しない」など、様々な種類に分けることができます。これが「空間(=エリア)でわけていく」ということです。

例えば、データ更新がない・または少ないウェブサーバーやアプリケーションサーバーであれば、あらかじめ同じデータを持つサーバーを複数用意することで、1台のサーバーがダウンしてもサービスを継続することができ、可用性が担保されています。

また一方、この状態は同じデータが何らかの枠組みで全てのサーバに保存済みであるなら、一貫性が保たれています。

つまり、一貫性も可用性も確保出来ています。なぜ2つとも満たされているのでしょう?それは、この時間のみこれらのサーバの間は数理的に(データ的に)分断されていない為なのです。

同じシステムにおいても、ある時間、ある空間においてだけ、C-A-Pの選択状況が異なることを意味しているのです。

 

xentec3.jpg

一般的なウェブサービスでは、コンテンツの更新頻度と参照頻度を比べた場合、参照頻度がずっと高いはずです。データ更新用サーバーと参照用サーバーを分け、更新用サーバーで更新されたコンテンツを参照用サーバーにコピーするような仕組みを用意することができるのであれば、更新用サーバーは一貫性を重視し、参照用サーバーは可用性を重視することで一貫性と可用性のベストバランスを実現できるでしょう。

※その場合、更新用サーバーから参照用サーバーへとコピーする時間を考慮する必要があります。コピーする時間(Δt)は、更新用サーバーと参照用サーバーのコンテンツに差異がある状態のため、人によってアクセスするサーバがことなり、異なるデータをみる、つまり一貫性が失われることを許容しなければなりません。これが許容できないならば、同期が終わるまでサーバを一時的に停止するしかありません(可用性が失われる)。

 

このように、サービスを構成するサーバー空間を決め、「一貫性が必要なもの」と「可用性を重視・高レスポンス性が必要なもの」にそれぞれ分離することで、エンドユーザーが感じる快適さを大きく向上することができます。言い方を換えると、エンドユーザーからのアクセスが、一貫性が必要なサーバーにアクセスしなければ、サーバーのレスポンスは向上し、快適なサービスができあがるのです。


時間と空間の両方で狭域化する!

可用性、つまり落ちないこと、レスポンスが良いシステムを作ることは、サーバエンジニアの極みです。どのようにすればそのようなシステムを作れるのでしょうか?

その鍵は「狭域化」にあります。時間、空間(エリア)を分割し、小さくしていくのです。時間Δtをなるべく小さくし空間を分割して広域から狭域へと狭めていく。

一貫性の必要な部分を時空で狭域化するというのは、システムが随所に持つ「ロック期間」をなるべく小さくすることを意味します。これを小さくするほど、マクロな視点では高レスポンスなのにデータが安全なシステムが生まれていくのです。

多くのシステムでは、可用性より一貫性が必要なサーバーは全体の中の一部、たとえばデータストア系(ストレージ、レポジトリ、RDB、KVSなどなど)です。一貫性が必要な時間的尺度Δtも異なります。にもかかわらず、システム全てに影響するインフラの中のさらにインフラ層であるSAN層に一貫性を担保させる場合、もっとも広域にCAP定理の空間範疇を決めたことになるのです。これではシステムが全体的に遅く非効率になってしまいます。

だからこそオンプレミスシステムと異なり、クラウドではエフェメラルディスクを用いて高速にやり取りを行い、データストアを別に用意する設計になることが多くなるのです。

自分が利用しているクラウドサービスにおいて、データ一貫性がどのような設計思想で考えられているのか?は重要な指標です。データ一貫性を重視するストレージシステムへの書き込みは、そうでないストレージに比べると遅く、また容量単価も高価になりがちだからです。

 

参考≫ テクニカルノート/超高速なInterconnected Storageを効果的に利用しつつデータを守る方法

 

最後に、当社のストレージサービスでは、次の様な性質を持ちます。これらをみながら、どんな設計を選ぶかを考えてみてください。

当社サービス名優先要件種別設計思想
Interconnected Storage(ICS) local利用独立型速度特化型インターコネクト型で、エフェメラルディスク並の高速性能を持ちつつ永続化されたストレージ。シリコンレベルでの冗長性を持つ。
Interconnected Storage(ICS) HCI利用一貫性データ保持優先型インターコネクト型で、エフェメラルディスク並の高速性能を持ちつつ永続化されたストレージ。シリコンレベルでの冗長性を持つ。このストレージを3ノードで分散し、一貫性を保つ。
Enterprise Storage(ES) SSPCで2台利用一貫性速度特化型Block Storage(ESB)とFile System(ESF)のどちらも切り出すことができる、速度特化型のStorage。ストレージ層はダブルパリティの冗長性をもつ。
SSPCで利用する場合、LDOM層でESBをZFSミラーを行うため、基本、一貫性を保つことができる。
Enterprise Storage(ES) HRPCで2台利用-速度特化型Block Storage(ESB)とFile System(ESF)のどちらも切り出すことができる、速度特化型のStorage。ストレージ層はダブルパリティの冗長性をもつ。
HRPCの場合ESは冗長利用することはできない。そのため、ES自体が持つスナップショットレプリケーション機能を利用し、RPOの単位で、レプリカを持つこと。可用性も一貫性もないため、通常の用途では推奨されない。
QuTS Cloud on InfiniCloud一貫性容量特化型容量の大きいHDD型を用いたIaaS(HCI)の上にQNAP社のQuTS Cloud(NAS仮想アプライアンス)を搭載したストレージ製品
IO保証はないが、ストレージ層のデータ一貫性がある。QNAPのNAS製品と同じWebUIをもつため、フレキシブルに使うことができ、バックアップ用途に向いている。

参考≫テクニカルノート/永続化されたストレージ (Persistent Storage)とは (ICS/EB/BSは何が違うのか?)
参考≫デザインパターン/High Response Private Cloud用Storageデザインパターンガイド

一般に、ストレージサービスは「データが消えてしまってはいけない」ため一貫性側を重要視します。しかし、それぞれ設計思想は異なり、特性は異なります。

CAP定理上、一貫性と可用性は同時に成り立たないため、可用性が必要になる場合は注意が必要です。利用するストレージサービスが一貫性に主軸を持っている場合、これまで説明したとおり「反応速度が悪く」なるため、応答性が低いシステム、つまり、Δtのタイムでは可用性は失われています。

可用性を重視する場合は、まず、独立型のICSのlocal利用をします。ICSのlocal利用はインターコネクト接続であり、相関関係が少ないため、もっともIOが速く、高速になります。しかし、このままでは冗長性がなくシングル利用になるため、ノード障害でサーバダウンに繋がります。そこで、他のノードでも同じようにICSのlocal利用をし、異なるノードの上に立てられた複数の仮想マシンのなかのミドルウェアでクラスタを組むことで、ロックが狭域化し、可用性を保つことができます。たとえば、HAProxyや、JettyのJava Servletアプリケーションサーバの多重化などに効果を持ちます。

参考として、ミドルウェアで行う分散技術には次の様なものがあります。

  • HAProxy
  • Java Servlet(tomcat, jettyなど)
  • MySQL/MariaDB
    • グループレプリケーション
  • PostgreSQL
    • レプリケーション
  • SeeweedFS
  • redis
  • DNS
  • LDAP
  • などなど・・・

これらの分散技術は、それぞれ異なる思想を持って作られているため、それぞれ特徴にあった物を選ぶ必要があります。


サーバーはいつか必ずダウンする

残念ながら、サーバーはいつかダウンします。そしてダウンしないシステムを構築する銀の弾はありません。

サービスの可用性(≒レスポンス速度)とデータの一貫性は、システムにおいては二律背反で、理想的にはどちらも重視したい事柄です。しかし、現実的には2つが天秤に乗せられている以上、その天秤に全てのシステムを乗せるのではなく、時空を支配し、時間軸でも空間軸でも、なるべく狭域で優先事項を切り替えることで、矛盾する目的は「なんとなく」達成できるようになるでしょう。