こんにちは。髙岡です。
弊社では、OSS Cassandraの商用版である米Datastaxのライセンス販売及び導入サポートサービスを提供しております。
この度、サービス体制強化の一環として、Datastax社のCassandra認定資格を取得しました。
- The Administrator Certification
- The Developer Certification
今後は、Cassandraの面白い機能や特徴を少しずつ紹介してきたいと思います。 尚、Cassandraは、RDBMSと同じ設計思想で構築してしまうとその特徴を活かすことができません。 そのため、RDBMSと同じ設計思想だと失敗しそうな点に注意して説明していきたいと思います。 今後説明するテーマの候補 * 諸事情により予告なく変更される場合があります。
- 分散型データベースとしての特徴 → 今回説明するテーマ
- データを分散配置しても一貫性を保つための仕組み
- Primary Keyの考え方
- UPSERTについて
- クラスタのノードの拡張
- 複数データセンター構成
- バックアップ・リカバリ
- 大量データのロード方法
- トランザクション
- アプリケーションドライバからの接続方法
第1回目の今回は、Cassandraの分散型データベースとしての特徴の一部をご紹介しようと思います。
分散型データベースとしての特徴
Cassandraの分散型データベースとしての主な特徴です。
2. 複製データが配置されたどのノードへ問い合わせても応答を返すことができる。
1.について、同じデータを複数ノードに分散配置した場合、一部のノードが障害時に更新が発生したとしても複数ノードに分散しているデータの更新が漏れないようにする仕組みが必要です。後述の検証でその挙動を説明します。
2.について、必ず最新データを返すことができるとは言いません。しかし、最新でないと困ると思う方が多いかと思います。この点はCassandraの特徴の一つで、一貫性の設定次第で調整することも可能です。 少し専門的な説明をしますと、Cassandraは分散コンピュータシステムのマシン間の情報複製に関するCAP定理の中で、可用性 (Availability)と分断耐性 (Partition-tolerance)を満たすが、一貫性 (Consistency)のレベルを調整できるという特徴をもっています。 問い合わせの結果が必ず最新データである必要がある場合は、Cassandraの一貫性に関する設定で実現可能ですが、分断耐性 (Partition-tolerance)が満たされなくなります。
分散型データベースの特徴を検証する
まずは、こんな構成のクラスタを作ってみて、
・ データ挿入と検索ができること ・ 一部のノードがダウンしてもデータ更新と検索ができること
をご紹介したいと思います。
cassandraは、1ノードをスケールアップするのではなく、複数ノードでスケールアウトしてデータを分散配置してこそ威力を発揮するデータベースです。 cassandraの特徴を理解するためには、最低でも3ノードで構成する必要があるかと思います。 今回は、検証用として最もお手軽に構築できると私が考えるDockerを活用し、3ノードクラスタのCassandraを構築して検証します。
3ノードクラスタの構築
まずは、ローカルPCで以下のコマンドを実行してください。
$ docker network create cassnw01 # node1を作成 $ docker run --name node1 -d --network cassnw01 -e CASSANDRA_SEEDS=node1 -e CASSANDRA_NUM_TOKENS=8 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_DC=DC1 -e CASSANDRA_RACK=rack1 cassandra:4.0 # node2を作成 $ docker run --name node2 -d --network cassnw01 -e CASSANDRA_SEEDS=node1 -e CASSANDRA_NUM_TOKENS=8 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_DC=DC1 -e CASSANDRA_RACK=rack1 cassandra:4.0 # node2を作成 $ docker run --name node3 -d --network cassnw01 -e CASSANDRA_SEEDS=node1 -e CASSANDRA_NUM_TOKENS=8 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_DC=DC1 -e CASSANDRA_RACK=rack1 cassandra:4.0
Dockerコンテナを作成できたかどうかを確認します。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 18d23e912f43 cassandra:4.0 "docker-entrypoint.s…" 4 minutes ago Up 2 minutes 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node3 7cc5f5886bb8 cassandra:4.0 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node2 d2fd102f6b5e cassandra:4.0 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node1
Cassandraクラスタが正常に起動しているかを確認します。 node1にログインします。
$ docker exec -it d2fd102f6b5e bash # nodetool status Datacenter: DC1 =============== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.20.0.3 73.65 KiB 8 63.9% 5e7c1c46-8750-44c8-87d6-8d37341c64c7 rack1 UN 172.20.0.4 132.65 KiB 8 72.4% 4a9047ea-631e-4850-9bb6-bca3d5b92f16 rack1 UN 172.20.0.2 73.74 KiB 8 63.7% 0cc60fd2-ba63-4225-94dc-2bf57c74c7bc rack1
nodetoolコマンドは、Cassandraの管理系のコマンドです。 様々なオプションがありますが、今回はクラスタの状態を確認するためのオプションstatusを使用しております。 最初の列が、UN(Up Normal)と表示されていれば、クラスタ内の全ノードが正常起動していることを確認することができます。
3ノードのクラスタの作成は以上です。 簡単にクラスタ構成を組むことができました。
データ挿入と検索
node1のCassandraにログインします。 Cassandraにログインするためには、cqlshというコマンドを使用します。 Postgresqlのpsqlコマンド、mariadbのmysqlコマンドと似たような位置付けのコマンドです。
# cqlsh Connected to Test Cluster at 127.0.0.1:9042 [cqlsh 6.0.0 | Cassandra 4.0-rc1 | CQL spec 3.4.5 | Native protocol v5] Use HELP for help. cqlsh>
Keyspaceを作成します。Keyspaceはこの時点では、RDBMSのスキーマのようなものだと考えてください。 以降、createやinsert,updateといったSQLに似たコマンドを使用しますが、CassandraではSQLを使用できません。CQLというSQLに似ていますが、SQLとは異なるCassandra独自のコマンドを使用します。
cqlsh> CREATE KEYSPACE killr_video WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': '2'} AND durable_writes = true;
ここでは、WITH replication = {‘class’: ‘NetworkTopologyStrategy’, ‘DC1’: ‘2’} に注目してください。
これは、データセンター「DC1」内で、データを2つ複製することを意味します。複数のノードに複製データを分散配置するということです。 CassandraのKeyspaceは、RDBMSスキーマのようにオブジェクトを論理的に分類するだけでなく、データを複製する単位でもあることを意味します。
Keyspace内にテーブル作成します。
cqlsh:killr_video> CREATE TABLE users ( ... email TEXT, ... name TEXT, ... age INT, ... date_joined DATE, ... PRIMARY KEY ((email)) ... );
データを3行挿入します。
cqlsh> use killr_video ; cqlsh:killr_video> INSERT INTO users (email, name, age, date_joined) VALUES ('atakaoka@sios.com', 'atakaoka', 25, '2020-01-01'); cqlsh:killr_video> INSERT INTO users (email, name, age, date_joined) VALUES ('btakaoka@sios.com', 'btakaoka', 25, '2020-01-01'); cqlsh:killr_video> INSERT INTO users (email, name, age, date_joined) VALUES ('ctakaoka@sios.com', 'ctakaoka', 25, '2020-01-01'); cqlsh:killr_video> select * from users ; email | age | date_joined | name -------------------+-----+-------------+---------- atakaoka@sios.com | 25 | 2020-01-01 | atakaoka ctakaoka@sios.com | 25 | 2020-01-01 | ctakaoka btakaoka@sios.com | 25 | 2020-01-01 | btakaoka (3 rows)
3行検索できました。 ここまでは、RDBMSと同じような感覚で違和感が無いと思います。
それでは、3行がどのノードに複製されているのかを調べてみます。 Primary Keyの値毎にどのノードに配置されえているかを。nodetoolコマンドのgetendpointsオプションを使って調べることができます。 書式:nodetool getendpoints キースペース名 テーブル名 ‘Primary Keyの値’
# nodetool getendpoints killr_video users 'atakaoka@sios.com' 172.20.0.3 172.20.0.2 # nodetool getendpoints killr_video users 'btakaoka@sios.com' 172.20.0.3 172.20.0.4 # nodetool getendpoints killr_video users 'ctakaoka@sios.com' 172.20.0.3 172.20.0.4
複数ノードに分散して配置されていることがわかります。
一部のノードがダウンしてもデータ検索と更新ができることの検証
1ノードがダウンしても、UPDATEもSELECTも成功することを検証してみます。
試しに、「atakaoka@sios.com」が格納されているnode1をシャットダウンさせてみて、 UPDATEとSELECTを実行してみたいと思います。 node1をシャットダウンします。
$ docker stop d2fd102f6b5e; d2fd102f6b5e $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 18d23e912f43 cassandra:4.0 "docker-entrypoint.s…" About an hour ago Up 59 minutes 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node3 7cc5f5886bb8 cassandra:4.0 "docker-entrypoint.s…" About an hour ago Up About an hour 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node2
node3にログインします。(node2でも構いません。)
$ docker exec -it 18d23e912f43 bash # nodetool status Datacenter: DC1 =============== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.20.0.3 193.42 KiB 8 63.9% 5e7c1c46-8750-44c8-87d6-8d37341c64c7 rack1 UN 172.20.0.4 156.48 KiB 8 72.4% 4a9047ea-631e-4850-9bb6-bca3d5b92f16 rack1 DN 172.20.0.2 192.92 KiB 8 63.7% 0cc60fd2-ba63-4225-94dc-2bf57c74c7bc rack1
172.20.0.2(node1)がDN(Down)と表示されています。 「atakaoka@sios.com」を検索してみます。 node2にも格納されているので、検索できる筈です。
Connected to Test Cluster at 127.0.0.1:9042 [cqlsh 6.0.0 | Cassandra 4.0-rc1 | CQL spec 3.4.5 | Native protocol v5] Use HELP for help. cqlsh> cqlsh> use killr_video ; cqlsh:killr_video> select * from users where email='atakaoka@sios.com' ... ; email | age | date_joined | name -------------------+-----+-------------+---------- atakaoka@sios.com | 25 | 2020-01-01 | atakaoka (1 rows)
検索できました。
次に、「atakaoka@sios.com」を更新してみます。
cqlsh:killr_video> update users set age=30 where email='atakaoka@sios.com'; cqlsh:killr_video> select * from users where email='atakaoka@sios.com'; email | age | date_joined | name -------------------+-----+-------------+---------- atakaoka@sios.com | 30 | 2020-01-01 | atakaoka (1 rows)
node1がダウンしても、更新、検索ができていることがわかると思います。
次に、「atakaoka@sios.com」が格納されているノードのnode1とnode2の両方をシャットダウンさせてみます。 node2をシャットダウンします。
$ docker stop 7cc5f5886bb8 7cc5f5886bb8 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 18d23e912f43 cassandra:4.0 "docker-entrypoint.s…" About an hour ago Up About an hour 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp node3
node3にログインします。
$ docker exec -it 18d23e912f43 bash # nodetool status Datacenter: DC1 =============== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack DN 172.20.0.3 193.42 KiB 8 63.9% 5e7c1c46-8750-44c8-87d6-8d37341c64c7 rack1 UN 172.20.0.4 156.48 KiB 8 72.4% 4a9047ea-631e-4850-9bb6-bca3d5b92f16 rack1 DN 172.20.0.2 192.92 KiB 8 63.7% 0cc60fd2-ba63-4225-94dc-2bf57c74c7bc rack1
172.20.0.2(node1)だけでなく、172.20.0.3(node2)もDN(Down)と表示されています。 「atakaoka@sios.com」を検索してみますが、node1もnode2もダウンしているので、検索できない筈です。
# cqlsh Connected to Test Cluster at 127.0.0.1:9042 [cqlsh 6.0.0 | Cassandra 4.0-rc1 | CQL spec 3.4.5 | Native protocol v5] Use HELP for help. cqlsh> use killr_video ; cqlsh:killr_video> select * from users where email='atakaoka@sios.com'; NoHostAvailable: ('Unable to complete the operation against any hosts', {})
「atakaoka@sios.com」を更新してみます。
cqlsh:killr_video> update users set age=40 where email='atakaoka@sios.com'; NoHostAvailable: ('Unable to complete the operation against any hosts', {: Unavailable('Error from server: code=1000 [Unavailable exception] message="Cannot achieve consistency level ONE" info={\'consistency\': \'ONE\', \'required_replicas\': 1, \'alive_replicas\': 0}')})
検索も更新もできませんでした。
先程、「atakaoka@sios.com」が格納されているnode1がダウンしている間に、node2の「atakaoka@sios.com」の年齢25を30に更新しました。 node2のデータは更新中に起動していなかったので、年齢は25のままのように思えますが、node1を起動して確認してみます。
わかりやすさのためにnode2をダウンさせて、node1の「atakaoka@sios.com」を検索できるようにします。 ノード1を起動します。
$ docker start d2fd102f6b5e d2fd102f6b5e
ノード2を停止します。
$ docker stop 7cc5f5886bb8 7cc5f5886bb8
node3にログインします。(node2でも構いません。)
$ docker exec -it 18d23e912f43 bash # nodetool status Datacenter: DC1 =============== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack DN 172.20.0.3 193.42 KiB 8 63.9% 5e7c1c46-8750-44c8-87d6-8d37341c64c7 rack1 UN 172.20.0.4 199.23 KiB 8 72.4% 4a9047ea-631e-4850-9bb6-bca3d5b92f16 rack1 UN 172.20.0.2 198.43 KiB 8 63.7% 0cc60fd2-ba63-4225-94dc-2bf57c74c7bc rack1
172.20.0.3(node2)がDN(Down)と表示されています。
node1の「atakaoka@sios.com」を検索してみます。
# cqlsh Connected to Test Cluster at 127.0.0.1:9042 [cqlsh 6.0.0 | Cassandra 4.0-rc1 | CQL spec 3.4.5 | Native protocol v5] Use HELP for help. cqlsh> use killr_video ; cqlsh:killr_video> cqlsh:killr_video> select * from users where email='atakaoka@sios.com'; email | age | date_joined | name -------------------+-----+-------------+---------- atakaoka@sios.com | 30 | 2020-01-01 | atakaoka
ageが、25から30に更新されており、node1がダウンしている間の更新が反映されていることがわかります。 これもCassanraの素晴らしい特徴の一つで、hinted handoffというデータ修復の仕組みが働いた結果となります。
以上の検証からわかること
- Cassandraは、データを複数ノードに分散配置する。データとは、正確にはPartition単位(Primary key単位ではありません)を意味します。
- 対象データが分散配置されたノードの一部がダウンしても、参照、更新は成功する。
- 対象データが分散配置されたノードの一部がダウンから復旧した場合、復旧後に更新が反映される。
以上、Cassandraの分散データベースとしての特徴の一部を紹介しました。 今後も、少しずづCassandraの特徴をご紹介していきたいと思います。