S-JIS[2010-02-20/2012-04-28] 変更履歴

HBase

HBase(エイチベース)は、GoogleのBigTableを元にしたオープンソースの列指向分散データベース。(リレーショナルデータベース(RDB)ではない)
Javaで作られており、Apache HadoopのHDFSを使用して稼動する。その為、Hadoopとの親和性が高いと思われる。


WindowsXPへのインストール

Windowsへのインストール方法は、TakaoさんのCygwinを利用してWindowsにHBaseをインストールに詳しく書かれています。


HBaseはUNIXを想定しているようなので、Windowsで試す為にはCygwinが必要。
sshが必要なので、CygwinのOpenSSHダウンロードして設定しておく。

HBaseはJavaで動くので、JDK1.6もインストールしておく。

※単独環境ではHadoopのインストールは不要(hbase-0.20.3のアーカイブの中にhadoopも含まれている)。

  1. HBaseダウンロードページから適当にミラーサイトを選んで、アーカイブをダウンロードする。(hbase-0.20.3.tar.gz)
  2. 適当な場所にアーカイブを展開する。(C:\temp\hbase-0.20.3\〜)
  3. 展開したディレクトリーをCygwinのディレクトリーに移動する。(C:\cygwin\usr\local\hbase-0.20.3\〜)
    (Cygwin(bash)から、/usr/local/hbase-0.20.3/でアクセスできるようになる。)
    以降、C:\cygwin\usr\local\hbase-0.20.3をHBASE_HOMEと呼ぶことにする。

HBaseの設定(Windows単独環境用)

  作業内容 設定内容・実施コマンド 備考
1 HBASE_HOME\conf\hbase-env.sh
を編集する。
export JAVA_HOME="C:/Program Files/Java/jdk1.6.0_13"
環境変数JAVA_HOMEの設定。
export HBASE_IDENT_STRING=$HOSTNAME
 
2 HBASE_HOME\conf\hbase-site.xml
を編集する。
(hbase-default.xmlから必要なプロパティーをコピーして、
値を書き換える。[2010-08-07]
  <property>
    <name>hbase.rootdir</name>
    <value>file:///C:/cygwin/tmp/hbase/data</value>
  </property>
Windows単独環境用の設定。

HBase0.89では単独環境でZooKeeperが使われなくなったので、
hbase.zookeeper.quorumは設定不要。[2010-07-06]
  <property>
    <name>hbase.tmp.dir</name>
    <value>C:/cygwin/tmp/hbase/tmp</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>127.0.0.1</value>
  </property>
3 上記で指定したディレクトリーを作っておく。
mkdir -p /tmp/hbase/data
mkdir -p /tmp/hbase/tmp
chmod 777 /tmp/hbase/data
chmod 777 /tmp/hbase/tmp
Cygwin(bash)から実行。

HBaseの動作確認

  作業内容 実施コマンド 備考
1 HBaseを起動する。
/usr/local/hbase-0.20.3/bin/start-hbase.sh
cygpath: cannot create short name of \\?\C:\cygwin\usr\local\hbase-0.20.3\logs
 (127.0.0.1)' can't be established.
RSA key fingerprint is 67:ce:b3:dd:a8:92:01:22:9e:56:69:5a:37:e8:2a:40.
Are you sure you want to continue connecting (yes/no)? yes
' (RSA) to the list of known hosts..0.1

: starting zookeeper, logging to /usr/local/hbase-0.20.3/bin/../logs/hbase-host-zookeeper-host.out
starting master, logging to /usr/local/hbase-0.20.3/bin/../logs/hbase-host-master-host.out
localhost: starting regionserver, logging to /usr/local/hbase-0.20.3/bin/../logs/hbase-host-regionserver-host.out
初回起動時は色々警告が出るが、
とりあえず大丈夫なようだ。
(最初はlogsディレクトリーが無いので
cygpathがエラーを出すが
次回以降は作られているので問題ない)
(最初はRSAの鍵が無いので、作成する)
起動にはちょっと時間がかかる。
2 HBase Shellを実行する。
/usr/local/hbase-0.20.3/bin/hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Version: 0.20.3, r902334, Mon Jan 25 13:13:08 PST 2010
hbase(main):001:0>
HBaseの対話型ツール。
「help」と入力してEnterキーを押すと
ヘルプが表示される。
  HBase Shellで色々入力してみるサンプル
3 HBase Shellを終了する。
exit
 
4 HBaseを停止する。
/usr/local/hbase-0.20.3/bin/stop-hbase.sh

stopping master............
: stopping zookeeper

停止もちょっと時間がかかる。
(「.」の個数は毎回違う)

HBaseの起動時にsshサービス(sshd)が動いていないとエラーになる。
 port 22: Connection refused.0.1
starting master, logging to /usr/local/hbase-0.20.3/bin/../logs/hbase-host-master-host.out
localhost: ssh: connect to host localhost port 22: Connection refused


HBaseのテーブルを操作するサンプル

HBase Shellでテーブルを作って表示してみる例。(HBase0.20)
太字が入力したコマンド。

$ /usr/local/hbase-0.20.3/bin/hbase shell			←HBase Shellの起動
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Version: 0.20.3, r902334, Mon Jan 25 13:13:08 PST 2010
hbase(main):001:0> create 'test','data'		←testという名前のテーブルを作成
0 row(s) in 2.5940 seconds
hbase(main):002:0> list	←テーブル一覧を表示
test

1 row(s) in 0.0310 seconds
hbase(main):003:0> describe 'test'		←テーブル定義を表示
DESCRIPTION                                                             ENABLED
 {NAME => 'test', FAMILIES => [{NAME => 'data', COMPRESSION => 'NONE',  true
 VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY
 => 'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0310 seconds
hbase(main):004:0> put 'test','row1','data:1','value1'	←テーブルにデータを入れてみる
0 row(s) in 0.0160 seconds
hbase(main):005:0> put 'test','row2','data:1','value2'
0 row(s) in 0.0160 seconds
hbase(main):006:0> scan 'test'	←テーブルのデータを表示してみる
ROW                          COLUMN+CELL
 row1                        column=data:1, timestamp=1266505167015, value=value1
 row2                        column=data:1, timestamp=1266505217843, value=value2
1 row(s) in 0.0310 seconds
hbase(main):007:0> disable 'test'		←テーブルを使用不可にする(削除する為には事前に使用不可にする必要がある)
0 row(s) in 2.0620 seconds
hbase(main):008:0> drop 'test'		←テーブルを削除する
0 row(s) in 0.0160 seconds
0 row(s) in 0.0000 seconds
0 row(s) in 0.0470 seconds
hbase(main):009:0> exit	←HBase Shellの終了

$

HBaseのテーブルの基礎

HBaseもテーブルを作ってデータを入れるのだが、RDBとはテーブルの構造が異なる。
(これについては豊月さんのSQLとHbaseの操作比較が分かりやすいです。
これだけあれば他の説明は要らない!くらいの感じですが、勉強も兼ねて自分でもメモしておきます。

まず、テーブル名があるのはRDBもHBaseも同じ。
(RDBでは基本的にテーブル名は大文字小文字の区別が無いが、HBaseでは区別される)

RDBでいう「項目(カラム・列)」は、HBaseでは列ファミリー(ColumnFamily、family)と言う。
で、RDBのデータ型に当たるものは、列ファミリーではマップしか無い。
マップにはキーと値が必要なわけだが、このキーをqualifier(column qualifierあるいはlabel)、値はセル(Cell)と呼ぶ。
セルの内容は値そのものと思っていいと思うが、履歴管理される。(普通にgetすれば最新の値が取れる)
(HBaseでは、familyとqualifierをコロン「:」で結んだものをcolumnと呼ぶ)

また、行(レコード・データ)を識別する為に、暗黙に「ROW」というプライマリキー相当の項目が存在する。

以上を踏まえて例示的にRDBのSQLとHBaseを対比させてみると、以下のような感じになると思う。
(テーブル名や項目名・データは豊月さんのHbase解説用Data図を真似てみた)
(RDBのデータ型にマップは無いので、その辺りはJava(や他の言語)風に表現してみた)

  RDB(SQL) HBase 備考
テーブル作成
create table TEST_TYPE
(
 ROW           String  primary key,
 ColumnFamilyA Map<String, Cell>,
 ColumnFamilyB Map<String, Cell>,
 ColumnFamilyC Map<String, Cell>,
 ColumnFamilyD Map<String, Cell>
);
create 'TEST_TYPE',
'ColumnFamilyA',
'ColumnFamilyB',
'ColumnFamilyC',
'ColumnFamilyD'
 
データ挿入
insert into TEST_TYPE
(
 ROW,
 ColumnFamilyA,
 ColumnFamilyB,
 ColumnFamilyC,
 ColumnFamilyD
) values (
 'KEY00001',
 { null      => 'k1_cfA0',
   'Column1' => 'k1_cfA_c1',
   'Column2' => 'k1_cfA_c2',
   'Column3' => 'k1_cfA_c3'
 },
 { null      => 'k1_cfB0',
〜
   'Column3' => 'k1_cfC_c3'
 }
);
put 'TEST_TYPE','KEY00001','ColumnFamilyA','k1_cfA0'
put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column1','k1_cfA_c1'
put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column2','k1_cfA_c2'
put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column3','k1_cfA_c3'
put 'TEST_TYPE','KEY00001','ColumnFamilyB','k1_cfB0'
put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column1','k1_cfB_c1'
put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column2','k1_cfB_c2'
put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column3','k1_cfB_c3'
put 'TEST_TYPE','KEY00001','ColumnFamilyC','k1_cfC0'
put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column1','k1_cfC_c1'
put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column2','k1_cfC_c2'
put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column3','k1_cfC_c3'
columnはマップのようなものなので、
全てを入れる必要は無い。
(左の例では全部入れているが)
入れなかったデータは
領域も確保されないので
効率が良い。
データ更新
update TEST_TYPE
set ColumnFamilyB.Column1 = 'B1'
where ROW = 'KEY00001'
;
put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column1','B1'
 
データ削除
update TEST_TYPE
set ColumnFamilyC.Column2 = null
where ROW = 'KEY00001'
;
deleteall 'TEST_TYPE','KEY00001','ColumnFamilyC:Column2'
columnはマップのようなものなので、
nullを入れるというよりは
キーを削除するイメージ。
レコード削除
delete from TEST_TYPE
where ROW = 'KEY00003'
;
deleteall 'TEST_TYPE','KEY00003'
[2010-03-06]

入るデータのイメージ
  ROW 'ColumnFamilyA' 'ColumnFamilyB' 'ColumnFamilyC' 'ColumnFamilyD'
レコード1 KEY00001
qualifier Cell
null k1_cfA0
Column1 k1_cfA_c1
Column2 k1_cfA_c2
Column3 k1_cfA_c3
qualifier Cell
null k1_cfB0
Column1 B1
Column2 k1_cfB_c2
Column3 k1_cfB_c3
qualifier Cell
null k1_cfC0
Column1 k1_cfC_c1
Column3 k1_cfC_c3
qualifier Cell
null k1_cfD0
Column1 k1_cfD_c1
Column2 k1_cfD_c2
Column3 k1_cfD_c3
レコード2 KEY00002
qualifier Cell
null k2_cfA0
Column1 k2_cfA_c1
Column2 k2_cfA_c2
Column3 k2_cfA_c3
qualifier Cell
null k2_cfB0
Column1 k2_cfB_c1
Column2 k2_cfB_c2
Column3 k2_cfB_c3
qualifier Cell
null k2_cfC0
Column1 k2_cfC_c1
Column2 k2_cfC_c2
Column3 k2_cfC_c3
qualifier Cell
null k2_cfD0
Column1 k2_cfD_c1
Column2 k2_cfD_c2
Column3 k2_cfD_c3

ついでにJavaのクラスで表すとどのようになるか考えてみた。
HBaseで提供されているAPIではなく、HBaseのテーブルの概念をクラスで表現してみたもの)

public final class TEST_TYPE {

	/* コンストラクター */
	private TEST_TYPE() {
	}

	private static final TEST_TYPE INSTANCE = new TEST_TYPE();

	// 唯一のインスタンスを返す
	public static TEST_TYPE getInstance() {
		return INSTANCE;
	}

	/** TEST_TYPEの項目名の定義 */
	public static enum ColumnFamily {
		ColumnFamilyA, ColumnFamilyB, ColumnFamilyC, ColumnFamilyD
	}

	/** 一行分のデータを保持するクラス */
	public static class Record extends EnumMap<ColumnFamily, Map<String, Cell>> {
		//コンストラクター
		private Record() {
			super(ColumnFamily.class);
		}
	}

	/** キー(ROW)毎の行を保持する */
	private Map<String, Record> recMap = new TreeMap<String, Record>();

	// データをセットする
	public void put(String row, ColumnFamily family, String qualifier, String value) {
		Record record = recMap.get(row);
		if (record == null) {
			record = new Record();
			recMap.put(row, record);
		}
		Map<String, Cell> colMap = record.get(family);
		if (colMap == null) {
			colMap = new TreeMap<String, Cell>();
			record.put(family, colMap);
		}
		Cell cell = colMap.get(qualifier);
		if (cell == null) {
			cell = new Cell();
			colMap.put(qualifier, cell);
		}
		cell.setValue(value);
	}

	// データを取得する
	public String get(String row, ColumnFamily family, String qualifier) {
		Record record = recMap.get(row);
		if (record == null) {
			return null;
		}
		Map<String, Cell> colMap = record.get(family);
		if (colMap == null) {
			return null;
		}
		Cell cell = colMap.get(qualifier);
		if (cell == null) {
			return null;
		}
		return cell.getValue();
	}

	// データを削除する
	public void delete(String row, ColumnFamily family, String qualifier) {
		Record record = recMap.get(row);
		if (record == null) {
			return;
		}
		Map<String, Cell> colMap = record.get(family);
		if (colMap == null) {
			return;
		}
		colMap.remove(qualifier);
	}

	// 全データをキー順に返す
	public Iterable<Entry<String, Record>> scan() {
		return recMap.entrySet();
	}
}
public final class Cell {

	private List<Value> history = new ArrayList<Value>();

	// 値をセットする
	public void setValue(String value) {
		Value data = new Value(value, System.currentTimeMillis());
		history.add(data);
	}

	// 最新の値を取得する
	public String getValue() {
		int last = history.size() - 1;
		Value data = history.get(last);
		return data.getValue();
	}

	@Override
	public String toString() {
		return getValue();
	}
}
public final class Value {
	private String value;
	private long timestamp;

	public Value(String value, long timestamp) {
		this.value = value;
		this.timestamp = timestamp;
	}

	public String getValue() {
		return value;
	}

	public long getTimestamp() {
		return timestamp;
	}
}
	public static void main(String[] args) {
		TEST_TYPE table = TEST_TYPE.getInstance();

		table.put("KEY00001", ColumnFamilyA,  null,     "k1_cfA0"  );
		table.put("KEY00001", ColumnFamilyA, "Column1", "k1_cfA_c1");
		table.put("KEY00002", ColumnFamilyA,  null,     "k2_cfA0"  );

		System.out.println(table.get("KEY00001", ColumnFamilyA, "Column1"));
	}

うーん、Map使いまくり(苦笑)(HBaseはキー順にソートされるらしいので、TreeMapを使ってみた)
こうして見ると、ROW以外を条件としてデータを取得するのは面倒だというのがよく分かる。
(列指向DBは項目の値を条件とした検索が苦手らしい。複数項目にまたがった条件指定の方法は提供されていない)


HBaseへアクセスする為の実際のJavaのコーディングを見ると、テーブル名や列名や値は、文字列Stringではなく全てバイト列byte[]で扱っている。


Java目次へ行く / Cassandraへ行く / 技術メモへ戻る
メールの送信先:ひしだま