
こんにちわ、7月に入社したばかりの@emorinsです。
題名の通りですが分散データベース『Apache Cassandra』を紹介したいと思います。
少し前はHadoop(とHBase)と比較されることの多かったCassandraですが、最近はHadoopの人気に押されつつあるようにも感じます。
しかし、CassandraとHadoopは特徴が異なり、よく言われるのがCassandraはリアルタイム処理に向き、一貫性のかわりに可用性を重視し、またHadoopとは違って単一障害点もありません。
今日はそんなHadoopとは違った魅力のある分散データベース『Apache Cassandra』をはじめてみましょう。
目次
- Cassandraとは
- アーキテクチャ
- Cassandraの特徴
- コンシステンシレベル
- データモデル
- MemtableとSSTable
- セットアップ
- storage-conf.xmlの設定
- Keyspace
- ColumnFamily
- MemtableThroughputInMB
- MemtableFlushAfterMinutes
- Cassandra-CLIの使い方
- ThriftAPIによるクライアントからの操作
- Thriftとは
- Thriftのインストール
- PHPによるクライアントアプリケーションを作る
- データを取得する簡単なプログラム
- ThriftAPI
- 最後に
- 本エントリで取り上げなかったこと
- 参考
Cassandraとは

そもそもCassandraとは何でしょうか。
長い間データベースと言えばRDBMS(MySQL、PostgreSQL、Oracle Databaseなど)と、それを操作するSQL言語を使用するのが主流でした。しかし、大規模データ処理を必要としたGoogleやAmazonは、リレーショナルデータベースより(ペタバイトクラスの膨大なデータ処理に対して)高速で可用性のある、スケールアウトしやすいデータベースを必要としました。そこで生まれたのがGoogleのBigtableと、AmazonのDynamoです。
Cassandraは、そんなBigtableやDynamoを参考にFacebookが開発したNoSQLデータベースです。現在はApacheソフトウェア財団に寄贈され、オープンソースとして開発が進められています。
アーキテクチャ
Cassandraの特徴
Cassandraの主な特徴として
- 高可用性
- イベンチュアル・コンシステンシー
- 一貫性と遅延のトレードオフを調整可能
- 単純なKey/Valueではないデータモデル
- 単一障害点(SPOF)がない
などが言えます。
特に可用性の高さとSPOFが無いのが特筆すべき点です。
コンシステンシレベル
- W=書き込みを保証するレプリケート数
- R=読み込むレプリケート数
- N=レプリケートしているノード数(storage-conf.xml内の<ReplicationFactor>)
- Q=QUORUM (Q = N / 2 + 1)
R + W > N
設定できるレベルは以下の4つです。
- Zero:保証なし
- One:W=1,R=1
- [書き込み]1つのノードの書き込みを保証
- [読み込み]最初に読み込んだノードから返す
- Quorum:w=Q,R=Q
- [書き込み](N+1)/2数のノードに書き込まれたことを保証
- [読み込み](N+1)/2数のノードから読み込み、最新を返す
- All:w=N,R=N
- [書き込み]N個のノードに書き込まれたことを保証する
- [読み込み]N個ののノードから読み込み、最新を返す
データモデル

- キースペース
- RDBMSでいうデータベースに当たるものです。Cassandraデータモデルの一次元目で、カラムファミリのコンテナになります。一般に1アプリケーションに1キースペースとされます。
- カラムファミリ
- RDBMSでいうテーブルに当たるものです。カラムのコンテナです。カラムファミリは後述するstorage-conf.xmlで設定して作成します。複数の行(Row)から構成されます。
- 行(Row)
- キーと複数のカラムを持つ。RDBMSでいえば、行はレコード、キーは主キーのイメージです。
- カラム
- Cassandraでの最小のデータ構造。名前(name)、値(value)、タイムスタンプ(timestamp)を持ちます。
- スーパーカラム
- 値(value)にソート済みの複数のカラムのリストを持つ。スーパーカラムは必ずしも必要ではありません。
- Cassandra-CLIでのデータ挿入
set Keyspace1.Standard2['jsmith']['job']='developer'
- SQLによるINSERT文(※syntax error)
INSERT INTO Keyspace1.Standard2 (job) VALUES('developer') WHERE id='jsmith';
MemtableとSSTable
ハードウェア上のデータの流れはどうなっているでしょうか。
まずCassandraに書き込み操作がされるとCommitLogに書き込まれます。その後、カラムファミリ毎にMemtableというメモリ空間に対して書き込まれます。Memtableが一杯になると、ディスク上にSSTableという形式で保存(フラッシュ)されます。
MemtableではSSTableにフラッシュする際に使用するキーを設定してソートしておくので、MySQLでのランダムアクセスとは違い、HDDにシーケンシャルに書き込みむので、高速です。
ここまで話を聞くと、MemtableからSSTableにフラッシュする回数を極力減らすため、Memtableの容量を設定したくなるかと思いますが、もちろん可能です。後述するstorage-conf.xmlという設定ファイルで、Memtableに使うメモリ容量や保存時間などが指定できます。
セットアップ
- FreeBSD 8.1
- JDK 1.6
- Cassandra 0.6.3
- PHP 5.3.2
# wget http://www.FreeBSDFoundation.org/cgi-bin/download?download=diablo-caffe-freebsd7-i386-1.6.0_07-b02.tar.bz2 # make install clean
# cd /usr/ports/databases/cassandra # make install clean
# cd /usr/local/share/cassandra # cp conf/log4j.properties.sample conf/log4j.properties # cp conf/storage-conf.xml.sample conf/storage-conf.xml
# /usr/local/share/cassandra/bin/cassandra
- -f:フォアグラウンドで起動
- -h:使用できるオプションを表示
- -p
:PIDを に出力。
storage-conf.xmlの設定
Cassandraの最も重要な設定ファイルがstorage-conf.xmlです。このファイル内で、クラスタの各設定や、カラムファミリの作成、Memtableの容量など設定できます。
全ては紹介できませんが、いくつか重要な設定箇所をピックアップします。詳しい情報や設定例はstorage-conf.xml.sampleを見てください。
Keyspace
キースペースを作成します。Name属性を指定して、カラムファミリはこのKeyspaceタグ内に記述します。
今回の設定例:
<Keyspace Name="Keyspace1"> ... </Keyspace>
ColumnFamily
- Name:カラムファミリ名
- ColumnType:カラムタイプ。デフォルトは"Standard"。スーパーカラムを持ちたい場合は"Super"にする。
- CompareWith:カラムのソート方法
- KeysCached:キーをキャッシュする個数(%をつけると割合)
<ColumnFamily Name="Standard2" CompareWith="UTF8Type" KeysCached="100%"/>
MemtableThroughputInMB
MemtableFlushAfterMinutes
フラッシュまでの最大時間。この時間を超えると強制的にフラッシュする。
その他詳しい設定例などは以下をご参考ください。
StorageConfiguration - Cassandra Wiki
Cassandra-CLIの使い方
データモデルの項でチラっと出てきましたが、Cassandra-CLIというクライアントツールを使って、Cassandraのデータを操作してみましょう。
Cassandraを起動した状態で、Cassandra-CLIを立ち上げます。
/usr/local/share/cassandra/bin/cassandra-cli --host localhost --port 9160
Connected to: "Test Cluster" on localhost/9160 Welcome to cassandra CLI. Type 'help' or '?' for help. Type 'quit' or 'exit' to quit. cassandra>
cassandra> set Keyspace1.Standard2['unoh']['age']='25'
set文で、Standard2カラムファミリに'unoh'キーを追加した上で、'age'という名前のカラムに25という値を挿入しました。
実際にちゃんとデータの追加ができているかget文で確認してみます。
cassandra> get Keyspace1.Standard2['unoh'] => (column=age, value=25, timestamp=1281559772819000) Returned 1 results.
さらに追加してみましょう。
cassandra> set Keyspace1.Standard2['unoh']['phone']='03-5766-3911' Value inserted. cassandra> set Keyspace1.Standard2['unoh']['web']='www.unoh.net' Value inserted.
cassandra> get Keyspace1.Standard2['unoh'] => (column=web, value=www.unoh.net, timestamp=1281560104883000) => (column=phone, value=03-5766-3911, timestamp=1281560057868000) => (column=age, value=25, timestamp=1281559772819000) Returned 3 results.
cassandra> help
ThriftAPIによるクライアントからの操作
Thriftとは
Thriftは、同じくFacebookが開発した言語間サービス開発のためのRPCフレームワークです。C++、C#、Java、OCaml、Perl、Python、PHP、Rubyなどで書かれたプログラム間を通信可能にしてくれます。
CassandraはJavaで書かれているわけですが、Thriftを使うことで、異なる言語からCassandraを操作することができるようになります。CassandraにはThiftインターフェースがあらかじめ用意されているので、Thrift(とCassandra)がサポートしている言語ならどの言語のクライアントコードからでも操作可能です。今回はタイトル通りPHPから利用してみましょう。
※CassandraのAPI/RPC/Thriftポートは9160番です。
Thriftのインストール
- Thriftインターフェース(.thrift)の作成
- そのインターフェースに沿った各言語のスケルトンを生成
- スケルトンを元にサーバー・クライアントのコーディング
という流れになります。Thriftインターフェース(.thrift)は、Cassandraのインストール先ディレクトリ下のinterfaceディレクトリに"cassandra.thrift"ファイルを利用するので、1の作業は必要ありません。また、2,3のサーバー(Cassandra)側のコーディングも必要ありません。
# cd /usr/ports/devel/thrift/ # make install clean
# mkdir ~/thrift
# wget http://ftp.riken.jp/net/apache/incubator/thrift/0.2.0-incubating/thrift-0.2.0-incubating.tar.gz # tar zxvf thrift-0.2.0-incubating.tar.gz # cp -R thrift-0.2.0/lib/php/src ~/thrift
# cd thrift # mkdir src/packages # cp /usr/local/share/cassandra/interface/cassandra.thrift ~/thrift/cassandra.thrift # thrift --gen php cassandra.thrift # mv -R gen-php/cassandra src/packages
/thrift
├ /src・・・Thriftプロトコルの実装ファイル
├ Thrift.php
├ autoload.php
├ /ext
├ /packages・・・各サーバごとのThriftクライアント(gen-phpとして生成されたコード)
└ /cassandra・・・cassandra用Thriftクライアントが入ったディレクトリ
├ Cassandra.php
├ cassandra_constants.php
└ cassandra_types.php
├ /protocol
└ /transport
└ /unoh・・・プロジェクトディレクトリ
└sample.php・・・クライアントアプリケーション(ここで具体的にCassandraを操作するコードを書きます)PHPによるクライアントアプリケーションを作る
データを取得する簡単なプログラム
<?php
$GLOBALS['THRIFT_ROOT'] = '../src';
require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/Cassandra.php';
require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/cassandra_types.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
try {
// Thriftの接続用インスタンスの作成
$socket = new TSocket('localhost', 9160);
$transport = new TBufferedTransport($socket, 1024, 1024);
$protocol = new TBinaryProtocolAccelerated($transport);
//Cassandraクライアントの作成
$client = new CassandraClient($protocol);
//接続開始
$transport->open();
// ColumnFamilyの指定
$columnParent = new cassandra_ColumnParent();
$columnParent->column_family = "Standard2";
//スライスの指定。切り出すColumnの範囲を指定できる。
$predicate = new cassandra_SlicePredicate();
$sliceRange = new cassandra_SliceRange();
//例えば、'age'から'phone'までのColumnを切り出す(Columnは昇順でソートされる)
$sliceRange->start = "age";
$sliceRange->finish = "phone";
$predicate->slice_range = $sliceRange;
//クエリを投げる
$result = $client->get_slice('Keyspace1', 'unoh', $columnParent,$predicate, cassandra_ConsistencyLevel::ONE);
//中身を出力してみる
var_dump($result);
//接続終了
$transport->close();
}
catch (TException $tx) {
print 'TException: '.$tx->why. ' Error: '.$tx->getMessage() . "\n";
}# php ~/thrift/unoh/sample.php
array(2) {
[0]=>
object(cassandra_ColumnOrSuperColumn)#9 (2) {
["column"]=>
object(cassandra_Column)#10 (3) {
["name"]=>
string(3) "age"
["value"]=>
string(2) "25"
["timestamp"]=>
float(1.281559772819E+15)
}
["super_column"]=>
NULL
}
[1]=>
object(cassandra_ColumnOrSuperColumn)#11 (2) {
["column"]=>
object(cassandra_Column)#12 (3) {
["name"]=>
string(5) "phone"
["value"]=>
string(12) "03-5766-3911"
["timestamp"]=>
float(1.281560057868E+15)
}
["super_column"]=>
NULL
}
}ThriftAPI
最後に
本エントリで取り上げなかったこと
参考
- FrontPage_JP - Cassandra Wiki
- 連載:Cassandraのはじめ方─手を動かしてNoSQLを体感しよう|gihyo.jp ... 技術評論社
- Cassandraメモ(Hishidama's Apache Cassandra Memo)