陈焕生
Oracle Real-World Performance Group 成员,senior performance engineer,专注于 OLTP、OLAP 系统 在 Exadata 平台和 In-Memory 特性上的最佳实践。个人博客 http://dbsid.com 。
Database In-Memory (DBIM) 是 Oracle 在 12.1.0.2 中引入的新特性,旨在加速分析型 SQL 的速度。In-Memory Columnar Store(IM列式存储)是位于 SGA 中独立于 Buffer Cache 的内存区域。只要设置表的 inmemory 属性,就可以把表的数据加载到 IM 列式存储。数据可以同时存在于 buffer cache 和 IM 列式存储,传统数据按行组织,以数据块为单位存于 buffer cache 和磁盘上,数据在 IM 列式存储中按照列式组织的。传统的 OLTP 应用依然通过 buffer cache 修改数据,Oracle 通过内部机制保证行式存储和列式存储的事务一致性。分析性的 SQL 从 IM 列式存储中扫描数据,避免物理读成为性能瓶颈。
与表空间类似,表空间中包含表或者索引等 segment,segment 由 extent 组成;IMCS 包含多个 In-memory segments,每个 IM segment 以 In-Memory Compress Unit (IMCU) 为存储单元。对于大部分 In-memory 压缩格式,每个 IMCU 默认包含 512k 行记录。
DBIM 带来的性能飞跃主要来自两方面,更详细的介绍请参考官方白皮书 (http://www.oracle.com/technetwork/database/in-memory/overview/twp-oracle-database-in-memory-2245633.html):
硬件方面:
软件特性方面:
默认为 distribute auto。自动选择以上三种分布方式的一种。对于分区表,会选择 by partition 或者 by subpartition 的方式,取决于哪个分区维度的 cardinality 更大。比如表有 64 个分区,每个分区有 32 个子分区,分区维度的 cardinality 更大,那么选择 by partition 的分布,每个分区的所有数据都分布在一个实例上。如果表有 32 个分区,每个分区包含 64 个子分区,会选择 by subpartition 分布,子分区循环分布在各个节点。为了利用 Partition Wise Join,在连接键上所做的 hash 分区,匹配的 hash 分区会分布在相同节点上。对于非分区表,只能为 by rowid range 分布方式,数据以地址区间为单位分布到每个实例。
RAC 是 share everything 架构,通过 Cache Fusion 机制,数据块可以通过 interconnect 在实例间传输。DBIM 在 RAC 上采用 share nothing 架构,IMCU 不能在实例间传输,每个实例上 IMCU 只能由当前实例的进程扫描。这带来两个问题:
本文我们通过一个星型模式上的测试,阐述 DBIMRAC share nothing 的分布架构带来的挑战和解决方法,以及如何合理使用 DBIM 特性,获得最佳的性能。
这是一个典型的星型模型,事实表 lineorder 有 3 亿条记录,维度表 part/customer/supplier 的数据在 1 百万左右,三个维度表没有进行分区。为了说明使用 DBIM 时,分区数据倾斜可能造成并行执行倾斜的问题,我使用事实表 lineorder 的 LO_STATUS 字段做列表分区,使每个分区的数据量和大小都严重倾斜。
表名 | 行数 |
---|---|
lineorder | 300005811 |
part | 1200000 |
customer | 1500000 |
supplier | 100000 |
通过以下脚本加载到把 4 个表加载到 IM 列式存储,首先我们选择 distribute auto 的分布方式,因为测试环境为 Exadata 两个节点的 RAC,为了模拟非 Engineered system 平台的 RAC,我需要指定 No Duplicate 属性,优先级 critical 使得 IMC 表在后台可以被自动加载,压缩级别为默认的 query low。
从 gv$im_segments 可以看到,表 customer 通过 by rowid range 分布加载到节点 1 和 2;bytes_not_populated 表明多少数据没有被加载到当前实例。表 part 和 supplier 较小没有分布到两个节点,part 被加载到节点 1,supplier 被加载到节点 2。对于事实表lineorder,实际是按 by partition 被加载到两个节点的,因为每个分区的 IMCU 都只存在于一个节点。而且在 by partition 的分布模式下,分区数据的倾斜会造成 IMCU 分布的倾斜。其中最大的分区 P_STATUS_COM 大小为 28.6G,全部分布示例 2,在实例 2 所占 IM 列式存储大小为 14.3G,压缩比大概为2。
因为 IMCU 被分布两个实例上,为了从 IMCU 获得所有的数据,而不是从 buffer cahce、global cache 或者通过直接路径读得到,我们必须使用并行查询,而且查询的并行度必须至少为实例的个数,才能保证每个实例都有一组并行进程扫描 IMCU。
并行查询时,为了使 QC 意识到到 IM Segment 的分布方式和每个 IMCU 位置,并分配相应的并行进程从 IMCU 中扫描数据,必须使用 AutoDoP (parallel_degree_policy=auto)。这是强制的要求,设置 parallel_degree_policy=limited 也不行。如果不使用 AutoDoP,而且非 engineered system 平台,IMCU 不能在每个实例复制 (duplicate all),sql 执行的过程中将很可能出现物理读。
下面是一个例子:
DBIM 是 share nothing 的架构,IMCU 不能在实例之间传输。下图为使用 AutoDoP 全表扫描 lineorder 时各个并行进程消耗的 CPU 资源。因为分区 P_STATUS_COM 只分布在实例 2 上,实例 2 上两组并行进程完成了绝大部分扫描工作,实例 1 的并行进程基本处于空闲的状态。下图直观显示了 IMCU 分布倾斜会造成并行执行倾斜,无法同时利用两个节点的 CPU 能力。这就是为什么 db time=3.6s,DoP=4,执行时间还需要 2s。理想情况 4 个并行进程平均工作的话应该执行时间应该在 1s 之内。
parallel_degree_policy=auto
对于星型模型的并行查询时的执行计划,一般都是一颗右深树。对维度表的并行分发 (distribution) 都是 PX SEND BROADCAST,事实表因为数据量大不用进行分发。因为 Lineorder 大部分 IMCU 都分布在实例 2,数据通过实例 2 上的并行进程扫描过滤之后,在之后整个的执行路径中,lineorder 的数据不会再分发给实例 1 的并行进程,后序大部分数据的 hash join 和 hash group by 操作,都由实例 2 上的负责扫描 IMCU 的并行进程完成的,这时并行执行倾斜的情况会更严重。我将在这一小节通过一个典型查询阐述这种情况。
测试之前的准备:
设置 _px_replication_enabled=false,使每个维度表都使用 PX SEND BROADCAST 的方式分发,12c 中,维度表可以由每组扫描事实表的并行进程重复扫描,避免使用 BROADCAST 分发,为了方便读者熟悉执行计划,使每个维度表都进行 broadcast 分发,我在这个测试中特意关掉这个特性。实际使用中,不建议修改这个参数。
设置 optimizer_adaptive_features=false 和 optimizer_dynamic_sampling=4 是为了避免使用 in-memory 查询时出现硬解析过长的情况。
我们执行三次查询,对比不使用 Smart Scan、使用 Smart Scan 和使用 IMC 查询时的性能。
查询的 sql 如下:
为了阅读的方便,在下面的输出中,三次以上查询的 sql 和 125 条结果集被省略。
以上输出看到,不使用 Exadata 的杀手锏 Smart Scan,sql 执行需要 28s;使用 Smart Scan 执行时间为 5s;启用 IMC 查询时,执行时间为 14s。下面我们通过 sql monitor report 来分析三次执行的性能差别,尤其是 IMC 查询为什么比使用 Smart Scan 的版本慢。(P.S. 我常常觉得惊讶:DBA/开发并不习惯使用 sql monitor report 调试问题。从 11g 开始,sql monitor report 无疑是研究 Oracle 性能问题最重要的利器,大部分的性能问题都可以从 sql monitor report 中找到原因或者线索,推荐读者在实际工作中使用 active 格式的 sql monitor report 分析问题)
By rowed range 重新分布之后,P_STATUS_FKC/P_STATUS_PRP/P_STATUS_COM 三个分区的 IMCU 分布在两个实例上。Bytes_not_populated 列不为 0,说明这个 segment 有多少数据不在本实例加载。对于最大的分区,P_STATUS_COM 平均分布在两个实例上。
重新执行查询,sql 的执行时间为 8s,相对之前 by partition 的分布方式,减少了 6s 的执行时间。db time 依然为 28.3s。此时 AAS=4,实例 1 和 2 上 parallel set 2 的并行进程平均分担了工作。By rowid range 的分布方式消除了 IMCU 在两个节点分布倾斜和并行执行倾斜。
对于 RAC,无论是传统的 cache fusion 机制或者集中式存储,都是 share everything 的,数据可以通过 interconnect 或者 IO 在每个实例共享。但是 RAC 上使用 DBIM 特性时,IMCU 是不能在实例之间传输的。这就是促使我最初写这篇文章的原因,从 RAC share everything 的架构切换到 DBIM share nothing 的架构,原来一些不合理的设计,可能会开始影响 DBIM 的使用效果。对于原来 share everything 的架构,这些问题可能并没有暴露出来。比如这个例子中的 Lineorder 列表分区策略导致分区数据倾斜,这种分区倾斜在不使用 DBIM 的时候并没有造成性能问题,因为并行查询的操作为“PX BLOCK ITERATOR”,granule 为 dba (data block address) range,可以确保分配给每个并行进程扫描的数据量是大致相当的。当加载到 IM 列式存储时,如果选择 by partition 的方式,分区的倾斜就会导致 IMCU 分布倾斜,每个实例的 IMCU 只能由当前实例的并行进程扫描,造成并行执行倾斜,影响 sql 的响应时间;如果对表 Lineorder 有大量的类似查询,就会导致实例 1CPU 空闲, 实例 2 CPU 繁忙的情况,影响系统的扩展性。
对于这种方式,如果不能更改分区策略消除分区数据倾斜,那就需要使用 by rowid range 的分布方式,确保没有 IMCU 分布倾斜。
至于 by rowid range 分布之后,IMC 查询 (8s) 为什么依然比使用 Smart Scan 的版本 (5s) 慢呢?首先,对 lineorder 的 IMCU 扫描的时间“In Memory CPU”占总 db time 的 44%,为 11.32s,而使用 Smart Scan 时,IO 的等待只有 3s;更重要的是,in-memory 查询时,每个 parallel set 2 的并行进程的执行路径更长:1,扫描 IMCU 使用 bloom filter 过滤数据; 2,过滤完的数据需要通过 3 次 hash join;3,hash group by。而 Smart Scan 的版本中,并行进程不需要花费 CPU 扫描过滤数据,这部分的工作卸载到 Cell 节点完成,执行路径更短,效率更高。Cell Offload Efficiency 达到 98% 是非常理想的情况,实际中,Exadata 平台使用 DBIM 特性时,IMC 查询一般会比 Smart Scan 快一些。
为了尝试 Vector Group By Transformation,加上 vector_transform hint,重新执行 sql,只需 3.71s。
使用 In-memory aggregation,sql 执行接近 4s,db time 为 12.3s,说明整个执行计划效率更高,消耗的资源更少,并行执行有 4 个 parallel group,AAS 最高的时候为 16。在设计中,优化器对于 Vector group by transformation 的代价估算趋于保守,虽然有些场景使用 vector group by transformation 执行时间更短,但是优化器对 vector group by transformation 执行计划的 cost 估算更高,所以默认没有选择进行 vector group by transformation。实际使用可能需要使用 VECTOR_TRANSFORMhint 手动打开这个特性。
我会对 RAC 上使用 DBIM 的常见问题做一些解释。
1. 使用 AutoDoP 如何控制并行度?
12c 对 AutoDoP 做了增强,可以使用以下参数对 AutoDoP 做一定的控制:
2. AutoDoP 并行度不足的已知问题。❶
AutoDoP 模式下,一条 sql 启用并行执行之前,优化器会检查两个条件:
Bug 18960760(已经在 12.1.0.2.0 包含这个 fix)中对于 IMC 查询跳过了条件 1,如果查询中存在一个 IMC 表被分布到多个实例。但是查询相对较小的 IMC 表时,DoP 可以依然可能为 1 或者不足实例的个数,Bug 19913129 会绕过第 2 个条件(目前这个 fix 还没发布)。
解决方法:
在 AutoDoP 模式下通过 hint 或者在 session 级别指定并行度,就像之前的例子中在 sql 中使用 parallel(4):
3. 把 IMC 表完全加载到 IM 列式存储之后,查询时为什么还有大量的物理读?
以下是常见的原因:
4. 在当前的 RAC 上,使用 service 做应用分区,负载隔离。启用 DBIM 时,一定需要把 IMC 表分布到每个实例吗? 可以指定加载到其中几个实例吗?❷
DBIM 支持在系统级别设置可以 parallel_instance_group 为某个 service,通知后台的加载进程 (w00) 把 IMC 表分布到支持此 service 的一个或者多个实例。为了从 IMCU 中获取全部的数据,应用也需要使用同一个 service 连接。这里举例说明。
Service IM12C_SINGLETON 跑在实例 1。
通过设置 parallel_instance_group参数,重新加载 Lineorder 之后,Lineorder 会被全部加载到实例 1。(注意 service 名字是区分大小写的)
对于 DBIM 和 Service 更详细的说明,可以参见 Oracle product management team 的博客 (https://blogs.oracle.com/In-Memory/entry/oracle_database_in_memory_on1)。
按 service 分布时,DBA 需要小心维护 parallel_instance_group 这个参数,在系统级别修改这个参数会导致所有 priority 不为 none 的 IMC 表动态重新分布。
5. DBIM 和 failover,当一个实例 down 掉之后,这个实例的 IMCU 会分布到剩余的实例吗?
因为把数据从磁盘加载到 IM 列式存储,生成 IMCU 是一个昂贵的过程(通常是一个 CPU intensive 的过程),某个实例 down 掉之后,这个实例上的 IMCU 并不会立即分布到其他实例,而是等一段时间,如果这个实例短时间复活了,那么只需要重新加载属于自己的 IMCU 即可。如果 down 掉的实例没有及时启动,这个 IMCU 就会在后台自动加载到其他实例 (priority != none),或者在之后首次扫描的时候触发重新分布 (priority = none),假设其他实例 IM 列式存储还有空闲的空间。
不同的分布方式重新加载的过程有些不同:
Bug 20097849 - ON IMC RAC WHEN ONE OR MORE INSTANCE DOWN WHOLE IMC TABLE MAY BE REDISTRIBUTED
当 down 掉的实例复活之后,所有属于这个实例的 IMCU 都会加载回来。
6. IMC 表分布方式的选择。
选择何种 IMCU 分布方式需要结合分区方式和典型的查询如何扫描数据综合决定。如果当前系统的分区策略合理,那么可以使用默认 distribute auto 的方式,这里举两个例子:
Distribute auto 不合适的情况:
7. 为了更稳定 DBIM 版本,我是否应该等到 12.2 再考虑升级?
根据 Note 742060.1,12.2.0.1 计划在 2016 上半年发布,DBIM 这么酷炫的特性你真的要等到一年半之后再考虑升级吗?
12.1.0.2 已经是 12.1 的最终版本,之后不会再有 12.1.0.3。BP1 12.1.0.2.1目前已经发布,建议测试的时候打上这个 Bundle patch,patch号为:19189240。
总结
本文介绍了 DBIM RAC share nothing 的架构在对IMCU分布,并行执行和执行计划方面带来的挑战,以及相应的应对方法。希望读者通过本文能够更好的了解 DBIM 特性,在测试或者实施 DBIM 特性时,能够获得最佳的性能。
在我看来,DBIM 是 Oracle 继 Exadata 一体机之后,最激动人心的特性。基于 DBIM,非 Exadata 平台的分析性应用,可以享受媲美甚至比 Exadata Smart Scan 更快的速度。DBIM 的精彩不容错过!