HBase 入門 (1)

Hadoop と HBase で分散処理が書けるのはわかったけど、いざ使ってみるとなるとドキュメントが少なくて……、というあなたの為の HBase 入門、今回はセットアップから JavaAPI で HBase を触るまでを勉強します。
使用するバージョンは Hadoop 0.17.2.1、HBase 0.2.1 です。

1. Hadoop のセットアップ

http://hadoop.apache.org/core/docs/r0.17.2/quickstart.html を参考にインストールします。Java 1.5.x とありますが、1.6 でもたぶん動きます。今回は学習用にスタンドアローンで動かすので、$HADOOP_HOME/conf/hadoop-env.sh の JAVA_HOME を書くだけでよいでしょう。サンプルで動作を確認しましょう。

2. HBase のセットアップ

ダウンロードページ からとってきて展開します。Hadoop と同じく、$HBASE_HOME/conf/hadoop-env.sh の JAVA_HOME だけ設定します。

3. HBase の起動

$HBASE_HOME/bin/start-hbase.sh を実行して HBase を起動します。

debian:~/lib/hbase-0.2.1% bin/start-hbase.sh
starting master, logging to /home/wanpark/lib/hbase-0.2.1/bin/../logs/hbase-wanpark-master-debian.out
localhost: starting regionserver, logging to /home/wanpark/lib/hbase-0.2.1/bin/../logs/hbase-wanpark-regionserver-debian.out

Hadoop の起動は不要です。

4. shell で HBase に慣れよう

$HBASE_HOME/bin/hbase shell でシェルが起動します。実体は irb です。いくつかコマンドを打って慣れましょう。

debian:~/lib/hbase-0.2.1% bin/hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Version: 0.2.1, r693938, Wed Sep 10 13:13:09 PDT 2008
hbase(main):001:0> help
HBASE SHELL COMMANDS:
 alter     Alter column family schema;  pass table name and a dictionary
 ...

次のような成績表を作りましょう(http://www.nabble.com/Re%3A-Map-Reduce-over-HBase---sample-code-p18253120.html を参考にしました)。

grade: course:math course:art
Dan 1 87 97
Dana 2 100 80

縦を column、横を row、データの1マス1マスを cell と呼びます。

hbase(main):002:0> create 'scores', 'grade', 'course'
0 row(s) in 4.1610 seconds
hbase(main):003:0> list
scores
1 row(s) in 0.0210 seconds
hbase(main):004:0> describe 'scores'
{NAME => 'scores', IS_ROOT => 'false', IS_META => 'false', FAMILIES => [{NAME => 'course', BLOOMFILTER => 'fal
se', IN_MEMORY => 'false', LENGTH => '2147483647', BLOCKCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRE
SSION => 'NONE'}, {NAME => 'grade', BLOOMFILTER => 'false', IN_MEMORY => 'false', LENGTH => '2147483647', BLOC
KCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRESSION => 'NONE'}]}
1 row(s) in 0.0130 seconds
hbase(main):005:0> put 'scores', 'Dan', 'grade:', '1'
0 row(s) in 0.0070 seconds
hbase(main):006:0> put 'scores', 'Dan', 'course:math', '87'
0 row(s) in 0.0040 seconds
hbase(main):007:0> put 'scores', 'Dan', 'course:art', '97'
0 row(s) in 0.0030 seconds
hbase(main):008:0> put 'scores', 'Dana', 'grade:', '2'
0 row(s) in 0.0040 seconds
hbase(main):009:0> put 'scores', 'Dana', 'course:math', '100'
0 row(s) in 0.0030 seconds
hbase(main):010:0> put 'scores', 'Dana', 'course:art', '80'
0 row(s) in 0.0050 seconds
hbase(main):011:0> get 'scores', 'Dan'
COLUMN                       CELL
 course:art                  timestamp=1224726394286, value=97
 course:math                 timestamp=1224726377027, value=87
 grade:                      timestamp=1224726360727, value=1
3 row(s) in 0.0070 seconds
hbase(main):012:0> scan 'scores'
ROW                          COLUMN+CELL
 Dan                         column=course:art, timestamp=1224726394286, value=97
 Dan                         column=course:math, timestamp=1224726377027, value=87
 Dan                         column=grade:, timestamp=1224726360727, value=1
 Dana                        column=course:art, timestamp=1224726424967, value=80
 Dana                        column=course:math, timestamp=1224726416145, value=100
 Dana                        column=grade:, timestamp=1224726404965, value=2
6 row(s) in 0.0410 seconds
hbase(main):013:0> scan 'scores', ['course:']
ROW                          COLUMN+CELL
 Dan                         column=course:art, timestamp=1224726394286, value=97
 Dan                         column=course:math, timestamp=1224726377027, value=87
 Dana                        column=course:art, timestamp=1224726424967, value=80
 Dana                        column=course:math, timestamp=1224726416145, value=100
4 row(s) in 0.0200 seconds

MySQL などの一般的な RDB では row 単位でデータを扱いますが、HBase では cell がデータの単位です。 のタプルが寄せ集まっていると思えばいいでしょう。
カラム名は (ファミリー名):(サブカラム名) となっていて、テーブルの定義ではファミリー名のみを指定します。サブカラム名はデータを挿入する際に自由に付けることができます。上の scan の例のように、同一ファミリーのカラムはまとめて指定できます。
各 cell にはエポックミリ秒の timestamp がついています。同一の row, column でもtimestamp が異なれば複数の cell が保存できます。ですが基本的に最新の timestamp を持つ cell の使用のみが想定されているようなので、同一 column 名での保存は上書きに近いです。

5. Java から HBase を使おう

Hadoop/HBase の jar と conf、それと必要なライブラリに classpath を通しておきます。ant などを使う場合は $HADOOP_HOME/lib 以下の jar ファイルをクラスパスに加えるといいでしょう。

debian:~/tmp/hbase% export HADOOP_HOME=~/lib/hadoop-0.17.2.1
debian:~/tmp/hbase% export HBASE_HOME=~/lib/hbase-0.2.1
debian:~/tmp/hbase% export CLASSPATH=.:$HADOOP_HOME/hadoop-0.17.2.1-core.jar:$HADOOP_HOME/conf:$HBASE_HOME/hbase-0.2.1.jar:$HBASE_HOME/conf:$HADOOP_HOME/lib/commons-logging-1.0.4.jar:$HADOOP_HOME/lib/log4j-1.2.13.jar


前項と同じ成績表を作ります。

import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.util.Map;

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.io.BatchUpdate;
import org.apache.hadoop.hbase.io.RowResult;
import org.apache.hadoop.hbase.io.Cell;
import org.apache.hadoop.hbase.util.Writables;

public class HBaseBasic {

    public static void main(String[] args) throws Exception {
        HBaseConfiguration config = new HBaseConfiguration();
        HBaseAdmin admin = new HBaseAdmin(config);

        if (admin.tableExists("scores")) {
            System.out.println("drop table");
            admin.disableTable("scores");
            admin.deleteTable("scores");
        }

        System.out.println("create table");
        HTableDescriptor tableDescripter = new HTableDescriptor("scores".getBytes());
        tableDescripter.addFamily(new HColumnDescriptor("grade:"));
        tableDescripter.addFamily(new HColumnDescriptor("course:"));
        admin.createTable(tableDescripter);

        HTable table = new HTable(config, "scores");

        System.out.println("add Dan's data");
        BatchUpdate danUpdate = new BatchUpdate("Dan");
        danUpdate.put("grade:", Writables.getBytes(new IntWritable(1)));
        danUpdate.put("course:math", Writables.getBytes(new IntWritable(87)));
        danUpdate.put("course:art", Writables.getBytes(new IntWritable(97)));
        table.commit(danUpdate);

        System.out.println("add Dana's data");
        BatchUpdate danaUpdate = new BatchUpdate("Dana");
        danaUpdate.put("grade:", Writables.getBytes(new IntWritable(2)));
        danaUpdate.put("course:math", Writables.getBytes(new IntWritable(100)));
        danaUpdate.put("course:art", Writables.getBytes(new IntWritable(80)));
        table.commit(danaUpdate);

        for (RowResult row : table.getScanner(new String[] { "course:" })) {
            System.out.format("ROW\t%s\n", new String(row.getRow()));
            for (Map.Entry<byte[], Cell> entry : row.entrySet()) {
                String column = new String(entry.getKey());
                Cell cell = entry.getValue();
                IntWritable value = new IntWritable();
                Writables.copyWritable(cell.getValue(), value);
                System.out.format("  COLUMN\t%s\t%d\n", column, value.get());
            }
        }
    }

}

HBase には byte 列でアクセスするのでややまどろっこしい事になっていますが、やっていることは shell の時と同じです。これを実行します。

debian:~/tmp/hbase% javac HBaseBasic.java
debian:~/tmp/hbase% java HBaseBasic
drop table
08/10/23 12:51:59 INFO client.HBaseAdmin: Disabled scores
08/10/23 12:51:59 INFO client.HBaseAdmin: Deleted scores
create table
add Dan's data
add Dana's data
ROW     Dan
  COLUMN        course:art      97
  COLUMN        course:math     87
ROW     Dana
  COLUMN        course:art      80
  COLUMN        course:math     100

お疲れ様でした。次回は「HadoopMapReduce で HBase を使おう」をお送りします。