Cassandraデータモデル入門 - Arin Sarkissian
この記事は、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とはいったい何なのかを理解して、素晴らしいアプリを作り始めてもらえたら幸いです。