この記事は、Arin Sarkissian氏のブログ記事「http://arin.me/blog/wtf-is-a-supercolumn-cassandra-data-model」を氏の許可を得て翻訳したものです。(原文公開日:2009年9月1日)
ここ1、2ヶ月というもの、DiggのエンジニアリングチームはCassandraについて調べ、遊び、最終的にはプロダクションにデプロイするためにかなりの時間を費やしてきました。これは実に楽しいプロジェクトでしたが、楽しくなる前にCassandraのデータモデルについて理解するために相当の時間を費やしたのです。「'super column'って何だよ」というフレーズが何度も口にされました。
もしあなたのバックグラウンドがRDBMSならば(ほとんどみんながそうでしょうが)、Cassandraのデータモデルについて学ぶ際に、いくつかのネーミング規約でつまづくことになるでしょう。私やDiggのチームメンバは、「解る」まで2日間に渡って徹底的に話し合ったのでした。最近、開発者メーリングリストで行われた熱い議論も、いくつかの混乱を避けるために、全くあたらしい命名スキームを提案するものでした。議論を通じて私が考え続けていたのは次のようなことです。「おそらくはまともな具体例が手の届く所にあれば、ネーミングのせいでこんなに混乱が起きることはないだろう。」そうしたわけで、私がCassandraのデータモデルを説明するにあたって用いるのは、このスタブです。これは実際にやってみる上での助けになることを意図したもので、詳細には立ち入りません。しかし、いくつかの考え方を明らかにするのには役に立ちます。
ところで、この記事は長いです。PDFバージョンが必要ならば、ここからダウンロードできます(英文)。
構成要素
まずは構成要素を見てから、それらがどのように結びつけられるかを見ていきましょう。
Column
column
は最下位で最も小さいデータの単位です。これは3つ子のタプルで、name、value、timestampを持ちます。
JSON風の記法でcolumn
を表現すると次のようになります。
{ // Column name: "emailAddress", value: "arin@example.com", timestamp: 123456789 }
これだけです。単純化するために、タイムスタンプは無視しましょう。nameとvalueのペアだと思って下さい。
ちなみに、name
とvalue
がどちらもバイナリ(実質はbyte[]
)であり、どんなサイズにもなり得ます。
SuperColumn
SuperColumn
は、バイナリのnameと、マップのvalueを持つタプルです。このマップはColumn
のnameをキーとして、複数のColumn
を含みます。先ほどのJSON風記法を使いましょう。
{ // SuperColumn name: "homeAddress", // 複数のColumnを含む value: { // キーはColumnのname street: {name: "street", value: "1234 x street", timestamp: 123456789}, city: {name: "city", value: "san francisco", timestamp: 123456789}, zip: {name: "zip", value: "94107", timestamp: 123456789}, } }
Column 対 SuperColumn
Column
とSuperColumn
はどちらも、nameとvalueを持つタプルです。ポイントとなる違いは、通常のColumn
のvalueが文字列であるのに対し、SuperColumn
のvalueはColumn
のマップであるということです。これが主な違いです・・・valueは異なるタイプのデータを保持するのです。もう1つ、より些細な違いをあげれば、SuperColumn
はtimestampを持ちません。
本格的に取りかかる前に
先に進む前に、2つのやり方で記法を単純化したいと思います。1) Column
からtimestampを省略する。2) キーと値のペアに見えるように、Column
とSuperColumn
のnameを外に出す。こうではなく
{ // super column name: "homeAddress", // columnのリスト value: { street: {name: "street", value: "1234 x street", timestamp: 123456789}, city: {name: "city", value: "san francisco", timestamp: 123456789}, zip: {name: "zip", value: "94107", timestamp: 123456789}, } }
こうなります
homeAddress: { street: "1234 x street", city: "san francisco", zip: "94107", }
グルーピングする
Column
とSuperColumn
をグループ化するのに使われる構造が1つあります。この構造はColumnFamily
と呼ばれるもので、Standard
とSuper
の2種類があります。
ColumnFamily
ColumnFamily
は、複数の行("Row")
を持つ構造です。え?行
ですって?そう、行
ですこの用語は、RDBMSにおいてテーブルを考えるかのようにして、頭に馴染ませるために使っています。
さて、行
はクライアント(つまりあなた)によって与えられたキーと、Column
のマップを持ちます。繰り返しますが、マップにおけるキーはColumn
のnameであり、valueはColumn
それ自体です。
UserProfile = { // ColumnFamily phatduckk: { // ColumnFamily内の行に対するキー // 行の中には複数のcolumnがある username: "phatduckk", email: "phatduckk@example.com", phone: "(900) 976-6666" }, // 行の終わり ieure: { // ColumnFamily内の別の行に対するキー // さっきとは別に複数のColumnがある username: "ieure", email: "ieure@example.com", phone: "(888) 555-1212" age: "66", gender: "undecided" }, }
備忘録: 単純化するために、Column
のvalueだけを示していますが、実際にはマップの中のvalueはColumn
全体です。
これについて、HashMap/dictionary
や連想配列
と考えることもできます。このように考え始めれることができれば、正しい方向に向かっていると言えます。
指摘しておくべきは、このレベルで強要されるスキーマがないということです。行
は、保持するColumn
のリストについて事前に解っていません。上記の例では、"ieure"をキーに持つ行には"age"と"gender"というnameを持つカラムがありましたが、"phatduckk"というキーで特定される行にはそのカラムがありません。これは、100%フレキシブルなものです。1つの行
が1,989のColumn
を持つのに対して、別の行
が2つしかColumn
を持たないということはあり得ます。また、ある行
が"foo"というColumn
を持つのに、他の行
にはそれがないということもあり得ます。これがCassandraが持つスキーマレスという特性です。
ColumnFamilyにもSuperがある
ColumnFamily
はStandard
型かSuper
型になります。
今見てきたのは、Standard
型の例でした。Standard
たる所以は、すべての行
が含んでいるのが普通の(つまり、Superではない)Column
であるということです。SuperColumn
は登場しません。
ColumnFamily
がSuper
型である場合は逆になります。各行
はSuperColumn
のマップを含みます。このマップは、各SuperColumn
のnameをキーとし、SuperColumn
自体を値として持ちます。そして、はっきりさせておきますが、このColumnFamily
がSuper型であるからと言って、Standard型のColumnFamily
が中に含まれるわけではありません。例を示します。
AddressBook = { // Super型のColumnFamily phatduckk: { // Super ColumnFamily内の行に対するキー // ここでキーはアドレスブックのオーナーの名前 // この行の中にはsuper columnが無制限に入る // この行のキーはSuperColumnのnameである // これらSuperColumnの1つ1つがアドレスブックのエントリとなる。 friend1: {street: "8th street", zip: "90210", city: "Beverley Hills", state: "CA"}, // これがphatduckkのアドレスブックにおけるJohnのエントリである John: {street: "Howard street", zip: "94404", city: "FC", state: "CA"}, Kim: {street: "X street", zip: "87876", city: "Balls", state: "VA"}, Tod: {street: "Jerry street", zip: "54556", city: "Cartoon", state: "CO"}, Bob: {street: "Q Blvd", zip: "24252", city: "Nowhere", state: "MN"}, ... // ここにSuperColumn(つまりアドレスブックのエントリ)を無制限に入れることができる。 }, // 行の終わり ieure: { // これはSuper ColumnFamily内にある別の行に対するキーである // ieure用のすべてのアドレスブックエントリ joey: {street: "A ave", zip: "55485", city: "Hell", state: "NV"}, William: {street: "Armpit Dr", zip: "93301", city: "Bakersfield", state: "CA"}, }, }
Keyspace
Keyspace
は最も外側でデータのグルーピングを行うものです。ColumnFamily
はすべてKeyspace
に含まれます。おそらく、Keyspace
の名前はアプリケーションにちなんで付けられるでしょう。
ここで、1つのKeyspace
は複数のColumnFamily
を持つことができますが、そこに関連がなくても構いません。例えば、MySQLのテーブルと異なり、結合
することはできません。同じように、ColumnFamily_1に"phatdukk"というキーを持った行
があるからと言って、ColumnnFamily_2に同じものがあるとは限りません。
Sorting
さて、ここまで様々なデータコンテナがどのようなものであるのかを見てきました。データモデルにおけるもう一つのポイントは、データのソートのされ方です。CassandraをSQLのように検索することはできません。フェッチを行う際にどうやってソートするかを決めることはできないのです(他にも色々違いはありますが)。データはクラスタに投入した瞬間にソートされ、常にソートされた状態になっているのです。これはリード時のパフォーマンスを大幅に向上させますが、その代償としてデータモデルの策定時にアクセスパターンに合ったようなやり方で設計しなければなりません。
Column
は行
内において常にColumn
のnameによってソートされます。重要なことなので繰り返しますが、Column
は常にnameによってソートされるのです。ソートにあたってのnameの比較は、ColumnFamily
のCompareWithオプションによって行われます。選べるものとしては以下のものがあります。BytesType、UTF8Type、LexicalUUIDType、TimeUUIDType、AsciiType、それにLongTypeです。それぞれのオプションは、Column
のnameを異なるデータ型として扱うのであり、それによって少なからぬ柔軟性を得ることができます。例えば、LongTypeを用いると、Column
のnameは64bitのLong
として扱われます。これについて、ソート前後のデータを見ることで、明らかにしていきましょう。
// これは特定の行においてランダムに並んだすべてのColumnを示したもの。 // ただし、Cassandraがデータをランダムに格納することは「決して」ありえないので、 // これは単なる具体例にすぎない。 // 同様に、valueについても、ソートには一切関係ないので無視できる。 {name: 123, value: "hello there"}, {name: 832416, value: "kjjkbcjkcbbd"}, {name: 3, value: "101010101010"}, {name: 976, value: "kjjkbcjkcbbd"}
LongTypeを使用していることから、これらのColumn
がソートされると以下のようになります。
<!-- storage-conf.xmlにおけるColumnFamilyの定義 --> <ColumnFamily CompareWith="LongType" Name="CF_NAME_HERE"/> // 各カラム名は64bit long型として扱われる。 // その結果、上記のColumnはnameの数字的な順序によって並べられる。 {name: 3, value: "101010101010"}, {name: 123, value: "hello there"}, {name: 976, value: "kjjkbcjkcbbd"}, {name: 832416, value: "kjjkbcjkcbbd"}
ご覧の通り、Column
のnameは64bit Long
(つまり、かなり大きな数を格納できる数字)として比較されます。ここで、別のCompareWithオプションを用いれば、結果も異なります。CompareWithをUTF8Typeに設定すると、Column
のnameはUTF8形式にエンコードされた文字列として扱われ、ソート順は以下のようになります。
<!-- storage-conf.xmlにおけるColumnFamilyの定義 --> <ColumnFamily CompareWith="UTF8Type" Name="CF_NAME_HERE"/> // 各カラムはUTF8の文字列として扱われる {name: 123, value: "hello there"}, {name: 3, value: "101010101010"}, {name: 832416, value: "kjjkbcjkcbbd"}, {name: 976, value: "kjjkbcjkcbbd"}
このように、結果は全く異なります!
ソートのこの原則は、SuperColumn
にも同じように当てはまります。しかしこれについては、別の次元が存在します。つまり、SuperColumn
が行
においてどのようにソートされるかを決めるだけでなく、各SuperColumn
の中でColumn
がどのようにソートされるかも決定しなければならないのです。各SuperColumn
内のColumn
のソートは、CompareSubcolumnsWithの値によって決定されます。例を示します。
// 2つのSuperColumnを持つ行の見え方を示す。 // 現在はランダムに並んでいる。 { // 行における最初のSuperColumn name: "workAddress", // そこに含まれるColumn value: { street: {name: "street", value: "1234 x street"}, city: {name: "city", value: "san francisco"}, zip: {name: "zip", value: "94107"} } }, { // 同じ行のもう一つのSuperColumn name: "homeAddress", // そこに含まれるColumn value: { street: {name: "street", value: "1234 x street"}, city: {name: "city", value: "san francisco"}, zip: {name: "zip", value: "94107"} } }
ここで、CompareSubcolumnsWithとCompareWithの両者をUTF8Typeに設定すると、以下の結果になります。
// ソート後 { // UTF8の文字列として扱われるので、こちらが先になる { // 同じ行のもう一つのSuperColumn // "homeAddress"は"workAddress"よりも前なので、この行が先に来る name: "homeAddress", // SuperColumn内のcolumnもnameによってソートされる value: { // これらもColumnのnameによってソートされる city: {name: "city", value: "san francisco"}, street: {name: "street", value: "1234 x street"}, zip: {name: "zip", value: "94107"} } }, { name: "workAddress", value: { // SuperColumn内のcolumnもnameによってソートされる city: {name: "city", value: "san francisco"}, street: {name: "street", value: "1234 x street"}, zip: {name: "zip", value: "94107"} } } }
最後の例では、 CompareSubcolumnsWithとCompareWithの両方がUTF8Typeに設定されていますが、必ずしもそうする必要はありません。CompareSubcolumnsWithとCompareWithは必要に応じて別々のものを組み合わせることができます。
ソートに関して最後に追記すると、ソートを実行するためのカスタムクラスを作ることもできます。ソートの機構はプラガブルになっており、CompareSubcolumnsWithやCompareWithの値には、org.apache.cassandra.db.marshal.ITypeを実装したクラスのフルパス名を設定することができます(つまり、カスタム比較子を作ることができるのです)。
スキーマの具体例
さて、パズルのピースが揃ったところで、それを組み合わせて、シンプルなブログアプリケーションのモデルを作ってみましょう。以下の仕様でシンプルなアプリをモデリングします。
- 単一のブログをサポートする。
- 複数の著者が存在できる。
- エントリには、タイトル、本文、スラグ(エントリのパス)、公開日付が含まれる。
- エントリには複数のタグがつけられる。
- コメントを残すことはできるが、自分を登録することはできず、毎回プロフィールを打ち込む必要がある(シンプルにするため)。
- コメントには次のものが含まれる。テキスト、時間、コメントを書いた人の名前とメールアドレス。
- すべてのポストは時系列の逆順で新しいものが先頭にくるように表示しなければならない。
- 与えられたタグを含むすべてのポストを時系列の逆順で表示しなければならない。
以下の節では、このアプリのKeyspaceにおいて定義されるColumnFamily
について説明します。まず、xmlの定義を示し、特定のソートオプションを選択したことの理由について説明すると同時に、JSON風の記法を用いてColumnFamily
のデータを表示します。
Authors ColumnFamily
著者のColumnFamily
のモデリングは、実に基本的で、特別なことは行いません。各著者に対して対応する行
が与えられ、著者のフルネームがキーとなります。行
の中で、各Column
は著者の「プロフィール」の各属性を表します。
これは、各行
を使ってオブジェクトを表現している例になります。この場合は、著者オブジェクトです。このアプローチにおいて、各Column
は属性として扱われます。実にシンプルですが、1点指摘しておきます。特定の行
においてどのColumn
がなければならないかということについての「定義」はありませんので、ある種のスキーマレスな設計を行うことになります。
このColumnFamily
における行
に対するアクセスはキーのルックアップによって行われ、キーに合致したものについてはすべてのColumn
が取得されます(上記の例でいうと、存在しない'foo'というキーでアクセスした場合に、行
内の3つのcolumnがフェッチされることはありません)。ここから、Column
がどのようにソートされているかを気にする必要はないので、ソートオプションとしてはBytesTypeを使うことにします。Column
のnameについてバリデーションを行う必要がないためです。
<!-- ColumnFamily: Authors 著者の情報はすべてここに保持する。 行のキー => 著者の氏名(氏名は一意でなければならない) ColumnのName: エントリの属性(title、body、など) ColumnのValue: 属性に紐づくvalue アクセス: 著者を氏名で取得する(つまり特定の行からすべてのcolumnを取得する) Authors : { // ColumnFamily Arin Sarkissian : { // 行のキー // 「プロフィール」の属性を示すcolumn numPosts: 11, twitter: phatduckk, email: arin@example.com, bio: "bla bla bla" }, // 別の著者 Author 2 { ... } } --> <ColumnFamily CompareWith="BytesType" Name="Authors"/>
BlogEntries ColumnFamily
ここでも、ColumnFamily
に対してはシンプルなキー/バリュー検索が行われます。各行
に対して1エントリが保持されます。この行
において、Column
はエントリの各属性、つまりタイトル、本文などとして扱われます(前の例と同様です)。若干の最適化が施されており、タグをColumn
に非正規化するにあたってカンマ区切りの文字列としています。画面上はColumn
の値を分割してタグのリストを取得します。
各行
に対するキーはエントリのスラグになります。つまり、単一のエントリを取得したいと思った時には、キー(スラグ)で検索すれば良いことになります。
<!-- ColumnFamily: ブログエントリ ここにすべてのブログエントリが入る 行のキー => ポストのスラグ(uri内のseo対策となる部分) ColumnのName: エントリの属性(title、body、など) ColumnのValue: 属性に紐づくvalue アクセス: スラグによってエントリをすべて取得する(該当する行内のすべてのColumnを常にフェッチする) 参考: タグは非正規化され、カンマ区切りのリストになっている ここで用いる記法と衝突するのを避けるためJSONは使わないが、 作成するアプリケーションが扱い方を理解している限り、 どのような記法も使うことができる BlogEntries : { // ColumnFamily i-got-a-new-guitar : { // 行のキー - エントリの一意の「スラグ」 title: これは僕の新品の素晴らしいギターについてのエントリです body: かっこいいエントリかくかくしかじか author: Arin Sarkissian // Authors CFのキー tags: life,guitar,music // カンマ区切りのタグリスト(基本的な非正規化) pubDate: 1250558004 // 公開日のunixtime slug: i-got-a-new-guitar }, // その他のエントリ another-cool-guitar : { ... tags: guitar, slug: another-cool-guitar }, scream-is-the-best-movie-ever : { ... tags: movie,horror, slug: scream-is-the-best-movie-ever } } --> <ColumnFamily CompareWith="BytesType" Name="BlogEntries"/>
TaggedPosts ColumnFamily
さて、ここから少し面白くなってきます。このColumnFamily
には、ちょっとした重労働があります。これには、タグとエントリの関連を保持するという役割がありますが、これは関連を保持するだけでなく、これによって特定のタグに紐づくすべてのBlogEntryをあらかじめソートされた順序でフェッチすることができるようになります(前述したソートの話を思い出して下さい)。
ここで挙げておくべき設計上のポイントとしては、アプリケーションの論理的なタグを作り、すべてのBlogEntryに"__notag__"タグを設定するというものがあります(これは今作ったタグです)。すべてのBlogEntryに"__notag__"タグを設定することで、このColumnFamily
を使ってすべてのBlogEntryのリストを事前にソートされた順序で保存することができるようになるのです。ある意味ごまかしているようですが、それによって一つのColumnFamily
で、「最近のポストをすべて表示」と「'foo'タグがついた最近のポストをすべて表示」の両方ができるようになります
このデータモデルに従うと、エントリに3つタグがあった場合、対応するColumn
が4つの行
の中にあることになります。つまり、それぞれのタグに対して1つずつ、"__notag__"タグに対して1つです。
エントリのリストを時系列順に表示したいので、各Column
のnameをtimeUUIDとし、ColumnFamily
のCompareWithをTimeUUIDTypeに設定する必要があります。これにより、Column
は時間によってソートされ、「時系列順」という要求を満たしますしたがって、「'foo'タグがついた最近の10エントリ」といった処理が、極めて効率的になるのです。
最近の10エントリを表示したい(例えばフロントページに)と思った時には以下の処理を行います。
行
内にある最新の10Column
を、"__notag__"(「全ポスト」タグ)をキーとして取得する。- 取得した
Column
をループする。 - ループに際して、各
Column
の値がBlogEntriesColumnFamily
内の行
に対するキーであることが分かっている。 - そこで、このキーをBlogEntries
ColumnFamily
から、エントリに対応する行
を取得するのに使う。これによって、このエントリのすべてのデータが取得できる。 - 取得したBlogEntriesの
行
が持つColumn
の1つが、"author"と名付けられており、そのvalueはAuthorsColumnFamily
のキーとなっている。これは著者のプロフィール情報を取得するのに必要である。 - ここで、エントリの情報と著者の情報を手にしたことになる。
- 次に、"tags"
Column
のvalueを分割してタグのリストを取得する。 - これでこのポストを表示するのに必要な情報がすべて取得できたことになる(まだコメントがないが、permalinkページではないのでよしとする)。
上記の処理はどんなタグに対しても行うことができます。したがって、「すべてのエントリ」にも「'foo'タグのついたエントリ」に対しても有効なのです。悪くないでしょう。
<!-- ColumnFamily: TaggedPosts あるタグに対してどのブログエントリが紐づけられているかを決定する2次インデックス 行のキー => tag Column Names: TimeUUIDType Column Value: BlogEntries CclumnFamilyにおける行のキー アクセス: 'foo'タグのついたエントリのスライスを取得する このColumnFamilyは、あるタグのページに対してどのブログエントリを表示するかを決めるのに使用する 多少小細工をして、__notag__という文字列を使って「タグで制限されていない」ということを意味している 各エントリはここにあるcolumnを取得する つまり、各ポストに対してタグの数+1のcolumnができることになる TaggedPosts : { // ColumnFamily // "guitar"タグがついたブログエントリ guitar : { // タグのnameが行のキーになる // columnのnameはTimeUUIDTypeであり、valueがBlogEntriesの行に対するキーである timeuuid_1 : i-got-a-new-guitar, timeuuid_2 : another-cool-guitar, }, // ここに全ブログエントリが入る __notag__ : { timeuuid_1b : i-got-a-new-guitar, // これはguitar行にも存在する timeuuid_2b : another-cool-guitar, // これはmovie行にも存在する timeuuid_2b : scream-is-the-best-movie-ever, }, // "movie"タグがついたブログエントリ movie: { timeuuid_1c: scream-is-the-best-movie-ever } } --> <ColumnFamily CompareWith="TimeUUIDType" Name="TaggedPosts"/>
Comments ColumnFamily
最後に、コメントをどのようにモデリングするのか見ていきましょう。ここではある種のSuperColumn
が登場します。
各エントリに対して、1つの行
が対応します。行
に対するキーはエントリのスラグになります。各行
内には、各コメントに対応するSuperColumn
が存在します。SuperColumn
のnameはUUIDなので、TimeUUIDTypeを採用することになります。これにより、エントリに対するコメントは時系列順にソートされることになります。各SuperColumn
内のColumn
はコメントにある様々な属性です(コメントを書いた人の名前、コメントの時刻など)。
これも非常にシンプルで、特別なことはなにもありません。
<!-- ColumnFamily: Comments すべてのコメントがここに入る 行のキー => BlogEntryの行のキー SuperColumn name: TimeUUIDType アクセス: エントリに対するすべてのコメントを取得する Comments : { // scream-is-the-best-movie-everに対するコメント scream-is-the-best-movie-ever : { // 行のキー = BlogEntryの行のキー // 最も古いコメントが先に来る timeuuid_1 : { // SuperColumn Name // SuperColumn内のすべてのColumnはコメントの属性である commenter: Joe Blow, email: joeb@example.com, comment: 何を言っているんだ、ゴッドファーザーが一番に決まっている commentTime: 1250438004 }, scream-is-the-best-movie-everに対する他のコメント // 最新のコメントが最後 timeuuid_2 : { commenter: Some Dude, email: sd@example.com, comment: 落ち着けJoe Blow、これはyoutubeじゃないんだ commentTime: 1250557004 }, }, // i-got-a-new-guitarへのコメント i-got-a-new-guitar : { timeuuid_1 : { // SuperColumn Name // SuperColumn内のすべてのColumnはコメントの属性である commenter: Johnny Guitar, email: guitardude@example.com, comment: nice axe dawg... commentTime: 1250438004 }, } .. // 他のエントリに対するSuperColumn } --> <ColumnFamily CompareWith="TimeUUIDType" ColumnType="Super" CompareSubcolumnsWith="BytesType" Name="Comments"/>
やったぞ!
これでおしまいです。この小さいブログアプリはモデリングされ、準備ができました。要約するにはちょっとした量でしたが、最終的にstorage-conf.xmlに記述するXMLはとても小さいものになります。
<Keyspace Name="BloggyAppy"> <!-- 他のkeyspaceコンフィグ --> <!-- ColumnFamily定義 --> <ColumnFamily CompareWith="BytesType" Name="Authors"/> <ColumnFamily CompareWith="BytesType" Name="BlogEntries"/> <ColumnFamily CompareWith="TimeUUIDType" Name="TaggedPosts"/> <ColumnFamily CompareWith="TimeUUIDType" Name="Comments" CompareSubcolumnsWith="BytesType" ColumnType="Super"/> </Keyspace>
残るは、Cassandraとデータのやり取りをするのにどうすれば良いかを明らかにするだけです。これは、Thriftのインタフェースを通じて行われます。様々なエンドポイントで何ができるのかを説明するにあたってはAPIのwikiページが良い仕事をしているので、ここでは詳細に立ち入りません。しかし、一般的には、cassandra.thrift
ファイルをコンパイルし、さまざまなエンドポイントにアクセスするのに、生成されたコードを使うことになるでしょう。そうでなければ、RubyクライアントかPythonクライアントが使えるでしょう。
さて、この記事が役に立ち、読者がSuperColumn
とはいったい何なのかを理解して、素晴らしいアプリを作り始めてもらえたら幸いです。