Skip to content
Published on

HBase実践ガイド:大規模NoSQLデータストアの設計から運用まで

Authors
  • Name
    Twitter

はじめに

HBaseはGoogle Bigtable論文を基に作られた分散NoSQLデータベースです。HDFS上で動作し、数十億行と数百万カラムを処理できる大規模ランダム読み書きに最適化されています。時系列データ、ログ分析、リアルタイムサービングレイヤーなど、大量のデータに低レイテンシでアクセスする必要があるシナリオで多く使用されています。

この記事では、HBaseの核心概念から実践運用のノウハウまで体系的に扱います。

1. HBaseとは?

主要特性

特性説明
分散HDFS上で水平スケーリング可能
Column-Familyベースカラムファミリー単位でデータを保存
バージョン管理セルごとに複数のバージョン(タイムスタンプ)を保存可能
強い一貫性単一行に対する原子的な読み書きを保証
自動シャーディングRegion単位でデータを自動分配
高スループット数十万QPSの処理が可能

HBaseを使うべきとき

適したケース:

  • 数十億行以上の大規模データ
  • 高速なランダム読み書きが必要な場合(< 10ms)
  • 時系列データ(IoTセンサー、ログ、メトリクス)
  • 幅広いテーブル(数百〜数千カラム)
  • HDFSとの統合が必要な場合

不適切なケース:

  • 数百万行以下の小規模データセット
  • 複雑なJOIN、トランザクションが必要な場合
  • 全文検索(Elasticsearchの方が適切)
  • アドホック分析クエリ(Hive、Spark SQLの方が適切)

2. HBaseアーキテクチャ

全体構造

┌────────────────────────────────────────────────────────┐
Client   (HBase Shell, Java API, REST, Thrift)└───────────────────────┬────────────────────────────────┘
                ┌───────▼───────┐
ZooKeeper                  (coordination│
& discovery)                └───┬───────┬───┘
                    │       │
            ┌───────▼──┐  ┌─▼──────────┐
HMaster  │  │  HMaster             (Active) (Standby)            └───────┬───┘  └────────────┘
     ┌──────────────┼──────────────┐
     │              │              │
┌────▼─────┐  ┌────▼─────┐  ┌────▼─────┐
│RegionSvr │  │RegionSvr │  │RegionSvr │
│┌────────┐│  │┌────────┐│  │┌────────┐│
││Region A││  ││Region C││  ││Region E││
│├────────┤│  │├────────┤│  │├────────┤│
││Region B││  ││Region D││  ││Region F││
│└────────┘│  │└────────┘│  │└────────┘│
└──────────┘  └──────────┘  └──────────┘
       │              │              │
       └──────────────┼──────────────┘
               ┌──────▼──────┐
HDFS                (Storage)               └─────────────┘

コンポーネントごとの役割

コンポーネント役割障害時の影響
HMasterRegionの割り当て、DDL処理、負荷分散DDL不可、自動バランシング停止(読み書きは可能)
RegionServerデータの読み書き処理、Region管理該当Regionへのアクセス不可(自動復旧)
ZooKeeperHMaster選出、RS状態追跡、メタ位置クラスタ全体が停止
HDFS実際のデータの永続的保存データ損失のリスク

Regionの内部構造

┌─────────────── Region ──────────────────┐
│                                          │
│  ┌─── Column Family: cf1 ─────────────┐ │
│  │  ┌──────────┐                      │ │
│  │  │ MemStore   (メモリ、書込バッファ)│ │
│  │  └──────────┘                      │ │
│  │  ┌──────────┐  ┌──────────┐       │ │
│  │  │ HFile 1  │  │ HFile 2  │       │ │
│  │  └──────────┘  └──────────┘       │ │
│  └────────────────────────────────────┘ │
│                                          │
│  ┌─── Column Family: cf2 ─────────────┐ │
│  │  ┌──────────┐                      │ │
│  │  │ MemStore │                      │ │
│  │  └──────────┘                      │ │
│  │  ┌──────────┐                      │ │
│  │  │ HFile 1  │                      │ │
│  │  └──────────┘                      │ │
│  └────────────────────────────────────┘ │
│                                          │
│  ┌──────────────────────────────────┐   │
│  │         WAL (Write-Ahead Log)    │   │
│  └──────────────────────────────────┘   │
└──────────────────────────────────────────┘

書き込みパス(Write Path)

1. ClientRegionServerにPutリクエスト
2. WAL(Write-Ahead Log)に記録(HDFSに保存、障害復旧用)
3. MemStoreに記録(メモリ)
4. Clientに成功レスポンス
5. MemStoreが満杯になったらHFileにFlush(HDFSに保存)

読み取りパス(Read Path)

1. ClientRegionServerにGetリクエスト
2. Block Cacheを確認(メモリ)
3. MemStoreを確認(メモリ)
4. HFileを確認(ディスク、Bloom Filterで高速フィルタリング)
5. 結果をマージ(最新バージョンを返却)

3. データモデル

論理データモデル

Table: user_activity
─────────────────────────────────────────────────────────────────
RowKey          | Column Family: info      | Column Family: stats
                | name     | email        | login_count | last_login
─────────────────────────────────────────────────────────────────
user001         | 金英柱   | yj@email.com | 142         | 2026-03-08
user002         | 朴瑞俊   | sj@email.com | 87          | 2026-03-07
user003         | 李河那   | hn@email.com | 256         | 2026-03-08
─────────────────────────────────────────────────────────────────

物理的保存構造

# 実際にはColumn Family単位で別々に保存
# Key-Value形式:(RowKey, CF:Qualifier, Timestamp)Value

# Column Family: info
(user001, info:name, t3)"金英柱"
(user001, info:name, t1)"金英柱(旧)"     # 以前のバージョン
(user001, info:email, t2)"yj@email.com"
(user002, info:name, t4)"朴瑞俊"
(user002, info:email, t4)"sj@email.com"

# Column Family: stats(別のHFile)
(user001, stats:login_count, t5)142
(user001, stats:last_login, t5)"2026-03-08"

主要用語の整理

用語説明RDBMSの対応
TableデータコンテナTable
RowRowKeyで識別される行Row
Column Familyカラムグループ(物理的保存単位)(なし)
Column QualifierCF内のカラム名Column
Cell(Row, CF:Qualifier, Timestamp)の値Cell
Timestampセルのバージョン(デフォルト:ミリ秒)(なし)
Regionテーブルの水平分割単位Partition

4. RowKey設計戦略

RowKey設計はHBaseパフォーマンスの80%を決定します。

ホットスポット問題

# 悪いRowKey:順次キー
# → すべての書き込みが最後のRegionに集中(ホットスポット)

RowKey: 20260308_000001
RowKey: 20260308_000002
RowKey: 20260308_000003
        ↓ すべての書き込みが1つのRegionに集中!

┌──────────┐ ┌──────────┐ ┌──────────┐
Region 1 │ │ Region 2 │ │ Region 3 () () (過負荷!)└──────────┘ └──────────┘ └──────────┘

解決策1:Salting(プレフィックスハッシュ)

// 元のRowKey:"20260308_user001"
// Salt適用:hash("20260308_user001") % NUM_REGIONS + "_" + 元のキー

int numRegions = 10;
String originalKey = "20260308_user001";
int salt = Math.abs(originalKey.hashCode() % numRegions);
String saltedKey = String.format("%02d_%s", salt, originalKey);
// 結果:"07_20260308_user001"

// データがRegionに均等に分配される
// Region 0: 00_xxx, Region 1: 01_xxx, ... Region 9: 09_xxx

メリット:書き込み負荷の均等分配 デメリット:範囲スキャン時にすべてのRegionをスキャンする必要がある

解決策2:Key Reversing(キー反転)

// ドメインベースのRowKeyを反転して分散
// 元:"com.google.www" → 反転:"www.google.com"
// 元:"com.google.mail" → 反転:"liam.elgoog.moc"

// タイムスタンプの反転
long reverseTimestamp = Long.MAX_VALUE - System.currentTimeMillis();
String rowKey = userId + "_" + reverseTimestamp;
// 最新データが先にソートされる(最も一般的なクエリパターン)

解決策3:Hashing

// MD5ハッシュの先頭N文字をprefixとして使用
String hashPrefix = DigestUtils.md5Hex(userId).substring(0, 4);
String rowKey = hashPrefix + "_" + userId + "_" + timestamp;
// 結果:"a3f2_user001_20260308120000"

RowKey設計原則のまとめ

原則説明
短く保つRowKeyはすべてのCellに繰り返し保存されるため、長いと無駄が生じる
読み取りパターンを考慮最も頻繁なクエリに合わせて設計
ホットスポット防止順次/単調増加キーの使用を禁止
バージョン逆順最新データを先に読むならreverse timestamp
複合キー区切り文字_ または \x00 を使用

用途別RowKeyの例

# 時系列データ(IoTセンサー)
salt_deviceId_reverseTimestamp
例:03_sensor042_9223370449055775807

# ユーザー活動ログ
userId_reverseTimestamp_activityType
例:user001_9223370449055775807_login

# Webページクローリング
reversedDomain_path_timestamp
例:moc.elgoog_/search_20260308

# メッセージングシステム
chatRoomId_reverseTimestamp_messageId
例:room001_9223370449055775807_msg12345

5. HBase Shell基本コマンド

テーブル管理

# HBase Shellの起動
hbase shell

# テーブルの作成
create 'user_activity', \
  {NAME => 'info', VERSIONS => 3, COMPRESSION => 'SNAPPY', BLOOMFILTER => 'ROW'}, \
  {NAME => 'stats', VERSIONS => 1, COMPRESSION => 'SNAPPY', TTL => 2592000}

# テーブル一覧
list

# テーブルの詳細情報
describe 'user_activity'

# テーブルの無効化/削除
disable 'user_activity'
drop 'user_activity'

# テーブル構造の変更(無効化が必要)
disable 'user_activity'
alter 'user_activity', {NAME => 'info', VERSIONS => 5}
alter 'user_activity', {NAME => 'logs'}  # 新しいCFの追加
enable 'user_activity'

# Pre-splitテーブルの作成(ホットスポット防止)
create 'events', 'data', SPLITS => ['10', '20', '30', '40', '50', '60', '70', '80', '90']

データCRUD

# Put(挿入/更新)
put 'user_activity', 'user001', 'info:name', '金英柱'
put 'user_activity', 'user001', 'info:email', 'yj@email.com'
put 'user_activity', 'user001', 'stats:login_count', '142'

# Get(単一行の照会)
get 'user_activity', 'user001'
get 'user_activity', 'user001', {COLUMN => 'info:name'}
get 'user_activity', 'user001', {COLUMN => 'info:name', VERSIONS => 3}
get 'user_activity', 'user001', {TIMERANGE => [1709856000000, 1709942400000]}

# Scan(範囲スキャン)
scan 'user_activity'
scan 'user_activity', {LIMIT => 10}
scan 'user_activity', {STARTROW => 'user001', STOPROW => 'user010'}
scan 'user_activity', {COLUMNS => ['info:name', 'stats:login_count']}
scan 'user_activity', {FILTER => "SingleColumnValueFilter('info','name',=,'binary:金英柱')"}

# Delete
delete 'user_activity', 'user001', 'info:email'  # 特定カラムの削除
deleteall 'user_activity', 'user001'               # 行全体の削除

# Count(注意:大量データでは遅い)
count 'user_activity'
count 'user_activity', INTERVAL => 100000

# Truncate
truncate 'user_activity'

管理コマンド

# クラスタの状態
status
status 'detailed'
status 'simple'

# Region管理
list_regions 'user_activity'

# 手動Region Split
split 'user_activity', 'user500'

# Major Compaction(手動実行、ピーク時間を避ける)
major_compact 'user_activity'

# Flush
flush 'user_activity'

# Balancerの状態
balancer_enabled
balance_switch true

6. Java APIコード例

接続設定

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

// Configurationの作成
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", "zk1,zk2,zk3");
config.set("hbase.zookeeper.property.clientPort", "2181");

// Connection(スレッドセーフ、アプリケーションのライフタイムと同じ)
Connection connection = ConnectionFactory.createConnection(config);

// Table(スレッドセーフではない、使用後にclose)
Table table = connection.getTable(TableName.valueOf("user_activity"));

CRUD操作

// Put(書き込み)
Put put = new Put(Bytes.toBytes("user001"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("金英柱"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("email"), Bytes.toBytes("yj@email.com"));
put.addColumn(Bytes.toBytes("stats"), Bytes.toBytes("login_count"), Bytes.toBytes(142));
table.put(put);

// Batch Put(大量書き込み)
List<Put> puts = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    Put p = new Put(Bytes.toBytes(String.format("user%06d", i)));
    p.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"),
                Bytes.toBytes("User " + i));
    puts.add(p);
}
table.put(puts);

// Get(読み取り)
Get get = new Get(Bytes.toBytes("user001"));
get.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name"));
System.out.println("Name: " + Bytes.toString(value));

// Scan(範囲スキャン)
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes("user001"));
scan.withStopRow(Bytes.toBytes("user100"));
scan.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"));
scan.setCaching(500);   // RPCごとの返却行数
scan.setBatch(10);      // 1行から返却するカラム数

try (ResultScanner scanner = table.getScanner(scan)) {
    for (Result r : scanner) {
        String rowKey = Bytes.toString(r.getRow());
        String name = Bytes.toString(r.getValue(Bytes.toBytes("info"), Bytes.toBytes("name")));
        System.out.println(rowKey + ": " + name);
    }
}

// Delete
Delete delete = new Delete(Bytes.toBytes("user001"));
delete.addColumn(Bytes.toBytes("info"), Bytes.toBytes("email"));
table.delete(delete);

// リソースの解放
table.close();
connection.close();

BufferedMutator(大量非同期書き込み)

BufferedMutatorParams params = new BufferedMutatorParams(TableName.valueOf("user_activity"))
    .writeBufferSize(5 * 1024 * 1024); // 5MBバッファ

try (BufferedMutator mutator = connection.getBufferedMutator(params)) {
    for (int i = 0; i < 1000000; i++) {
        Put put = new Put(Bytes.toBytes(String.format("user%08d", i)));
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"),
                      Bytes.toBytes("User " + i));
        mutator.mutate(put);
    }
    mutator.flush(); // バッファに残ったデータを送信
}

7. 読み書きパフォーマンス最適化

書き込み最適化

方法説明効果
WAL無効化put.setDurability(Durability.SKIP_WAL)高速だがデータ損失リスク
バッチPuttable.put(List<Put>)ネットワーク往復の削減
BufferedMutator非同期バッファリング書き込み大量ローディングに最適
Pre-splitテーブル作成時にRegionを事前分割初期ホットスポットの防止
BulkLoadMapReduceでHFileを直接生成超大量データのローディング
圧縮SNAPPY、LZ4を使用I/Oの削減

読み取り最適化

方法説明効果
Block CacheLRU + Bucket Cacheの設定頻繁に読むブロックのキャッシュ
Bloom FilterROWまたはROWCOLレベル不要なHFile読み取りの防止
Scan Cachingscan.setCaching(500)RPC呼び出しの削減
カラム指定必要なカラムのみ照会I/Oの削減
Coprocessorサーバーサイド処理ネットワークトラフィックの削減
Short-circuit ReadローカルDataNodeからの直接読み取りHDFSオーバーヘッドの除去

BulkLoadの例

# 1. CSVをHFileに変換(MapReduce)
hbase org.apache.hadoop.hbase.mapreduce.ImportTsv \
  -Dimporttsv.separator=',' \
  -Dimporttsv.columns='HBASE_ROW_KEY,info:name,info:email,stats:login_count' \
  -Dimporttsv.bulk.output='/tmp/hbase-bulkload' \
  user_activity \
  /input/users.csv

# 2. HFileをHBaseにロード
hbase org.apache.hadoop.hbase.tool.LoadIncrementalHFiles \
  /tmp/hbase-bulkload \
  user_activity

8. Region SplitとCompaction

Region Split

# Regionが一定サイズに達すると自動分割
# デフォルト分割ポリシー:IncreasingToUpperBoundRegionSplitPolicy

# Regionサイズの計算:
# min(r^2 * memstore.flush.size * 2, hbase.hregion.max.filesize)
# r = 同じテーブルの同じRSにあるRegion数

# 手動設定
hbase.hregion.max.filesize = 10737418240  # 10GB

# 分割プロセス:
# 1. 分割ポイント(midpoint)の決定
# 2. 2つの新しいRegion(daughter)の作成
# 3. 既存Regionの無効化
# 4. メタテーブルの更新
# 5. 新しいRegionの有効化
# 6. 既存Regionのクリーンアップ(compaction後)

Compaction

# Minor Compaction
# - 小さなHFileをより大きなHFileに統合
# - 削除マーカー(tombstone)を保持
# - 自動で随時実行

# Major Compaction
# - すべてのHFileを1つのHFileに統合
# - 削除マーカーの除去、期限切れバージョンの除去
# - ディスクI/Oが多い → ピーク時間を避ける
# - デフォルト7日周期で自動実行

# 手動Major Compaction
major_compact 'user_activity'

# Major Compactionの自動実行を無効化
# hbase-site.xml
# hbase.hregion.majorcompaction = 0
# → cronでオフピーク時間に手動実行
<!-- hbase-site.xml Compaction設定 -->
<configuration>
    <!-- Minor Compactionのトリガー閾値 -->
    <property>
        <name>hbase.hstore.compactionThreshold</name>
        <value>3</value>  <!-- HFileが3つ以上でMinor Compaction -->
    </property>

    <!-- Major Compaction周期(0 = 無効化) -->
    <property>
        <name>hbase.hregion.majorcompaction</name>
        <value>604800000</value>  <!-- 7日、0に設定で無効化 -->
    </property>

    <!-- MemStore Flushサイズ -->
    <property>
        <name>hbase.hregion.memstore.flush.size</name>
        <value>134217728</value>  <!-- 128MB -->
    </property>
</configuration>

9. モニタリングと管理

重要モニタリングメトリクス

カテゴリメトリクス警告基準
RegionServerGC Pause Time> 5秒
RegionServerHeap Used %> 80%
RegionServerCompaction Queue Size> 10(持続時)
RegionServerMemStore SizeFlush閾値の90%
RegionRequest Count (R/W)特定Regionに集中
RegionStore File Count> 10(Compaction必要)
HMasterDead RegionServers> 0
HMasterRIT (Regions in Transition)> 0(持続時)

HBase Web UI

# HMaster UI: http://hmaster:16010
# RegionServer UI: http://regionserver:16030

# JMXメトリクスの確認
curl http://regionserver:16030/jmx?qry=Hadoop:service=HBase,name=RegionServer,sub=Server

運用スクリプト

#!/bin/bash
# HBaseクラスタ状態チェックスクリプト

echo "=== HBase Cluster Status ==="
echo "status" | hbase shell 2>/dev/null | grep -E "servers|dead|regions"

echo ""
echo "=== Region Distribution ==="
echo "status 'detailed'" | hbase shell 2>/dev/null | grep -E "regionserver|regions="

echo ""
echo "=== Table Sizes ==="
for table in $(echo "list" | hbase shell 2>/dev/null | grep -v "TABLE\|row(s)"); do
    size=$(hdfs dfs -du -s -h /hbase/data/default/$table 2>/dev/null | awk '{print $1 $2}')
    echo "$table: $size"
done

10. HBase vs Cassandra vs MongoDB

項目HBaseCassandraMongoDB
データモデルColumn-FamilyWide ColumnDocument (JSON)
一貫性強い一貫性チューニング可能(デフォルトAP)チューニング可能
スケーラビリティ水平スケーリング水平スケーリング(優秀)水平スケーリング(Sharding)
クエリ言語Scan/Get APICQL(SQL風)MQL(JSON風)
セカンダリインデックス限定的標準対応豊富なインデックス
JOIN非対応非対応$lookup(限定的)
運用の複雑さ高い(HDFS、ZKが必要)中程度低い
適したワークロード大規模シーケンシャルスキャン + ランダム読み取り高い書き込みスループット柔軟なスキーマ、CRUD
エコシステムHadoop(Hive、Spark)独立型Atlas、Realm
最大データ規模PB級PB級TB〜PB級

11. トラブルシューティング

RegionServer障害

# RegionServerの状態確認
echo "status" | hbase shell

# 特定RegionServerのログ確認
tail -f /var/log/hbase/hbase-hbase-regionserver-hostname.log

# Regionの再割り当て
hbase hbck -reassign <encoded-region-name>

# hbck2(HBase 2.x)
hbase hbck -j /path/to/hbase-hbck2.jar assigns <encoded-region-name>

GCチューニング

# hbase-env.sh
export HBASE_REGIONSERVER_OPTS="
  -Xmx32g -Xms32g
  -XX:+UseG1GC
  -XX:MaxGCPauseMillis=100
  -XX:+ParallelRefProcEnabled
  -XX:G1HeapRegionSize=16m
  -XX:InitiatingHeapOccupancyPercent=65
  -verbose:gc
  -XX:+PrintGCDetails
  -XX:+PrintGCDateStamps
  -Xloggc:/var/log/hbase/gc-regionserver.log
"

ホットスポットRegionの分離

# 特定Regionのリクエスト数を確認
echo "status 'detailed'" | hbase shell | grep -A2 "requestsPerSecond"

# ホットスポットRegionの手動分割
split 'ENCODED_REGION_NAME', 'split_key'

# またはテーブルレベルの分割
split 'user_activity', 'user500000'

RIT(Regions in Transition)の解決

# RIT状態の確認
echo "status 'detailed'" | hbase shell | grep "transition"

# 強制割り当て
hbase hbck -j /path/to/hbase-hbck2.jar assigns <region-encoded-name>

# メタテーブルの修復
hbase hbck -j /path/to/hbase-hbck2.jar fixMeta

12. 運用チェックリスト

初期設計チェックリスト

  • RowKey設計:ホットスポット防止戦略の適用(Salting、Hashing)
  • Column Family数の最小化(2〜3個以下推奨)
  • TTL設定:不要なデータの自動削除
  • VERSIONS設定:必要なバージョン数のみ保持
  • Pre-split:予想されるデータ分布に応じたRegion分割
  • Bloom Filter:ROWまたはROWCOLの設定
  • 圧縮:SNAPPYまたはLZ4の設定
  • Coprocessorの必要性を検討

日常運用チェックリスト

  • RegionServerヒープ使用率のモニタリング
  • Compaction Queueサイズの確認
  • GC Pause時間の確認(5秒以上で警告)
  • Dead RegionServerの確認
  • Regionホットスポットの確認
  • HDFS容量の確認
  • ZooKeeperの状態確認

定期点検チェックリスト

  • オフピーク時間にMajor Compactionを実行
  • hbase hbckを実行してテーブルの整合性を確認
  • Regionバランシング状態の確認
  • テーブルごとのディスク使用量の推移分析
  • GCログの分析とチューニング
  • バックアップ/リカバリのテスト(Snapshot、ExportSnapshot)
  • HBase、Hadoopのセキュリティパッチ確認

まとめ

HBaseは大規模データを低レイテンシで処理する必要があるシナリオにおいて、強力なツールです。ただし、RDBMSとは完全に異なる考え方が必要です。

まとめ:

  1. RowKeyがすべて:ホットスポット防止と読み取りパターンに合わせた設計が核心
  2. Column Familyは少なく:物理的保存単位であるため、2〜3個以下を維持
  3. Compaction管理:Major Compactionはオフピーク時間に手動実行
  4. モニタリング必須:GC Pause、Region分布、Compaction Queueに集中監視
  5. 読み書きパターンの分離:大量書き込みにはBulkLoad、リアルタイム読み取りにはBlock Cache + Bloom Filterを活用