CUBE SUGAR CONTAINER

技術系のこと書きます。

CentOS7 で Apache HBase を使ってみる

今回は分散データベースの一つである 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 コマンドで確認してみる。 この中の HMasterHQuorumPeerHRegionServer が 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 の一種として扱う分には良さそうだ。

今回も、この本が参考になった。

Hadoop徹底入門 第2版

Hadoop徹底入門 第2版