usergridのEd Anuff氏は、Cassandra SF 2011でインデクシングテクニックについてのプレゼンテーションを行った。Cassandraはバージョン0.7以降、セカンダリインデックスをサポートしているが、Anuff氏によれば、セカンダリインデックスはカーディナリティが高い場合にはうまく機能しない。また、少なくとも1度は等価比較を実行しなければならず、検索するとソートされていない値が返される。Anuff氏はプレゼンテーションの中で、ワイドローやCassandra 0.8.1で追加された複合コンパレータといった、これらの制限を打開するための代替インデクシングパターンや、スーパーカラムを利用するうえでの注意点について述べた。
- ネイティブセカンダリインデックス
- 検索とグルーピングに利用されるワイドロー
- カスタムセカンダリインデックス
スーパーカラム
初期のCassandraでは、スーパーカラムが代替インデックスとして使われるのが一般的であった。しかし、スーパーカラムは気をつけて使ったほうがよい、とAnuff氏は言う。パフォーマンスの問題や、サブカラムがソートされないといった問題、スーパーカラムを二重にネストできないなどの理由から、多くのプロジェクトがスーパーカラムを避けるようになったのだ。Cassandraのカラムファミリーはセカンダリインデックスを実装する手段として使われているために、ソート順とコンパレータを持っていることにAnuff氏は気がついた。
ネイティブセカンダリインデックス
Anuff氏の説明によれば、ネイティブセカンダリインデックスは、独立した隠しカラムファミリーに格納されるインデックスとして実装されている。各ノードは、自身が保存するローにインデックスを作成する。そして、クエリが発行されると、クエリは全ノードに送られ、分散して処理される。Cassandra 0.8.1はインデックスを等価演算のために利用し、レンジ演算はコーディネーターノードによってインメモリで実行される。これらの特徴はアプリケーションを制限し、また、Cassandraがネイティブに理解できるデータ型しか使えないという制限がある。
ワイドロー
Cassandraに初めて触れた人々は、なぜ1つのローに20億ものカラムが必要なのか不思議がることが多い。だが、Cassandraにおいて、カラムはインデクシング、データの整理、項目間の関連の土台となるものであり、もしカラム数が100を超えるローがないデータモデルなのであれば、それは何か間違いを犯しているかCassandraを使うべきではないかのどちらかだ、とAnuff氏は主張する。ワイドローは、例えば下記の企業の部門テーブルのような、巨大なコレクションのモデルに利用できる。
departments = {
"Engineering" : {"137acd" : null, "e245116" : null, ... },
"Sales" : { "334762" : null, "17a632" : null, ... },
...
}
Anuff氏はワイドローを使うことにより、次のような利点があると指摘する。
- カラム操作が高速
- カラムスライスをレンジで取り出すことができる
- 結果が常にソートされる
- 対象のキーが時間ベースのUUIDなら、タイムスタンプによってグルーピングとソートの両方が可能
複合カラム
しかしならが、ワイドローのアプローチはルックアップメカニズムを提供するわけではなく、主キーとして機能するだけである。一般に、ワイドローは1:1マッピングへの使用(つまり、それぞれの値がローの中に一度だけ現れる場合)に制限される。ここで、名字によってインデクシングされたエンティティ用のカラムファミリーを考えよう。Anuff氏は、このような場合には複合キーを利用することを推奨する。複合キーはCassandra 0.8.1でサポートされたもので、2つの新しいコンパレータが関係している。CompositeTypeは基礎となるコンパレータで、このコンパレータを使う場合はインデックス毎に1つずつカラムファミリーを作成し、複合キーに含まれる複数の型と、それぞれの型のソート順を静的に指定する。DynamicCompositeTypeは動的なコンパレータであり、複数のインデックスを1つのカラムファミリーで実現したい場合に使用する。このコンパレータの場合、それぞれのローにはソート順の異なる別々のインデックスを格納することができる。Anuff氏によれば、DynamicCompositeTypeはHectorプロジェクトのJPA実装の、自動生成されるインデックスに使われている。HectorはCassandraのJavaクライアントの1つで、Anuff氏もコントリビュータの一人である。
複合キーは以下のようなものである。
User_Keys_By_Last_Name = {
"Engineering" : {"anderson", 1 : "ac1263", "anderson", 2 : "724f02", ... },
"Sales" : { "adams", 1 : "b32704", "alden", 1 : "1553bd", ... },
...
}
これらの複合インデックスに対する検索は容易だが、一方でデータ更新は困難である、とAnuff氏は指摘する。データを更新するためには、古いデータを削除し、新しいデータを挿入する必要があるからだ。一般に、書き込み前にデータを読み取ることは、Cassandraでは問題となる。Anuff氏は、例えばZooKeeperのようなものを使ってロックを行うのではなく、3つのカラムファミリーを使ったテクニックを提示している。例えば、UsersカラムファミリーとIndexesカラムファミリーがある場合、Users_Index_Entriesカラムファミリーも作成する。更新時には、一貫性の問題を回避するために、まずUsers_Index_Entriesから更新前の値を読み取り、ロックが必要になるのを避けるためUsers_Index_EntriesとUsersの両方でタイムスタンプのついたカラムを使用する。このテクニックを使った実装のサンプルは、Anuff氏のgithubプロジェクト、CassandraIndexedCollectionsで参照することができる。また、このプレゼンテーションのスライドはここから参照できる。