表有 1000 萬筆資料,RowFilter(CompareOp.EQUAL, BinaryComparator(rowkey)) 查詢極慢,但 SingleColumnValueFilter 在有二級索引的欄位上很快。同樣指定 RowKey,為何 Filter 慢?
RowFilter 屬於 Server 端過濾器,仍需掃描 Region 內的所有 RowKey 進行比對,不會利用 RowKey 的排序特性直接定位。而 Get(rowkey) 直接透過 Region 定位機制(MemStore + HFile 索引 + Bloom Filter)在 O(1) 內定位到唯一儲存位置。
| 操作 | 執行路徑 | 時間複雜度 |
|---|---|---|
table.get(Get) | Client → RegionServer 定位 Region → MemStore/HFile 索引 → 直接讀取 | O(log N) 實際毫秒級 |
scan.setFilter(RowFilter) | Client → RegionServer → 掃描 Region 範圍內**所有** KV → 逐一比對 RowKey → 符合才回傳 | O(N) 全表掃描 |
scan.setFilter(SingleColumnValueFilter) | 若欄位有二級索引(如 Apache Phoenix、或自建倒排),可走索引查找 | 視索引而定 |
Get get = new Get(Bytes.toBytes("name_id"));
Result result = table.get(get); // 毫秒級返回
// 處理 result
PrefixFilter(可利用 RowKey 排序,範圍掃描)RowFilter + RegexStringComparator / InclusiveStopFilterSingleColumnValueFilter、FamilyFilter 等userId_eventTime),盡量用 Get 或 PrefixFilter。Scan 至少加 setStartRow/setStopRow 限制範圍。BLOOMFILTER = ROW 或 ROWCOL,加速不存在 RowKey 的判斷。BlockCache,減少磁碟 IO。// ❌ 錯誤:已知 RowKey 卻用 RowFilter
Scan scan = new Scan();
scan.setFilter(new RowFilter(CompareFilter.CompareOp.EQUAL,
new BinaryComparator(Bytes.toBytes("name_id"))));
ResultScanner scanner = table.getScanner(scan); // 全表掃描,極慢
// ✅ 正確:直接 Get
Get get = new Get(Bytes.toBytes("name_id"));
Result result = table.get(get); // 瞬間返回
// ✅ 正確:RowKey 前綴查詢
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("prefix_"));
scan.setStopRow(Bytes.toBytes("prefix_~")); // ~ 為 ASCII 最大可列印字元
ResultScanner scanner = table.getScanner(scan); // 範圍掃描,利用 RowKey 排序
已知完整 RowKey 絕對不要用 RowFilter,直接呼叫 table.get(Get) 才是 HBase 設計意圖。RowFilter 適用於「不知道完整 RowKey,只能用模式比對」的場景,但會觸發全 Region 掃描,效能隨資料量線性增長。生產環境 1000 萬行用 RowFilter 可能耗時秒至分鐘,Get 僅需毫秒。