HBaseを導入してスタンドアローンで利用した際のメモ。
HBaseはGoogleのBigTableをオープンソースで実現した列指向データベース。行、Family、Qualifierの3つの次元でデータを扱える。
HBaseのデータ構造のイメージはここの絵を見るとわかりやすい
http://www.ibm.com/developerworks/jp/opensource/library/os-hbase/?ca=drs-jp
簡略化すると、下記のようなデータが持てると思えば良い?
行 | Family | Qualifier | 値 |
---|---|---|---|
落合博満 | 打率 | 生涯 | .311 |
年間最高 | .367 | ||
本塁打 | 生涯 | 510 | |
年間最高 | 52 | ||
門田博光 | 打率 | 生涯 | .289 |
年間最高 | .313 | ||
本塁打 | 生涯 | 567 | |
年間最高 | 44 |
こんな感じでRDBの表に近い構造を作ることができる。
Familyはテーブルが構造として持っている要素なので、追加、削除する時は一度テーブルを無効にしてAlterコマンドで操作をしないといけない。RDBのカラムに似ている。
Qualifierはテーブル構造を触らなくても動的に好きな値が設定できる。NULLでも構わない。
上で紹介したURLの本文中にも書かれていたように、Familyは中身が空のものが多数存在するような素な列を扱うのにも向いているらしい。この辺はRDBとのデータ構造の違いを感じるところ。
Key-Valueストアなので、行に対する検索は高速。指定した範囲の行に対する検索も高速。但し、KVSらしく高速に検索できるのはあくまで行に対してのみで、RDBのようにカラム1つずつに対して検索条件を設定するような小器用なことはできない。Filter使えばできなくもないだろうけど、もちろん速度は落ちる。
HBaseはHDFS上で動作します。が、スタンドアローンでも意外と悪くない動きをしてくれます。
とりあえずここではスタンドアローンで動かすところまでの手順。JDKは入っているものとして進めます。CDH3利用。OSはUbuntu。
まずは必要になりそうなものを入れておきます。
sudo apt-get install ssh
sudo apt-get install rsync
sudo apt-get install ntp
sudo apt-get install curl
clouderaのレポジトリを使ってapt-getでHBaseをインストールします。
$ sudo vi /etc/apt/sources.list.d/cloudera.list
// 以下の行を記述
deb http://archive.cloudera.com/debian lucid-cdh3 contrib
deb-src http://archive.cloudera.com/debian lucid-cdh3 contrib
$ curl -s http://archive.cloudera.com/debian/archive.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install hadoop-hbase hadoop-hbase-master
このままだとJavaはどこですかエラーになるので、hbase-env.shにJAVA_HOMEのパスを書いておく。
$ sudo vi /usr/lib/hbase/conf/hbase-env.sh
// 以下の記述を追加
export JAVA_HOME=JAVA_HOMEのパス
次にhbase-site.xmlにHBase関連のファイルが出力されるパスを指定します。以下はユーザのhome直下にhbaseというディレクトリを作って設定する場合の例。
$ mkdir /home/user/hbase
$ sudo chown hdfs:hdfs /home/user/hbase
$ sudo vi /usr/lib/hbase/conf/hbase-site.xml
フォルダの権限をhbaseユーザにしています。hbaseユーザはapt-getでhbaseを入れた際に自動で作成されています。
で、configuration要素の中に以下のように記述します。
<property>
<name>hbase.rootdir</name>
<value>/home/user/hbase</value>
</property>
これでだいたい導入は完了です。
HBaseが起動しているかどうか調べる。
$ sudo service hadoop-hbase-master status
起動してなかったらとりあえずstartで起動。restartで再起動、stopで停止。
$ sudo service hadoop-hbase-master start
とりあえずクライアントを立ち上げます。
$ hbase shell
このクライアントはJRubyで作られています。なので文法はRubyと同じになります。
// テーブルを作成
> create "table1", "family1", "family2"
// これで、family1とfamily2というファミリーを持った、table1というテーブルができました。
// テーブルはdisable状態にしたり、enable状態にすることができます。テーブルをdropする時は、disable状態である必要があります。
// テーブルの削除(disableしてからdrop)
> disable "table1"
> drop "table1"
// もう1回テーブルを作成
> create( "table1", "family1", 'family2' )
// テーブル一覧を表示する
> list
table1
// テーブルの存在チェックをする
> exists "table1"
Table table1 does exist
> exists "table2"
Table table2 does not exist
// ファミリーを追加する(テーブルを利用不可→追加→利用再開)
> disable "table1"
> alter "table1", {NAME=>"family3"}
> enable "table1"
// テーブルの状態を確認する
> describe "table1"
// カラムの情報とかが表示される
// データの登録
> put "table1", "data1", "family1", "val1"
// これはtable1に対して、行がdata1で、family1にval1という値を設定した値を投入している
// データの参照(Rowがdata1のデータを参照する)
> get "table1", "data1"
family1: timestamp=1291001025105, value=val1
// family2にデータを投入
> put "table1", "data1", "family2", "val2"
// データの参照
> get "table1", "data1"
family1: timestamp=1291001025105, value=val1
family2: timestamp=1291002142691, value=val2
// 別の行にデータをわらわら投入
> put "table1", "data2", "family1", "value data2"
> put "table1", "data3", "family1", "value data3"
> put "table1", "data4", "family1", "value data4"
> put "table1", "data5", "family1", "value data5"
// テーブルの全データをまとめて取得
> scan "table1"
data1 column=family1:, timestamp=1291001025105, value=val1
data1 column=family2:, timestamp=1291002142691, value=val2
data2 column=family1:, timestamp=1291002252111, value=val1_1
data3 column=family1:, timestamp=1291002320958, value=value data3
data4 column=family1:, timestamp=1291002324012, value=value data4
data5 column=family1:, timestamp=1291002327258, value=value data5
//scanはJavaから実行していれば、ResultSetみたいに1行ずつ結果を受け取って処理できる。
// テーブルの指定Familyのデータだけscan
> scan "table1", {COLUMNS=>"family2"}
data1 column=family2:, timestamp=1291002142691, value=val2
// 複数のFamilyを指定してデータをscan
> scan "table1", {COLUMNS=>["family1", "family2"]}
data1 column=family1:, timestamp=1291001025105, value=val1
data1 column=family2:, timestamp=1291002142691, value=val2
data2 column=family1:, timestamp=1291002252111, value=val1_1
data3 column=family1:, timestamp=1291002320958, value=value data3
data4 column=family1:, timestamp=1291002324012, value=value data4
data5 column=family1:, timestamp=1291002327258, value=value data5
// 範囲指定でデータをscan
> scan "table1", {"STARTROW"=>"data3", "STOPROW"=>"data5"}
data3 column=family1:, timestamp=1291002320958, value=value data3
data4 column=family1:, timestamp=1291002324012, value=value data4
// 上記例ではRowがdata3〜data4を取得している。STOPROW自体は含まれない。
// 取得件数を指定してscan
> scan "table1", {"LIMIT" => 2, "STARTROW"=>"data2"}
data2 column=family1:, timestamp=1291002252111, value=val1_1
data3 column=family1:, timestamp=1291002320958, value=value data3
// データを削除する
> delete "table1", "data3", "family1"
// データをRowごと削除する
> deleteall "table1", "data1"
// テーブルのデータを全件削除する
> truncate "table1"
// 登録されている値のインクリメント
// とりあえずデータに1を登録
> put "table1", "data2", "family1:cnt", "\x00\x00\x00\x00\x00\x00\x00\x01"
// インクリメントする
> incr "table1", "data2", "family1:cnt"
// ※incrするカラムはqualifier必須らしい
// 結果を見る
> get "table1", "data2"
family1:cnt timestamp=1294078940706, value=\x00\x00\x00\x00\x00\x00\x00\x02
// 末尾が\x01だったのが、\x02に増えている。