携程OceanBase开源实践——索引统计功能实现
【作者】
施纬,携程数据库研发工程师,主要负责数据库运维和内核研发。
姜贤富,携程高级数据库研发工程师,主要负责携程数据库监控运维及工具研发,拥有十年运维经验。
【概述】
自从2021年OceanBase开源以来,携程一直致力于其在实际业务场景下的应用实践,探索新一代分布式数据库的可能性。随着携程业务需求的增长和多样化,OceanBase的应用范围也越来越广,与之对应的,我们积累的实践经验也越来越丰富,希望进一步参与到OceanBase开源社区的建设中,推进国产分布式数据库的发展。其中,携程和OceanBase共同实现了索引统计功能,本文将介绍此次开源实践。
【开源共建】
经过2年的探索实践,携程已经形成了比较完善的OceanBase应用体系。2021年,我们将OceanBase初步应用于日志库场景,以评估OceanBase的性能、稳定性及数据压缩率。与此同时,我们进行了周边工具的建设,如发布工具,Canal工具等,并完善了标准化的上下线流程,为深化应用打下基础。随着使用流程的逐渐成熟,以及生产数据量的不断增长,我们拓展了OceanBase的使用场景,将其作为历史冷数据的归档库(见图1),利用其高压缩率节省85%的存储成本。如今,携程越来越多的业务场景使用OceanBase,如结算业务、推荐业务及其他在线OLTP业务场景,得益于分布式的扩容能力,我们可以更加轻松地应对各种高负载的情况。
图 1 OceanBase存储归档冷数据
除了体会到分布式数据库高性能、大容量、灵活扩展等优势外,我们也面临运维体系的挑战。因为我们已经有多年的MySQL使用经验,形成了相对成熟的MySQL开发和运维流程,所以我们在使用OceanBase时,希望它可以尽量兼容现有的各项开发、运维流程,包括数据库的上下线、表结构的发布变更、应用的访问方式、大数据取数等;还能提供超越MySQL的运维潜力。方便我们将OceanBase平滑接入现有的自动化运维系统,同时借助强大的分布式拓展能力进一步提升我们的运维能力。
对此,我们针对OceanBase建立了监控告警系统、慢日志审计系统、数据库迁移工具等,但在使用过程中,我们发现相比MySQL,OceanBase 4.2.1并不能完全满足我们的运维工作需求,例如没有提供类似MySQL的索引统计功能、详细的表级增删改查统计功能等。针对这些问题,我们向OceanBase团队提出了反馈,一方面我们希望OceanBase可以功能更加丰富,完全满足我们的使用需求,另一方面,希望参与分布式数据库的建设,为OceanBase社区贡献一份力量。
基于此,我们很荣幸和OceanBase团队达成了源码共建的合作关系,由我们提出需求协作开发并最终并入社区版源码,目前我们已在OceanBase 4.3中合作完成了索引统计功能(源码链接),下面就该功能的设计和实现进行详细的介绍。
【需求背景】
OceanBase 4.2.1缺乏针对索引在使用上的监控,例如索引被引用的次数,一段时间内的频率,读写访问的次数等。这些统计数据对于数据库的运维是非常重要的,存在很多应用场景:
- 删除索引时需要确认索引是否被访问。用户一般通过创建索引来加速查询的过程,然而过多的索引,可能会占用过多的磁盘空间,查询时的索引选择和争用的开销也会上升,索引走错的概率也会增大,因此需要清理冗余索引。业务SQL发生变化时也可能需要下线老的索引,索引统计功能可以极大地降低删除索引的风险。
- 通过采集各个时间段的索引使用情况,我们可以感知整张表的访问热度趋势。例如图2中的例子,用户可以根据图中趋势分析出这张表的访问高峰时段,以及各个不同索引的访问趋势,在添加新索引后可以观察趋势验证索引效果,例如图中idx_b_c索引添加之后idx_b索引就逐渐不再被访问。
- 通过采集索引的读写访问次数,可以分析表的访问模式,以进行进一步的优化。MySQL的索引统计功能就提供了表级别的读写统计数据,利用这些数据,用户可以采集每张表的读写访问趋势,从而判断这些表是读多写少还是写多读少等并根据访问模式选择更合适的优化方案,例如使用缓存、优化写入方式等。
图 2 索引访问数据示例
传统的数据库MySQL和Oracle都提供了索引监控功能,前者利用performance_shcema中的内存表详细统计了每个索引被访问的次数及各种类型语句(增删改查)的执行次数,携程就是利用这些数据评估各个表的和索引的访问情况和负载类型;后者则将索引统计信息定期持久化到磁盘,DBA可以通过特定视图访问。
综上所述,我们认为索引监控功能对于OceanBase也是非常有必要的。
【设计方案】
为同时适应MySQL和Oracle模式下的用户访问,OceanBase索引统计功能采用持久化存储设计,数据存在内部表,两种模式下的用户可以通过不同视图访问;由于OceanBase是多租户设计,索引统计功能也需要具备租户级别的控制和统计能力;另外,为了尽可能降低性能影响,参考Oracle,该功能提供抽样统计模式选项。
如图3所示,索引统计采用增量数据写入内存,定期刷到内部表的方式实现,各个租户的增量数据、相关配置、定时任务以及内部表都是独立的。不同的SQL线程在访问到索引时会更新其租户下的索引统计数据,各个租户的定时任务会负责配置的刷新和内部表的更新,用户可以通过视图查询索引信息。
图 3 索引统计实现
【实现细节】
索引统计功能的主要流程可以抽象为三个阶段。
- 数据收集:收集索引使用数据。
- 数据管理:在内存中维护管理索引统计数据。
- 数据上报:将收集到的数据持久化到内部表中。
图3中的流程就是这三个阶段的具体体现。另外,我们在实现的过程中也重点考虑了索引统计对数据库性能的影响,并进行了相应的优化。
【阶段1:数据收集】
考虑到兼容MySQL和Oracle的索引统计功能,收集的数据包括索引的使用次数、上次使用的时间、扫描的行数、各种类型查询的数量等。OceanBase 支持 SQL / OBKV(TableAPI) 两种方式对数据进行访问,数据统计需要考虑 SQL 和 TableAPI 两种访问形式。但由于 TableApi 使用与 SQL 模式不同的TableScan算子,ObTableApiScanExecutor 和ObTableScanOp,若在TableScan算子层统计索引使用,那么就需要分别适配 TableAPI 和 SQL 两种模式的算子;如果在更底层DAS层进行统计,则只能按分区粒度统计,无法获取表级数据。因此决定在TableScan算子层实现两种统计方式,在算子关闭时更新内存中的索引使用信息。目前,索引统计会收集索引的访问次数和访问时间。
图 4 索引统计数据收集流程图
【阶段2:数据管理】
各个租户会维护自身的索引统计数据,具体到内存中,增量数据储存在多个hashmap中,租户初始化时会根据自身的内存和CPU资源选择合适的hashmap数量来保证最优性能。索引数据更新时会随机选择hashmap写入数据,目的是降低并发访问时的锁竞争,同时数据采样也发生在此处,将根据租户配置的采样比随机写入数据。选择在写入时采样一方面可以减少性能影响和内存占用,另一方面不在数据上报时采样可以更精确地检测索引是否被删除。
【阶段3:数据上报】
租户内存中的增量数据会通过定时任务的方式写入内部表完成数据上报,该任务使用OceanBaseSharedTimer的方式实现,避免消耗额外的线程资源。上报过程中会对内存中每个hashmap进行全量的遍历,通过batch insert的方式将增量数据写入内部表,这个过程还会检查对应索引表是否已经删除,hashmap和内部表中的数据将在索引删除一段时间之后被删除。
【索引统计性能优化】
在开发和测试过程中,我们发现索引统计信息写入内存的过程有可能对整体的语句执行主流程产生性能影响。从图4的流程也可以看到,索引统计发生在整个语句执行的关键路径中,因此这一步的性能优化尤为重要。最开始我们采用单个hashmap进行数据存储,在数据上报时进行采样的方式。
由于统计是在TableScan算子关闭时写入hashmap,每次执行使用索引的语句都会对hashmap进行写入,在高并发访问相同的索引场景下,由于需要写入相同的hash桶,就会产生较多的锁竞争,从火焰图可以看到,整个调用栈中锁等待占用了较长时间。针对这个问题,我们和OceanBase的同学进行讨论后决定对hashmap进行分片,使用多个hashmap的方式来减少锁竞争,并将采样逻辑移到数据收集阶段,减少写hashmap的次数,这样一来,索引统计整体的性能影响就降低了很多。当然,用户也可以动态配置采样比例或者暂时关闭采样模式以获取更为准确的索引统计信息。
图 5 单Hashmap模式访问火焰图
【数据展示】
内部表:__all_index_usage_info
视图:DBA_INDEX_USAGE, CDB_INDEX_USAGE
内部表用于定时任务内部数据存储,视图则是呈现给用户使用,兼容Oracle的DBA_INDEX_USAGE字段,目前支持索引使用次数和使用时间的展示,后续可以进一步拓展。
图 6 索引统计视图示例
【展望未来】
本次开源共建,一方面丰富了OceanBase的功能,使其满足了我们的使用需求,另一方面也加强了我们团队的源码开发能力,为日后进一步的使用和共建打下了基础。非常感谢OceanBase团队提供的共建机会和技术支持,我们希望以后可以继续合作,共同推进国产分布式数据库的发展,实现共建共赢。