こんにちは。髙岡です。
弊社では、OSS Cassandraの商用版である米Datastaxのライセンス販売及び導入サポートサービスを提供しております。
この度、サービス体制強化の一環として、Datastax社のCassandra認定資格を取得しました。
- The Administrator Certification
- The Developer Certification
今後は、Cassandraの面白い機能や特徴を少しずつ紹介してきたいと思います。
尚、Cassandraは、RDBMSと同じ設計思想で構築してしまうとその特徴を活かすことができません。
そのため、RDBMSと同じ設計思想だと失敗しそうな点に注意して説明していきたいと思います。 今後説明するテーマの候補
* 諸事情により予告なく変更される場合があります。
- 分散型データベースとしての特徴
- wirteの一貫性
- readの一貫性 その1
- readの一貫性 その2 → 今回説明するテーマ
- Primary Keyの考え方
- UPSERTについて
- クラスタのノードの拡張
- 複数データセンター構成
- バックアップ・リカバリ
- 大量データのロード方法
- トランザクション
- アプリケーションドライバからの接続方法
今回は、readの一貫性のその2です。
その1では、複製先ノードのデータがreadされた時に、複製されたデータが全て一致することをもって、クライアントにデータを返す条件とする一貫性all(CL=ALL)のケースを説明しました。
しかし、一貫性all(CL=ALL)としてしまうと、CAP定理で言うところの、一貫性 (Consistency)と可用性 (Availability)は満たされるが、分断耐性 (Partition-tolerance)が満たされない状態となってしまう欠点があることを説明しました。
Cassandraでは、一貫性 (Consistency)が完全に満たされることを犠牲にして、可用性 (Availability)と分断耐性 (Partition-tolerance)が満たされるように、一貫性 (Consistency)を緩めに調整することができます。
今回は、一貫性 (Consistency)を緩めに調整する機能と、実機検証での挙動をご紹介いたします。
readのシーケンス
前回と同様にreadのシーケンスを説明します。
ここでの例では、データの複製数は3で設定していると仮定します。(RF=3)
今回は、複製された3ノードの内、全ノードからの応答が返ってくる一貫性all(CL=ALL)ではなく、2ノードから応答が返ってくればよしとする緩めの条件、すなわち、一貫性2(CL=2)と仮定して説明します。
2ノードのデータが一致しているケース
まずは、ノード1、2のデータが最新であり、一致しているケースを説明します。
この場合の挙動は、前回説明した全ノードのデータが一致しているケースと同じです。ノード数が一つ減った点のみが異なります。
①クライアントが指定したノードがcoordinator nodeとなり、readリクエストを受信する。
②~③coordinator nodeは、要求されたデータが存在するノードを知っている。その中で、最も反応がよいノードにreadしたいデータを要求する。
④coordinator nodeは要求したデータを取得する。
⑤coordinator nodeは、ノード2からは、要求されたデータのチェックサムを送ってもらう。
実データよりもチェックサムを送ってもらうほうがが早いからです。
⑥coordinator nodeがノード1、2、3のハッシュ値を比較した結果、一致していることがわかった。
⑦coordinator nodeは、クライアントに要求されたデータを返す。
2ノードのデータが一致していないケース
次に、ノード1のデータが古くて、ノード2のデータが最新であり、一致していないケースを説明します。
この場合の挙動も、前回説明した全ノードのデータが一致していないケース
と同じです。ノード数が一つ減った点のみが異なります。
①~⑤までは先程説明した一致しているケースと同じなので、⑥以降の挙動を説明します。
⑥coordinator nodeがノード1、2のハッシュ値を比較した結果、一致していないことがわかった。
⑦~⑧coordinator nodeがノード1、2のハッシュ値でなく実データを取得する。
⑨coordinator nodeがtimestampを比較して最新データを判別する。
⑩~⑫coordinator nodeが、ノード1のデータをtimestampが最新のデータでアップデートする。
⑬coordinator nodeが、最新のデータをクライアントに返す。
勘がいい方はお気づきかと思いますが、比較対象がノード1とノード2で、ノード2に最新データが入っていたため、Read repairが機能して最新データがクライアントに返ってきました。
では、ノード1とノード2のデータは古いデータで一致していたが、運悪く最新データがノード3に入っていた場合は?
と疑ってみたくなるかと思います。
こんなケースです。
この場合は、古いデータがクライアントに返ってきます。
このような状況になってしまうことを可能な限り避けるために、手動でnodetool repairやNode Repairといった、古くなったままになっているデータを最新化する処理を定期的に実行する運用がCassandraでは重要となります。
実機検証
これまの説明を、一貫性all(CL=ALL)で複製データが一致していない場合の実機検証を通じて確認してみます。 こんなケースを再現してみます。
- データの複製数はKeyspaceにて3で設定(RF=3)し、ノード3、ノード4、ノード5に複製されている。
- なんらかの理由でノード3の複製データが失われている。
- ノード4とノード5がダウンした状態でノード3に向けてSELECTクエリーを投げる際に、一貫性レベルが厳しいCL=ALLだと失敗するが、一貫性レベルが緩いCL=ONEだと成功する。
- ノード4とノード5を起動した状態でノード3にRF=ALLでSELECTクエリーを投げるとSELECTクエリーが成功する。
- ノード3の複製データがRead Repairで復旧している。
複製数3でキースペースを作成し、データを挿入します。
cqlsh CREATE KEYSPACE testks WITH replication = {'class': 'NetworkTopologyStrategy', 'east-side': '3'} AND durable_writes = true; use testks ; cqlsh> use testks ; cqlsh:testks> CREATE TABLE users ( email TEXT, name TEXT, age INT, date_joined DATE, PRIMARY KEY ((email)) ); cqlsh:testks> INSERT INTO users (email, name, age, date_joined) VALUES ('btakaoka@sios.com', 'btakaoka', 25, '2020-01-01'); cqlsh:testks> SELECT * FROM users WHERE email='btakaoka@sios.com'; email | age | date_joined | name -------------------+-----+-------------+---------- btakaoka@sios.com | 25 | 2020-01-01 | btakaoka (1 rows)
挿入したデータが存在するノードを確認します。
nodetool getendpoints testks users 'btakaoka@sios.com' 172.18.0.4 172.18.0.5 172.18.0.6
ノード3のデータが失われた状況を再現するために、ノード3にログインして以下を実行します。</>
# nodetool flush # cd /var/lib/cassandra/data/ # tree ./testks ./testks └── users-2f3761705cb411ec995b61d2b70f9b25 ├── backups ├── nb-1-big-CompressionInfo.db ├── nb-1-big-Data.db ├── nb-1-big-Digest.crc32 ├── nb-1-big-Filter.db ├── nb-1-big-Index.db ├── nb-1-big-Statistics.db ├── nb-1-big-Summary.db └── nb-1-big-TOC.txt 2 directories, 8 files # nodetool drain # rm -fr /var/lib/cassandra/data/testks/*
nodetool drainは、メモリのキャッシュデータをCassandraの物理ファイルにフラッシュし、他ノードからの接続を停止するコマンドです。 また、/var/lib/cassandra/data/配下にはCassandraのSSTableと呼ばれる物理ファイルがキースペース毎に配置されております。この物理ファイルを削除して、ノード3の複製データが失われた状況を再現しました。
ノード4(172.18.0.5)とノード5(172.18.0.6)をダウンさせて、ノード3(172.18.0.4)のみにSELECTできる状況を作ります。
# 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 262.59 KiB 8 61.4% 6942c64b-5dc7-447f-907f-d3fcfa0be93e RACK01 DN 172.18.0.6 229.76 KiB 8 60.3% db02b65b-8e61-448e-ae6a-a5172f0661da RACK01 UN 172.18.0.3 267.53 KiB 8 58.2% c144f1d5-bb2d-476c-9f2a-bde5cff9a50f RACK01 UN 172.18.0.2 203.19 KiB 8 59.2% 7edb1796-5697-4d90-9391-65a4f9dba464 RACK01 DN 172.18.0.5 252.55 KiB 8 60.8% ff84ebc2-cdda-4dc5-aa9e-23d979a38046 RACK01
ノード3(172.18.0.4)で、CL=ONEでSELECTを実行します。
cqlsh: testks> CONSISTENCY Current consistency level is ONE. cqlsh:testks> SELECT * FROM users WHERE email='btakaoka@sios.com'; email | age | date_joined | name -------+-----+-------------+------ (0 rows)
物理ファイルを削除したので、テーブルは空です。 一貫性レベルがCL=ONEのように緩く設定されているため、データが空であるという問題はありますが、クエリー自体は成功します。
ノード3にて、CL=ALLでSELECTを実行すると、複製先ノードの3つの内、2ノードがダウンしているので、SELECTが失敗します。先程のCL=ONEと比べると、一貫性レベルが厳しく設定されている例となります。
cqlsh:testks> CONSISTENCY ALL Consistency level set to ALL. cqlsh:testks> SELECT * FROM users WHERE email='btakaoka@sios.com'; NoHostAvailable: ('Unable to complete the operation against any hosts', {: Unavailable('Error from server: code=1000 [Unavailable exception] message="Cannot achieve consistency level ALL" info={\'consistency\': \'ALL\', \'required_replicas\': 3, \'alive_replicas\': 1}')})
ノード4(172.18.0.5)とノード5(172.18.0.6)を起動しました。
# 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 200.44 KiB 8 61.4% 6942c64b-5dc7-447f-907f-d3fcfa0be93e RACK01 UN 172.18.0.6 252.73 KiB 8 60.3% db02b65b-8e61-448e-ae6a-a5172f0661da RACK01 UN 172.18.0.3 267.53 KiB 8 58.2% c144f1d5-bb2d-476c-9f2a-bde5cff9a50f RACK01 UN 172.18.0.2 203.19 KiB 8 59.2% 7edb1796-5697-4d90-9391-65a4f9dba464 RACK01 UN 172.18.0.5 275.27 KiB 8 60.8% ff84ebc2-cdda-4dc5-aa9e-23d979a38046 RACK01
先程と同じSELECTをCL=ALLで実行してみます。
cqlsh:testks> CONSISTENCY ALL Consistency level set to ALL. TRACING ON cqlsh:testks> SELECT * FROM users WHERE email='btakaoka@sios.com'; email | age | date_joined | name -------------------+-----+-------------+---------- btakaoka@sios.com | 25 | 2020-01-01 | btakaoka (1 rows)
先程は、CL=ALLのSELECTが失敗しましたが、今回は成功しただけでなく、無かったデータが復旧していることがわかります。 SELECTのトレースを見てみると、Read Repairが実行されていることがわかります。