データの可用性と一貫性と分散システム(社内勉強会資料)

データの可用性と一貫性と分散システム(社内勉強会資料)

はじめに

この記事は株式会社digglueの新卒を含む社員向けの勉強会で利用した内容です。今回のテーマは可用性と一貫性と分散システムについて。内容や表現の間違いなどがあるかもしれませんがご了承ください。

トランザクション処理とは

トランザクション処理とは、まるで一つの操作のようにまとめられる処理の単位です。

例えば、残高10,000円の預金に8,000円を振り込むといった場合、「10,000円を読み取って、8,000円を足し、18,000円を書き込む」を1セットとして実行します。

この操作の中には読み取り操作と書き込み操作が含まれますが、仮に読み取り操作と書き込み操作が分離されていて、他のプロセスが割り込むと結果の整合性が取れなくなる場合があります。

例えば、8,000円の振り込みの途中にちょうどクレジットカード会社から2,000円の引き落としのプロセスが割り込んできたとしましょう。

トランザクション処理がない場合、以下のようになります。

トランザクションがない場合

Aさんが銀行に8,000円を振り込む時、ちょうどクレジット会社からの2,000円の引き落とし処理があったとします。

タイミングが悪ければ、以下のような状態になり、最終残高が16,000円となるはずが8,000円となってしまいます。

トランザクションがある場合

一方で、トランザクションがある場合書き込みリクエストをワンセットとして実行できるように書き込み・読み取りをロックして他の操作が割り込まないようにします。

トランザクション処理をすることで初めて通常人間が想定する処理がなされます。

トランザクションのメリットとデメリット

上の例では当然トランザクションを利用した方が良いように思うかもしれませんが、トランザクションを利用すると読み取り・書き込みが一時的にロックされレスポンスを止めてしまうという側面があります。

銀行データであればトランザクションを重視しますが、毎日何億とアクセスのあるFacebookのいいね数の更新などはたまに不整合があっても対して問題ではなく、読み取りを許可してしまった方がユーザー体験は高まるでしょう。

このようにトランザクションはいい側面と悪い側面があるのですが、ここで問題となっている性質がそれぞれ可用性と一貫性です。

可用性と一貫性

可用性とは、読み取りリクエストを送ったらレスポンスが帰ってくる性質です。
また、一貫性とは読み取りリクエストを送ったら最新の正しいデータが帰ってくる性質です。

一般に、この二つの性質は一般にトレードオフの関係にあります。上の例でいうと、

  1. トランザクションがある場合、トランザクションロックされている間読み取りができない(可用性がない)が、書き込みリクエスト後の最新データが取得できます(一貫性がある)。
  2. トランザクションがない場合、すぐに読み取りができる(可用性がある)が更新される前の古いデータ(一貫性がない)を取得してしまいます。

これらのトレードオフは完全にどちらかを選ぶ問題ではなく、程度問題であり最適なバランスを調整する必要があります。

複数のデータベースの場合のデータの整合性

さて、ここでデータベースへのアクセスが増えてリクエスト処理能力が足りなくなったため、データベースを複数並べて並行してリクエストをさばけるようにしたとしましょう。
この時、データの整合性はどうなるでしょうか。二つのデータベースがデータを同期しあい、並行してリクエストを受け付けるデーベースを考えてみましょう。(MongoDBなどのNoSQLデータベースがこの構造)

最初の事例と同じように10,000円のAさんの預金に8,000円の振り込みと2,000円の引き落としが同時にあったとします。

この例でも、16,000円になるはずの残高が8,000円となってしまいます。

ちなみにここで、DB1とDB2に跨ってトランザクションを実行しようとしたらどうなるでしょうか??

その場合、DB1とDB2をロックする必要があります。そうすると、大量のリクエストを並列して捌きたいという当初の目的を果たせなくなってしまいます。

また、NoSQLでは単一DBでのトランザクション処理もサポートしていない(場合が多い)です。

そのため、書き込みリクエストのすぐ後に読み取りリクエストを送ると更新される前のデータが戻ってくるといったケースは珍しくなく、頻繁にあります。

RDBのスケーリング

ここで、一貫性が重視されるケースで利用されるMySQLのスケーリング構造がどうなっているかをみてみましょう。
MySQLでは、書き込みリクエストは並列処理できず一箇所のサーバーで受付つけて同期するが、読み取り処理は並列処理するという構成を取っています。

DB同士の同期のタイミングによっては若干古いデータを読み取ってしまう可能性はありますが、必ず書き込みリクエストは矛盾なく処理されます。

一般に書き込みリクエストより読み取りリクエストの数の方が多いので、逐一全ての行動ログを取ったりするようなシステムでなければある程度まではこの仕組みでスケーリングに対応することができます。

一貫性を保持するための仕組みは順番づけ

MySQLのデータの整合性を見れば分かる通り、書き込みリクエストを順番に処理していくと一貫性を保つことができることが分かります。

NoSQLの例では、書き込みリクエストは並列して実行した後に同期していたのが問題でした。

MySQLの例では、書き込みリクエストを受け付けるサーバーを一箇所に絞ることで書き込みリクエストを順番に処理するようにしているのです。

ブロックチェーンにおける一貫性を担保する仕組み

分散してノードが存在するブロックチェーンでも、この順番に処理するという性質を利用しています。

分散したノードの各々がデータの書き込みリクエストを受け付けた後に同期すると、どうしてもデータの整合性に矛盾が生じます。

そのため分散したノードが受け取った書き込みリクエストをすぐ反映させる前に、ネットワークで全てのトランザクションを同期してブロックという単位でまとめます。

ブロックの中では一定数のトランザクションが順番に並べられており、その順番に並べられたブロックという単位でデータを更新していきます。

このブロックはちょうど「RDBでいうトランザクション」の単位であり、ひとまとめになってデータを更新します。

ブロックにまとめるタイミングはコンセンサスアルゴリズムによって変わり、一定時間であったり、一定数のトランザクションが集まってマイニングされたタイミングだったりします。

コンセンサスアルゴリズムとデータの一貫性

問題がDBのレプリケーションであろうとブロックチェーンであろうと 、リクエストを受け付けるノードが増えるとデータの一貫性をどうとるかという問題が発生します。

この問題を解決するアルゴリズムがコンセンサスアルゴリズムです。

よく許可制ブロックチェーンで利用されるRaftなどは、複数のノードが受け付けるリクエストを一回リーダーとなるノードに集め、タイムスタンプなどを基準に順番づけしてブロックの単位にまとめて他のノードに返します。

Hyperledger-Fabricなどではこの役割がオーダリングサービスなどと呼ばれています。

その他

他にも、リクエストを値で考えるのでなく、ベクトルとして考えると意図しない更新を防ぐ手法があります。

Bという値に書き換えてください、ではなくA⇒Bにしてください、とリクエストする。

この時、A⇒CというリクエストとA⇒Bというリクエストはどちらかしか成功せず、別のリクエストが先に処理されることで発生する意図に反した挙動を検知して拒否することができます。

カイジの鉄骨渡りのシーンを例に説明してみましょう。カイジが鉄骨の上で「みんなが生きている時に(A)」、「報酬はなしでいいから電気を止めてくれ(B)」と言ったが、「何人か落下してゲームをクリアした後(A⇒C)」に利根川に「報酬はなしで電気止めた(C⇒B)」だと言われたらフザケンナ利根川ってなります。「みんなが生きているうちに報酬はなしでいいから電気を止めてくれ」(A⇒B)と要求していたらこのようなことにはなりませんでした。

このベクトルの発想を利用しているのがバージョン番号で、データ取得時にバージョンを取得し、更新リクエストにバージョンを1繰り上げてリクエストを送ります。

データベース側でこのバージョン番号が未使用ならリクエストを正常に受けつけますが、バージョン番号がすでに利用されていればリクエストを失敗させて、データの更新があった事を通知します。

人材募集!

株式会社digglueではコンサルタント及びエンジニアを募集しています。

ブロックチェーンに関して、既によく理解している方だけでなく、これから学びたい方、仕事として実績を積みたい方、将来を考えてテクノロジーに触れておきたい方などでも大歓迎です。

ビジョン・ミッションなどに関しては、こちらのnote(digglueはどこへ向かうか) を参照ください。

ご質問・応募はコンタクトフォームかinfo@digglue.comまでご連絡ください。

雑記: 毎週勉強会開催してます。でもコンスタントにネタ考えて準備するの大変です。モチベのために誰かトークンください。

社内勉強会カテゴリの最新記事