イントロダクション

Cassandraは4次元または5次元ハッシュなデータモデルを持っています。

基本コンセプトとしては:

まずはボトムアップで一番小さい粒度のデータ構造であるカラムから順にみていきましょう。

カラム

カラムはCassandraにおける最小限のデータ構造です。その実体はタプル(triplet)で、名前、値、タイムスタンプを持ちます。

Thriftインタフェースのカラムの定義は以下のようになります。

struct Column {
  1: binary                        name,
  2: binary                        value,
  3: i64                           timestamp,
}

JSONぽい書き方をしたカラムは以下のようになります。

{
  "name": "emailAddress",
  "value": "foo@bar.com",
  "timestamp": 123456789
}

タイムスタンプを含むカラムの全ての値はクライアントから渡されます。これはどういう事かというと、クライアントのクロックはCassandraサーバ環境間で同期が取れていなければいけないということです(カラムのタイムスタンプは衝突の解決に便利です)。ほとんどのケースでは、タイムスタンプはクライアントアプリケーションでは使われないので、カラムは名前と値のペアと考えると比較的楽に思い浮かびます。このドキュメントの以降の説明では、タイムスタンプを読みやすさのため明示的に載せないようにしています。また、実際保存されるカラムの名前と値はバイナリ値ですが、ほとんどのアプリケーションではUTF8でシリアライズされた文字列なのでこのドキュメントでもそうします。

タイムスタンプは何でもいいのですが、便宜上マイクロ秒記載としておいてください。どのように使っても良いですが、アプリケーション間では一貫している必要があります。そうでない場合、新らしい書き込みで上書きされてしまう可能性があります。

カラムファミリ

カラムファミリはカラムのコンテナといえます。リレーショナルモデルでいうところのテーブルにあたります。カラムファミリはstorage-conf.xmlで定義され、Cassandraが再起動されるまでは修正や新規カラムファミリの追加は出来ません(注:0.6以降で変更の可能性アリ)。カラムファミリはカラムネーム順でソートされたカラムのリストを持ちます。

カラムファミリは各行ごとのカラム順序を設定可能になっていて、その設定はThriftのAPI経由でget_sliceメソッドを呼んだ場合の挙動に影響を与えます。 順序の維持の実装は、ASCII、UTF8、Long、レキシカルUUID、TimeUUIDの中からデフォルトでは選択できます(注:自前でも実装可能)。

Cassandraでは、各カラムファミリは別個のファイルにて管理され、行ごと(例えばキー)にソートされています。あなたが一緒にアクセスするであろう、関連したカラムは同じカラムファミリ内に保存されているはずです。

行キーはどんなマシンでデータを永続化するかを決定します(The row key is what determines what machine data is stored on.)。それゆえ、複数のカラムファミリから取得したデータの各キーは関連している必要があります。 しかしながらそれらは論理的に分離されているので、Thriftインタフェースでは一回のアクセスで1つのカラムファミリのキーしか取れないようになっています。

JSON表現では、キー -> カラムファミリ -> カラムの構造は以下のようになります:

{
   "mccv":{
      "Users":{
         "emailAddress":{"name":"emailAddress", "value":"foo@bar.com"},
         "webSite":{"name":"webSite", "value":"http://bar.com"}
      },
      "Stats":{
         "visits":{"name":"visits", "value":"243"}
      }
   },
   "user2":{
      "Users":{
         "emailAddress":{"name":"emailAddress", "value":"user2@bar.com"},
         "twitter":{"name":"twitter", "value":"user2"}
      }
   }
}

上記のサンプルで、"mccv"キーが2つの異なるカラムファミリ、"Users"と"Stats"、を識別します。これはカラムファミリ間でデータの関連性があるということを示しているわけではない点に注意です。1つのキーで異なるカラムファミリを取得できるという事に関してどういう意味があるかは、アプリケーションにゆだねられています。また、"Users"カラムファミリをみると、"mccv"と"user2"というキーで別々のカラム名が定義されている点に注目してください。Cassandraではこのような定義は全く問題ありません。事実として、Cassandraではカラム名のセットを無限に作成することがおそらく出来るので、カラム名を実行時に増加させたりする使い方も一般的だということです。これは永続化システムではあまり一般的ではないことです。(特にRDBMSの世界から凝られた開発者の方には)

キースペース

キースペースはカラムファミリのコンテナです。Cassandraハッシュの1次元目に位置します。キースペースはRDBMSワールドでいうところのスキーマまたはデータベース、論理的なテーブルの集合を扱う概念、と大体同じ粒度のものです。キースペースはカラムファミリに対しての設定と管理ポイントを設けており、バッチインサートが適用される構造でもあります。

スーパーカラム

ここまでで通常のカラムと行はおおまかにカバーしました。それに加え、Cassandraはスーパーカラムをサポートしています。スーパーカラムとは、ソート済みのカラムの連想配列のことです。

そのため、カラムとスーパーカラムの関係はマップとして考えることも出来ます。一般的なカラムファミリの1つの行はカラム名と値のソート済みマップで、スーパーカラムファミリの1つの行は、スーパーカラム名をキーとして、値がカラム名と値のマップのソート済みマップといえます。

JSON記述ではこのようなデータ構造になります:

{
  "mccv": {
    "Tags": {
      "cassandra": {
        "incubator": {"incubator": "http://incubator.apache.org/cassandra/"},
        "jira": {"jira": "http://issues.apache.org/jira/browse/CASSANDRA"}
      },
      "thrift": {
        "jira": {"jira": "http://issues.apache.org/jira/browse/THRIFT"}
      }
    }  
  }
}

この例ではカラムファミリは"Tags"で、"cassandra"と"thrift"という2つのスーパーカラムを持ちます。またカラムとして、名前付きのブックマークを持ちます。

普通のカラムと同じように、スーパーカラムは互いに疎な関係で、各行はそれぞれ大小に関わらず別個のカラムを持つことが出来ます。Cassandraはそこに制約はありません。

レンジクエリ

Cassandraは比較的小さなコードで、プラガブルなスキーマパーティショニングをサポートします。デフォルトでは、CassandraはハッシュベースのRandomPartitionerOrderPreservingPartitionerを持っています。RandomPartitionerは追加で何もせずとも、よいロードバランスするパーティションを提供します。一方、OrderPreservingPartitionerはストアされたキーでレンジクエリを実行できるようにしますが、ノードトークンを慎重に選択するか積極的なロードバランシングが必須になります。ハッシュベースのパーティショニングしかサポートしないシステムにおいては、レンジクエリは有効な手段とはならないでしょう。

モデルを作成する

クエリをを流すために、エンティティやリレーションを作ったり、インデックスを追加したりする事が必須な手段であるリレーショナルモデルベースのシステムと異なり、Cassandraではあなたがどのようなクエリが自分のシステムにとって効果的を考えて実施し、モデルを適切に作る必要があります。Cassandraでは自動的にインデックスがはられたりはしないので、1つのクエリに対し1カラムファミリという点を重点的にみていく必要があります。これは関係モデルでいうところのテーブルとクエリの関係に似ています。Cassandraはリレーショナルシステムより圧倒的に早いので、非正規化を恐れる必要はありません。

DiggのArin SarkissianさんがCassandraのデータモデルに関して、素晴らしいエントリを書いていますのでそちらも参考にしてください。

モデルを作成する際に注意しなくてはいけない点はCassandraの制約にまとめてあるのでそちらも参考にしてください。

例:検索アプリのためのスーパーカラム

検索アプリにおいては、各スーパーカラムの名前は用語として、カラムはdocidとランク、その他属性を持つと考えることが出来ます。 ユーザIDをキーにもつとすると、各ユーザごとのインデックスをこの形式の中に押し込めることが出来ます。このようにすると、用語検索における各ユーザ毎のインデックスがFacebookのInboxサーチのように考えることが可能になります。更に、時間別にディスクにデータを書いておけば、"最新10個のメッセージをください"といったクエリにも用意に対応できるシステムを作ることが出来ます。これらについてもっと図解つきの説明はSIGMOD 2008のCassandra資料を参照してみてください。

例: ブログアプリケーション

TODO

Thrift API

APIに移動しました。

Attribution

Thanks to phatduckk and asenchi for coming up with examples, text, and reviewing concepts.

stats

DataModel_JP (last edited 2013-11-13 21:10:07 by GehrigKunz)