Apache Phoenix 效能最佳做法

Apache Phoenix 效能最重要的層面是優化基礎 Apache HBase。 Phoenix 會在 HBase 上建立關係型數據模型,將 SQL 查詢轉換成 HBase 作業,例如掃描。 數據表架構的設計、主鍵中字段的選取和排序,以及索引的使用都會影響 Phoenix 效能。

數據表架構設計

當您在 Phoenix 中建立資料表時,該數據表會儲存在 HBase 數據表中。 HBase 資料表包含一起存取的數據行群組(數據行系列)。 Phoenix 數據表中的數據列是 HBase 數據表中的數據列,其中每個數據列都包含與一或多個數據行相關聯的版本化單元格。 邏輯上,單一 HBase 數據列是索引鍵/值組的集合,每個數據列都有相同的數據列索引鍵值。 也就是說,每個索引鍵/值組都有一個 rowkey 屬性,而該 rowkey 屬性的值針對特定數據列則相同。

Phoenix 數據表的架構設計包括主鍵設計、數據行系列設計、個別數據行設計,以及數據分割的方式。

主鍵設計

Phoenix 中數據表上定義的主鍵會決定如何在基礎 HBase 數據表的數據列機碼中儲存數據。 在 HBase 中,存取特定數據列的唯一方式是使用 rowkey。 此外,儲存在 HBase 數據表中的數據會依 rowkey 排序。 Phoenix 會藉由串連數據列中每個數據行的值,依主鍵中定義的順序來建置數據列索引鍵值。

例如,聯繫人的數據表具有同一個數據行系列中的名字、姓氏、電話號碼和位址。 您可以根據遞增的序號來定義主鍵:

rowkey address 電話 firstName lastName
1000 1111 聖加布里埃爾博士 1-425-000-0002 John 多爾
8396 5415 聖加布里埃爾博士 1-230-555-0191 卡爾文 拉吉

不過,如果您經常依 lastName 查詢,這個主鍵可能無法正常執行,因為每個查詢都需要完整表掃描才能讀取每個 lastName 的值。 相反地,您可以在 lastName、firstName 和 social security number 數據行上定義主鍵。 最後一欄是用同名同名來釐清兩個居民,例如父親和兒子。

rowkey address 電話 firstName lastName socialSecurityNum
1000 1111 聖加布里埃爾博士 1-425-000-0002 John 多爾 111
8396 5415 聖加布里埃爾博士 1-230-555-0191 卡爾文 拉吉 222

有了這個新的主鍵,Phoenix 所產生的數據列索引鍵會是:

rowkey address 電話 firstName lastName socialSecurityNum
Dole-John-111 1111 聖加布里埃爾博士 1-425-000-0002 John 多爾 111
Raji-Calvin-222 5415 聖加布里埃爾博士 1-230-555-0191 卡爾文 拉吉 222

在給定數據表的第一個數據列中,數據列索引鍵的數據會以如下所示表示:

rowkey 索引鍵 value
Dole-John-111 address 1111 聖加布里埃爾博士
Dole-John-111 電話 1-425-000-0002
Dole-John-111 firstName John
Dole-John-111 lastName 多爾
Dole-John-111 socialSecurityNum 111

此數據列機碼現在會儲存重複的數據復本。 請考慮主鍵中包含的數據行大小和數目,因為此值會包含在基礎 HBase 數據表中的每個儲存格中。

此外,如果主鍵具有單調增加的值,您應該使用 Salt 貯 體建立數據表,以協助避免建立寫入熱點 - 請參閱 數據分割數據

數據行系列設計

如果某些數據行的存取頻率高於其他數據行,您應該建立多個數據行系列,以分隔經常存取的數據行與很少存取的數據行。

此外,如果某些數據行傾向於一起存取,請將這些數據行放在相同的數據行系列中。

數據行設計

  • 由於大型數據行的 I/O 成本,將 VARCHAR 數據行保持在大約 1 MB 以內。 處理查詢時,HBase 會將儲存格完整具體化,再將其傳送至用戶端,而用戶端會在將數據格交給應用程式程式碼之前完整接收它們。
  • 使用精簡格式儲存數據行值,例如 protobuf、Avro、msgpack 或 BSON。 不建議使用 JSON,因為它更大。
  • 請考慮在記憶體之前壓縮數據,以減少延遲和 I/O 成本。

數據分割數據

Phoenix 可讓您控制數據分散的區域數目,這可大幅提升讀取/寫入效能。 建立 Phoenix 數據表時,您可以鹽或預先分割數據。

若要在建立期間為數據表加鹽,請指定鹽桶的數目:

CREATE TABLE CONTACTS (...) SALT_BUCKETS = 16

此 Salting 會將數據表與主鍵的值分割,並自動選擇這些值。

若要控制數據表分割發生的位置,您可以藉由提供分割發生的範圍值,預先分割數據表。 例如,若要建立沿著三個區域的數據表分割:

CREATE TABLE CONTACTS (...) SPLIT ON ('CS','EU','NA')

索引設計

Phoenix 索引是 HBase 數據表,可儲存索引數據表中部分或所有數據的複本。 索引可改善特定查詢類型的效能。

當您已定義多個索引,然後查詢數據表時,Phoenix 會自動選取查詢的最佳索引。 主要索引會根據您選取的主鍵自動建立。

針對預期的查詢,您也可以藉由指定其數據行來建立次要索引。

設計索引時:

  • 只建立您需要的索引。
  • 限制經常更新數據表上的索引數目。 更新 數據表會轉譯成主數據表和索引數據表的寫入。

建立次要索引

次要索引可以藉由將完整數據表掃描轉換成點查閱,以節省儲存空間和寫入速度來改善讀取效能。 次要索引可以在建立數據表之後新增或移除,而且不需要變更現有的查詢 – 查詢的執行速度會更快。 根據您的需求,請考慮建立涵蓋的索引、功能索引或兩者。

使用涵蓋的索引

涵蓋的索引是索引,除了已編製索引的值之外,還包含數據列的數據。 尋找所需的索引項目之後,就不需要存取主數據表。

例如,在範例聯繫人數據表中,您可以只針對 socialSecurityNum 數據行建立次要索引。 此次要索引會加速依 socialSecurityNum 值篩選的查詢,但擷取其他域值需要針對主數據表進行另一個讀取。

rowkey address 電話 firstName lastName socialSecurityNum
Dole-John-111 1111 聖加布里埃爾博士 1-425-000-0002 John 多爾 111
Raji-Calvin-222 5415 聖加布里埃爾博士 1-230-555-0191 卡爾文 拉吉 222

不過,如果您通常想要在 socialSecurityNum 指定的情況下查閱 firstName 和 lastName,您可以建立涵蓋的索引,其中包含 firstName 和 lastName 做為索引數據表中的實際數據:

CREATE INDEX ssn_idx ON CONTACTS (socialSecurityNum) INCLUDE(firstName, lastName);

這個涵蓋的索引可讓下列查詢只藉由從包含次要索引的數據表讀取來取得所有資料:

SELECT socialSecurityNum, firstName, lastName FROM CONTACTS WHERE socialSecurityNum > 100;

使用功能索引

功能索引可讓您在預期用於查詢的任意運算式上建立索引。 一旦您有功能索引就地,且查詢使用該表達式,就可以使用索引來擷取結果,而不是數據表。

例如,您可以建立索引,讓您在合併的名字和姓氏上執行不區分大小寫的搜尋:

CREATE INDEX FULLNAME_UPPER_IDX ON "Contacts" (UPPER("firstName"||' '||"lastName"));

查詢設計

查詢設計的主要考慮包括:

  • 瞭解查詢計劃並確認其預期行為。
  • 有效率地加入。

了解查詢計劃

在 SQLLine,使用 EXPLAIN 後面接著您的 SQL 查詢來檢視 Phoenix 執行的作業計畫。 檢查計劃:

  • 適當時,使用您的主鍵。
  • 使用適當的次要索引,而不是數據表。
  • 盡可能使用 RANGE SCAN 或 SKIP SCAN,而不是 TABLE SCAN。

計劃範例

例如,假設您有一個名為 FLIGHTS 的數據表,可儲存航班延誤資訊。

若要選取具有 airlineid19805的所有正式發行前小眾測試版,其中 airlineid 是不在主鍵或任何索引中的欄位:

select * from "FLIGHTS" where airlineid = '19805';

執行說明的命令,如下所示:

explain select * from "FLIGHTS" where airlineid = '19805';

查詢計劃如下所示:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER FLIGHTS
   SERVER FILTER BY AIRLINEID = '19805'

在此方案中,請記下完整掃描 OVER FLIGHTS 一詞。 此片語表示執行會在資料表中的所有數據列上執行 TABLE SCAN,而不是使用更有效率的 RANGE SCAN 或 SKIP SCAN 選項。

現在,假設您想要在 2014 年 1 月 2 日查詢其航班數量大於 1 的航空公司 AA 。 假設範例數據表中有年、月、日、日、航母和 flightnum 數據行,而且都是複合主鍵的一部分。 查詢如下所示:

select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;

讓我們使用下列項目來檢查此查詢的計劃:

explain select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;

產生的計劃如下:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER FLIGHTS [2014,1,2,'AA',2] - [2014,1,2,'AA',*]

方括弧中的值是主鍵的值範圍。 在此情況下,範圍值是固定年份為 2014 年、月 1 日和 dayofmonth 2,但允許從 2 和向上開始的 flightnum 值。* 此查詢計劃會確認主鍵如預期般使用。

接下來,在只位於 carrier2_idx 貨運公司字段的 FLIGHTS 數據表上建立索引。 此索引也包含 flightdatetailnumoriginflightnum 作為涵蓋的數據行,其數據也會儲存在索引中。

CREATE INDEX carrier2_idx ON FLIGHTS (carrier) INCLUDE(FLIGHTDATE,TAILNUM,ORIGIN,FLIGHTNUM);

假設您想要搭配 flightdatetailnum取得貨運公司,如下列查詢所示:

explain select carrier,flightdate,tailnum from "FLIGHTS" where carrier = 'AA';

您應該會看到使用此索引:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER CARRIER2_IDX ['AA']

如需可在說明計劃結果中顯示的專案完整清單,請參閱Apache Phoenix微調指南中的說明計劃一節。

有效率地加入

一般而言,除非一端很小,特別是在頻繁的查詢上,否則您想要避免聯結。

如有必要,您可以使用提示執行大型聯 /*+ USE_SORT_MERGE_JOIN */ 結,但大型聯結是大量數據列的昂貴作業。 如果所有右側數據表的整體大小會超過可用的記憶體,請使用 /*+ NO_STAR_JOIN */ 提示。

案例

下列指導方針說明一些常見的模式。

大量讀取工作負載

針對大量讀取使用案例,請確定您使用的是索引。 此外,若要節省讀取時間的額外負荷,請考慮建立涵蓋的索引。

大量寫入工作負載

對於主鍵單調增加的大量寫入工作負載,請建立 Salt 貯體,以協助避免寫入熱點,而犧牲整體讀取輸送量,因為需要額外的掃描。 此外,使用 UPSERT 寫入大量記錄時,請關閉 autoCommit 並批處理記錄。

大量刪除

刪除大型數據集時,請在發出 DELETE 查詢之前開啟 autoCommit,讓用戶端不需要記住所有已刪除資料列索引鍵。 AutoCommit 可防止用戶端緩衝處理受 DELETE 影響的數據列,讓 Phoenix 可以直接在區域伺服器上刪除它們,而不需要將它們傳回給客戶端的費用。

不可變和僅限附加

如果您的案例偏好寫入速度超過數據完整性,請考慮在建立數據表時停用預先寫入記錄:

CREATE TABLE CONTACTS (...) DISABLE_WAL=true;

如需此選項和其他選項的詳細資訊,請參閱 Apache Phoenix 文法

下一步