Phoenix 二級索引

Colar_發表於2020-11-11

Phoenix 二級索引


HBase中只能通過行鍵(row key)進行索引查詢資料,否則都可能會掃描全表。

Phoenix二級索引可以將索引列或表示式生成備用的行鍵。

Phoenix官網:http://phoenix.apache.org/secondary_indexing.html

索引型別

覆蓋索引(Covered Indexes)

覆蓋索引是將資料打包進索引行中,查詢時無需查詢原表。

SQL示例

CREATE INDEX my_index ON my_table (v1,v2) INCLUDE(v3)

執行計劃

EXPLAIN SELECT v3 FROM my_table WHERE v1 = '' AND v2 = '';
CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER MY_INDEX
    SERVER FILTER BY ( AND )

這裡只查詢了索引表,因為v3列作為v1、v2的附加資料已經包含在索引中。

HBase索引表資料

hbase(main):001:0> scan 'MY_INDEX'
ROW                                                          COLUMN+CELL
 v1\x00v2                                                    column=0:\x00\x00\x00\x00, timestamp=1604912241250, value=x
 v1\x00v2                                                    column=0:\x80\x0B, timestamp=1604912241250, value=v3

可以看出,v3列作為附加列已經寫入了索引表中。

函式索引(Functional Indexes)(僅支援4.3及以上版本)

Phoenix不僅可以將列作為二級索引,而且可以使用函式表示式建立二級索引。

SQL示例

-- 用函式表示式作為索引列建立索引
CREATE INDEX UPPER_NAME_IDX ON EMP (UPPER(FIRST_NAME||' '||LAST_NAME))

執行計劃

EXPLAIN SELECT EMP_ID FROM EMP WHERE UPPER(FIRST_NAME||' '||LAST_NAME)='JOHN DOE'
CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER UPPER_NAME_IDX ['JOHN DOE']
    SERVER FILTER BY FIRST KEY ONLY

這裡將函式整體視作一列作為索引,查詢時可以直接查詢索引表。

HBase中索引表資料

hbase(main):006:0> scan 'UPPER_NAME_IDX'
ROW                                                          COLUMN+CELL
 KOBE BYRANT\x001                                            column=0:\x00\x00\x00\x00, timestamp=1604912053676, value=\x00\x00\x00\x00

可以看出,函式索引就是將函式的結果作為row key寫入了HBase中的索引表。

全域性索引和本地索引

全域性索引(Global Indexes)

全域性索引會擷取更新表(DELETE和UPSERT)操作,然後對HBase中的索引表進行更新;查詢時,選擇要使用的索引表進行查詢。特點是查詢快,寫入慢。

預設情況下,使用索引之外的列作為查詢列(SELECT後的列)或查詢條件(WHERE中的列),將不會使用該索引。

SQL示例

-- 預設情況下使用的就是全域性索引
CREATE INDEX my_index ON my_table (v2, v3);

HBase中索引表資料

hbase(main):004:0> scan 'MY_INDEX'
ROW                                                          COLUMN+CELL
 v2\x00v3\x00v1                                              column=0:\x00\x00\x00\x00, timestamp=1604911907530, value=\x00\x00\x00\x00
  • 全域性索引就是將所有索引列和原資料row key拼接在一起作為新的row key,寫入HBase中的索引表;
  • 對於全域性覆蓋索引,則會新增一列存放INCLUDE中的列,因此寫入比較慢,但是查詢可以直接從索引表中獲取資料,因此查詢快。

本地索引(Local Indexes)

本地索引將索引資料存放在和表資料相同的主機上,從而防止寫入期間的網路開銷。特點是寫入快,查詢慢。

即使查詢列未完全涵蓋,也可以使用本地索引。

SQL示例

建立表
-- 為了方便說明,使用加鹽分割槽分為3個region
CREATE TABLE my_table (v1 VARCHAR NOT NULL PRIMARY KEY, v2 VARCHAR, v3 VARCHAR) SALT_BUCKETS = 3;
建立索引
-- 使用LOCAL關鍵字建立本地索引
CREATE LOCAL INDEX my_index ON my_table (v1, v2);

HBase索引表資料

hbase(main):011:0> scan 'MY_TABLE'
ROW                                                          COLUMN+CELL
 \x00\x00\x00v1_1\x00v2_1                                    column=L#0:\x00\x00\x00\x00, timestamp=1604913262299, value=\x00\x00\x00\x00
 \x00v1_1                                                    column=0:\x00\x00\x00\x00, timestamp=1604913262299, value=x
 \x00v1_1                                                    column=0:\x80\x0B, timestamp=1604913262299, value=v2_1
 \x00v1_1                                                    column=0:\x80\x0C, timestamp=1604913262299, value=v3_1
 \x01\x00\x00v1_2\x00v2_2                                    column=L#0:\x00\x00\x00\x00, timestamp=1604913265267, value=\x00\x00\x00\x00
 \x01v1_2                                                    column=0:\x00\x00\x00\x00, timestamp=1604913265267, value=x
 \x01v1_2                                                    column=0:\x80\x0B, timestamp=1604913265267, value=v2_2
 \x01v1_2                                                    column=0:\x80\x0C, timestamp=1604913265267, value=v3_2
 \x02\x00\x00v1_3\x00v2_3                                    column=L#0:\x00\x00\x00\x00, timestamp=1604913268957, value=\x00\x00\x00\x00
 \x02v1_3                                                    column=0:\x00\x00\x00\x00, timestamp=1604913268957, value=x
 \x02v1_3                                                    column=0:\x80\x0B, timestamp=1604913268957, value=v2_3
 \x02v1_3                                                    column=0:\x80\x0C, timestamp=1604913268957, value=v3_3
  • 在版本4.8.0之後,本地索引不會單獨在HBase中建立索引表,而是直接將索引資料寫入到原表中;

  • 本地索引行的row key為:\x00\x00v1\x00v2,含義為:{原資料所在region的start key}\x00{第一個索引列}\x00{第二個索引列}...。以原資料所在region的start key開頭是為了將索引資料與原資料分在同一個分割槽。這是為了寫入索引時,將索引直接寫入到與原資料相同的分割槽,減少網路開銷,因此寫入快;

  • 查詢時,首先會在不同region中定位二級索引獲取原資料的row key,然後從本地region中獲取原資料。但是定位二級索引時,必須檢查每個region的資料,因此讀取慢;

  • 對於本地覆蓋索引,不會再單獨存入INCLUDE中的列,而是直接根據從二級索引中獲取的原資料row key從原表中查詢,因此相對全域性覆蓋索引寫入快,查詢慢。

構建索引引數

和建立表一樣,建立索引也可以使用一些引數,如下:

CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
    SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE'

注意:

  • 本地索引不能加鹽分割槽;
  • 如果資料表使用了加鹽分割槽,全域性索引會自動以資料表相同的方式加鹽。

非同步構建索引

Phoenix在建立索引時,會同步將索引資料寫入索引表,但是表如果太大,則可能會耗時過長導致失敗。在Phoenix 4.5之後,支援非同步索引構建。

構建步驟

  1. 執行構建SQL,此時並未將資料真正寫入索引表,索引表狀態為BUILDING:

    `CREATE INDEX async_index ON my_schema.my_table (v) ASYNC`
    
  2. 執行MapReduce,真正將資料寫入索引表,完成後索引表狀態為ACTIVE:

    ${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
      --schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX
      --output-path ASYNC_IDX_HFILES
    

批量構建

可以使用如下命令對所有狀態為BUILDING的索引進行填充:

${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.automation.PhoenixMRJobSubmitter

相關配置

配置項配置說明
phoenix.index.async.threshold允許建立同步索引的索引表大小閾值,單位為bytes,超出該閾值則不能同步建立索引。

強制使用索引

假如有一個表建表語句為:CREATE TABLE t1 (v1 VARCHAR NOT NULL PRIMARY KEY, v2 VARCHAR, v3 VARHCAR);

現在建立了索引:CREATE INDEX i1 ON t1 (v2)

如果此時使用該查詢,並不會使用索引i1:SELECT v2, v3 FROM t1 WHERE v2 = 'foo'

這種情況下,有三種方法可以強制其使用索引:

  • 覆蓋索引:建立索引時,使用CREATE INDEX i2 ON t1 (v2) INCLUDE (v3)進行建立,將資料v3列寫入索引表中。

  • 強制使用索引:查詢時候,使用SELECT /*+ INDEX(t1 i1) */ v2, v3 FROM t1 WHERE v2 = 'foo'強制該查詢使用索引i1,該查詢的流程為:

    1. 查詢索引表i1,獲取全部v2值為“foo”的行的row key;
    2. 查詢資料表t1,獲取上一步row key的所有行。

    如果表中v2值為“foo”的行比較少,那麼會提升查詢效率。但是如果比較多,效率可能還不如不使用索引,因為需要查詢兩個表。

  • 本地索引:建立索引時,使用CREATE LOCAL INDEX i3 ON t1 (v2)進行建立,但是查詢效率會略有降低,詳情檢視[本地索引](#‘本地索引(Local Indexes)’)說明。

一致性保證

事務表

如果資料表為事務表,可以在表和索引之間提供最高階別的一致性保證。

缺點:

  • 如果是不可變表,事務開銷很小,那麼使用事務表是比較適用的。但是如果是可變表,則需要接受事務和衝突檢測的開銷;
  • 具有二級索引的事務表可能會降低寫入效能,因為只有索引表和資料表都可用時才能寫入資料。

不可變表

IMMUTABLE_ROWS=true可以將表宣告為不可變表(只能追加不能修改),注意,宣告後該資料表依舊是可變的,只有該表的索引表是隻能追加不能修改。全域性不可變索引在客戶端維護,本地不可變索引表在服務端維護。

-- 建立不可變表
CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS = true

-- 更新表為可變表
ALTER TABLE my_table SET IMMUTABLE_ROWS = false

注意:如果對資料表進行了修改(也就是使用UPSERT對某行進行了修改而不是追加),那麼索引表中會追加一行而不會對原索引進行修改,也就是說該索引表沒有完全進行同步。

一致性保證:如果不可變索引提交失敗了,由於更新操作是冪等的,所以索引表會一直重試直到追加成功。

可變表

對於非事務性的可變表,資料表更新某一行時:

  1. 協處理器攔截對HRegion的寫入操作;
  2. 將索引附加到資料表的WAL中,如果出現故障則返回給客戶端,並且不會保留資料且資料對客戶端不可見;
  3. 更新資料表和索引,索引更新是並行的,HBase會保證資料和索引表寫入操作的原子性。預設先寫資料表再寫索引表,如果禁用了WAL則相反。

資料寫入WAL前,如果出現故障則返回客戶端,且資料對客戶端不可見;

資料寫入WAL後,即使出現故障,資料表和索引表資料也對客戶端可見,故障情況如下:

  • 如果服務端崩潰,則從WAL中進行恢復,並利用更新操作的冪等性確保成功更新索引;
  • 如果服務端沒有崩潰,則將索引更新插入到各自的索引表中:
    • 如果Phoenix的SYSTEM.CATALOG表無法訪問,則強制終止服務端(終止失敗則呼叫JVM的System.exit()進行終止),再重新從WAL中進行恢復,這樣可以確保索引處於已知無效狀態時不會繼續使用;
    • 如果插入索引表失敗,可以採用以下容錯機制保障資料一致性。

容錯機制

禁止表寫入

禁止表寫入是可變索引一致性最高的策略。在發生故障時,Phoenix將記下時間戳,並且不允許對資料表的寫入操作,直到索引一致。配置項如下:

配置說明
phoenix.index.failure.block.write需設定為true,索引插入失敗時,禁止寫入操作
phoenix.index.failure.handling.rebuild需設定為true(預設值),索引插入失敗時,後臺自動重建可變索引
禁用可變索引

禁用可變索引是預設策略。發生故障時,將索引標記為禁用並在後臺進行重建,此時查詢不能使用該索引,直到重建完成。配置項如下:

配置說明
phoenix.index.failure.handling.rebuild需設定為true(預設值),索引插入失敗時,後臺自動重建可變索引
phoenix.index.failure.handling.rebuild.interval預設值為10000(10秒),服務端檢測是否需要重建索引的頻率
phoenix.index.failure.handling.rebuild.overlap.time預設值為1,重建時發生故障的時間戳返回的毫秒數
禁用可變索引並手動重建

禁用可變索引並手動重建是可變索引一致性最低的策略。發生故障時,將索引標記為禁用,需要手動進行重建才能使用該索引。配置項如下:

配置說明
phoenix.index.failure.handling.rebuild需設定為true(預設值),索引插入失敗時,後臺自動重建可變索引

批量匯入工具限制

  • 使用BulkLoadTools(CSVBulkLoadTool和JSONBulkLoadTool)匯入資料不能正確更新索引;
  • 如果使用BulkLoadTools匯入資料,必須在匯入完成後刪除並重新建立所有索引表;
  • 可以使用psql.py工具代替BulkLoadTools,可以完成索引正確更新;
  • 可以編寫MapReduce任務解析資料直接寫入Phoenix,可以完成索引正確更新。

相關配置

配置支援二級索引

在每個HBase節點上修改hbase-site.xml:

<!--Phoenix 4.12以上版本使用如下配置支援可變表二級索引-->
<property>
  <name>hbase.regionserver.wal.codec</name>
  <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>

升級Phoenix 4.8之前建立的索引

Phoenix升級到4.8及以上版本後,需要刪除hbase-site.xml中關於Phoenix 4.7之前的索引配置(如果有的話):

<property>
  <name>hbase.master.loadbalancer.class</name>
  <value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property>
  <name>hbase.coprocessor.master.classes</name>
  <value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>
<property>
  <name>hbase.coprocessor.regionserver.classes</name>
  <value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>

配置客戶端:

​ phoenix.client.localIndexUpgrade:預設為true,表示線上升級,false為離線升級。

離線升級命令:psql [zookeeper地址] -l

索引優化配置

配置項預設值說明備註
index.builder.threads.max10構建索引更新的執行緒數值越大,訪問HRegion效率越高,但是值太大HRegion會出現瓶頸。
index.builder.threads.keepalivetime60構建索引更新執行緒過期時間(秒)在該值時間後,未使用的執行緒會立即釋放,但是會保留核心執行緒
index.writer.threads.max10寫入索引表的執行緒數在每個表的基礎上,應大致對應索引表的數量
index.writer.threads.keepalivetime60寫入索引表執行緒過期時間(秒)在該值時間後,未使用的執行緒會立即釋放,但是會保留核心執行緒
hbase.htable.threads.max2147483647寫入每個索引HTable的執行緒數值越大則支援更多的併發索引更新,增大吞吐量
hbase.htable.threads.keepalivetime60寫入每個索引HTable執行緒超時時間
index.tablefactory.cache.size10快取中索引HTable數量快取HTable可以不用每次都建立HTable,但是會增大記憶體壓力
org.apache.phoenix.regionserver.index.priority.min1000索引優先順序最低值
org.apache.phoenix.regionserver.index.priority.max1050索引優先順序最高值索引優先順序並不代表索引會被更快地處理
org.apache.phoenix.regionserver.index.handler.count30全域性索引的寫入請求執行緒數

索引工具

索引檢查工具(Phoenix 4.12以上版本)

Phoenix提供了IndexScrutinyTool工具驗證索引表對其資料表是否有效(一致),將找到所有無效行寫入檔案或輸出到表PHOENIX_INDEX_SCRUTINY中。

IndexScrutinyTool提供一些計數器:

  • VALID_ROW_COUNT
  • INVALID_ROW_COUNT
  • BAD_COVERED_COL_VAL_COUNT

計數器和其他該任務的後設資料會寫入表PHOENIX_INDEX_SCRUTINY_METADATA中。

使用方法如下:

# HBase命令執行
hbase org.apache.phoenix.mapreduce.index.IndexScrutinyTool -dt my_table -it my_index -o

# Hadoop命令執行
HADOOP_CLASSPATH=$(hbase mapredcp) hadoop jar phoenix-<version>-server.jar org.apache.phoenix.mapreduce.index.IndexScrutinyTool -dt my_table -it my_index -o

引數說明:

引數說明
-dt,–data-table資料表表名(必填項)
-it,–index-table索引表表名(必填項)
-s,–schema資料庫名稱
-src,–source可選值為DATA_TABLE_SOURCE、INDEX_TABLE_SOURCE,或者兩個都選,預設為都選
-o,–output是否輸出無效行,預設關閉
-of,–output-format輸出到表還是檔案,預設為表
-om,–output-max每個mapper輸出的最大無效行數量,預設為1M
-op,–output-path檔案輸出的HDFS路徑
-t,–time檢查時間,預設為當前時間-60秒
-b,–batch-size一次比較的行數

相關文章