こんにちわ、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)