Cassandraの分散データベースとしての特徴を語る (第1回)

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

  • The Administrator Certification
  • The Developer Certification

今後は、Cassandraの面白い機能や特徴を少しずつ紹介してきたいと思います。 尚、Cassandraは、RDBMSと同じ設計思想で構築してしまうとその特徴を活かすことができません。 そのため、RDBMSと同じ設計思想だと失敗しそうな点に注意して説明していきたいと思います。 今後説明するテーマの候補 * 諸事情により予告なく変更される場合があります。

 

  • データを分散配置しても一貫性を保つための仕組み
  • Primary Keyの考え方
  • UPSERTについて
  • クラスタのノードの拡張
  • 複数データセンター構成
  • バックアップ・リカバリ
  • 大量データのロード方法
  • トランザクション
  • アプリケーションドライバからの接続方法

第1回目の今回は、Cassandraの分散型データベースとしての特徴の一部をご紹介しようと思います。

分散型データベースとしての特徴

Cassandraの分散型データベースとしての主な特徴です。

1. マスター、スレーブの概念が無く、データを複数ノードに複製して配置することができる。そのためSPOFが無い。
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の特徴をご紹介していきたいと思います。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

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

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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です