下面的章節介紹影響 Impala 功能性能的各類因素,並對 Impala 查詢和其餘 SQL 操做進行性能調整、監控和基準測試。 html
這一章節一樣描述了最大化 Impala 可擴展性的技術。可擴展性與性能相關:它意味着當系統負載增長時仍保持高性能(Scalability is tied to performance: it means that performance remains high as the system workload increases)。例如,減小查詢的硬盤 I/O 能夠加快單個的查詢,與此同時,致使能夠同時運行更多查詢,從而提高了可擴展性。有時候,一種優化技術提高了性能的同時更增長了可擴展性。例如,減小查詢的內存使用可能不會很大的提升查詢性能,可是經過容許同時運行更多的 Impala 查詢或其餘類型的做業而不會耗盡內存,從而提高了可擴展性。 java
在開始任何性能調整和基準測試以前,請確保你的系統已經按照 Post-Installation Configuration for Impala 中的設置進行配置。 node
表的全部數據文件默認放在一個目錄下。分區是一項基於一個或多個上的值,在載入時物理拆分數據的技術。例如,對於根據 year 列分區的 school_records 表來講,對於每個不一樣的年份都有一個單獨的數據目錄,而且這一年的全部數據都存放在這個目錄下的數據文件中。一個包含 WHERE 條件如 YEAR=1966, YEAR IN (1989,1999), YEAR BETWEEN 1984 AND 1989 的查詢,能夠只從對應的一個或多個目錄下檢索數據文件,極大的減小了讀取和測試的數據的數量。 web
分區一般對應: sql
在 Impala SQL 語法中,分區會影響到這些語句: shell
參見 Attaching an External Partitioned Table to an HDFS Directory Structure 中的例子,演示了建立分區表的語法,HDFS 中底層的目錄結構,以及如何鏈接到 Impala 外部分區表中存出來 HDFS 其餘位置的數據文件(how to attach a partitioned Impala external table to data files stored elsewhere in HDFS) 數據庫
參見 Partitioning for Parquet Tables 瞭解 Parquet 分區表的性能注意事項。 apache
參見 NULL 瞭解分區表中 NULL 值如何對應。 瀏覽器
分區修剪(Partition pruning)指的是一種查詢能夠跳過一個或多個分區對應的數據文件不進行讀取的技術。假如你能安排你的查詢從查詢計劃中剪除大量的沒必要要的分區,查詢使用更少的資源,所以與剪除的沒必要要的分區成比例的變快,而且更可擴展(If you can arrange for queries to prune large numbers of unnecessary partitions from the query execution plan, the queries use fewer resources and are thus proportionally faster and more scalable)。 緩存
例如,若是一個表使用 YEAR, MONTH, DAY 分區,這樣如 WHERE year = 2013, WHERE year < 2010, WHERE year BETWEEN 1995 AND 1998 等 WHERE 子句容許 Impala 除了指定範圍的分區外,跳過全部其餘分區的數據文件。一樣的,WHERE year = 2013 AND month BETWEEN 1 AND 3 甚至能夠剪除更多的分區,只讀取一年中的一部分數據文件。
在執行查詢以前,經過檢查 EXPLAIN 查詢的輸出來檢查查詢分區修剪的效果。例如,下面例子中的表有 3 個分區,而查詢只讀取其中 1 個。EXOLAIN 計劃中的標識符 #partitions=1/3 證實 Impala 能夠進行對應的分區修剪。
[localhost:21000] > insert into census partition (year=2010) values ('Smith'),('Jones'); [localhost:21000] > insert into census partition (year=2011) values ('Smith'),('Jones'),('Doe'); [localhost:21000] > insert into census partition (year=2012) values ('Smith'),('Doe'); [localhost:21000] > select name from census where year=2010; +-------+ | name | +-------+ | Smith | | Jones | +-------+ [localhost:21000] > explain select name from census where year=2010; +------------------------------------------------------------------+ | Explain String | +------------------------------------------------------------------+ | PLAN FRAGMENT 0 | | PARTITION: UNPARTITIONED | | | | 1:EXCHANGE | | | | PLAN FRAGMENT 1 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 1 | | UNPARTITIONED | | | | 0:SCAN HDFS | | table=predicate_propagation.census #partitions=1/3 size=12B | +------------------------------------------------------------------+
經過WHERE 子句中其餘部分的中間屬性,甚至在分區鍵列沒有明確指定常量值的時候,Impala 均可以進行分區修剪(Impala can even do partition pruning in cases where the partition key column is not directly compared to a constant, by applying the transitive property to other parts of the WHERE clause)。這一技術稱爲謂詞傳播(predicate propagation),自 Impala 1.2.2 開始可用。在下面例子裏,表 census 中包含另外一個列存放數據收集的時間(是 10 年採集的)。即便分區鍵列 (YEAR) 沒有對應一個常量, Impala 也能夠推斷只有 YEAR=2010 分區是必需的,並再次只讀取了總分區中 1/3 個分區。
[localhost:21000] > drop table census; [localhost:21000] > create table census (name string, census_year int) partitioned by (year int); [localhost:21000] > insert into census partition (year=2010) values ('Smith',2010),('Jones',2010); [localhost:21000] > insert into census partition (year=2011) values ('Smith',2020),('Jones',2020),('Doe',2020); [localhost:21000] > insert into census partition (year=2012) values ('Smith',2020),('Doe',2020); [localhost:21000] > select name from census where year = census_year and census_year=2010; +-------+ | name | +-------+ | Smith | | Jones | +-------+ [localhost:21000] > explain select name from census where year = census_year and census_year=2010; +------------------------------------------------------------------+ | Explain String | +------------------------------------------------------------------+ | PLAN FRAGMENT 0 | | PARTITION: UNPARTITIONED | | | | 1:EXCHANGE | | | | PLAN FRAGMENT 1 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 1 | | UNPARTITIONED | | | | 0:SCAN HDFS | | table=predicate_propagation.census #partitions=1/3 size=22B | | predicates: census_year = 2010, year = census_year | +------------------------------------------------------------------+
在執行查詢以後,馬上檢查 PROFILE 語句的輸出,瞭解實際讀取和處理的數據量更詳細的分析。
假如是在分區表上創建的視圖,全部的分區修剪都是由原使得查詢子句肯定。即便在試圖上的查詢包含了引用分區鍵列的 WHERE 子句,Impala 不會修剪添加的列(If a view applies to a partitioned table, any partition pruning is determined by the clauses in the original query. Impala does not prune additional columns if the query on the view includes extra WHEREclauses referencing the partition key columns)。
你選擇的分區列應當是那種常常在重要的、大型的查詢中過濾查詢結果的列。一般來講,數據與時間值有關時,使用年、月、日的組合做爲分區列,數據與一些位置有關時使用地理區域做爲分區列。
分區表具備爲不一樣的分區設置不一樣的文件格式的靈活性。例如,你原來是接收文本格式數據,而後是 RCFile 格式,最終會接收 Parquet 格式,全部這些數據能夠存放在同一個表裏進行查詢。你只須要確保該表的結構是使用不一樣文件格式的的數據文件分別在單獨的分區。
例如,下面是當你收到不一樣年份的數據時,你可能從文本切換到 Parquet:
[localhost:21000] > create table census (name string) partitioned by (year smallint); [localhost:21000] > alter table census add partition (year=2012); -- Text format; [localhost:21000] > alter table census add partition (year=2013); -- Text format switches to Parquet before data loaded; [localhost:21000] > alter table census partition (year=2013) set fileformat parquet; [localhost:21000] > insert into census partition (year=2012) values ('Smith'),('Jones'),('Lee'),('Singh'); [localhost:21000] > insert into census partition (year=2013) values ('Flores'),('Bogomolov'),('Cooper'),('Appiah');
如上所述,HDFS 目錄 year=2012 包含文本格式數據文件,而 HDFS 目錄 year=2013 包含 Parquet 數據文件。一如既往,當加載實際數據時,你應當使用 INSERT ... SELECT 或 LOAD DATA 來導入大批量的數據,而不是使用產生少許的對實際查詢低效的文件的 INSERT ... VALUES 語句。
對於其餘的 Impala 沒法本地建立的文件類型,你能夠切換到 Hive 並執行 ALTER TABLE ... SET FILEFORMAT 語句,並在這裏執行 INSERT 或 LOAD DATA 語句。當切換回 Impala 後,執行 REFRESH table_name 語句以便 Impala 感知到經過 Hive 添加的任意分區或新數據。
涉及鏈接操做的查詢一般比只引用單個表的查詢更須要調整。鏈接查詢結果集的最大大小是全部鏈接的表中行數的乘積。當鏈接幾個百萬或十億記錄的表時,任何過濾結果集的失誤,或查詢中其餘的低效操做,都將會致使操做沒法完成不得不取消 。
調整 Impala 鏈接查詢的最簡單的技術就是在參與鏈接的每一個表上使用 COMPUTE STATS 語句採集統計信息,而後讓 Impala 基於每個表的大小、每個列不一樣值的個數、等等信息自動的優化查詢。COMPUTE STATS 語句和 鏈接優化(join optimization)是 Impala 1.2.2 引入的新功能。爲了保證每一個表上統計信息的精確,請在表加載數據以後執行 COMPUTE STATS 語句,並在因 INSERT, LOAD DATA, 添加分區等操做致使數據大幅變化以後再次執行。
假如鏈接查詢中全部表的統計信息不可用,或 Impala 選擇的鏈接順序不是最優,你能夠經過在 SELECT 關鍵字以後馬上緊跟 STRAIGHT_JOIN 關鍵字,來覆蓋自動的鏈接順序優化。這時候,Impala 使用表在查詢中出現的順序來指導鏈接如何處理。首先是最大的表,而後是次大的,依此類推。術語"最大"和"最小"指中間結果集的大小,這些基於做爲結果集一部分的每一個表的行數和列數(The terms "largest" and "smallest" refers to the size of the intermediate result set based on the number of rows and columns from each table that are part of the result set)。例如,若是你鏈接了表 sales 和 customers,查詢多是從產生了 5000 次購買的 100 個用戶中查找結果集。這時候,你應該使用 SELECT ... FROM sales JOIN customers ..., 把 customers 放在右側,由於在這個查詢上下文中它更小。
依賴於表的絕對和相對的大小,Impala 查詢計劃器在執行鏈接查詢的不一樣技術之間進行選擇。廣播鏈接(Broadcast joins) 是默認方式,右側的表被認爲比左側的表小,而且它的內容被髮送到查詢涉及到的其餘節點上。替代的技術稱做分割鏈接(partitioned join) (與分區表無關),更適用於近乎相同大小的大型表的鏈接。使用這一技術,每個表的部份內容被髮送到對應的其餘節點,而後這些行的子集能夠並行處理。廣播和分區鏈接的選擇仍然依賴於鏈接中全部表的可用的、使用 COMPUTE STATS 語句手機的統計信息。
對查詢執行 EXPLAIN 語句,查看該查詢採用了哪一種鏈接策略。若是你發現一個查詢使用了廣播鏈接,而你經過基準測試知道分割鏈接更高效,或者相反狀況時,在查詢上添加提示指定使用的精確的鏈接機制。參見 Hints 瞭解詳細信息。
假如鏈接中的一些表的表或列統計信息不可用,Impala 仍然使用可用的那部分信息從新排列表,包含可用統計信息的表放在鏈接的左側,按照總體大小和基數降序排列(Tables with statistics are placed on the left side of the join order, in descending order of cost based on overall size and cardinality)。沒有統計信息的表被認爲大小爲 0,也就是說,它們老是放置在鏈接查詢的右側。
假如由於過期的統計信息或意外的數據分佈, Impala 鏈接查詢很低效,你能夠經過在 SELECT 關鍵字以後緊跟着 STRAIGHT_JOIN 關鍵字來從新排序鏈接的表,使的 Impala 高效。STRAIGHT_JOIN 關鍵字關閉 Impala 內部使用的鏈接子句的從新排序,並根據 查詢中 join 子句中列出的順序優化(The STRAIGHT_JOIN keyword turns off the reordering of join clauses that Impala does internally, and produces a plan that relies on the join clauses being ordered optimally in the query text)。這時,重寫查詢以便最大的表在最左側,跟着是次大的,依此類推直到最小的表放在最右側。
在下面的例子裏,基於 BIG 表的子查詢產生一個很是小的結果集,可是這個表仍被視爲好像它是最大的並放置在鏈接順序的第一位。爲最後的鏈接子句使用 STRAIGHT_JOIN 關鍵字,防止最終的表從新排序,保持它做爲最右邊表的鏈接順序(Using STRAIGHT_JOIN for the last join clause prevents the final table from being reordered, keeping it as the rightmost table in the join order)。
select straight_join x from medium join small join (select * from big where c1 < 10) as big where medium.id = small.id and small.id = big.id;
下面的例子演示了10億、2億、1百萬行表之間的鏈接(這時,表都是未分區的,使用 Parquet 格式)。最小的表是最大的表的一個子集,方便起見在惟一的 ID 列上進行鏈接。最小的表只包含其餘表中列的一個子集。
[localhost:21000] > create table big stored as parquet as select * from raw_data; +----------------------------+ | summary | +----------------------------+ | Inserted 1000000000 row(s) | +----------------------------+ Returned 1 row(s) in 671.56s [localhost:21000] > desc big; +-----------+---------+---------+ | name | type | comment | +-----------+---------+---------+ | id | int | | | val | int | | | zfill | string | | | name | string | | | assertion | boolean | | +-----------+---------+---------+ Returned 5 row(s) in 0.01s [localhost:21000] > create table medium stored as parquet as select * from big limit 200 * floor(1e6); +---------------------------+ | summary | +---------------------------+ | Inserted 200000000 row(s) | +---------------------------+ Returned 1 row(s) in 138.31s [localhost:21000] > create table small stored as parquet as select id,val,name from big where assertion = true limit 1 * floor(1e6); +-------------------------+ | summary | +-------------------------+ | Inserted 1000000 row(s) | +-------------------------+ Returned 1 row(s) in 6.32s
對於任意類型的性能測試,使用 EXPLAIN 語句查看將執行的查詢是如何的昂貴(expensive)而不須要實際運行它,而且啓用詳細的 EXPLAIN 計劃包含更詳細的性能導向的信息:最有趣的計劃行---展現了沒有統計信息的鏈接的表--以黑體突出,Impala 沒法正確的估算處理的每一個階段中涉及的行數,一般採用廣播鏈接機制把其中之一的表的完整數據發送到各個節點上(Impala cannot make a good estimate of the number of rows involved at each stage of processing,and is likely to stick with the BROADCAST join mechanism that sends a complete copy of one of the tables to each node)。
[localhost:21000] > set explain_level=verbose; EXPLAIN_LEVEL set to verbose [localhost:21000] > explain select count(*) from big join medium where big.id = medium.id; +----------------------------------------------------------+ | Explain String | +----------------------------------------------------------+ | Estimated Per-Host Requirements: Memory=2.10GB VCores=2 | | | | PLAN FRAGMENT 0 | | PARTITION: UNPARTITIONED | | | | 6:AGGREGATE (merge finalize) | | | output: SUM(COUNT(*)) | | | cardinality: 1 | | | per-host memory: unavailable | | | tuple ids: 2 | | | | | 5:EXCHANGE | | cardinality: 1 | | per-host memory: unavailable | | tuple ids: 2 | | | | PLAN FRAGMENT 1 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 5 | | UNPARTITIONED | | | | 3:AGGREGATE | | | output: COUNT(*) | | | cardinality: 1 | | | per-host memory: 10.00MB | | | tuple ids: 2 | | | | | 2:HASH JOIN | | | join op: INNER JOIN (BROADCAST) | | | hash predicates: | | | big.id = medium.id | | | cardinality: unavailable | | | per-host memory: 2.00GB | | | tuple ids: 0 1 | | | | | |----4:EXCHANGE | | | cardinality: unavailable | | | per-host memory: 0B | | | tuple ids: 1 | | | | | 0:SCAN HDFS | | table=join_order.big #partitions=1/1 size=23.12GB | | table stats: unavailable | | column stats: unavailable | | cardinality: unavailable | | per-host memory: 88.00MB | | tuple ids: 0 | | | | PLAN FRAGMENT 2 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 4 | | UNPARTITIONED | | | | 1:SCAN HDFS | | table=join_order.medium #partitions=1/1 size=4.62GB | | table stats: unavailable | | column stats: unavailable | | cardinality: unavailable | | per-host memory: 88.00MB | | tuple ids: 1 | +----------------------------------------------------------+ Returned 64 row(s) in 0.04s
採集全部表的統計信息很簡單,在每個表上執行 COMPUTE STATS 語句:
[localhost:21000] > compute stats small; +-----------------------------------------+ | summary | +-----------------------------------------+ | Updated 1 partition(s) and 3 column(s). | +-----------------------------------------+ Returned 1 row(s) in 4.26s [localhost:21000] > compute stats medium; +-----------------------------------------+ | summary | +-----------------------------------------+ | Updated 1 partition(s) and 5 column(s). | +-----------------------------------------+ Returned 1 row(s) in 42.11s [localhost:21000] > compute stats big; +-----------------------------------------+ | summary | +-----------------------------------------+ | Updated 1 partition(s) and 5 column(s). | +-----------------------------------------+ Returned 1 row(s) in 165.44s
有了統計信息,Impala 能夠選擇更有效的鏈接順序而不是按照查詢中從左到右各個表的順序,而且能夠基於表的大小和行數選擇廣播鏈接或分割鏈接策略:
[localhost:21000] > explain select count(*) from medium join big where big.id = medium.id; Query: explain select count(*) from medium join big where big.id = medium.id +-----------------------------------------------------------+ | Explain String | +-----------------------------------------------------------+ | Estimated Per-Host Requirements: Memory=937.23MB VCores=2 | | | | PLAN FRAGMENT 0 | | PARTITION: UNPARTITIONED | | | | 6:AGGREGATE (merge finalize) | | | output: SUM(COUNT(*)) | | | cardinality: 1 | | | per-host memory: unavailable | | | tuple ids: 2 | | | | | 5:EXCHANGE | | cardinality: 1 | | per-host memory: unavailable | | tuple ids: 2 | | | | PLAN FRAGMENT 1 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 5 | | UNPARTITIONED | | | | 3:AGGREGATE | | | output: COUNT(*) | | | cardinality: 1 | | | per-host memory: 10.00MB | | | tuple ids: 2 | | | | | 2:HASH JOIN | | | join op: INNER JOIN (BROADCAST) | | | hash predicates: | | | big.id = medium.id | | | cardinality: 1443004441 | | | per-host memory: 839.23MB | | | tuple ids: 1 0 | | | | | |----4:EXCHANGE | | | cardinality: 200000000 | | | per-host memory: 0B | | | tuple ids: 0 | | | | | 1:SCAN HDFS | | table=join_order.big #partitions=1/1 size=23.12GB | | table stats: 1000000000 rows total | | column stats: all | | cardinality: 1000000000 | | per-host memory: 88.00MB | | tuple ids: 1 | | | | PLAN FRAGMENT 2 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 4 | | UNPARTITIONED | | | | 0:SCAN HDFS | | table=join_order.medium #partitions=1/1 size=4.62GB | | table stats: 200000000 rows total | | column stats: all | | cardinality: 200000000 | | per-host memory: 88.00MB | | tuple ids: 0 | +-----------------------------------------------------------+ Returned 64 row(s) in 0.04s [localhost:21000] > explain select count(*) from small join big where big.id = small.id; Query: explain select count(*) from small join big where big.id = small.id +-----------------------------------------------------------+ | Explain String | +-----------------------------------------------------------+ | Estimated Per-Host Requirements: Memory=101.15MB VCores=2 | | | | PLAN FRAGMENT 0 | | PARTITION: UNPARTITIONED | | | | 6:AGGREGATE (merge finalize) | | | output: SUM(COUNT(*)) | | | cardinality: 1 | | | per-host memory: unavailable | | | tuple ids: 2 | | | | | 5:EXCHANGE | | cardinality: 1 | | per-host memory: unavailable | | tuple ids: 2 | | | | PLAN FRAGMENT 1 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 5 | | UNPARTITIONED | | | | 3:AGGREGATE | | | output: COUNT(*) | | | cardinality: 1 | | | per-host memory: 10.00MB | | | tuple ids: 2 | | | | | 2:HASH JOIN | | | join op: INNER JOIN (BROADCAST) | | | hash predicates: | | | big.id = small.id | | | cardinality: 1000000000 | | | per-host memory: 3.15MB | | | tuple ids: 1 0 | | | | | |----4:EXCHANGE | | | cardinality: 1000000 | | | per-host memory: 0B | | | tuple ids: 0 | | | | | 1:SCAN HDFS | | table=join_order.big #partitions=1/1 size=23.12GB | | table stats: 1000000000 rows total | | column stats: all | | cardinality: 1000000000 | | per-host memory: 88.00MB | | tuple ids: 1 | | | | PLAN FRAGMENT 2 | | PARTITION: RANDOM | | | | STREAM DATA SINK | | EXCHANGE ID: 4 | | UNPARTITIONED | | | | 0:SCAN HDFS | | table=join_order.small #partitions=1/1 size=17.93MB | | table stats: 1000000 rows total | | column stats: all | | cardinality: 1000000 | | per-host memory: 32.00MB | | tuple ids: 0 | +-----------------------------------------------------------+ Returned 64 row(s) in 0.03s
當相似這些的查詢實際運行時,執行時間是相對固定的,無論查詢語句中表的順序如何。下面的例子使用了惟一的 ID 列和包含重複值的 VAL 列:
[localhost:21000] > select count(*) from big join small on (big.id = small.id); Query: select count(*) from big join small on (big.id = small.id) +----------+ | count(*) | +----------+ | 1000000 | +----------+ Returned 1 row(s) in 21.68s [localhost:21000] > select count(*) from small join big on (big.id = small.id); Query: select count(*) from small join big on (big.id = small.id) +----------+ | count(*) | +----------+ | 1000000 | +----------+ Returned 1 row(s) in 20.45s [localhost:21000] > select count(*) from big join small on (big.val = small.val); +------------+ | count(*) | +------------+ | 2000948962 | +------------+ Returned 1 row(s) in 108.85s [localhost:21000] > select count(*) from small join big on (big.val = small.val); +------------+ | count(*) | +------------+ | 2000948962 | +------------+ Returned 1 row(s) in 100.76s
當統計信息可用時,Impala 能夠更好的優化複雜的或多表查詢,能夠更好地理解數據量和值的分佈,並使用這些信息幫助查詢並行處理和分佈負載。下面的章節描述了 Impala 可使用的統計信息的分類,以及如何產生這些信息並保持最新。
原來 Impala 依靠 Hive 採集統計信息的機制,經過 Hive ANALYZE TABLE 語句初始化一個 MapReduce 做業進行。爲了更好的性能、用戶友好性和可靠性, 在 1.2.1 以後,Impala 實現了本身的 COMPUTE STATS 語句,以及相關的 SHOW TABLE STATS 和 SHOW COLUMN STATS 語句。
當 metastore 數據庫中的元數據可用時,Impala 查詢計劃器可使用整個表和分區的統計信息。這些元數據用於本表的某些優化,並和列統計信息組合用於其餘優化。
當向表或分區加載數據加載數據後,使用如下技術之一採集表的統計信息:
ANALYZE TABLEtablename[PARTITION(partcol1[=val1],partcol2[=val2], ...)] COMPUTE STATISTICS [NOSCAN];例如,爲非分區表採集統計信息:
ANALYZE TABLE customer COMPUTE STATISTICS;爲以 state 和 city 分區列的分區表 store 表採集全部分區的統計信息:
ANALYZE TABLE store PARTITION(s_state, s_county) COMPUTE STATISTICS;只採集分區表 store 中 California 分區的統計信息:
ANALYZE TABLE store PARTITION(s_state='CA', s_county) COMPUTE STATISTICS;
使用 SHOW TABLE STATS table_name 語句,查看錶的統計信息是否可用,以及統計信息的詳細內容。參考 SHOW Statement 瞭解詳細信息。
假如你使用基於 Hive 的方法採集統計信息,參見 the Hive wiki 瞭解關於 Hive 的配置要求。 Cloudera 推薦使用 Impala COMPUTE STATS 語句以免 Hive 採集統計信息程序潛在的配置和可擴展性方面的問題。
當 metastore 數據庫中的元數據可用時,Impala 查詢計劃器可使用單個列的統計信息。這一技術對於比較鏈接查詢中全部表的鏈接列,以幫助評估查詢中每個表將返回多少行最有價值。目前 Impala 自身不會自動建立這些元數據。使用 Hive 中的 ANALYZE TABLE 語句收集這些統計信息(不管表是在 Impala 仍是 Hive 中建立的,這一語句均可以正常工做)。
對於特定的一組列,使用 SHOW COLUMN STATS table_name 語句檢查列統計信息是否可用,或檢查針對引用這系列的表的查詢的擴展的 EXPLAIN 輸出。參見 SHOW 語句 和 EXPLAIN 語句瞭解詳細信息。
全部統計信息中最關鍵的部分是表(未分區的表)或分區(分區表)中的行數。COMPUTE STATS 語句老是採集全部列的統計信息以及整個表的統計信息。假如在添加了一個分區或插入數據以後,進行完整的 COMPUTE STATS 操做實際不可行時,或者當行數不一樣時,能夠預見 Impala 將產生更好的執行計劃時,你能夠經過 ALTER TABLE 語句手工設置行數:
create table analysis_data stored as parquet as select * from raw_data; Inserted 1000000000 rows in 181.98s compute stats analysis_data; insert into analysis_data select * from smaller_table_we_forgot_before; Inserted 1000000 rows in 15.32s -- 如今表裏共有 1001000000 行。咱們能夠更新統計信息中的這一個數據點 alter table analysis_data set tblproperties('numRows'='1001000000');
對於分區表,同時更新每個分區的行數和整個表的行數:
-- 若是原來表中包含 1000000 行,咱們新添加了一個分區 -- 修改該分區和整個表的 numRows 屬性 alter table partitioned_data partition(year=2009, month=4) set tblproperties ('numRows'='30000'); alter table partitioned_data set tblproperties ('numRows'='1030000');
實際上,COMPUTE STATS 語句已經夠快了,這一技術是沒必要要的。這一方法最大的價值就是能夠調整 numRows 值的大小來產生理想的鏈接順序從而解決性能問題(It is most useful as a workaround for in case of performance issues where you might adjust the numRowsvalue higher or lower to produce the ideal join order)。
下面的例子經過一系列的 SHOW TABLE STATS, SHOW COLUMN STATS, ALTER TABLE, SELECT , INSERT 語句來演示了 Impala 如何使用統計信息幫助優化查詢的各個方面。
這一例子展現了 STORE 表的表和列的統計信息,這個表使用的是 TPC-DS 決策支持系統基準測試中的表。這是一個只有 12 行數據的小表。最初,在使用 COMPUTE STATS 採集統計信息以前,大多數數字列顯示佔位符 -1,表示這一數字是未知的。這一待填充的數值是容易在物理層計量或推斷出的,如文件個數,文件的總數據大小,以及對具備固定大小如 INT,FLOAT,TIMESTAMP 等數據類型的最大和平均大小(The figures that are filled in are values that are easily countable or deducible at the physical level, such as the number of files, total data size of the files, and the maximum and average sizes for data types that have a constant size such as INT, FLOAT, and TIMESTAMP)。
[localhost:21000] > show table stats store; +-------+--------+--------+--------+ | #Rows | #Files | Size | Format | +-------+--------+--------+--------+ | -1 | 1 | 3.08KB | TEXT | +-------+--------+--------+--------+ Returned 1 row(s) in 0.03s [localhost:21000] > show column stats store; +--------------------+-----------+------------------+--------+----------+----------+ | Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size | +--------------------+-----------+------------------+--------+----------+----------+ | s_store_sk | INT | -1 | -1 | 4 | 4 | | s_store_id | STRING | -1 | -1 | -1 | -1 | | s_rec_start_date | TIMESTAMP | -1 | -1 | 16 | 16 | | s_rec_end_date | TIMESTAMP | -1 | -1 | 16 | 16 | | s_closed_date_sk | INT | -1 | -1 | 4 | 4 | | s_store_name | STRING | -1 | -1 | -1 | -1 | | s_number_employees | INT | -1 | -1 | 4 | 4 | | s_floor_space | INT | -1 | -1 | 4 | 4 | | s_hours | STRING | -1 | -1 | -1 | -1 | | s_manager | STRING | -1 | -1 | -1 | -1 | | s_market_id | INT | -1 | -1 | 4 | 4 | | s_geography_class | STRING | -1 | -1 | -1 | -1 | | s_market_desc | STRING | -1 | -1 | -1 | -1 | | s_market_manager | STRING | -1 | -1 | -1 | -1 | | s_division_id | INT | -1 | -1 | 4 | 4 | | s_division_name | STRING | -1 | -1 | -1 | -1 | | s_company_id | INT | -1 | -1 | 4 | 4 | | s_company_name | STRING | -1 | -1 | -1 | -1 | | s_street_number | STRING | -1 | -1 | -1 | -1 | | s_street_name | STRING | -1 | -1 | -1 | -1 | | s_street_type | STRING | -1 | -1 | -1 | -1 | | s_suite_number | STRING | -1 | -1 | -1 | -1 | | s_city | STRING | -1 | -1 | -1 | -1 | | s_county | STRING | -1 | -1 | -1 | -1 | | s_state | STRING | -1 | -1 | -1 | -1 | | s_zip | STRING | -1 | -1 | -1 | -1 | | s_country | STRING | -1 | -1 | -1 | -1 | | s_gmt_offset | FLOAT | -1 | -1 | 4 | 4 | | s_tax_precentage | FLOAT | -1 | -1 | 4 | 4 | +--------------------+-----------+------------------+--------+----------+----------+ Returned 29 row(s) in 0.04s
使用 Hive ANALYZE TABLE 語句採集列的統計信息,你必須指定要採集統計信息的每個列。而 Impala COMPUTE STATS 語句自動採集全部列的統計信息,由於它較快的讀取整個表並高效的計算全部列的值。下面例子展現了執行 COMPUTE STATS 語句以後,表和全部列的統計信息都被填充:
[localhost:21000] > compute stats store; +------------------------------------------+ | summary | +------------------------------------------+ | Updated 1 partition(s) and 29 column(s). | +------------------------------------------+ Returned 1 row(s) in 1.88s [localhost:21000] > show table stats store; +-------+--------+--------+--------+ | #Rows | #Files | Size | Format | +-------+--------+--------+--------+ | 12 | 1 | 3.08KB | TEXT | +-------+--------+--------+--------+ Returned 1 row(s) in 0.02s [localhost:21000] > show column stats store; +--------------------+-----------+------------------+--------+----------+-------------------+ | Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size | +--------------------+-----------+------------------+--------+----------+-------------------+ | s_store_sk | INT | 12 | 0 | 4 | 4 | | s_store_id | STRING | 6 | 0 | 16 | 16 | | s_rec_start_date | TIMESTAMP | 4 | 0 | 16 | 16 | | s_rec_end_date | TIMESTAMP | 3 | 6 | 16 | 16 | | s_closed_date_sk | INT | 3 | 9 | 4 | 4 | | s_store_name | STRING | 8 | 0 | 5 | 4.25 | | s_number_employees | INT | 9 | 0 | 4 | 4 | | s_floor_space | INT | 10 | 0 | 4 | 4 | | s_hours | STRING | 2 | 0 | 8 | 7.083300113677979 | | s_manager | STRING | 7 | 0 | 15 | 12 | | s_market_id | INT | 7 | 0 | 4 | 4 | | s_geography_class | STRING | 1 | 0 | 7 | 7 | | s_market_desc | STRING | 10 | 0 | 94 | 55.5 | | s_market_manager | STRING | 7 | 0 | 16 | 14 | | s_division_id | INT | 1 | 0 | 4 | 4 | | s_division_name | STRING | 1 | 0 | 7 | 7 | | s_company_id | INT | 1 | 0 | 4 | 4 | | s_company_name | STRING | 1 | 0 | 7 | 7 | | s_street_number | STRING | 9 | 0 | 3 | 2.833300113677979 | | s_street_name | STRING | 12 | 0 | 11 | 6.583300113677979 | | s_street_type | STRING | 8 | 0 | 9 | 4.833300113677979 | | s_suite_number | STRING | 11 | 0 | 9 | 8.25 | | s_city | STRING | 2 | 0 | 8 | 6.5 | | s_county | STRING | 1 | 0 | 17 | 17 | | s_state | STRING | 1 | 0 | 2 | 2 | | s_zip | STRING | 2 | 0 | 5 | 5 | | s_country | STRING | 1 | 0 | 13 | 13 | | s_gmt_offset | FLOAT | 1 | 0 | 4 | 4 | | s_tax_precentage | FLOAT | 5 | 0 | 4 | 4 | +--------------------+-----------+------------------+--------+----------+-------------------+ Returned 29 row(s) in 0.04s
下面的例子展現了分區表中統計信息如何表示。這時,咱們設置了一個存放世界上最瑣碎的戶籍數據的表,包含一個 STRING 字段,根據 YEAR 列進行分區。表統計信息中每個分區都包含一個單獨的實體,再加上最終的總數。對於分區列,列統計信息中包含一些容易推斷的事實,如不一樣值的個數(分區子目錄的個數) 和 NULL 值的個數(分區列中不可能出現)。
localhost:21000] > describe census; +------+----------+---------+ | name | type | comment | +------+----------+---------+ | name | string | | | year | smallint | | +------+----------+---------+ Returned 2 row(s) in 0.02s [localhost:21000] > show table stats census; +-------+-------+--------+------+---------+ | year | #Rows | #Files | Size | Format | +-------+-------+--------+------+---------+ | 2000 | -1 | 0 | 0B | TEXT | | 2004 | -1 | 0 | 0B | TEXT | | 2008 | -1 | 0 | 0B | TEXT | | 2010 | -1 | 0 | 0B | TEXT | | 2011 | 0 | 1 | 22B | TEXT | | 2012 | -1 | 1 | 22B | TEXT | | 2013 | -1 | 1 | 231B | PARQUET | | Total | 0 | 3 | 275B | | +-------+-------+--------+------+---------+ Returned 8 row(s) in 0.02s [localhost:21000] > show column stats census; +--------+----------+------------------+--------+----------+----------+ | Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size | +--------+----------+------------------+--------+----------+----------+ | name | STRING | -1 | -1 | -1 | -1 | | year | SMALLINT | 7 | 0 | 2 | 2 | +--------+----------+------------------+--------+----------+----------+ Returned 2 row(s) in 0.02s
下面的例子演示了在 Impala 中執行 COMPUTE STATS 語句後統計信息是如何填充的。
[localhost:21000] > compute stats census; +-----------------------------------------+ | summary | +-----------------------------------------+ | Updated 3 partition(s) and 1 column(s). | +-----------------------------------------+ Returned 1 row(s) in 2.16s [localhost:21000] > show table stats census; +-------+-------+--------+------+---------+ | year | #Rows | #Files | Size | Format | +-------+-------+--------+------+---------+ | 2000 | -1 | 0 | 0B | TEXT | | 2004 | -1 | 0 | 0B | TEXT | | 2008 | -1 | 0 | 0B | TEXT | | 2010 | -1 | 0 | 0B | TEXT | | 2011 | 4 | 1 | 22B | TEXT | | 2012 | 4 | 1 | 22B | TEXT | | 2013 | 1 | 1 | 231B | PARQUET | | Total | 9 | 3 | 275B | | +-------+-------+--------+------+---------+ Returned 8 row(s) in 0.02s [localhost:21000] > show column stats census; +--------+----------+------------------+--------+----------+----------+ | Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size | +--------+----------+------------------+--------+----------+----------+ | name | STRING | 4 | 1 | 5 | 4.5 | | year | SMALLINT | 7 | 0 | 2 | 2 | +--------+----------+------------------+--------+----------+----------+ Returned 2 row(s) in 0.02s
關於在統計信息可用時,演示一些查詢工做方式不一樣的例子,參見 Examples of Join Order Optimization。在採集統計信息以前和以後,觀察 EXPLAIN 的輸出,你能夠看到 Impala 使用不一樣方式執行同一個查詢。對比以前和以後的查詢時間,檢查以前和以後 PROFILE 輸出的吞吐量的值, to verify how much the improved plan speeds up performance.
與其餘 Hadoop 組件相似,由於 Impala 是設計用來處理分佈式環境中大量數據的,因此應使用真實的數據和集羣配置進行性能測試。使用多節點的集羣而不是單節點的;對包含 TB 數據的表進行查詢而不是幾十G的。Impala 全部使用的並行處理技術最適合用於超出了單個服務器容量的負載。
當你執行查詢返回大量的行,打印輸出結果所花費的 CPU 時間是巨大的,爲實際查詢時間添加了不許確的度量(the CPU time to pretty-print the output can be substantial, giving an inaccurate measurement of the actual query time)。請考慮在 impala-設立了 命令中使用 -B 選項關閉打印結果,而可選的 -o 選項能夠保存查詢結果到一個文件而不是打印到屏幕上。參見 impala-shell Command-Line Options 瞭解詳細信息。
經過爲 impalad 守護進程指定 -mem_limits 選項,你能夠限制查詢執行時 Impala 使用的內存量。參見 Modifying Impala Startup Options 瞭解詳細信息。這一限制僅對查詢直接消耗的內存有效;Impala 在啓動時保留了額外的內存,例如用於緩存元數據。
對於生產部署,Cloudera 推薦使用如 cgroups 機制實現資源隔離,能夠在 Cloudera Manager 中配置。參見 Managing Clusters with Cloudera Manager 瞭解詳細信息。
當你結合 CDH 5 使用 Impala 時,你能夠像在 Using Resource Management with Impala (CDH 5 Only) 中描述的那樣使用 YARN 資源管理框架。目前 CDH 5 還是 beta 版;用於 CDH 5 beta 版的對應 Impala 版本是 1.2.0。
EXPLAIN 語句提供查詢將要執行的邏輯步驟的大綱,例如工做在節點之間如何分佈,以及中間結果如何組合產生最終結果集。你能夠在實際執行查詢以前看到這些詳細信息。你可使用這些信息來檢查查詢是否使用一些很是意外的或低效的方式執行。
在查詢 profile 報告的開始部分,EXPLAIN 計劃一樣被打印出來,以便於檢查查詢的邏輯和物理的各個方面。
EXPLAIN 輸出的細節的數量由 EXPLAIN_LEVEL 查詢選項控制。當性能調整時複覈表和列的統計信息時,或與 CDH5 中資源管理功能聯合評估查詢資源使用狀況時(or when estimating query resource usage in conjunction with the resource management features in CDH 5),一般從 normal 修改成 verbose (或 0 到 1)。
PROFILE 語句在 impala-shell 中可用,產生一個最近執行語句的詳細的底層報告。 不像在 Understanding the EXPLAIN Plan 中描述的 EXPLAIN 那樣,這一信息僅當查詢執行完成後可用。它展現了物理細節如每一節點讀取的字節數,最大內存使用等等信息。你可使用這些信息肯定查詢是 I/O 密集(I/O-bound)仍是 CPU 密集(CPU-bound),是否一些網絡條件達到瓶頸,是否一臺放緩影響到了部分節點而不影響另外一部分(whether a slowdown is affecting some nodes but not others),並檢查推薦配置如 short-circuit local reads 是否生效。
EXPLAIN plan 一樣被打印在查詢 profile 報告的開始,以便於檢查查詢的邏輯和物理的各個方面。在 EXPLAIN_LEVEL 中描述的 EXPLAIN_LEVEL 查詢選項,一樣對控制 PROFILE 命令中產生的 EXPLAIN 輸出打印的詳細程度有效。
測試以確保 Impala 爲性能進行了最優配置。假如你沒有使用 Cloudera Manager 安裝的 Impala,完成本主題中描述的內容以幫助確認已經合適的配置。即便你使用 Cloudera Manager 安裝的 Impala,已經自動應用合適的配置,這一過程能夠檢驗 Impala 設置是否正確。
你可使用瀏覽器鏈接到 Impala 服務器檢查 Impala 的配置值:
檢查 Impala 配置值:
例如,檢查你的系統是否啓用了本地塊跟蹤信息(block locality tracking information),應檢查 dfs.datanode.hdfs-blocks-metadata.enabled 的值是否爲 true
檢查數據本地化(data locality):
[impalad-host:21000] > SELECT COUNT (*) FROM MyTable
Total remote scan volume = 0
遠程掃描的存在標識 impalad 沒有運行在正確的節點上。當一些數據節點上沒有運行 impalad 或沒法運行,由於啓動查詢的 impalad 實例沒法鏈接到一個或多個 impalad 實例(This can be because some DataNodes do not have impalad running or it can be because the impalad instance that is starting the query is unable to contact one or more of the impalad instances)。
理解這些問題的緣由:
你能夠複查 Impala 日誌的內容,查找短路讀取(short-circuit reads)或塊本地跟蹤(block location tracking)沒有正常運行的標誌。在檢查日誌以前,對一個小的 HDFS 數據集執行一個簡單的查詢。完成一個查詢任務使用當前設置產生日誌信息。啓動 Impala 和執行查詢的信息能夠在 Starting Impala 和 Using the Impala Shell 找到。登陸信息能夠在 Using Impala Logging 中找到。日誌信息和對應的描述以下:
Log Message |
Interpretation |
---|---|
Unknown disk id. This will negatively affect performance. Check your hdfs settings to enable block location metadata |
Tracking block locality 未啓用 |
Unable to load native-hadoop library for your platform... using builtin-java classes where applicable |
Native checksumming 未啓用 |