Cassandraの分散データベースとしての特徴を語る (第2回)writeの一貫性

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【4/18開催】VSCode Dev Containersで楽々開発環境構築祭り〜Python/Reactなどなど〜
Visual Studio Codeの拡張機能であるDev Containersを使ってReactとかPythonとかSpring Bootとかの開発環境をラクチンで構築する方法を紹介するイベントです。
https://tech-lab.connpass.com/event/311864/

こんにちは。髙岡です。
弊社では、OSS Cassandraの商用版である米Datastaxのライセンス販売及び導入サポートサービスを提供しております。
この度、サービス体制強化の一環として、Datastax社のCassandra認定資格を取得しました。

  • The Administrator Certification
  • The Developer Certification

今後は、Cassandraの面白い機能や特徴を少しずつ紹介してきたいと思います。
尚、Cassandraは、RDBMSと同じ設計思想で構築してしまうとその特徴を活かすことができません。
そのため、RDBMSと同じ設計思想だと失敗しそうな点に注意して説明していきたいと思います。

今後説明するテーマの候補
* 諸事情により予告なく変更される場合があります。

 → 今回説明するテーマ

  • readの一貫性
  • Primary Keyの考え方
  • UPSERTについて
  • クラスタのノードの拡張
  • 複数データセンター構成
  • バックアップ・リカバリ
  • 大量データのロード方法
  • トランザクション
  • アプリケーションドライバからの接続方法

今回は、wirteの一貫性をご紹介いたします。

同じデータを別のノードに複製しても大丈夫?

同じデータを複数ノードに複製して配置する場合、以下のような素朴な疑問が思い浮かぶのではないでしょうか。

  1. データが更新された時に複製ノードに障害が発生して書き込みできなかったらどうなるのか?
  2. 複数ノードへ同じデータが複製されることを保証してくれる仕組みは無いと困るが大丈夫か?
  3. 他のノードに複製されたデータが何らかの理由でread時に更新されていなかった場合でも、最新データが返ってくるのか?

今回は1.と2.の疑問に答えるためにwriteの一貫性について説明します。
3.は次回のreadの一貫性で説明する予定です。

writeのシーケンス

まず最初に、writeのシーケンスをCassandra用語を使わない簡易な言葉で説明してみます。

ここでの例では、データの複製数は3で設定していると仮定します。
尚、前回のブログで説明した通り、複製数はKeyspace毎に設定します。

①クライアントが指定したノードがwriteリクエスト受信する。
②受信したノードが複製先のノードを確認する。
③そのノードは、writeリクエストデータを複製先の3つのノードに送信する。
④wirteリクエストを受け取ったノードのテーブルのデータがwirteされる。
⑤ノードがwirte成功の応答を返すと、クライアントが指定したノードにwirte成功の応答が返る。
⑥クライアントが指定したノードが、クライアントにwirte成功の応答を返す。

このシーケンスを正確に理解するためのCassandra用語を説明します。

coordinator node
coordinator nodeは、クライアントからリクエストを受けるノードです。
coordinator nodeの役割は多岐にわたりますが、ここの説明を理解するという目的に限定して説明しますと、クライアントからwrite等のリクエストを受けて複製先のノード送り、成功または失敗の結果をクライアントに返す役割を担っている、と理解してください。
尚、CassandraはMaster/Slave型ではないので、どのノードでもcoordinator nodeになることができます。

Partition
CassandraはPartition単位で複製先のノードを決定します。
Partitionはこの時点ではPrimary Keyと考えてください。必ずしもPartition=Primary Keyではありませんが、ここでは、とりあえずPartition=Primary Keyという前提で読み進めていただければと思います。
CassandraにおけるPartition、Primary Keyの考え方は、非常に重要な概念なので、詳細は別の機会に説明します。

これらの用語を使ってwriteのシーケンスを言い換えます。

①coordinator nodeがクライアントからwriteリクエストを受信する。
②coordinator nodeがwriteリクエストデータのPartition値を元に複製先のノードを決める。
③複製先のノードにwriteリクエストを送信する。
④wirteリクエストを受け取ったノードのテーブルのPartitionのデータがwirteされる。
⑤ノードがwirte成功の応答をcoordinator nodeに返す。
⑥coordinator nodeは、クライアントにwirte成功の応答を返す。

⑥について、誤解を招かないようにするための補足説明します。

“ 複数ノードに同じデータがwirteされる場合、全ノードのwirteが成功したことを確認してからクライアントに成功したと返答すべきだよね ”

と直感的に考えたくなるかと思います。図の例でもそのように表現しています。
しかし、Cassandraでは、

“ 複数ノードに同じデータがwirteされる場合、全ノードのwirteが成功したことを確認しなくても、一部のノードのwirteが成功したことをもってクライアントに成功したと返答する調整ができる ”

のです。
Cassandraは、CAP定理の一貫性 (Consistency)を調整できるということです。

以下の図は、⑤のシーケンスでのwirte成功の応答がノード1のみから返ってきており、ノード2とノード3が返さなくてもよい一貫性のパタンです。

全ノードが応答を返すことをもってwirte完了とする条件としてしまうと、クライアントへの応答性能が悪くなることや、一部のノードに障害が発生した場合にシステムの可用性が低下することが想定されます。
一貫性を調整することによって、応答を返すノードは1ノードであってもクライアントにwirte成功の応答を返す、とする設定も可能なのです。
一貫性は、性能や耐障害性とのトレードオフとして調整する必要があります。

複数ノードへの分散配置の挙動を確認

実際にデータを更新してみて、複数ノードへのwirteの複製に関する挙動を確認してみます。
上記の図で例として挙げた5ノードクラスタは作成済みです。

/# nodetool status
Datacenter: east-side
=====================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address     Load        Tokens  Owns (effective)  Host ID                               Rack  
UN  172.18.0.4  132.07 KiB  8       61.4%             6942c64b-5dc7-447f-907f-d3fcfa0be93e  RACK01
UN  172.18.0.6  120.39 KiB  8       60.3%             db02b65b-8e61-448e-ae6a-a5172f0661da  RACK01
UN  172.18.0.3  132.44 KiB  8       58.2%             c144f1d5-bb2d-476c-9f2a-bde5cff9a50f  RACK01
UN  172.18.0.2  143.04 KiB  8       59.2%             7edb1796-5697-4d90-9391-65a4f9dba464  RACK01
UN  172.18.0.5  125.61 KiB  8       60.8%             ff84ebc2-cdda-4dc5-aa9e-23d979a38046  RACK01

また、3ノードに複製するように、Keyspaceを作成済みです。
Keyspace「killr_video」では、データセンター「east-side」の3ノードに複製するという設定をしました。

cqlsh:killr_video>  DESCRIBE keyspace killr_video ;

CREATE KEYSPACE killr_video WITH replication = {'class': 'NetworkTopologyStrategy', 'east-side': '3'}  AND durable_writes = true;

まずは、デフォルトの一貫性の設定を確認してみます。

cqlsh>  CONSISTENCY 
Current consistency level is ONE.

これは、先の説明で、

“ 複数ノードに同じデータが複製される場合、全ノードのwirteが成功したことを確認しなくても、一部のノードのwirteが成功したことをもってクライアントに成功したと返答する調整ができる ”

に該当し、クライアントにwirteが成功したことの応答を返すのは全3ノードでなくてもよくて、1ノードでよいという一貫性の設定です。

サンプルデータとして、以下のデータを挿入済みです。

cqlsh:killr_video>  select * from users;

 email              | age | date_joined | name
--------------------+-----+-------------+-----------
 a_takaoka@sios.com |  25 |  2020-01-01 | a_takaoka

このデータは、以下の3ノードに分散配置されております。

# nodetool getendpoints killr_video users 'a_takaoka@sios.com'
172.18.0.4
172.18.0.6
172.18.0.2

updateしてみます。

cqlsh:killr_video>  update users set age=40 where email='a_takaoka@sios.com';
cqlsh:killr_video>  select * from users where email='a_takaoka@sios.com';

 email              | age | date_joined | name
--------------------+-----+-------------+-----------
 a_takaoka@sios.com |  40 |  2020-01-01 | a_takaoka

成功しました。

それでは、

“ 複数ノードに同じデータがwirteされる場合、全ノードのwirteが成功したことを確認してからクライアントに成功したと返答すべきだよね ”

に該当するケースを試してみます。

<!–– .4と.6をダウンさせる –>
複製先の3ノードの内、172.18.0.4と172.18.0.6の2ノードをダウンさせます。
7行目と8行目のDNがダウンしているという意味です。

# nodetool status
Datacenter: east-side
=====================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address     Load        Tokens  Owns (effective)  Host ID                               Rack  
DN  172.18.0.4  132.07 KiB  8       61.4%             6942c64b-5dc7-447f-907f-d3fcfa0be93e  RACK01
DN  172.18.0.6  120.39 KiB  8       60.3%             db02b65b-8e61-448e-ae6a-a5172f0661da  RACK01
UN  172.18.0.3  236.23 KiB  8       58.2%             c144f1d5-bb2d-476c-9f2a-bde5cff9a50f  RACK01
UN  172.18.0.2  143.04 KiB  8       59.2%             7edb1796-5697-4d90-9391-65a4f9dba464  RACK01
UN  172.18.0.5  228.91 KiB  8       60.8%             ff84ebc2-cdda-4dc5-aa9e-23d979a38046  RACK01

“ 複数ノードに同じデータがwirteされる場合、全ノードのwirteが成功したことを確認してからクライアントに成功したと返答すべきだよね ”

に該当する一貫性に変更します。

cqlsh:killr_video>  CONSISTENCY THREE
Consistency level set to THREE.

「CONSISTENCY THREE」とは、複製先の3ノード全てからの応答が必要という一貫性です。

updateしてみます。

cqlsh:killr_video>  update users set age=50 where email='a_takaoka@sios.com';
NoHostAvailable: ('Unable to complete the operation against any hosts', {: Unavailable('Error from server: code=1000 [Unavailable exception] message="Cannot achieve consistency level THREE" info={\'consistency\': \'THREE\', \'required_replicas\': 3, \'alive_replicas\': 1}')})

失敗しました。
エラーメッセージの中に、「Cannot achieve consistency level THREE」と記載されております。

では、失敗したwriteは取り消されるのか? rollbackすべきでは?と考えたくなるかと思います。

安心してください、writeが取り消させることがありません。
CassandraにはHinted Handoffという仕組みがあり、writeの複製に失敗したノードが復活した時に備えて、coordinator nodeがwriteできなかったデータを一定期間保持してくれます。保持期間はデフォルトで3時間です。

coordinator nodeが保持しているhintsデータの例です。

# ll /var/lib/cassandra/hints/
total 8
drwxr-xr-x 4 cassandra root 128 Aug 21 15:02 ./
drwxr-xr-x 8 cassandra root 256 Aug 11 11:52 ../
-rw-r--r-- 1 root      root 162 Aug 21 15:02 5d6067d1-9296-48e3-909b-bfa7a349ba03-1629558162112-2.hints
-rw-r--r-- 1 root      root 162 Aug 21 15:02 cd600cf2-75be-4d10-8569-51bde08c2787-1629558162117-2.hints

2ノードへのwriteの複製ができなかったので、2つのhintsファイルをcoordinator nodeが保持しています。
複製に失敗した2ノードが復旧すると、coordinator nodeはhintsファイルから2ノードに対してwriteの複製を実施して復旧します。復旧が終わるとhintsファイルが消えます。
ログには、以下のようなメッセージが出力されます。

INFO  [HintsDispatcher:5] 2021-08-21 15:13:18,126 HintsStore.java:133 - Deleted hint file 5d6067d1-9296-48e3-909b-bfa7a349ba03-1629558162112-2.hints
INFO  [HintsDispatcher:5] 2021-08-21 15:13:18,128 HintsDispatchExecutor.java:282 - Finished hinted handoff of file 5d6067d1-9296-48e3-909b-bfa7a349ba03-1629558162112-2.hints to endpoint /172.18.0.6:7000: 5d6067d1-9296-48e3-909b-bfa7a349ba03

ここで1点、誤解を招かないようにするための補足説明をします。

複製先を3ノード、CONSISTENCY ONEで設定した場合、複製先の2ノードがダウンしていてもクライアントには成功と応答する。
この場合、クライアントに1ノードへのwirteが成功したと応答してしまえば複製先の2ノードが復旧してもwriteされないのか?

というと、そうではないということです。
複製先を3ノードと指定していれば、CONSISTENCYの設定に関わらず、全ての複製先へのwriteは裏で実施されます。安心してください。

まとめ

このブログの冒頭で記載した素朴な質問に対する回答をまとめてみます。

  1. データが更新された時に複製ノードに障害が発生して書き込みできなかったらどうなるのか?
  2. 複数ノードへ同じデータが複製されることを保証してくれる仕組みは無いと困るが大丈夫か?

<回答>
データがwriteされた時に複製ノードに障害が発生してwriteできなかった場合には、coodinator nodeがhintsファイルとして障害中にwriteできなかったデータを保持してくれます。
障害復旧後は、このhintsファイルからwriteするはずだったデータを復旧したノードにwriteします。このような仕組みがあるため、複数ノードへ同じデータが複製されることが保証されるのです。

アバター画像
About 髙岡 貴史 21 Articles
登山家兼投資家兼インフラ寄りのエンジニア。最近はDatastaxを中心としたデータ処理基盤に関心あり。
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

1人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる