Carpe Diem

備忘録

Cassandraのデータモデルを理解する

概要

Cassandraはワイドカラム型のKVSでデータモデル図もネット上には色々上がっていますが、実際にはTable Schemaもあって普通のRDBのような表に見えます。
またkeyも

  • primary key
  • row key
  • partition key
  • composite key
  • clustering key

とたくさん出てきてややこしいです。
しかしCassandraを有効に活用するにはこの辺をきちんと理解しておかないといけないので、整理して分かりやすくしようと思います。

環境

  • Cassandra 3.11.6

Cassandraのデータモデル図

Cassandraのデータモデル図は以下のようになっています。
1つの行(Row)に複数の列(Column)が入っており、このColumnは最大で20億個まで増やせます。

f:id:quoll00:20200316132624p:plain

ref: 4. The Cassandra Query Language - Cassandra: The Definitive Guide, 2nd Edition [Book]

さらにClustering KeyやStatic Columnを考慮すると以下のようになっています。

f:id:quoll00:20200316132703p:plain

ref: 4. The Cassandra Query Language - Cassandra: The Definitive Guide, 2nd Edition [Book]

各keyの役割

次に各keyの説明をします。

key 説明
partition key WHERE句で探索する最小単位。
データがどのノードに分散配置されるかの責務を負う。
partition keyの値が同じであれば同一ノードに保存さる。
row key partition keyと同じ
Thrift APIの頃はrow keyと呼んでいたが、
今はCQLのためpartition keyと呼ぶ
clustering key partition内で特定のフィールド値でグループ化、
ソートして格納する際に使用。
WHERE句にpartition keyが含まれていればclustering keyも指定できる
primary key データの識別子。PRIMARY KEY ()で指定したkey。
1つだけであればpartition keyと同値
partition keyとclustering keyの複合ケースもある。
識別子なのでprimary keyが同じ値でinsertすると上書きされる。
composite key 複数のcolumnをまとめて指定するケースは皆composite keyといえる。

具体例

具体的にtable schemaを使って各keyがどのように作用するかを説明します。
例として国ごとの学校を管理するテーブルを作ってみます。

primary keyが1つのとき

一番シンプルなケースで、RDBと似たようなデータの持ち方になります。

CREATE TABLE schools (  
   school_name text,
   city text,
   province text,
   country_code text,
   opening_date timestamp,
   PRIMARY KEY (school_name)
);

partition keyとclustering keyは以下のようになります。

key どのフィールドか
partition key school_name
clustering key なし

この場合school_nameがユニークで、データを検索する時はschool_nameで行います。

primary keyが複数(composite key)のとき

場所別で検索したい場合は以下のようにPRIMARY KEYを設定します。

CREATE TABLE schools_by_location (  
   school_name text,
   city text,
   province text,
   country_code text,
   opening_date timestamp,
   PRIMARY KEY (country_code, province, city, opening_date)
);

この場合最初に指定したcolumnのみpartition keyとなります。つまり国コードです。

key どのフィールドか
partition key country_code
clustering key province, city, opening_date

clustering keyは、パーティション内のデータのソートを担当します。
したがってクエリ結果は最初に昇順でprovince、次に昇順でcity、最後に昇順でopening_dateの順に並べられます。
先程のテーブル作成クエリは以下と同じです。

CREATE TABLE test.schools_by_location (
    country_code text,
    province text,
    city text,
    opening_date timestamp,
    school_name text,
    PRIMARY KEY (country_code, province, city, opening_date)
) WITH CLUSTERING ORDER BY (province ASC, city ASC, opening_date ASC);

複合partition keyにしたいとき(composite partition key)

先程の例だとその国の全ての学校が1パーティションに入ってしまい非常に大きな列数の行になってしまいます。
ユースケース的にもっと絞っていい、例えば市ごとで管理したいのであれば

CREATE TABLE schools_by_location (  
   school_name text,
   city text,
   province text,
   country_code text,
   opening_date timestamp,
   PRIMARY KEY ((country_code, province, city), opening_date)
);

このように複合partition keyとなるcolumnを括弧でくくります。

key どのフィールドか
partition key country_code, province, city
clustering key opening_date

複合partition keyの場合、WHERE句は

  • WHERE country_code='xxx'
  • WHERE country_code='xxx' AND province='yyy'
  • WHERE country_code='xxx' AND province='yyy' AND city='zzz'

のように先頭から順に指定して抽出することが可能です。
partition keyが全て揃っていればclustering keyもWHERE句に含められます。

  • WHERE country_code='xxx' AND province='yyy' AND city='zzz' AND opening_date > 'some date'

PRIMARY KEY ()の書き方まとめ

上記具体例のようにPRIMARY KEY ()の書き方によってpartition keyとclustering keyが変わるので以下の表でまとめます。

書き方 partition key clustering key
PRIMARY KEY (a) a なし
PRIMARY KEY (a, b) a b
PRIMARY KEY ((a, b)) (a, b) なし
PRIMARY KEY (a, b, c) a (b, c)
PRIMARY KEY ((a, b), c) (a, b) c
PRIMARY KEY ((a, b), c, d) (a, b) (c, d)

Table Schemaと実際のデータ保存の仕方

先程データモデル図では列がどんどん拡張できるように見えますが、Table SchemaやCQLでのクエリ結果を見るとRowが伸びているように見えるため、実際のデータがどう保存されるかを示してそのギャップを埋めます。

シンプルなケース

まずはPrimary Keyが一つでRDBのような使い方のケースです。
以下のようにTableを作ります。

CREATE TABLE example (
    field1 int PRIMARY KEY,
    field2 int,
    field3 int);

データを投入します。

INSERT INTO example (field1, field2, field3) VALUES (1,2,3);
INSERT INTO example (field1, field2, field3) VALUES (4,5,6);
INSERT INTO example (field1, field2, field3) VALUES (7,8,9);

実際保存されるデータはどうなっているかというと

RowKey: 1
=> (column=, value=, timestamp=1584347787299000)
=> (column=field2, value=2, timestamp=1584347787299000)
=> (column=field3, value=3, timestamp=1584347787299000)
-------------------
RowKey: 4
=> (column=, value=, timestamp=1584347787299000)
=> (column=field2, value=5, timestamp=1584347787299000)
=> (column=field3, value=6, timestamp=1584347787299000)
-------------------
RowKey: 7
=> (column=, value=, timestamp=1584347787299000)
=> (column=field2, value=8, timestamp=1584347787299000)
=> (column=field3, value=9, timestamp=1584347787299000)

こんな感じになっています。
最初のcolumn(field1)はパーティションキーでもあるため、Cassandraは値を繰り返しません。なのでcolumn=, value=,となっています。
次の2つの列(field2, field3)には、関連する列名や値が保持されます。

RDBと一緒(に見える)なので理解しやすいですが、columnにはfield2field3といった列名が入っているため列は増えません(=ワイドカラムにならない)。

複雑なケース

次にpartition key、clustering keyが混ざった複雑なケースです。

CREATE TABLE example (
    partitionKey1 text,
    partitionKey2 text,
    clusterKey1 text,
    clusterKey2 text,
    normalField1 text,
    normalField2 text,
    PRIMARY KEY (
        (partitionKey1, partitionKey2),
        clusterKey1, clusterKey2
        )
    );

データを投入します。

INSERT INTO example (
    partitionKey1,
    partitionKey2,
    clusterKey1,
    clusterKey2,
    normalField1,
    normalField2
    ) VALUES (
    'partitionVal1',
    'partitionVal2',
    'clusterVal1',
    'clusterVal2',
    'normalVal1',
    'normalVal2');

実際保存されるデータはどうなっているかというと

RowKey: partitionVal1:partitionVal2
=> (column=clusterVal1:clusterVal2:, value=, timestamp=1584347787473000)
=> (column=clusterVal1:clusterVal2:normalField1, value=normalVal1, timestamp=1584347787473000)
=> (column=clusterVal1:clusterVal2:normalField2, value=normalVal2, timestamp=1584347787473000)

こんな感じになります。

columnにclusterVal1:clusterVal2といったclustering keyで指定した列の値が入っています。
つまりclustering keyで指定した列の値が異なれば新しい列として保存されるので、列が広がっていくようになります。

例えば時系列データの列をclustering keyに含めれば、時刻毎に列が追加され時系列データが1行(パーティション)にまとまって保存されるようになります。

まとめ

Cassandraがどのようにデータを管理するかを説明しました。
Cassandra、HBase、BigTableといったワイドカラム型DBは

  • メトリクスデータ
  • 履歴系
  • メッセージング

といった時系列データが1行にまとまり、RDBのように何千、何万行も走査しなくて済むのでユースケースに応じて選択していきたいですね。

ソース