Chapter 2. Eventual Consistency : CouchDB: The Definitive Guide

CouchDB勉強会のために訳しましたので、公開します。
原著者にこれから連絡するので、やめて、と言う話であれば削除するかもしれません。
※執筆中の書籍版を基にしているのでWeb上の章立てと少し違うようです。

  == Chapter 2. Eventual Consistency ==

  • 前章では CouchDB の柔軟性が、我々のアプリケーションに拡張と変化を可能にするってことをみてきた。この章では、CouchDBの原料から動作を概観し、我々のシステムを自然とスケーラブルな分散システムにする事に役立つ「シンプルさ」について説明する。

== 2.1. Working With The Grain ==

  • 分散システムは広域ネットワーク上で頑健に動作するシステムだ。ネットワークコンピューティングの特徴は、ネットワーク接続は切れる可能性があること、ネットワークセグメントを管理する戦略が必要になること。CouchDBが他の eventual consistency 対応の方法を違うのは、RDBMSやPaxosのようなraw availabilityに対する完全なconsistencyを作るのではないということ。一般的に、多くの人が同時にアクセスするデータの動作が異なる。一貫性(consistency)、可用性(availability)、耐分割性(partition tolerance)についての優先度が異なる。
  • 分散システムを扱うのはとてもトリッキーだ。やっていくと、たくさんの注意点や「わかったぞ」に出会う事になる。我々は全ての解決策を持っている訳ではないし、CouchDBは万能薬(panacea)ではないが、CouchDBをよく理解して行えば、自然にスケールするアプリケーションに向けての障害の少ない道を歩めるだろう。
  • もちろん分散システムの構築は始まったばかり。1つのデータベースで作られたウェブサイトは半分の時間で作れるが、役立たずになりやすい。残念ながら、伝統的なリレーショナルデータベースのアプローチがもつ一貫性(consistency)は、プログラマがグローバルな状態/クロック/高可用性を信頼できるので、とても簡単 - うまく動いている事さえ確認できれば。CouchDBのスケーラビリティを検証する前に、分散システムが直面する制約についてみておく。そのあと、各パーツがお互いに通信できることを保証できない場合に発生する問題について確認し、CouchDBが、高可用アプリケーションのモデリングに関して直感的で便利な方法を提供するってことをみていく。

== 2.2. The CAP Theorem ==

  • CAP理論によって、ネットワークを越える分散アプリケーションロジックの戦略の違いが説明できる。CouchDBでは、参加ノード間でアプリケーションの変化をレプリケーションで伝播(propagate)する。これは、一致アルゴリズム(consensus algorythm)とリレーショナルデータベースとは違うアプローチ -- 一貫性(consistency)、可用性(availability)、耐分割性(partition tolerance)について、異なる組み合わせを採用している、ということがいえる。
  • CAP理論は図2-1に示すように、3つの領域:
    • 一貫性(Consistency): 全てのデータベースクライアントは同じデータを参照する。更新が同時に発生したとしても。
    • 可用性(Availability): 全てのデータベースクライアントは、データのいくつかのバージョンにアクセスできる
    • 耐分割性(partition tolerance): データベースは複数のサーバにまたがって分割できる
    • このうち2つだけをえらべ
  • システムが単一のデータベースノードに収まらないくらい大きくなったら、サーバを追加するのはいい解決方法だ。ノードを追加するときには、データを複数ノードにどのように分割するかを考え始めなければならない。全く同じデータを複数のデータベースにおさめるか?それぞれのデータベースサーバに違うデータを入れるか?特定のデータベースに書き込み、他のデータベースは読み込みだけをおこなうようにするか?
  • どのアプローチをとるかに関係なく、我々がハマりつづける1つの問題は、データベースサーバの同期(syncronization)だ。1つのノードに情報を書きこんだら、どうやって別のサーバからその新しい情報が読み出せる事を保証するにはどうしたらいい?このイベントはミリ秒ごとに起こる。限られた数のデータベースサーバであっても、この問題はきわめて複雑になることがある。
  • 全てのクライアントがデータベースの一致したビュー(consistent view)を参照できる事が、絶対必要な場合、あるノードのユーザは他の全てのノードで読み書きが可能にという条件が満たされるまで待たなければならない。このケースでは、可用性(availability)は一貫性(consistency)より後回しになる。しかし、一貫性(consistency)が可用性(availability)を切り捨てる状況もある
    • — Werner Vogels, Amazon CTO and Vice President
  • "システムの各ノードは純粋にローカル状態に従って動作できるようにする必要がある。問題が起こって負荷が高くなった場合でも、なんとかして条件を満たそうとした場合、あなたは迷子になる。スケーラビリティを考えると、そういう条件を満たすためのアルゴリズムは、結局ボトルネックになる。あるがままを受け入れるしかない"
    • Werner Vogels, Amazon CTO and Vice President
  • 可用性が優先される場合、クライアントは、他のノードが条件にいたる前に、そのノードのデータベースに書き込むことができる。もしデータベースがノード間のこういう処理を仲裁する方法を知っている場合、高可用(HA)のかわりに"結果整合性(eventual consistency)"といったものをおすすめする。多くのアプリケーションにおいて、驚くほど妥当なトレードオフをもつ。
  • 伝統的なリレーショナルデータベースと異なり、それぞれの動作が発生するのは、データベース間の一貫性チェックだけになる。CouchDBは、即時の一貫性を犠牲にするかわりに、シンプルな分散による大きなパフォーマンス改善をえるようなアプリケーションの構築を、とても簡単に実現できる。

== 2.3. Local Consistency ==

  • クラスタでのCouchDBの動作を理解する前に、単一ノードでの内部動作をしっかり把握しておく事が重要だ。CouchDB APIは利便性を提供できるように設計されているが、データベースコアのラッパとしては薄い。データベースコアについて詳しくみていく事で、それを取り巻くAPIをより理解できるようにあなるだろう。

=== 2.3.1. The Key to Your Data ===

  • CouchDBの心臓部は強力なB木ストレージエンジンだ。B木とは対数時間で検索/追加/削除が可能なソート済みデータ構造だ。図2-2で示すように、CouchDBはこのB-treeストレージエンジンを全ての内部データ、ドキュメント、ビューに使っている。そのうち1つを理解すてば、ほかも理解できる。
  • CouchDB は ビューの結果を計算するのに MapReduce を用いる。MapReduce は2つの関数 "map" と "Reduce" を使い、それぞれ各ドキュメントに独立に適用する。これらの操作を独立に実行できるようなっているということは、すなわち、ビューの計算は並列かつインクリメンタルな計算でできると言う事を意味する。さらに重要なのは、これらの関数はkey/valueペアを生成するため、CouchDBはそのままB木ストレージエンジンに追加し、キーでソートする事が可能になっている、ということだ。キーおよびキーのレンジで検索する事は、B木では極めて効率的に処理可能で、O記法でいうと、それぞれO(log n) および O(log N + K)になる。
  • CouchDBでは、ドキュメントとビューへのアクセスはキーとキーのレンジで行う。これはCouchDBのB木ストレージエンジンの処理に直接マッピングされる。文書と挿入と更新について、このように直接マッピングしているということが、CouchDB APIはデータベースコアの薄いラッパだという表現をした理由だ。
  • キーのみで結果にアクセスできるということは非常に重要な制約で、これによって非常に良いパフォーマンスが得られる。大きく高速化すると同時に、各ノードにいちいち問い合わせる必要なしに、データを複数ノードに分割することを可能にする。 BigTable, Hadoop, SimpleDB, memcached はまさにこれらの理由からキーによるオブジェクト検索の制約を設けている。


=== 2.3.2. No Locking ===

  • リレーショナルデータベースにおけるテーブルは、ひとつデータ構造だ。テーブルの修正や列の更新をしたい場合、データベースシステムは、他の誰かがその列を更新してしまわないように、また氏の列の更新中に誰かが読み取ってしまわないようにする必要がある。これを行う一般的な方法はロックとよばれる。複数のくらいなとがテーブルにアクセスしたい場合、最初のクライアントがロックを取得し、他のクライアントは待つ。最初のクライアントの処理がおわったら、次のクライアントは他のクライアントが待ちやそれに類する状態であれば、アクセスできるようになる。このように要求を順番に処理することは、要求が並列で発生する場合ですら行われ、多くのサーバの処理時間を捨ててしまう。高負荷下では、リレーショナルデータベースでは、実際の処理以上に、誰がどの操作を許可されているかを調べるために、より多くの時間が必要になる可能性がある。
  • CouchDBでは、ロックの代わりに MVCC(Multi Version Concurrency Control) を使ってデータベースアクセスの同時発生を管理している。図2-3はMVCCと伝統的なロック機構との違いを視覚化している。MVCCによって、高い負荷の下でも、CouchDBがフルスピードで動作することができる。要求は並列で動作し、サーバの処理能力の最後の一滴まで使い切る事ができる。
  • CouchDBの文書は、Subversionなどの一般的なバージョン管理システムとおなじように、世代管理される。あるドキュメントの値を変更する場合、旧いバージョンを完全にコピーした新しいバージョンを作成する事になる。この結果、同じドキュメントの2つのバージョン(新旧)が作成される。
  • この方法がロックより優れているのはなぜか? それは複数の要求が1つのドキュメントにアクセスしたい場合を考えてみよう。最初の要求はドキュメントを読み出す。それを処理している間に、2つ目の要求がそのドキュメントを変更する。2つ目の要求は新しいバージョンのドキュメントを作るので、CouchDBは読み出しの完了を待たずに、新しいバージョンをデータベースに追加する事ができる。
  • 3つ目の要求が同じドキュメントを読み出したい場合、CouchDBは書かれたばかりの新しいバージョンを提示する。これら処理の間、最初の要求は最初のバージョンを読みっぱなしでもかまわない。読み出し要求は常に最新のスナップショットを参照する。

== 2.3.3. Validation ==

  • アプリケーション開発者としては、どういった入力値を受け入れ、どういった入力値を排除するかについて考える必要がある。伝統的なリレーショナルデータベースにある複雑なデータについて、バリデーションを行う場合の表現力には、まだまだ不満な点がある。幸運な事に、CouchDBはドキュメントごとのバリデーションをデータベース内で行うために強力な方法を提供している。
  • CouchDBは、MapReduceでやっているのと同じように、ドキュメントのバリデーションもJavaScriptを使う事ができる。ドキュメントを変更しようとするとき、CouceDBはバリデーション関数を呼び出すことで、その更新を許可するか排除するか決める機会がある。
  • CouchDBのこの機能を使う事で、大量のCPUサイクル(SQLからオブジェクトグラフをシリアライズしてドメインオブジェクトにコンバートし、アプリケーションレベルのバリデーションを求めるための)を節約する事ができる。

=== 2.4. Distributed Consistency ===

  • 多くのデータベースにおいてシングルノードの一貫性(consistency)を管理するのは比較的簡単だ。本当の問題は複数のデータベースサーバの間の一貫性を管理しようとしたときに表面化しはじめる。あるクライアントがサーバAに書き込みを行ったとき、どうやってサーバB、サーバC、サーバDが一致している事を確認することができるだろうか?リレーショナルデータベースでは、それだけで本が何冊か書けるくらい複雑な問題である。マルチマスタ、マスタ/スレーブ、分割、共有、ライトスルーキャッシュ、他にもいろいろな複雑なテクニックがある。

=== 2.4.1. Incremental Replication ===

  • CouchDBの操作はシングルドキュメントのコンテキスト(context)で行われるため、もし2つのデータベースノードを使いたい場合でも、普通の通信しかなく、気にする必要がない。CouchDBは、データベース間の漸進的レプリケーションを使って、結果整合性(eventual consistency)をとっている。漸進的レプリケーションとは、ドキュメントが変化したら定期的にサーバ間でコピーされるプロセスのことだ。データベースのいわゆるシェアードナッシングクラスタとよばれる構成を作る事ができる。各ノードは独立し、自己充足的で、システム全体のシングルポイントになりそうな部分が存在しない。
  • CouchDBデータベースクラスタををスケールアウトする必要がある? サーバを追加投入すればいい。
  • 図2-4で示す通り、CouchDBの漸進的レプリケーションによって、データを2つのデータベース間で同期する事ができる。好きな方法で、好きなタイミングで。レプリケーション後は、各データベースは独立して動作する。
  • この機能は、クラスタの中の複数のデータベースサーバを同期するのにもつかるし、データセンタ間でジョブスケジューラ(cronなど)による同期にも使えるし、旅行中に仕事するためのラップトップへの同期にも使える。各データベースはいつもどおり普通に使えるし、変更はあとで双方向に行う事ができる。
  • 異なるデータベース上の同じドキュメントを同時に変更したら何がおこるだろうか?CouchDBレプリケーションシステムは自動的にコンフリクト検出と解消を行う。CouchDBがあるドキュメントが両方のデータベースで変更された事を検知すると、コンフリクトフラグをたて、一般のバージョンコントロールシステムとほとんど同じような処理をする。
  • それは迷惑に聞こえるかもしれないが、そうでもない。2つのバージョンのドキュメントがレプリケーション中にコンフリクトした場合、勝った(winning)バージョンはドキュメントのヒストリに保存される。負けたバージョンは捨てられるのではなく、推測通り、過去のバージョンとしてヒストリに追加されるので、必要になったらアクセスが可能だ。これにより、自動的かつ一貫して行われるので、両方のデータベースが完全に同じ方を(勝ち負けを)選択する。
  • アプリケーションでコンフリクトをわかるようにする事も可能だ。選択されたドキュメントバージョンを捨て、過去バージョンに戻すこともできるし、2つのバージョンをマージして結果を保存する事も可能だ。

=== 2.4.2. Case Study ===
(訳してません)

== 2.5. Wrapping Up ==

  • CouchDBの設計はWebアーキテクチャから、大きな分散システムへデプロイするアーキテクチャについて多くを学んでいる。このアーキテクチャがなぜ動作するのかを理解することで、またアプリケーションのどの部分は分散しやすくて、どの部分がそうでないかを明らかにする事で、分散かつスケーラブルなアプリケーションを設計する事ができるようになる。CouchDBを使おうと使うまいと。
  • ここでは CouchDBの一貫性(consistency)モデルに関連した話をメインに書き、CouchDBを使ったとき/使わない場合の利点のいくつかのヒントを述べた。理論は十分なので、実際に動かしてその辺をみてみよう。