下面的章节介绍影响 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 未启用 |