如今,超过 1,000 名客户使用 Apache Impala 来支持他们在本地和基于云的部署中的分析。分析师和开发人员组成的大型用户社区受益于 Impala 的快速查询执行,帮助他们更有效地完成工作。对于这些用户而言,性能和并发性始终是首要考虑因素。
确保良好性能和并发性的一项重要技术是有效地使用内存。如果我们可以更好地利用内存,查询排队等待空闲内存的时间就会减少,因此结果会更快地返回。同样,随着可用内存的更好利用,更多用户可以在任何给定时间查询数据,因此更多人可以同时使用仓库。最终结果——更快乐的用户,以及更多的用户。
这篇文章解释了 Cloudera 数据平台 (CDP) 中提供的 Impala 如何能够从可用内存中获取更多的新技术。
Impala 一直专注于效率和速度,使用 C++ 编写并有效地使用运行时代码生成和多线程等技术。您可以在此处阅读有关 Impala 性能和查询技术的先前博客文章 - “ Apache Impala 的新多线程模型”、“保持小查询快速 - Apache Impala 中的短查询优化”和“选择性查询的更快性能”。
分析 SQL的工作负载大量使用聚合和连接。因此,在 Impala 等分析引擎中优化此类运算符的性能和效率可能非常有益。现在,我们将研究一种用于在 TPC-DS 10000 工作负载上将聚合和连接的峰值内存使用量减少多达 50%,并将每个节点级别的峰值节点内存使用量减少 18% 的技术。
Impala 中的聚合和连接都使用哈希表,我们将展示如何减少操作的大小。Impala 中的HashTable类实现包含一个连续的Bucket数组,每个Bucket包含一个指向数据的指针或一个指向名为DuplicateNode的重复条目的链接列表的指针。
这些是Bucket和DuplicateNode的结构(为简单起见,更改了一些细节):
struct DuplicateNode {
bool matched; // 1-byte
// padding of 7-bytes
DuplicateNode* next; // 8-byte pointer to next DuplicateNode
Data* data; // 8-byte pointer to data being hashed
};
struct Bucket {
bool filled; // 1-byte
bool matched; // 1-byte
bool hasDuplicates; // 1-byte
// padding of 1-byte
uint32_t hash; // 4-byte
// bucketData is a pointer to DuplicateNode or
// pointer to Data.
union {
Data* data; // pointer to data being hashed
DuplicateNode* duplicates;
} bucketData; // 8-byte
};
在评估struct的大小时,这些是内存对齐的一些规则,假设是 64 位系统:
根据上述规则,上述代码段中的Bucket注释为每个成员占用的大小,并在需要的地方进行填充。Bucket的总大小为 16 个字节。同样,DuplicateNode的总大小为 24 字节。
我们决定通过从两者中删除 bool 字段来减小Bucket和DuplicateNode的大小,将大小分别减小到 12 字节和 16 字节。但是 12 字节不是Bucket的有效大小,因为它需要是 8 字节的倍数(结构的最大成员的大小)。在这种情况下,我们可以使用__attribute__ ((packed))来确保 struct 打包,使大小为 12 字节。
我们如何实现删除这些布尔值,因为它们需要存在于每个 Bucket 和 DuplicateNode 中?
我们决定删除所有bool成员,方法是将它们折叠成一个已经是struct一部分的指针。
Intel Level 5 提议 64 位内存地址
在 64 位架构上,指针使用 8 个字节存储内存地址。但在 x86 和 ARM 等架构上,线性地址长度限制为 48 位,其中 49 到 64 位保留供将来使用。在未来英特尔的第 5 级分页提案(白皮书)中,它计划在 x86 上放宽对 57 位的限制,这意味着我们可以使用最重要的 7 位——即 58 到 64 位——来存储额外的数据。需要注意的是,即使读取内存只需要 64 位中的 48 位,处理器也会检查有效位 (48…64) 是否相同——即符号扩展。如果不是,这样的地址将导致故障。这意味着折叠指针可能并不总是存储有效的可寻址内存。因此,折叠指针需要在取消引用之前进行符号扩展。
我们使用上述技术将填充、匹配和hasDuplicates 折叠到指针bucketData中。折叠和结构打包后,我们将得到一个12 字节的Bucket大小。同样DuplicateNode可以减少到 16 字节而不是 24 字节。总的来说,我们将这两个结构的内存需求从 40 字节减少到 28 字节,减少了 30%。
在我们的实现中,要求Bucket的大小和哈希表中的桶数必须是2的幂。这些要求是出于以下原因:
当 N 是 2 的幂时,可以使用较快的按位运算 (hash & (N-1)),而不是使用缓慢的模运算 (hash % N)。
因此,从Bucket中删除了一个 4 字节的哈希字段,并将其单独存储在HashTable类中的新数组hash_array_中。这样可以确保sizeof(Bucket)为 8,即 2 的幂。分离哈希的另一个优点是现在不需要打包Bucket 。
我们对该技术进行了广泛的评估,以了解它如何影响性能和内存利用率。我们使用了 3 个基准:
图 2a内存基准
图 2a 显示了内存基准测试的结果。基准名称采用memory_XX_YY格式,其中XX是插入哈希表的值的数量,YY表示唯一值的百分比。我们看到构建哈希表时内存消耗减少了 30%。
图 2b 运行时基准
图 2b 显示了性能基准测试的结果。build_XX_YY表示构建基准,其中插入了XX值, YY是唯一值的百分比。类似地,probe_XX_YY将探测由XX行和YY唯一值构建的哈希表。这些基准测试运行 60 次,并重复 10 次以找出每毫秒的迭代次数。图 2b 显示了对这 60 次运行测量的迭代次数的第 90 个百分位数。由于这种变化,我们观察到这些哈希表操作的运行时间没有显着差异。
我们为此基准使用了 TPC-DS销售和项目表。sales有s_item_id (int)、s_quantity(int) 、s_date(date)列,而items有i_item_id (int)和i_price (double)列。sales有 10 亿行,items有 3000 万行。
我们对销售额进行了 Group By 查询,以测量构建哈希表的性能和内存。
查询:
select count(*) from sales group by s_item_id having count(*) > 9999999999;
分组聚合内存使用 | |||
---|---|---|---|
随着变化 | 没有变化 | ||
峰值分配 | 累积 分配 | 峰值分配 | 累积 分配 |
1.14G | 1.85G | 1.38G | 2.36GB |
图 3a
如图 3a 所示,我们看到峰值分配减少了 17%,累积分配减少了21%。在运行 20 次时,我们没有看到任何性能下降。在这两种情况下,有变化和没有变化的 Geomean 都在 68 秒左右。
为了测量探针,我们在items和sales之间运行了一个连接查询,其中sales在探针端,items在构建端。由于我们仅在提议的连接中的较小表上构建哈希表,因此该基准测试的目标不是测量内存的减少,而是测量通过sales表探测 10 亿行时的任何性能差异。
但是,我们为此目的创建了 3 种销售表:
我们在两次运行中都看到了相似的性能,我们的更改在sales_base上稍快,如图 3b 所示。因此,在减少内存消耗的同时,我们没有测量聚合查询运行时的任何退化。
表类型 | GEOMEAN 超过 20 次运行(秒) | |
---|---|---|
随着变化 | 无变化 | |
销售量 | 110.8551081 | 114.6912898 |
销售额_30 | 103.2863058 | 102.4787489 |
销售额_60 | 84.12813181 | 84.8765098 |
图 3b
我们针对规模为 10000 的 TPC-DS 工作负载评估了新的哈希实现。我们在一个 17 节点集群上运行所有工作负载查询,数据存储在 HDFS 中。
图 4a
对于每个查询,我们计算了单个 Join 和 Aggregation 运算符的最大内存减少百分比。我们只考虑了大于 10 MB 的运算符。根据图 4a,我们发现对于 99 个查询中的 42 个,内存消耗减少了 10% 以上。此外,对于其中 24 个查询,我们发现内存消耗减少了 20% 以上。
在计算所涉及节点的平均峰值内存消耗时,28 个查询显示内存减少超过 5%,11 个查询显示内存减少超过 10%,如图 4b 所示。此外,我们看到 q72 最多减少了 18%。
图 4b
图 4c
考虑到查询在任何节点中消耗的最大峰值内存,27 个查询显示减少了 5%,11 个查询显示减少了 10% 以上,如图 4c 所示。对于 q65,观察到的最大减少量超过 20%。
如上一节所示,我们看到节点级别和操作级别的内存显着减少,而没有任何性能下降。
这种内存效率和性能优化,以及 Impala 中的许多其他优化,使其成为商业智能和分析工作负载的首选,特别是在规模上。现在越来越多的数据仓库在云中完成,其中大部分在 Cloudera Data Warehouse 数据服务中,性能提升直接等同于成本节约。查询运行得越快,资源就可以越快释放,因此用户不再为它们付费。第三方最近的一项基准测试显示 Cloudera 如何在云数据仓库市场上具有最佳性价比。
原文作者:Justin Hayes
原文链接:https://blog.cloudera.com/memory-optimizations-for-analytic-queries-in-cloudera-data-warehouse/