HBaseの紹介とMongoDBとの比較
HBase は正式名称を Hadoop Database といい、高信頼性・高性能・カラム指向でスケーラブルな分散ストレージシステムです。安価な PC サーバー上に大規模な構造化ストレージクラスタを構築できます。
HBase の目標は大量データの保存と処理です。標準的なハードウェア構成だけで、数千の行と列を含む膨大なデータを扱うことができ、PB 級のデータストレージと億単位の QPS のクエリを支えられます。
リアルタイム性の要求が高くない業務シナリオに適しています。HBase が保存するのは Byte 配列であり、データ型を意識しないため、動的で柔軟なデータモデルが可能になります。
アーキテクチャの解説
HBase は HMaster と HRegionServer で構成され、マスター・スレーブ型のサーバーアーキテクチャに従います。HBase は論理テーブルを複数のデータブロック HRegion に分割し、それらを HRegionServer に格納します。
HMaster はすべての HRegionServer の管理を担当します。HMaster 自身はデータを一切保存せず、データから HRegionServer へのマッピング(メタデータ)だけを保持します。
クラスタ内のすべてのノードは Zookeeper によって調整され、HBase の稼働中に発生しうるさまざまな問題への対処に使われます。HBase の基本アーキテクチャは次のとおりです。

クライアント: HBase の RPC メカニズムを使って HMaster および HRegionServer と通信し、リクエストを送信して結果を受け取ります。管理系の操作では、クライアントは HMaster を通じて RPC を実行し、データの読み書き操作では HRegionServer を通じて RPC を実行します。
Zookeeper: クラスタ内の各ノードの状態情報が Zookeeper に登録されるため、HMaster は各 HRegionServer の健全性を常に把握でき、同時に HMaster の単一障害点も回避できます。
HMaster: すべての HRegionServer を管理し、どの HRegion を維持すべきかを指示し、すべての HRegionServer の稼働状況を監視します。新しい HRegionServer が HMaster に登録されると、HMaster はデータ割り当てを待つよう指示します。ある HRegion が失効した場合、HMaster はその HRegion が担当していたすべての HRegion を未割り当てとしてマークし、他の HRegionServer に再割り当てします。HMaster には単一障害点の問題がありません。HBase は複数の HMaster を起動でき、Zookeeper の選出メカニズムによってクラスタ内で常に 1 つの HMaster が稼働していることが保証され、クラスタの可用性が高まります。
HRegion: テーブルのサイズが事前設定された値を超えると、HBase は自動的にテーブルを複数のリージョンに分割し、各リージョンにはテーブル内の全行のサブセットが含まれます。ユーザーから見れば、各テーブルは主キー(RowKey)で区別される 1 つのデータ集合です。物理的には、1 つのテーブルは複数のブロックに分割され、各ブロックが 1 つの HRegion であり、「テーブル名 + 開始/終了主キー」で区別されます。1 つの HRegion はテーブル内の連続した一区間のデータを保持し、テーブル全体のデータは複数の HRegion に格納されます。
HRegionServer: HBase のすべてのデータは通常、下層の HDFS に保存され、ユーザーは HRegionServer を通じてこれらのデータにアクセスします。一般に、クラスタの 1 ノード上では 1 つの HRegionServer だけが稼働し、各 HRegion も 1 つの HRegionServer だけによって維持されます。HRegionServer は主にユーザーの I/O リクエストに応答し、HDFS ファイルシステムへのデータ読み書きを担う、HBase の中核モジュールです。HRegionServer は内部で一連の HRegion オブジェクトを管理し、各 HRegion は論理テーブル内の連続した一区間のデータに対応します。HRegion は複数の HStore で構成され、各 HStore は論理テーブル内の 1 つのカラムファミリーのストレージに対応します。つまり、各カラムファミリーは 1 つの集中したストレージ単位であり、操作効率を高めるためには、同じ I/O 特性を持つ列を同じカラムファミリーにまとめるのが望ましいということです。
HStore: HBase ストレージの中核であり、MemStore と StoreFile で構成されます。MemStore はメモリバッファで、ユーザーが書き込んだデータはまず MemStore に入ります。MemStore が満杯になると、Flush されて 1 つの StoreFile(下層の実装は HFile)になります。StoreFile のファイル数がある閾値まで増えると、Compact マージ操作がトリガーされ、複数の StoreFile が 1 つにマージされ、その過程でバージョンのマージとデータ削除が実行されます。このことから分かるように、HBase はデータを追記し続けるだけであり、すべての更新と削除は後続の Compact プロセスで行われます。ユーザーの書き込み操作はメモリに入った時点ですぐに返せるため、HBase の書き込み性能が保証されます。StoreFile は Compact を繰り返すうちに次第に大きなファイルになっていきます。単一の StoreFile のサイズがある閾値を超えると Split 操作がトリガーされ、現在の HRegion が 2 つの HRegion に分割されます。親 HRegion はオフラインになり、HMaster が 2 つの子 HRegion を対応する HRegionServer に割り当てることで、元の HRegion の負荷がこの 2 つの HRegion に分散されます。
HLog: 各 HRegionServer は 1 つの HLog オブジェクトを持ちます。これは先行書き込みログ(Write-Ahead Log)を実装するクラスです。ユーザーが MemStore にデータを書き込むたびに、データのコピーが HLog ファイルにも書き込まれます。HLog ファイルは定期的にロールされ、古いファイル(その中のデータはすでに StoreFile に永続化済み)は削除されます。HMaster が Zookeeper を通じてある HRegionServer の異常終了を検知すると、まず残された HLog ファイルを処理し、その中のログデータを HRegion ごとに分割して対応する HRegion のディレクトリに配置し、その後これらの失効した HRegion を再割り当てします。これらの HRegion を引き継いだ HRegionServer は、ロードの過程で処理すべき過去の HLog があることを発見し、HLog 内のデータを MemStore に Replay してから StoreFile にフラッシュし、データ復旧を完了します。
HBase は BigTable モデルに基づいており、スパースで、長期保存(HDFS 上)され、多次元でソートされたマップテーブルです。このテーブルのインデックスは行キー、列キー、タイムスタンプです。HBase のデータはすべて文字列であり、型を持ちません。

HBase の読み書きプロセス
下図は HRegionServer のデータストレージ関係図です。前述のとおり、HBase は MemStore と StoreFile を使ってテーブルへの更新を保存します。データは更新時にまず HLog と MemStore に書き込まれ、MemStore 内のデータはソートされています。

MemStore がある閾値まで蓄積されると、新しい MemStore が作成され、古い MemStore は Flush キューに追加され、専用スレッドによってディスクにフラッシュされて StoreFile になります。同時に、システムは Zookeeper に CheckPoint を記録し、その時点以前のデータ変更が永続化済みであることを示します。システムに異常が発生した場合、MemStore 内のデータは失われる可能性がありますが、その場合は HLog を使って CheckPoint 以降のデータを復旧します。
StoreFile は読み取り専用で、一度作成されると変更できません。そのため HBase の更新は実際には追記操作です。HStore 内の StoreFile の数がある閾値に達するとマージ操作が実行され、同じ key に対する変更が 1 つの大きな StoreFile にマージされます。StoreFile のサイズがある閾値に達すると、今度は 2 つの StoreFile に分割されます。
書き込み操作の流れ
- ステップ 1:クライアントは Zookeeper のスケジューリングを通じて HRegionServer に書き込みリクエストを送信し、データを HRegion に書き込みます。
- ステップ 2:データは HRegion の MemStore に書き込まれ、MemStore が事前設定の閾値に達するまで続きます。
- ステップ 3:MemStore 内のデータが Flush されて StoreFile になります。
- ステップ 4:StoreFile のファイル数が特定の閾値まで増えると、Compact マージ操作がトリガーされ、複数の StoreFile が 1 つにマージされ、同時にバージョンのマージとデータ削除が行われます。
- ステップ 5:StoreFile は継続的な Compact 操作を経て、次第に大きな StoreFile になっていきます。
- ステップ 6:単一の StoreFile のサイズが閾値を超えると Split 操作がトリガーされ、現在の HRegion が 2 つの新しい HRegion に分割されます。親 HRegion はオフラインになり、新たに分割された 2 つの子 HRegion は HMaster によって対応する HRegionServer に割り当てられ、元の HRegion の負荷が分散されます。
読み取り操作の流れ
- ステップ 1:クライアントは Zookeeper にアクセスして -ROOT- テーブルを見つけ、そこから .META. テーブルの情報を取得します。
- ステップ 2:.META. テーブルから対象データが存在する HRegion の情報を照会し、対応する HRegionServer を見つけます。
- ステップ 3:HRegionServer を通じて目的のデータを取得します。
- ステップ 4:HRegionServer のメモリは MemStore と BlockCache の 2 つの部分に分かれています。MemStore は主に書き込み用、BlockCache は主に読み取り用です。読み取りリクエストはまず MemStore でデータを探し、見つからなければ BlockCache を探し、それでも見つからなければ StoreFile にアクセスし、読み取った結果を BlockCache に格納します。
HBase のユースケース
半構造化・非構造化データ:フィールドを事前に定義できない、あるいは雑多なデータ構造は、固定スキーマでモデリングするのが難しく、HBase に任せるのに向いています。ビジネスの成長に伴ってより多くのフィールドを保存する必要が出た場合、RDBMS ではテーブル構造の変更にダウンタイムを伴うメンテナンスが必要ですが、HBase は動的な列の追加をサポートしています。
レコードが非常にスパース:RDBMS のテーブルは列数が固定で、値が空の列も無駄にストレージを消費します。HBase では空の列はストレージを占有しないため、容量を節約できるうえに読み取り性能も向上します。
マルチバージョンデータ:RowKey と列識別子で特定される値は、(タイムスタンプの異なる)任意の数のバージョンを持てるため、変更履歴を保存する必要のあるデータには HBase が非常に便利です。
大量データ:データ量がどんどん増えていくと、RDBMS は次第に耐えられなくなります。そこでまず読み書き分離を行い、マスター 1 台が書き込み、複数のスレーブが読み取りを担当するようにしますが、サーバーコストは倍増します。負荷がさらに増えてマスターが耐えられなくなると、データベースの分割(シャーディング)を始め、ほとんど関連のないデータを分けてデプロイします。すると一部の join クエリが使えなくなり、中間層の導入が必要になります。データ量がさらに増えて単一テーブルのレコード数が増え続けると、クエリが非常に遅くなり、単一テーブルのレコード数を減らすためにテーブル分割(例えば ID の剰余で複数テーブルに分割)を余儀なくされます。このプロセスを経験した人なら、その煩雑さをよく知っているはずです。
HBase ならずっとシンプルです。クラスタに新しいノードを追加するだけで、HBase は自動的に水平分割を行い、さらに Hadoop とのシームレスな統合により、データの信頼性(HDFS)と高性能な大規模データ分析能力(MapReduce)が保証されます。
HBase と MapReduce

HBase における Table と Region の関係は、HDFS における File と Block の関係にやや似ています。HBase は TableInputFormat や TableOutputFormat など、MapReduce と連携する API を提供しているため、HBase のデータテーブルを Hadoop MapReduce の入力・出力として直接使うことができ、MapReduce アプリケーションの開発が容易になり、HBase システム自体の詳細を意識する必要もありません。
データ構造の分析
HBase は LSM(Log-Structured Merge)ツリーを使用し、従来のデータベースの InnoDB は B+ ツリーを使用します。
LSM ツリーの特徴:
- 書き込み重視
- 読み取りは複数のツリーへのアクセスが必要で、IO 回数が B+ ツリーより多く、ばらつきが大きい
- 書き込みはすべてシーケンシャル IO。ランダム読み取りはランダム IO、シーケンシャル読み取りはシーケンシャル IO
メリット:
- 書き込み性能が大幅に向上する
- SSD のランダム書き込み増幅の影響を受けない
- 空間増幅の影響を受けない
デメリット:
- 読み取り性能が犠牲になり、IO 回数が B+ ツリーより多い
- 定期的な Compaction が必要で、ネットワーク/ディスク IO 全体に対する増幅が存在する
HBase のメリット
- データがカラム単位で保存されるため、クエリ時には関係する列にだけアクセスすればよく、システムの I/O を大幅に削減できます。同じ列のデータは型が一致し、NULL 値は保存されないため、効率的な圧縮保存が可能です。
- Key/Value 型のストレージ方式のため、データが膨大に増え続けても、クエリ性能の低下はほとんど起こりません。
- カラム型データベース(従来の行指向データベースに対して)として、1 つのテーブルのフィールドが非常に多い場合、異なる列を(Region 単位で)別々のサーバーインスタンスに分散させ、負荷を分散できます。
HBase のデメリット
- ネイティブではセカンダリインデックスをサポートせず、主キーでしかアクセスできません。コミュニティ実装のセカンダリインデックスはデータ更新との間に遅延があり、悩ましい整合性の問題を引き起こします
- ワイドテーブルモデルの概念は分かりにくく理解が難しいうえ、事前のモデリングが必要で柔軟性に欠けます
- サポートするプログラミング言語の種類が少なく(Java、Thrift、RESTful API)、SQL がなく API しか使えません
- クラスタ構造が複雑で、8 種類もの異なるタイプのノードがあります
- データ型が低レベルで、バイトストリームしかサポートせず、開発者にやさしくありません
- 一貫性スナップショット機能がありません
- 定期的な Compact が必要で、継続的な読み書きのシナリオへの影響が大きいです
- テーブルの結合操作をサポートしないため、データ分析は HBase の弱点であり、よくある group by や order by は MapReduce を書くことでしか実現できません
まとめ
HBase は特に大量データの書き込みシナリオに適しています。データを書き込んだ後、非同期でデータを分析・処理するという使い方です。対応する業務シナリオは、リアルタイム性の要求は高くないが書き込み量が特に大きいもの、例えばハードウェアや車載システムのデータモニタリング収集、ユーザープロファイリングなどです。主に大規模ストレージの問題を解決し、その後データ分析コンポーネントを統合することで、ビッグデータ分析、ユーザー行動分析、リアルタイムレコメンド、リスク管理などの機能を実現できます。
MongoDB については本記事では詳しく紹介していません。同等のマシンクラスタ構成では、MongoDB の書き込み能力は HBase に及びません。しかし MongoDB はデータ書き込み時に対応するインデックスを作成するため、クエリ速度とクエリ可能な次元は HBase を上回ります。HBase はデータをクエリできる次元が限られており、クエリの柔軟性やさまざまな条件下でのクエリ効率という点では MongoDB がやや優勢で、リアルタイムのクエリ分析が必要なシナリオに向いています。MongoDB の書き込み能力を高めるには、より多くの高性能なクラスタノードを追加する必要があります。実際の技術選定では、やはり自分の業務シナリオに合わせて分析しマッチングすべきです。
COMMENTS