本稿のサンプルコードはGitHubにもアップしています。併せてご参照ください。
はじめに
HBaseを使うことのメリットは、RDBで扱いきれないような大規模なデータを扱えることです。しかし、スキーマ設計を間違えるとまったくスケールしなくなってしまい、HBaseを使う意味がなくなってしまいます。ここがHBaseの難しい部分ではありますが、1度覚えてしまえばそんなに難しくはありません。
今回は簡易ブログサービスを通して、HBaseにおけるスキーマ設計のテクニックを紹介していきたいと思います。
対象読者
- HBaseを使ってみたいけどどう使ったらよいか分からない方
- MySQLなどのRDB以外のデータベースを使ってみたい方
開発の準備
まずは、HBaseを使ってアプリケーションを作るための準備をしましょう。
HBaseのインストールのおさらい
最初にHBaseのインストールのおさらいをします。
インストールについては本連載ですでに説明しましたが、HBaseのステーブルのバージョンが上がっているのでそちらに変更しています。
まず、HBaseをダウンロードします。
$ cd /usr/local $ curl -O http://ftp.kddilabs.jp/infosystems/apache/hbase/hbase-0.94.6.1/hbase-0.94.6.1.tar.gz
ダウンロードが完了したら、解凍します。
$ tar -zxvf hbase-0.94.6.1.tar.gz $ cd hbase-0.94.6.1
Javaへのパスが通っていない場合は、以下を実行します
$ export JAVA_HOME=<Javaのインストールパス>
HBaseを起動します。
$ bin/start-hbase.sh
HBaseを停止したい場合は、以下のコマンドを実行します。
$ bin/stop-hbase.sh
Java+Maven
開発言語としてはJavaを使います。また、ビルドツールとしてはMavenを使います。JavaやMavenのインストール方法については割愛します。
HBaseを用いたアプリケーションを作成するために、pom.xmlに以下のdependencyを指定してください。
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-core</artifactId> <version>1.0.4</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase</artifactId> <version>0.94.6.1</version> </dependency>
HBaseのJava API
アプリケーションを作る前にHBaseのJava APIを紹介します。ここで使うTableを以下のようにHBase shellで作成してください。
$ bin/hbase shell hbase(main):001:0> create "tbl", "fam" 0 row(s) in 1.0730 seconds
HTable
まず、HTableについて紹介します。HTableは、HBaseのクライアントのベースとなるクラスです。HTable経由でCRUD操作やIncreament、CAS操作などを行います。
HTableは以下のように生成します。
Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.quorum", "localhost"); // HTableオブジェクトを生成する際に、ConfigurationオブジェクトとTable名を指定 HTable table = new HTable(conf, "tbl"); ... // 使い終わったらclose table.close();
Configurationは名前のとおり、設定を管理するクラスです。Configurationオブジェクトに対して、"hbase.zookeeper.quorum"という設定に"localhost"をセットしています。
前回にも説明しましたが、HBaseのクライアントはまずZookeeperにアクセスします。その際に、どのZookeeperにアクセスするかという設定が"hbase.zookeeper.quorum"です。
今回はローカルにHBaseを立てているので、"localhost"を指定しています。
Put
次に、HBaseに対してデータをPutしてみましょう。以下が、Putのコードになります。
byte[] fam = Bytes.toBytes("fam"); byte[] row = Bytes.toBytes("row"); byte[] col1 = Bytes.toBytes("col1"); byte[] val1 = Bytes.toBytes("val1"); byte[] col2 = Bytes.toBytes("col2"); byte[] val2 = Bytes.toBytes("val2"); // Putオブジェクトを生成する際にRowKeyを指定 Put put = new Put(row); // PutするColumnをaddする put.add(fam, col1, val1); put.add(fam, col2, val2); // Putする table.put(put);
上記では、"row"というRowKeyのRowに対して、ColumnFamilyが"fam"で2つのColumnの"col1"と"col2"をPutしています。2つのColumnの"col1"と"col2"のValueはそれぞれ"val1"と"val2"になります。ここでは2つのColumnを指定しPutしていますが、この操作はアトミックに行われます。
HBaseでは、基本的にRowKeyやColumnなど、byte配列で扱うことが多いので、Bytesというユーティリティクラスが用意されています。Bytes.toBytesで、文字列型をbyte配列に変換することができます。
上記のプログラムを実行したら、HBase shellでデータが入っているかどうかを確認してみましょう。
hbase(main):001:0> scan "tbl" ROW COLUMN+CELL row column=fam:col1, timestamp=1366522283779, value=val1 row column=fam:col2, timestamp=1366522283779, value=val2 1 row(s) in 0.0420 seconds
データが入っているのが分かります。Timestamp指定でPutすることも可能です。
// Timestamp long ts = System.currentTimeMillis(); put.add(fam, col1, ts, val1); put.add(fam, col2, ts, val2);
本連載ですでに説明しましたが、HBaseのストレージフォーマットは各エントリでTimestampを持っており、これを用いてバージョニングを行っています。
これによって、後からデータを取り出すときに複数バージョン(デフォルトで3)を取得することができます。
Timestampを指定でPutすることよって、古いデータを後から入れることもできます。これは、使い方によっては面白いことができるので、覚えておくと良いでしょう。
ちなみに、Timestampを指定せずにPutした場合は、実際にデータが扱われるHRegionServerの時間が使われます。