LINE株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。 LINEヤフー Tech Blog

Blog


HBase アプリケーションローカルテスティング

こんにちは、LINEでGame Platformを開発している 趙 です。LINEではHBaseを様々な場面で活用しており、今回はGame Platformでの利用例を紹介させていただきます。

LINE Game PlatformはHBaseをメインストレージとして利用します。私達が新規の機能開発時にHBaseを利用する場合、ローカル開発環境におけるユニットテストなどのアプリケーションテストは専用のリモートクラスターで実行されるHBaseに接続していました。リモートクラスターはHadoopアドミニストレータが構築、管理してくれます。チームメンバーとHBaseを使ったアプリケーションの数が増えるにしたがい、このテスト方式は以下に示す問題があり、不便に感じていました。

  • 共通クラスターを使うので、他人のテストとの衝突が起きやすい。
  • 案件によって、利用するHBaseのバージョンが違います。複数のテスト専用クラスターを持つのは非効率的。
  • クラスターは社内にありますが、IP制限などにより社外での作業、デバッグが不便。

これらの問題は、ローカル開発環境でHBaseのインスタンスを起動すれば問題を解決できます。これから紹介する内容は、私たちが使っている二つ方式を紹介したいと思います。HBaseTestingUtilityを利用して、テストする時にHBaseを起動する方法とDockerを利用してHBaseをローカル環境に設置する方法です。

HBaseTestingUtility

HBaseはHBaseTestingUtilityというテスティングツールを提供してくれています。以下のようにプロジェクトにdependencyを追加すればすぐ使用できます。

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-testing-util</artifactId>
    <version>${hbaseversion}</version>
    <scope>test</scope>
</dependency>

HBaseTestingUtilityはユニットテスト時、Mini Cluster(StandaloneモードのHBase)を起動することが可能です。テーブルの作成、削除などの管理作業も可能です。以下はHBase 0.98バージョンの一例です。

private HBaseTestingUtility hBaseTestingUtility = new HBaseTestingUtility();
private byte[] table = Bytes.toBytes("testTable");
private byte[] family = Bytes.toBytes("f1");
private HTable hTable;

@Before
public void before() throws Exception {
// Mini Clusterを起動します
    hBaseTestingUtility.startMiniCluster();
// tableを作成します
    hBaseTestingUtility.createTable(table, family);
// Mini Clusterの配置で、table connectorを作成します
    hTable = new HTable(hBaseTestingUtility.getConfiguration(), table);
}

@After
public void after() throws Exception {
// Mini Clusterを終了します
    hBaseTestingUtility.shutdownMiniCluster();
}

@Test
public void testPutAngGet() throws Exception {
    byte[] targetRow = Bytes.toBytes("row1");
    byte[] targetColumn = Bytes.toBytes("c1");
    byte[] value = Bytes.toBytes("v1");

    Put put = new Put(targetRow);
    put.add(family, targetColumn, value);
    hTable.put(put);

    Get get = new Get(targetRow);
    assertThat(hTable.get(get).getValue(family, targetColumn), is(value));
}

HBaseバージョンごとにHBaseTestingUtilityがありますので、バージョン問題はありません。そして、システムとHBaseの設定は必要ありません。テスト時のみHBaseを起動します、そのためCIツールとの相性は高いです。ただし、幾つかの問題が存在し、実際に使うときにちょっと不便なところもあります。
まずは、Mini Clusterの起動は少し時間がかかります(私の環境では10秒ほど)。複数またはプロジェクト全体のテストを行うときには、最初のテストが始まる前に一回だけでMini Clusterを起動すれば、まったく問題ありませんが、単体テストをするときに退屈な感じがします。つぎに、テスト、特にデッバグをする時に、データを確認したい場合がありますが、Mini Cluster中のデータ確認は難しいです。そして、依存関係の競合問題です。たとえば、hbase-testing-utilはあるライブラリAの古いバージョンに依存します、他のコードはAの新しいバージョンに依存します。もしAの古いバージョンと新しいバージョンのインタフェースが異なる場合、依存関係の競合が発生します。私たちの場合は依存ライブラリのバージョンを合わせないので、Apache Maven Shade Pluginを使い、この競合問題を解除しました。このプラグインはdependencyをリネームして、一つのuber-jarに取り込むことができます。

<relocations>
  <relocation>
    <pattern>com.google.guava</pattern>
    <shadedPattern>shadow.hbase.guava</shadedPattern>
  </relocation>
  <relocation>
    <pattern>org.joda</pattern>
    <shadedPattern>shadow.hbase.joda</shadedPattern>
  </relocation>
  ...
</relocations>

( このセクションのです )

Docker

ローカル環境にHBaseを設置する方法はたくさんありますが、Dockerを使えば比較的簡単です。Dockerストアに複数バージョン・モードのHBaseイメージがあります。そして、特定のバージョン・モードのHBaseイメージを作成することも難しくありません。以下は私がHBaseイメージを作成するときに注意したところについて紹介したいと思います。

基本イメージの選択

基本イメージはHBaseの運行環境を提供します。テスト専用の構成で、複雑な設定は必要ありません、JDKがあれば十分です。openjdkのような、JDK、JAVA_HOMEを設定したイメージを選択したら楽です。

HBaseの設定

まずはHBaseのモードを決めます。standaloneモードはライトウエート、簡易設定のモードです。このモードでは、すべてのHBaseデーモンはひとつのJVMに存在します。各デーモンのポートは毎回起動するたびに変更します。もしプロジェクトをDockerイメージにいれてテストするなら問題ないですが、私たちのケースは、プロジェクトは外にあります。たくさんのポートまたはポートの区間を開くことは理想的ではないので、pseudo-distributedモードを使っています。pseudo-distributedモードはノード一つだけを使ってるdistributedモードです。そしてファイルシステムですが、アプリケーションテストをするだけなので、HDFSを使う必要性はありません。以下はHBase設定ファイルhbase-site.xmlの一例です。

<configuration>
  <!-- distributed モード -->
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <!-- hdfsは使わない -->
  <property>
    <name>hbase.rootdir</name>
    <value>file:///hbase/hbase</value>
  </property>
  <!-- other configurations ... -->
</configuration>

HBaseの起動

テストアプリケーションによって必要なデーモンが違いますが、ここは最小限のデーモンのみを起動します。

${HBASE_HOME}/bin/hbase-daemon.sh start zookeeper
${HBASE_HOME}/bin/hbase-daemon.sh start regionserver
${HBASE_HOME}/bin/hbase-daemon.sh start master

起動スクリプトをイメージに入れて、Docker起動する時に、このスクリプトを実行してHBaseを起動します。 containerの外から通信するため、必要なポートを露出します。

component port(HBase 0.98 default)
zookeeper 2181
master 60000
region 60020

以下は起動コマンドの一例です。

docker run -h HOSTNAME -itd -p 2181:2181 -p 60000:60000 -p 60020:60020 IMAGE

最後はhostnameをホストファイルにいれます。

127.0.0.1 HOSTNAME

HBaseのアクセス

アプリケーションからこのHBaseに接続する時に、zookeeper quorumはHOSTNAME、portは2181です。HBaseのデータを確認する時に、containerに入って、HBase shellを利用して簡単に確認することはできます。

  • Docker psコマンドでCONTAINER IDを獲得します
  • Docker attachコマンドでcontainerに入ります
  • $HBASE_HOME/bin/hbase shellコマンドでHBase shellを起動します

Dockerを利用するテスト方式は、HBaseTestingUtilityの起動が遅い、依存競合などの問題はありませんが、設定と運用は少し複雑です。そして、CIツールでテストを行う時にDockerの起動・停止、複数イメージを同時に起動する時のポート管理も気をつけなければならないです。
( このセクションのです )
HBaseアプリケーションローカルテスト方法を紹介しました。私たちはこの二つ方法を同時に使っています。ローカル開発、テストはDockerを利用します、CIツールでのインテグレーションテストは頑張ってdependency競合問題を解決して、HBaseTestingUtilityを使っています。