向量化最佳实践
这一小节会讨论如何高效地使用向量化执行引擎。
1 场景
首先,对于不同的场景,向量化执行引擎相比传统的面向行的执行引擎,性能的提升是不同的,对此,你可以通过概述部分有一个基本的认识和理解。
例如,能够完全顺序操作的算子,比如一般地表达式计算、选择算子和聚集算子,能够最大程度得到性能提升。而对于引入了随机性的算子,比如排序算子(Sort)、哈希算子(Hash),则性能提升空间有限。
2 数据类型
2.1 尽可能使用原始类型
你需要尽可能使用原始类型(Primitive type),如 int32, int64 等,来提高向量化的性能提升效率。
一般枚举类型字段往往用于过滤操作,比如国家字段或中国省份字段,可以使用字符串表示,如"China"、"USA",或 "Beijing"、"Xinjiang" 等,但是如果预处理一下,使用 smallint 表示,那么对于过滤操作将会有巨大的性能提升。
2.2 尽可能使用能够覆盖数据值域范围的长度最小的类型
如果数据中的年龄字段用 smallint 表示就足够了,那么就不需要使用 int32 甚至 int64 来表示。
对于传统的执行引擎,不管数值类型是什么,最后结果中的元组(Tuple)中往往是用 Datum 来表示处理,占用 64bits。Datum 可以表示任何 SQL 数据类型的单个值,但其本身不包含任何有关数据类型的信息。所以使用不同类型对于处理速度没有影响。
但是对于向量化执行引擎,数据是连续紧密布局的。数值类型越短,对于 CPU 缓存更友好,SIMD 寄存器也能存储更多数据。所以对某一列进行过滤或者聚集,类型使用 int64 会比 int16 慢四倍。
3 性能分析与调优
3.1 仅用向量化执行引擎
注意!
由于时间和资源受限,我们并没有实现所有数据库标准支持的数据类型、表达式和查询,但为了保证可用性,当遇到无法支持的情况时,我们会部分或者全部使用传统的执行引擎进行运算,现象体现为性能提升不明显或者没有提升。
你可以手动关闭 matrix.enable_mxv_fallback_expression
参数以确定查询是否使用了传统的执行引擎,此参数默认开启。
参数 | 描述 |
---|---|
matrix.enable_mxv_fallback_expression | 对于当前向量化执行引擎不能处理的表达式,是否要回退到传统执行引擎执行,默认开启 |
$ SET matrix.enable_mxv_fallback_expression TO off;
当 matrix.enable_mxv_fallback_expression
处于关闭状态且遇到如下错误,那么说明查询中包含当前版本向量化引擎无法处理的数据类型、表达式或者查询。
$ not implemented yet
3.2 Hash Agg vs. Group Agg
Hash Agg 和 Group Agg 是聚集算子的两种具体实现,有各自的适用场景。Hash Agg 使用哈希表实现聚合,Group Agg 对分组后数据进行聚合。
通常情况下,优化器根据统计信息能够针对查询选择合适的算子。但是如果统计信息不准确或者对查询优化不足,可能会选择一个非最优的路径。这里介绍两个 GUC 帮助分析优化查询。
参数 | 描述 |
---|---|
matrix.enable_mxv_aggregate | 用于控制是否开启 Group Agg 功能,默认开启 |
matrix.enable_mxv_hash_aggregate | 用于控制是否开启 Hash Agg 功能,默认开启 |
两两组合共计四种可能性。
- 都关闭,使用传统执行引擎的聚集功能;
- 都开启,由优化器选择最佳的路径;
- 开启
matrix.enable_mxv_aggregate
且关闭matrix.enable_mxv_hash_aggregate
,那么强制使用 Group Agg 聚集实现; - 开启
matrix.enable_mxv_hash_aggregate
且关闭matrix.enable_mxv_aggregate
,那么强制使用 Hash Agg 聚集实现。
3.3 向量化与 MARS2、MARS3 存储引擎结合
3.3.1 MARS2、MARS3 排序键的选择
在 MARS2 和 MARS3 中,数据都是有序存储的。为了最大化利用顺序性带来的性能提升,最好选择经常使用且过滤效果好的字段作为排序键(即排序顺序涉及的字段)。比如设备监控表,可以采用事件时间戳和设备 ID 作为排序键。
MARS2 和 MARS3 的排序键均只能指定一次,不能修改,不能新增,不能删除。
不同的是,MARS2 在创建表时:
- 需要通过创建索引的方式制定排序的顺序。
而 MARS3 在创建表时:
- 需要通过指定排序列(可以多列)的方式制定排序的顺序。
- 如果排序键是文本类型,且能接受按照字节顺序排序,那么在这个列采用
COLLATE C
能够加速排序。 - 指定排序键的 SQL 关键词:ORDER BY。
MARS2 可以通过如下 SQL 指定一组排序键:
=# CREATE TABLE t (
tag int,
ts timestamp,
name text,
region int
) USING MARS2;
=# CREATE INDEX ON t USING mars2_btree (ts, tag);
MARS3 可以通过如下 SQL 指定一组排序键:
=# CREATE TABLE t (
tag int,
ts timestamp,
name text,
region int
) USING MARS3
ORDER BY (ts, tag);
对于 MARS2 和 MARS3 而言,排序键的选择对于查询的性能都至关重要,数据有序不仅能够加速数据的扫描和存储引擎的块筛选,而且对于排序、聚集也有所帮助。
例如,我们指定某表的排序键为 (c1,c2)
,那么其内部数据按照 (c1,c2)
有序。现在我们想执行以下两种只涉及排序操作的查询语句:
=# SELECT * FROM t ORDER BY c1, c2;
=# SELECT * FROM t ORDER BY c1, c3;
第一个查询不再需要排序,向量化执行引擎会优化掉排序算子。对于第二个查询,c1 已经有序了,那么向量化执行引擎会对这种情况有特殊优化,仅对相同 c1 的不同 c3 进行排序。
只涉及聚集的查询:
=# SELECT sum(c1), sum(c2) FROM t GROUP BY c1, c2;
=# SELECT sum(c1), sum(c3) FROM t GROUP BY c1, c3;
如果使用 Group Agg,那么类似于排序,能够大幅提升性能,只需要顺序扫描数据进行聚集操作即可。对于 Hash Agg,数据是否有序对性能几乎没有影响。如前所述,对于顺序算子向量化执行引擎能最大程度发挥其性能,所以针对上面两个查询,尤其是第一个,使用 Group Agg 往往耗时更低。
综上,需要分析和结合业务,根据查询的特征(需要排序、聚集、过滤的列)来选择排序键。
3.3.2 MARS2 的 minmax 索引与 MARS3 的 Brin 索引
根据排序键,MARS2、MARS3 中存储的数据都是有序的,一段连续有序的数据称为 Run。Run 的元信息存储了最小最大值,用于在查询时进行过滤。因为是通过元信息进行过滤,不需要加载数据本身,相比于顺序扫描(先访问数据本身,再进行过滤)其 I/O 开销更低,能更快的进行过滤。
- MARS2 通过建表时启用 minmax 索引的方式实现查询过滤。其建表时不会默认记录最小最大值,需要显式声明。
- MARS3 则通过额外创建 Brin 索引来实现过滤。
下面分别举例:
对于 MARS2 表,示例 DDL 的所有列上都建立了 minmax 索引(实际工作中可以根据需要建立)。
=# CREATE TABLE t (
tag int encoding (minmax),
ts timestamp encoding (minmax),
name text encoding (minmax),
region int encoding (minmax)
) USING MARS2;
=# CREATE INDEX ON t USING mars2_btree(tag);
对于 MARS3 表,创建 Brin 索引示例 DDL 如下:
=# CREATE INDEX idx_mars3 ON t USING mars3_brin(ts, tag);
我们想要从上述 MARS2(或 MARS3)表查询今年某个区域的数据:
=# SELECT * FROM t WHERE ts > '2022-01-01' AND region = 2;
实际上,minmax 索引(Brin 索引)不仅能够使得存储引擎进行块筛选,还能够加速向量化执行引擎的计算。
从存储引擎的角度看,如果一个块(包含某列数据的若干行)的 ts
上最大值小于 2022-01-01
,那么存储引擎不再读取这一块,节省 I/O 资源,同时,也减少了引擎的计算量。
向量化执行引擎会利用 minmax (Brin)信息加速计算。比如某块 Region 的最大值和最小值都是 2
,那么执行引擎只会在该块应用谓词 ts > '2022-01-01'
过滤出符合条件的数据,而不会进行任何和 Region 有关的运算,提高性能。
minmax 索引(Brin 索引)和排序键是独立设置的,前者可以用于所有列,而后者往往只会选择几列。对于过滤、排序等算子的影响不尽相同,但也有交叉,所以可以综合考虑使用这两者以发挥各自长处。
3.3.3 禁用索引扫描
MARS2、MARS3 均提供了索引扫描,但是当前版本的向量化执行引擎只对接了 minmax 索引(Brin 索引)及顺序扫描,没有和索引扫描对接,那么对于一些查询,虽然开启了向量化执行引擎的开关,但是没有使用向量化执行引擎。计划中会出现 Custom Scan (sortheapscan)
字样。
此时,可以通过禁用索引扫描以使用向量化执行引擎。
$ SET enable_indexscan TO off;
MARS2、MARS3 更多详细信息请见 存储引擎
3.4 向量化对接 ORCA 优化器
注意!
目前 ORCA 优化器仅支持直接对接 HEAP,AO 表,不支持 MARS2/3。 与向量化配合使用,仅支持 AOCO 表。
首先,手动开启 ORCA 优化器,optimizer
是会话级别参数,如重进/更换会话需要再次设置才能生效。
=# SET optimizer TO on;
开启 optimizer
参数并正确收集表的统计信息后,向量化即可自动对接 ORCA 优化器生成相应查询计划,例如:
=# EXPLAIN SELECT * FROM t;
QUERY PLAN
-------------------------------------------
Result (cost=0.00..0.00 rows=0 width=20)
One-Time Filter: false
Optimizer: Pivotal Optimizer (GPORCA)
(3 rows)
注意!
更多 ORCA 参数请见Greenplum ORCA 优化器配置参数。