今回は分散データベースの一つである Apache HBase を使ってみる。
これは、いわゆる NoSQL と呼ばれるものの一つ。
Hadoop ファミリーの一員だけど MapReduce などは使わず直接 HDFS を触るアーキテクチャになっている。
HBase は、分散データベースの性質を紹介するときによく使われる CAP 定理でいうと C (一貫性) と P (分断耐性) を重視している。
このことから、何かの拍子に古いデータが見えたりするようなことがなく、ネットワーク障害が起きてもサービス提供は継続できる。
ただし、(冗長化できるとはいえ) マスターサーバがあることから、そこに障害が起きるとサービス提供ができなくなってしまう。
また、一口に NoSQL といってもどのようなデータ構造を持つかはソフトウェアによって全く異なる。
(これは NoSQL がリレーショナル・データベース以外のデータベースをおおまかに分類するための語なので当然といえる)
HBase においては論理的にリレーショナル・データベースのようなテーブル構造を持つものの、その表現方法がなかなか面白い。
通常、リレーショナル・データベースであればデータはレコード (行) 単位で管理している。
それが HBase では、さらに細かくデータをセル (リレーショナル・データベースでいえばレコードの中の一つのカラム) 単位で持っている。
どのように実現しているかといえば、セルごとにキー・バリューのペアを一つ持っているイメージだ。
リレーショナル・データベースでいうところの主キーといえる行キーに対して、セルを表現するキー・バリューのペアがたくさん紐づく。
ここらへんは、次のスライドが分かりやすかった。
www.slideshare.net
今回は、そんな HBase を CentOS7 で使ってみることにする。
試すのは疑似分散モードにしたので、動作環境として前述した HDFS が必要になる。
なので、例えば以前このブログで書いたエントリなどを参考にして HDFS が使えるようになっていることが前提になる。
次のエントリでは Hadoop を擬似分散モードで立ち上げていて、そのとき HDFS も一緒に使えるようになる。
blog.amedama.jp
インストール手順書については、次の公式ドキュメントを参考にした。
ちなみに、一つ上にあるローカルディスクを使う手順なら HDFS をセットアップする必要がない。
ただ、手元で検証するのになるべく本番運用に近い形を、となると HDFS を用意して疑似分散モードで使った方が良さそう。
Apache HBase ™ Reference Guide
使った環境は次の通り。
$ cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)
$ uname -r
3.10.0-514.el7.x86_64
インストールする
まずは Apache のミラーサイトから HBase のバイナリをダウンロードする。
現在 (2017/5/25) の安定版リリースは 1.2.5 らしい。
www.apache.org
ダウンロードしたら解凍する。
$ wget http://ftp.riken.jp/net/apache/hbase/1.2.5/hbase-1.2.5-bin.tar.gz
$ tar xf hbase-1.2.5-bin.tar.gz
ここからは HBase のディレクトリ内で作業する。
$ cd hbase-1.2.5
まずは HBase の設定ファイルを編集する。
一番上の hbase.rootdir
では、作業ディレクトリとして使う場所を HDFS 上のパスに指定している。
真ん中の hbase.cluster.distributed
では動作モードを分散モードに設定している。
今回使うのは一つのホストだけど分散モードに設定することで擬似分散モードとして使えるようになる。
そして、最後の hbase.zookeeper.property.clientPort
は、公式の手順書には無かったけど無いとエラーになるので追加した。
ここでは Apache ZooKeeper のクライアントが接続するポートを指定している。
これは、HBase がマスターサーバの選出を ZooKeeper で行っているためらしい。
$ cat << "EOF" > /tmp/hbase-site.xml.property
<property>
<name>hbase.rootdir</name>
<value>hdfs://localhost:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
EOF
$ sed -i -e '
/^<configuration>$/r /tmp/hbase-site.xml.property
' conf/hbase-site.xml
上記の操作で、設定ファイルはこんな感じになる。
$ tail -n 14 conf/hbase-site.xml
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://localhost:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
</configuration>
ちなみに、上記で指定した HDFS のデータディレクトリは勝手に作られる。
なので、あえて自分で作る必要はないようだ。
次に HBase の動作に必要なので環境変数 JAVA_HOME
を設定する。
$ export JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk
これは HBase の環境設定を行うスクリプト hbase-env.sh
に書いても良い。
こちらに書いておくと毎回呼ばなくて良いので楽ちん。
$ sed -i -e '
s:^# export JAVA_HOME=.*$:export JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk:
' conf/hbase-env.sh
起動する
これで下準備は整ったので HBase を起動してみよう。
$ bin/start-hbase.sh
localhost: starting zookeeper, logging to /home/vagrant/hbase-1.2.5/bin/../logs/hbase-vagrant-zookeeper-localhost.localdomain.out
starting master, logging to /home/vagrant/hbase-1.2.5/bin/../logs/hbase-vagrant-master-localhost.localdomain.out
OpenJDK 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
starting regionserver, logging to /home/vagrant/hbase-1.2.5/bin/../logs/hbase-vagrant-1-regionserver-localhost.localdomain.out
Java で動いているプロセスを jps
コマンドで確認してみる。
この中の HMaster
、HQuorumPeer
と HRegionServer
が HBase の動作に直接関わっているプロセスだ。
$ jps
32496 Jps
31505 ResourceManager
31330 SecondaryNameNode
31607 NodeManager
32281 HMaster
32219 HQuorumPeer
32365 HRegionServer
31038 NameNode
31167 DataNode
また、HBase 用の作業ディレクリが HDFS 上にできていることも確認してみよう。
$ export HADOOP_HOME=~/hadoop-2.8.0
$ $HADOOP_HOME/bin/hdfs dfs -ls /hbase
Found 7 items
drwxr-xr-x - vagrant supergroup 0 2017-05-25 18:47 /hbase/.tmp
drwxr-xr-x - vagrant supergroup 0 2017-05-25 18:47 /hbase/MasterProcWALs
drwxr-xr-x - vagrant supergroup 0 2017-05-25 18:47 /hbase/WALs
drwxr-xr-x - vagrant supergroup 0 2017-05-25 18:47 /hbase/data
-rw-r--r-- 3 vagrant supergroup 42 2017-05-25 18:47 /hbase/hbase.id
-rw-r--r-- 3 vagrant supergroup 7 2017-05-25 18:47 /hbase/hbase.version
drwxr-xr-x - vagrant supergroup 0 2017-05-25 18:47 /hbase/oldWALs
ちゃんとできているみたい。
使ってみる
ここからは HBase を実際に使ってみることにする。
HBase には各種プログラミング言語で実装されたクライアントがあるけど、同梱されているシェルを使うのが一番簡単かな。
$ bin/hbase shell
HBase のシェルを起動すると、こんな感じでコマンドが入力できるようになる。
$ bin/hbase shell
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/vagrant/hbase-1.2.5/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/vagrant/hadoop-2.8.0/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 1.2.5, rd7b05f79dee10e0ada614765bb354b93d615a157, Wed Mar 1 00:34:48 CST 2017
hbase(main):001:0>
例えばクラスタの状態を確認するには status
コマンドを使う。
疑似分散モードでは一つのホストしか使わないので、あんまり意味ないけど。
> status
1 active master, 0 backup masters, 1 servers, 0 dead, 2.0000 average load
テーブルの状態を確認するには list
を使う。
> list
TABLE
0 row(s) in 0.0580 seconds
=> []
試しに users
テーブルを作ってみよう。
このとき、テーブル名と一緒にカラムファミリーの名前も指定する。
カラムファミリーというのは、セルを表現するキー・バリューペアのキーをグループ化したもの。
例えば、今回作った profile
というカラムファミリーにはキーとして age
だの bloodtype
だのが入ることになる。
> create 'users', 'profile'
0 row(s) in 2.3290 seconds
=> Hbase::Table - users
これで users
テーブルができた。
> list
TABLE
users
1 row(s) in 0.0140 seconds
=> ["users"]
テーブルに関する情報は describe
コマンドで確認できる。
> describe 'users'
Table users is ENABLED
users
COLUMN FAMILIES DESCRIPTION
{NAME => 'profile', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false',
KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER',
COMPRESSION => 'NONE', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE =>
'65536', REPLICATION_SCOPE => '0'}
1 row(s) in 0.1230 seconds
それでは users
テーブルにセルを追加してみよう。
HBase ではデータをリレーショナル・データベースのようなレコード単位ではなくセル単位で追加していく。
以下の例では users
テーブルに主キー相当として Alice
という行キーを追加している。
その行キーに対応するセルとしては profile
カラムファミリーに age
というキーで 20
という値を追加した。
> put 'users', 'Alice', 'profile:age', '20'
0 row(s) in 0.1030 seconds
ちなみに HBase にはデータ型という概念がない。
代わりに、すべてバイト配列として扱われる。
つまり、どのカラムをどのような表現で扱うかはアプリケーション側でケアしなければならない。
テーブル全体の状況は scan
コマンドで確認できる。
ちゃんと先ほど入れた Alice
のセルが格納されている。
> scan 'users'
ROW COLUMN+CELL
Alice column=profile:age, timestamp=1495716559615, value=20
1 row(s) in 0.0330 seconds
ピンポイントで一つの行データを手に入れるには get
コマンドを使う。
> get 'users', 'Alice'
COLUMN CELL
profile:age timestamp=1495716559615, value=20
1 row(s) in 0.0350 seconds
また、カラムファミリーはテーブルの定義時に指定しなきゃいけないけど、その中のキーは後から自由に定義できる。
血液型の bloodtype
キーを追加してみよう。
> put 'users', 'Alice', 'profile:bloodtype', 'A'
0 row(s) in 0.0200 seconds
こんな感じで動的に追加できるようになっている。
> get 'users', 'Alice'
COLUMN CELL
profile:age timestamp=1495716559615, value=20
profile:bloodtype timestamp=1495716597831, value=A
2 row(s) in 0.0120 seconds
行の中で特定のカラムだけを取得したいときは get
コマンドでカラムファミリーやキーを指定する。
> get 'users', 'Alice', 'profile:age'
COLUMN CELL
profile:age timestamp=1495716559615, value=20
1 row(s) in 0.0140 seconds
> get 'users', 'Alice', 'profile:age', 'profile:bloodtype'
COLUMN CELL
profile:age timestamp=1495716559615, value=20
profile:bloodtype timestamp=1495716597831, value=A
2 row(s) in 0.0150 seconds
セルを削除するときは delete
コマンドを指定する。
> delete 'users', 'Alice', 'profile:bloodtype'
0 row(s) in 0.0370 seconds
> get 'users', 'Alice'
COLUMN CELL
profile:age timestamp=1495716559615, value=20
1 row(s) in 0.0140 seconds
ちなみに、なんとなく雰囲気で分かるだろうけどテーブルのカラムは行ごとにあったりなかったりしても構わない。
試しに Bob
という行を追加してみよう。
この行には血液型だけを追加する。
> put 'users', 'Bob', 'profile:bloodtype', 'B'
0 row(s) in 0.0150 seconds
テーブルの状態を確認すると Alice
には profile:age
だけがあるのに対し Bob
には profile:bloodtype
だけがある。
HBase では、こうした歯抜けの状態も許されている。
> scan 'users'
ROW COLUMN+CELL
Alice column=profile:age, timestamp=1495716559615, value=20
Bob column=profile:bloodtype, timestamp=1495716657793, value=B
2 row(s) in 0.0260 seconds
テーブル自体を削除するには、まずテーブルを disable
コマンドで無効にする。
> disable 'users'
0 row(s) in 2.2960 seconds
無効にしただけなら、まだテーブルとしては確認できる。
> list
TABLE
users
1 row(s) in 0.0110 seconds
=> ["users"]
ただし get
コマンドなど、実際の操作は受け付けられずエラーになる。
> get 'users', 'Alice'
COLUMN CELL
ERROR: users is disabled.
...(snip)...
本当に削除するときは disable
にした状態で drop
コマンドを使う。
> drop 'users'
0 row(s) in 1.2790 seconds
これでテーブル自体がなくなった。
> list
TABLE
0 row(s) in 0.0100 seconds
=> []
ちなみに、この HBase のシェルは JRuby で実装されたインタプリタがそのまま使われている。
なので、こんなこともできる。
> RUBY_VERSION
=> "1.8.7"
> 10.times { |i| p i }
0
1
2
3
4
5
6
7
8
9
=> 10
ちなみに、注意点として HBase では複数行のアトミックな変更ができない。
もしトランザクションのようなものが欲しいときは Apache Omid などを使う必要があるようだ。
Apache Omid – What is Omid?
まとめ
今回は分散データベースの一つである Apache HBase を使ってみた。
性質をよく把握した上でスケールする KVS の一種として扱う分には良さそうだ。
今回も、この本が参考になった。