Impala是Cloudera开发和开源的数仓查询引擎,以性能优秀著称。除了Apache Impala开源项目,业界知名的Apache Doris和StarRocks、SelectDB项目也跟Impala有千丝万缕的联系。笔者所在的网易数帆大数据团队,是最早一批将其作为分析型数仓查询引擎的团队,目前正基于Impala打造网易高性能数仓引擎。
本系列文章结合实际开发和使用经验,聊聊可以从哪些方面对数仓查询引擎进行优化。文章大致可以分为这几个部分:首先会对简单介绍下Impala的架构和元数据管理,以便后续内容展开;接着从执行引擎,存储优化,物化视图,数据缓存和虚拟数仓等维度进行探讨。本文为执行引擎篇。
Impala集群包含一个 Catalog Server (Catalogd)、一个 Statestore Server (Statestored) 和若干个 Impala Daemon (Impalad)。Catalogd 主要负责元数据的获取和DDL的执行,Statestored主要负责消息/元数据的广播,Impalad主要负责查询的接收和执行。
Impalad 又可配置为 coordinator only、 executor only 或 coordinator and executor(默认)三种模式。Coordinator角色的Impalad负责查询的接收、计划生成、查询的调度等,Executor角色的Impalad负责数据的读取和计算。默认配置下每个Impalad既是Coordinator又是Executor。生产环境建议做好角色分离,即每个Impalad要么是Coordinator要么是Executor。
Impala的元数据缓存在catalogd和各个Coordinator角色的Impalad中。Catalogd中的缓存是最新的,各个Coordinator都缓存的是Catalogd内元数据的一个复本。元数据由Catalogd向外部系统获取,并通过 Statestored 传播给各个 Coordinator。详见Impala元数据简介。
以Hive表为例,Catalogd中的元数据分别从Hive Metastore(HMS)和HDFS NameNode(NN)获取。从HMS获取的信息包括元数据信息和统计信息两部分,元数据信息指有哪些库和表,表定义,列类型等,对应“show databases,show tables,show create table xxx,show ”等操作。统计信息包括表的大小,行数,分区和各列的信息等,对应“show table stats xx,show column stats xx”等操作,详见Table and Column Statistics 。从NN获取的是文件粒度的信息,包括文件存储位置,副本和文件块信息等。
管理服务器是网易高性能数仓增加的Impala模块,提供集群粒度的SQL查看界面,持久化保存历史查询信息并展示,SQL审计,查询错误和查询性能分析,自动进行统计信息计算等。
在执行模型这块,目前主要有动态代码生成(code generation或just in time/JIT)和向量化计算两个流派,Impala主要是基于JIT进行性能优化,对于向量化引擎,Impala社区版目前并没有相关规划,网易高性能数仓团队也有计划对其进行向量化改造。
在具体实现上,Impala属于改进版的火山模型,官方论文描述为“The execution model is the traditional Volcano‑style with Exchange operators [7]. Processing is performed batch‑at‑a‑time: each GetNext() call operates over batches of rows, similar to [10]”,即在传统的火山模型的基础上加入Exchange操作符,用于进行不同执行节点的数据交换。每次会获取一批记录而不是一条记录。
不管是JIT还是矢量化,其目的都是尽可能地减少执行引擎核心代码流程的调用次数并提高函数执行效率,这对于需要处理海量记录时非常重要。Impala通过每次获取一批记录来减少调用次数,再利用JIT技术来生成针对特定类型数据的执行流程函数,提高每次调用的效率。
更进一步,Impala采用数据流水线(streaming pipelined)执行机制,充分利用计算资源进行并发执行。在Impala 4.0版本,完整支持了executor节点的多线程执行模型,进一步提高并发能力,压榨计算资源。
JIT技术与静态编译技术相反,其是在具体的查询运行之前才进行代码编译,此时,查询中需要处理的列类型,用到的算子和函数都已经确定,可以为该查询生成特定版本的处理函数。如下图所示:
左侧是通用的从文件读取记录(tuple)并解析的行数,外层一个for循环用于对每一列进行处理,内层的switch用于判断列的类型并调用特定的解析函数。如果我们已经知道该记录由三列组成,类型分别为int,bool和int,那么JIT技术就可以生成如图右侧的函数版本,不需要for循环,也不需要switch判断,显然,执行效率更高。
总的来说,Impala使用LLVM来进行JIT优化,生成对于某个具体查询最优的函数实现。其优化项具体包括移除条件分支(Removing conditionals,如上所示)、移除内存加载和内联虚函数调用等。
启用动态代码生成时,在查询执行前需要先动态生成其执行代码,因此有一定的时间消耗,对于小查询,动态代码生成可能是有害的,生成代码的时间都有可能超过SQL执行时间。Impala提供了DISABLE_CODEGEN_ROWS_THRESHOLD参数,默认为50000,如果SQL需要处理的记录数小于该值,则不会使用动态代码生成进行执行优化。Impala 4.0版本对JIT进行了进一步优化,采用异步化改造来避免生成JIT代码对查询性能的影响,当编译未完成时使用原函数,完成后无缝切换成优化后的函数代码。
Impala属于SQL on Hadoop的一种,基于MPP(Massively Parallel Processing,即大规模并行处理)架构,正常情况下,查询涉及的各种操作均在内存中完成的,因此,可用内存的多少及对其的利用效率,对Impala查询性能有极大影响。同样地,作为一个OLAP查询引擎,可用的CPU资源对查询性能也至光重要。Impala虽提供了少数CPU相关配置项,如num_threads_per_core 等,但对CPU使用的控制能力较差。本小节后续仅介绍内存资源相关,CPU计算后续另开一篇单独介绍。
Impala有比较丰富的资源使用限制方式,称为准入控制。其中资源池(resource pool)是Impala进行并发控制的主要手段,可以决定某个查询是否会被拒绝,或执行,或排队。其主要有两种控制方式,一种是手动设置最大并发数控制,超过阈值的请求会进行排队,可以设置允许排队的最大请求数和排队时长,超过阈值的请求直接返回失败;另一种是基于内存的并发控制,下面进行重点介绍。
Impala集群支持通过fair-scheduler.xml设置多个资源池并规定其最大可用内存(maxResources),再通过llama-site.xml为每个资源池设置请求级别的内存限制,包括内存分配上下限max-query-mem-limit和min-query-mem-limit,及clamp-mem-limit-query-option。除了通过资源池相关配置控制请求的内存使用,还可以通过MEM_LIMIT请求选项设置内存限制。而clamp-mem-limit-query-option就是设置是否允许MEM_LIMIT设置的内存突破资源池内存配置的限制。
需要注意的是,max-query-mem-limit,min-query-mem-limit和MEM_LIMIT设置的是请求在每个executor节点允许申请的最大内存,请求申请的总内存还需要乘上执行该请求的executor节点个数。
若Impala通过预估发现查询所需的内存资源超过集群总内存资源,该查询会被拒绝;若总资源满足,但由于部分资源已被其他查询占用,则会将至放入请求队列,待可用资源满足查询要求时再按查询提交的先后顺序调度执行。
若预估的内存资源超过了设置的max-query-mem-limit,则以max-query-mem-limit为准,若小于min-query-mem-limit,则以min-query-mem-limit为准。假设查询请求设置了MEM_LIMIT,需先判断clamp-mem-limit-query-option的值,若为true,则仍然受max-query-mem-limit,min-query-mem-limit约束。下面举个例子进行说明:
假设一个Impala集群有5个executor节点,集群配置了一个最大可用内存为100GB的资源池。查询请求的内存上下限为10GB和2GB,若clamp-mem-limit-query-option为true,Impala为某个查询请求A预估的内存为14GB(或设置了MEM_LIMIT为14GB),则查询A在每个executor最多只能分配10GB内存。若clamp-mem-limit-query-option为false,查询A最多可分配14GB内存。假设clamp-mem-limit-query-option为true,则该资源池最多只能同时执行2个查询A这样的请求(2 * 5 * 10GB)。
<< · Back Index ·>>