本文分享自华为云社区《GaussDB(DWS)存储引擎:从CU入手优化HStore表》,作者: yd_261437590。
1. 前言
- 适用版本:【8.2.1(及以上)】
HStore同时拥有处理传统TP场景的事务能力和强大的数据分析能力,但是强大的数据分析能力很可能被小CU问题给破坏,另外,将多个CU排序可以增加HStore的数据聚簇性,因此作者通过解决小CU问题和提升数据聚簇性两种方式对HStore表的存取能力进行优化。
2. HStore简介
2.1 行存储
传统OLTP(OnLine Transaction Processsing 联机事务处理)场景与功能、业务强相关,数据需要进行频繁的增删改查,这时比较适合使用行存储式。行存储的优势主要有两个方面:首先是点查性能好,在点查场景下可以直接索引到某行数据的元组位置;其次就是更新效率高,行存储在实时并发入库,并发更新方面依然有着比较大的优势。
2.2 列存储
传统行存储形式的数据库主要为业务服务,但是如果涉及到分析查询场景,特别是在数据量大且复杂的查询时,就会遇到性能瓶颈了,性能瓶颈是数据存储方式决定的。因此OLTP(OnLine Transaction Processsing 联机事务处理)场景一般会交给列存储引擎去做。列存储的优势主要有两方面:首先是批量查询性能好,当分析查询只涉及某列或者某几列,不需要访问无关列,特别是在表的宽度比较大时(如一千列),优势更加明显;其次就是列存储的压缩性能更高,原因就是因为数据按列存储,单列类型相同。
列存储引擎的最小存储单位是CU(Compression Unit, 压缩单元):一个CU是由表中某一列的一部分数据组成的压缩数据块, 通过(cu_id,col_id)标识一个CU。
图1 列存储
另外,列存引擎通过delta表,避免了小CU的产生,显著提升列存表单条导入的性能,同时解决由于小CU导致的数据膨胀问题。当单条或小批量数据导入到列存表时,需先存入delta表,当delta表中数据积攒到指定行数时再存入新产生的CU中。
2.3 HStore
列存储优势明显,但是劣势也比较明显,传统列存表基本无法支持并发更新入库。随着业务复杂程度的提升,出现了对于实时入库和实时查询有较强诉求的场景,这要求数据库同时拥有处理传统TP场景的事务能力和强大的数据分析能力。这时就可以使用HStore来处理这些场景了。
图2 HStore存储
HStore利用delta表存储update/delete/insert等操作信息。之后依赖后台常驻autovacuum来做merge操作将数据写入主表。
HStore的Delta表与普通列存Delta表的对比
数仓类型 | 列存的delta表 | HStore的delta表 |
---|---|---|
表结构 | 与列存主表的表定义一致 | 与主表表定义不一样。 |
功能 | 用于暂存小批量insert的数据,满阈值后再merge到主表,避免直接insert到主表产生大量小CU。 | 用于持久化存储update/delete/insert等操作信息。 |
缺陷 | 来不及merge导致delta表膨胀,影响查询性能,同时无法解决并发update的锁冲突问题 | 依赖后台常驻autovacuum来做merge操作。 |
利用特有的delta表,HStore解决了传统列存表CU锁的问题,支持上游upsert/update等操作实时并发入库。同时还能保证和普通列存表相近的数据分析与数据压缩能力。
HStore表技术特点如下:
- 完整的事务一致性:支持全面的事务能力,数据插入或者更新提交后即可见不存在时延,保证数据ACID一致性。
- 全面的功能支持:提供和当前列存一样全面的功能和语法支持。
- 查询性能好:适用多表关联等复杂AP查询场景,相对于传统行存表,拥有更完善的分布式查询计划与更先进的分布式执行器,性能优势明显。支持复杂的子查询和存储过程,支持主键等传统索引能力去重和加速点查,也支持分区、全局字典、局部排序等方式进一步加速AP查询。
- 入库快:彻底解决列存CU锁冲突问题,支持高并发的更新入库操作,典型场景下,并发更新性能是之前的百倍以上。
- 高压缩:数据在MERGE进入列存主表后,按列存储具有天然的压缩优势,能极大地节省磁盘空间与IO资源。
3. 小CU问题
3.1 问题诱因
- 有些实时表入库量并不大,不定期会有入库,因为merge的判断标准有两个:行数或者时间,超过时间没有入库后也会强制merge,这种情况下merge产生的CU的行数不可控,可能产生小CU;
- 对于缓慢变化维表来说,可能很长时间才改变一次,每次都可能产生一个小CU,虽然不会有太多这种小CU,但长期运行后,这种维度表数量还很多的情况下,小CU的数量就会到达影响系统性能的级别;
- 频繁upsert、update、delete等更新后,CU中大部分数据被标记删除,这样的CU虽然会被列存vacuum通过填充NULL进行回收,但是依然会导致小IO和cudesc表的膨胀,进而影响性能。
3.2 问题影响
- CUDesc并不会因为CU变小而变小,因此当小CU过多会导致存储利用率过低。比如一个1000列的大宽表产生的CU只包含1行数据,但是因为每一列都会在CUDESC表中记录,CUDesc也会增加一千多行数据;
- 只剩下几十行甚至几行的小CU会引发大量的小IO;
- 粗过滤效率降低,因为CUDESC表中会存储CU的最值,当进行查询时可以先通过最值进行粗过滤,但是如果CU中数据太少导致数据范围小,则会降低粗过滤效率;
- 降低压缩率。因为数据压缩是以CU为单位的,但是CU过小会导致压缩表现达不到预期
可以认为0 CU其实是小CU的一种特殊极端情况,0 CU相对非0的小CU对于性能影响小很多,因为0 CU只用加载deletemap。
图3 CU管理
3.3 解决思路
3.3.1 小CU合并
小CU合并不是直接产生新的CU,而是将小CU数据重新插入到delta表后标记删除,然后依赖delta表的自动merge攒够后再产生完整的新CU;
图4 小CU合并
图5 小CU合并时单条数据的处理
小CU合并的事务可见性基于现有的csn机制,compaction inprogress或者回滚对外不可见,还是看到老记录,compaction提交老记录就不再可见看到新记录。
具体一个CU中剩余多少条数据才算是小CU,应该是与业务强相关。因此,小CU阈值应该可以使用GUC参数调节3.3.2 0CU清理
0CU的处理比小CU的处理简单的多,我们直接从CUDESC表中将0CU记录删除即可。这里指的删除天然支持MVCC,因此老的快照查询依然可以访问被删除的记录。
小CU合并的过程就是不断的尝试把小于一定阈值的CU标记删除,转移数据到delta中,直到这个CU全部被标记删除后变成0 CU,就可以当做0 CU彻底清理。3.3.3 效果
成功解决小CU问题,并且在小CU合并期间对实时入库性能几乎没有影响(推荐小CU行数阈值下upsert性能劣化1%),但是因为小CU问题的解决,可以很好的解决查询性能劣化,空间膨胀等问题,并且小CU合并完成后,最终实时入库性能还是会有显著提升。
4. 提升数据聚簇性
4.1 需求来源
在对HStore进行点查时,会首先通过CU的min/max来进行粗过滤,我们希望通过min/max过滤掉大部分数据,这就要求每个CU的数据尽可能的接近,而不能过于分散。目前GaussDB已经实现了局部聚簇 (Partial Cluster Key, 简称PCK),在数据批插过程中就会进行排序。但还是会有如下几种情况导致CU的聚簇性无法达到要求:
- 写入数据时,如果不是批量导入,则不会把数据写入排序器,而是直接插入delta表,当delta表merge的时候,也不会先走排序逻辑,而是直接将数据写入CU;
- 当CU中的数据被删除的足够多时,就变成了小CU,聚簇性本身就会变差,就算进行了小CU合并,也依然不会走排序逻辑,而是将数据直接写入delta表,merge流程与1)一致;
- 实际上就是增加数据+删除数据。
4.2 解决思路
通过将HStore中多个CU的数据根据partial cluster key进行排序,生成新的CU再重新写入,新CU的数据会有更高的聚集性,即CU的min,max会在一个较小的区间内。异步排序时的并发处理与小CU合并类似,见3.3.1。
图6 异步排序基本原理
4.3 效果
经过测试,排序后的CU聚簇性极大提升,粗过滤效率的提升与原本的数据特征有关。但是排序过程中会对所有参与排序的CU加CU级锁,此过程会阻塞部分DML操作,因此不建议在业务高峰期使用此功能。
5. 总结
本文主要讲解了如下几个方面:
- 大致介绍了GaussDB实时数仓的重要解决方案:HStore;
- 引出小CU问题并给出了解决方案;
- 从数据的聚簇性作为切入点,提出异步排序来优化HStore表的scan性能;
6. 参考文档
数仓实时入库利器:HStore表原理与应用实践详解。 作者:马俊松(华为云GaussDB(DWS) 技术布道师)
标签:数仓,存储,HStore,merge,delta,数据,CU From: https://www.cnblogs.com/huaweiyun/p/18036137