Cassandra のデータ構造
Cassandra の Column Family は、全体としては以下のような2次元のMapのような構造をしています。
Map<RowKey, SortedMap<ColumnKey, ColumnValue>>
上記の RowKey は CQL では Partition Keyと呼ばれていて、この Partition Key 単位でノードにデータが配置されます。
また、CQLでは主キーかつPartition Keyでない ColumnKey をClustering Columnと呼んでいます (名前の通り、あるPartition中でこのキーでKVの塊をつくるから)。
単一パーティションにread/write が大量に発生すると、特定のノードの負荷が上がることになります。
負荷分散を考慮してPartition Keyを決める必要があります。
refs: http://ameblo.jp/principia-ca/entry-11886808914.html
CQL で作ったデータの物理的構造
以下のようなテーブルを作成し、データをinsertします。
CREATE KEYSPACE key_space_test WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
use key_space_test;
CREATE TABLE testtable (
pkey1 text,
pkey2 text,
skey int,
value text,
cvalue map<text,text>,
PRIMARY KEY ((pkey1, pkey2), skey)
) WITH CLUSTERING ORDER BY (skey DESC);
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_1', 100, {'hoge': 'fuga'}, 'value11_100');
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_2', 'pkey2_1', 200, {'hoge': 'fuga'}, 'value21_200');
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_2', 101, {'hoge1': 'fuga1'}, 'value12_101');
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_3', 102, {'hoge1': 'fuga1'}), 'value13_102';
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_1', 101, {'hoge1': 'fuga1'}, 'value11_101');
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_1', 103, {'hoge1': 'fuga1'}, 'value11_103');
INSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_1', 102, {'hoge1': 'fuga1'}, 'value11_102');
NSERT INTO testtable (pkey1, pkey2, skey, cvalue, value)
VALUES('pkey1_1', 'pkey2_1', 12, {'hoge': 'fuga'}, 'value11_12')
cqlsh> use key_space_test ;
cqlsh:key_space_test> select * from testtable;
pkey1 | pkey2 | skey | cvalue | value
---------+---------+------+--------------------+-------------
pkey1_1 | pkey2_1 | 103 | {'hoge1': 'fuga1'} | value11_103
pkey1_1 | pkey2_1 | 102 | {'hoge1': 'fuga1'} | value11_102
pkey1_1 | pkey2_1 | 101 | {'hoge1': 'fuga1'} | value11_101
pkey1_1 | pkey2_1 | 100 | {'hoge': 'fuga'} | value11_100
pkey1_1 | pkey2_1 | 12 | {'hoge': 'fuga'} | value11_12
pkey1_2 | pkey2_1 | 200 | {'hoge': 'fuga'} | value21_200
pkey1_1 | pkey2_3 | 102 | {'hoge1': 'fuga1'} | value13_102
pkey1_1 | pkey2_2 | 101 | {'hoge1': 'fuga1'} | value12_101
(7 rows)
上記状態を cassandra-cli で見ると以下のように見えます。
[default@unknown] use key_space_test;
Authenticated to keyspace: key_space_test
[default@key_space_test] list testtable;
Using default limit of 100
Using default cell limit of 100
-------------------
RowKey: pkey2_1:pkey2_1
-------------------
RowKey: pkey1_1:pkey2_1
=> (name=103:, value=, timestamp=1409032006570000)
=> (name=103:cvalue:686f676531, value=6675676131, timestamp=1409032006570000)
=> (name=103:value, value=76616c756531315f313033, timestamp=1409049933189000)
=> (name=102:, value=, timestamp=1409032011667000)
=> (name=102:cvalue:686f676531, value=6675676131, timestamp=1409032011667000)
=> (name=102:value, value=76616c756531315f313032, timestamp=1409049943179000)
=> (name=101:, value=, timestamp=1409031979603000)
=> (name=101:cvalue:686f676531, value=6675676131, timestamp=1409031979603000)
=> (name=101:value, value=76616c756531315f313031, timestamp=1409049954219000)
=> (name=100:, value=, timestamp=1409029158989000)
=> (name=100:cvalue:686f6765, value=66756761, timestamp=1409029158989000)
=> (name=100:value, value=76616c756531315f313030, timestamp=1409049974715000)
=> (name=12:, value=, timestamp=1409051010713000)
=> (name=12:cvalue:686f6765, value=66756761, timestamp=1409051010713000)
=> (name=12:value, value=76616c756531315f3132, timestamp=1409051010713000)
-------------------
RowKey: pkey1_2:pkey2_1
=> (name=200:, value=, timestamp=1409029194029000)
=> (name=200:cvalue:686f6765, value=66756761, timestamp=1409029194029000)
=> (name=200:value, value=76616c756532315f323030, timestamp=1409050124020000)
-------------------
RowKey: pkey1_1:pkey2_3
=> (name=102:, value=, timestamp=1409029279630000)
=> (name=102:cvalue:686f676531, value=6675676131, timestamp=1409029279630000)
=> (name=102:value, value=76616c756531335f313032, timestamp=1409050027490000)
-------------------
RowKey: pkey1_1:pkey2_2
=> (name=101:, value=, timestamp=1409029232070000)
=> (name=101:cvalue:686f676531, value=6675676131, timestamp=1409029232070000)
=> (name=101:value, value=76616c756531325f313031, timestamp=1409050045987000)
5 Rows Returned.
Elapsed time: 154 msec(s).
つまり、
- CQL で複数の Partition Key (上記の例では pkey1, pkey2) を指定すると、PKに指定した 値 が ':' で繋がれて一つの RowKey になる。
- ColumnKey は Partition Key でないカラムキー名を ':' で繋いだものになる (例外として 主キーとして指定されたキーは、値が使用される)
- この理由により、CLUSTERING ORDER に指定できるキーが主キーに制限されている
- Map のキーは :<キー名> のフォーマットで ColumnKey の一部になる
- CQL の CLUSTERING ORDER に指定したキーは、ColumnKey に 値 が入りソートされる (valueに入れてもソートできないため)
- 値の型がinteger だと、ちゃんと数値の大小でソートされる (キーに入ってるのにどうやって型を識別してるのか不明。素晴らしい)
CQLでRowKey でない主キー (Clustering Column: 上記例の場合 skey) を指定すると、RowKeyでない主キーは 値 がColumnKey に設定されることで、別のキーと分離される。
これにより、主キー的な動きとなる。
DynamoDB との比較
DynamoDB のデータ構造
上記のように、DynamoDB の Hash Key は Casandraで言うところのRowKey。ここは完全に一致。
Range Key は、CasandraではRowKeyでない主キーをつなぎあわせたものになっている。
インデックス
Cassandra では、DynamoDBの「ローカルセカンダリインデックス」相当 (たぶん) のインデックスを作成できる。
いつindexを使うべきでないか?
- カーディナリティが高い (値の種類が多い)データ
- 頻繁に更新されるカラム
- 大量のカラムがあるpartition中でindexでナロイングする場合
逆に、使いどきがよくわからん。
Cassandra での検索
SELECT 文の WHERE 句に指定できるキー
- Partition Key: 指定する場合、全てのPartition Keyの指定が必須
- Partition Key以外の主キー。数値/日時の比較には大小関係の比較が可能。
- Indexを付けたキー (not 主キー)
Partition Key を指定しないでクエリを行う場合、複数のパーティションをまたいでクエリが走るので ALLOW FILTERING オプションを付ける必要がある。
このオプションを付ける場合、データが大量にあるときのクエリ時間が不定になるためできるだけ付けないでクエリが実行できるテーブル構成にするべき。