作者:康凯森,StarRocks PMC,负责查询方向的研发(本文转自其个人博客“编程小梦”)
从 2015 年开始,我在美团先后维护和研发过 Apache HBase、Apache Kylin、Apache Druid 和 Apache Doris,对大数据系统和 OLAP 数据库有了深入理解。
2020 年开始,我加入了 StarRocks 社区。我们先后打造了业界领先的向量化执行器、CBO 优化器、Pipeline 并行引擎、支持高效 Update 的存储引擎和极速数据湖分析。支持存储分离的 Cloud Native 版本也即将面世。
一路走来,随着 StarRocks 项目攻克一个又一个技术难题,解决一个又一个用户需求和难题,服务着越来越多的用户,我对如何打造一个强大且成熟的数据库有了更深的理解。在这篇文章中,我会和大家介绍打造一个强大且成熟的数据库都有哪些难点。
无论是一个年轻的社区团队,还是一家大公司的数据库部门,打造一款强大且成熟的数据库都会有下面两个本质矛盾:
有限人力和无限工作之间的矛盾
各种各样的用户需求,无止境的性能优化,永远也修不完的 Bug,一个又一个 Killer Feature 的打造,时刻不停的代码重构,增加不完的测试 Case,不间断的技术调研和系统设计。这些工作都需要大量人力,但无论哪家公司,人力总是不足的,优秀的数据库人才更是稀缺。
飞速增长与成熟稳定的矛盾
如果数据库的代码不大变,面对的应用场景不大变,我们让一个数据库变稳定会容易很多。但是如果一个数据库的代码在飞速变化和增长,应对的应用场景不断丰富和变化,让一个数据库稳定下来就会很难。
一款数据库想要在产品和商业上取得成功,想在很多竞品中脱颖而出,就必须有清晰的定位和自己的独特性。一款数据库的独特性就是自己的价值点,比如目前 StarRocks 的价值点就是极速统一。
一旦定位和独特性定义清楚,便意味着产研资源重心和方向的倾斜。而一款数据库想要让自己的独特性成为 Killer Feature,就必须超越所有竞品。这就意味着,这个数据库团队必须有足够强大的创新能力,足够强大的工程能力,才能做到其他数据库公司一定时间内无法做到的。
一个数据库的架构决定了一个数据库未来的上限,决定了一个数据库未来支持更多用户需求的难易程度。如果架构设计有误,后面的很多功能和优化成本就会很高,甚至无法实现。不过在当下,云时代数据库的架构逐渐趋同,想要有足够的独特性和明显的优势还是比较困难。
Killer Feature 的创新性和独特性
一般数据库在产品功能上肯定会推出几个 Killer Feature,作为自己产品的卖点。但是想作为 Killer Feature,就必须比竞品好很多,这时候 Killer Feature 就必须拥有一定的创新性和独特性。
持续不断的用户需求
用户越来越多,用户的需求自然就会越多,有些用户的需求很小众。但有时候为了服务一个用户,我们却不得不做,而且有时候做了还会给系统带来额外的复杂性。
各种各样的认证
认证里面的一些功能其实并不是你的数据库产品所必须的,做了不会明显增加产品竞争力。为了满足并获取更多企业级用户,还是得投入大量人力去通过认证。
用户迁移时的功能对齐
当一个新系统越来越好,要扩大市场规模,必然要去替换用户已有的旧数据库,但是替换过程中经常会遇到语法、函数、功能不对齐的场景。这时候,如果这个用户很重要,你就不得不去做一些和传统数据库功能对齐的事情。
系统越强大会越复杂
越强大的优化可能跨模块越多:比如 StarRocks 的低基数优化和导入、查询、Delete、Update、Compaction、Schema Change、 MVCC 等很多模块都有关系,所以这类功能的测试本身就会很复杂
一个新的功能或者优化可能会打破旧的功能或者优化的假设:比如 Tablet 并行可能和 Local exchange 有冲突,比如 Query Cache 可能和 Shared Scan 有冲突
系统越成熟,新的功能和优化要求越高,大规模使用周期越长
新系统的第一个版本往往会给用户建立一个 Baseline,之后的版本做的功能和优化就必须考虑所有用户的场景,在自己的用户场景下没有或者很少有 Bad Case
用户使用一个系统稳定后,升级的动力会越来越弱,所以越靠后的版本得到用户大规模验证的周期也会更长
CBO 优化器 Plan 的稳定性和正确性
统计信息的变化会导致查询 Plan 发生剧变
某些优化就是会导致部分查询变快, 部分查询变慢
选择度估计和基数估计一般都假设了数据的均匀分布,但这和实际情况并不相符
统计信息收集如何做到相对准确又不耗费大量的系统资源
单核性能优化
单核性能优化是一个没有止境的过程,我在 StarRocks 技术内幕:向量化编程精髓 一文中已经解释了单核优化的关键点和方法论,所有算子和函数的深度优化是需要数十人年的事情。
多核扩展性优化
StarRocks 单核性能登顶 ClickBench 后,我们在优化多核性能上投入了大量精力。相比单核,突破多核扩展性的瓶颈要更困难些,目前已经做了大量优化、还远远不够。不同类型的查询在高并发下会遇到不同的瓶颈:Lock,线程池,RPC,调度问题,NUMA,CPU Cache,内存管理,IO 异步等。
多机扩展性优化
多机的扩展性的常见瓶颈点主要包括:
RPC 的扩展性
元数据的扩展性
如何解决数据倾斜
如何通过调度策略解决热点,充分调用整个集群的算力
优化器要确保每个算子,函数都可以生成分布式的执行计划,不会有单点执行的瓶颈
存储引擎的优化
存储引擎需要在导入性能和查询性能之间进行权衡,一般情况下,导入时候做的事情越多,查询时候的代价就更低些:
更新能力的持续优化
导入能力的持续优化
压缩和编码
Compaction 策略的持续优化
事务能力的优化
内存使用上的优化
各个级别 Cache 能力的优化
针对特定场景的性能优化一般会增加系统复杂性
数据库一般都是面向通用场景开发,但是在特定场景下可以获取更多信息和上下文,这时就可以进行更多的针对性优化。但这样也会带来问题,比如系统的代码逻辑更加复杂,测试和维护的难度更高。
如何保证性能不退化
当系统复杂之后,多人协作经常会出现的问题是,一个人之前精心写的一段对性能影响很大的代码,被后人不小心改掉了。或者是优化 A 优化了某些场景的性能,但是却导致之前优化 B 的优化失效了。
如何能 Cover 不同的硬件环境
数据库这个复杂的软件是构建在硬件之上的,硬件是决定性能的基础。但是 CPU 有不同的型号,网络带宽有高有低,磁盘的吞吐和 IOPS 也有很大的差别,有可能一些软件层面的优化只对某些硬件环境生效。
一款数据库的成熟,主要体现在稳定性上,而打造一个稳定的数据库有如下难点:
SQL 是声明式的
机器可以生成成千上万行的 SQL,SQL 各种算子和函数的组合是无法穷尽的,要保证任何一条 SQL 没有 bug,是极其困难的
SQL 里面的 Null 和 Nullable 是比较令人烦恼的,不仅对性能有很大影响,对正确性也有较大影响
成百上千的用户场景
每个用户的硬件配置,环境信息,应用特点不一样,都可能引发不同的问题。
各种各样的集群规模
很多时候,只有当集群规模、数据量、并发量到一定程度,一些稳定性和扩展性的问题才会暴露出来。如何在产品发布之前,在有限的资源下,通过测试暴露这些问题也是一个难点。
功能的组合
很多时候,一个功能单独 Work 没有问题,但是多个功能相互影响时,一些 Bug 才会暴露出来。比如节点下线,触发数据的均衡和复制,又会触发数据版本的问题,进而触发查询的问题。比如导入、查询、Compaction、系统任务对 IO 资源的共同影响,这些复杂功能组合时的测试难度会更大。
函数的预期行为没有标准
比如 Date 类型和数字的比较,字符串和数字的比较,应该是直接报错还是隐式转换,隐式转换的公共类型转成啥。这些其实都没有统一的标准,每个数据库都有自己的实现。关键问题是即使改成最合理的表现,一些用户可能会因为习惯自己熟悉的数据库的表现,觉得当时最合理的表现是不合理的。
还有聚合函数溢出后行为,Decimal 运算精度的确定,函数一些异常行为是抛异常还是转 Null。我们不仅要兼容用户期望的行为和正确性,还要兼顾正确性和性能。
多版本维护
由于快速迭代和发展,我们必然要维护很多版本,这给稳定性提出了更大的挑战:
定位,复现问题成本会更高,比如研发得用指定版本的代码部署环境,复现问题
解决问题后,也必须给所有版本 Cherry-pick,这时候很容易遗漏某个版本
要确定某个 Bug 到底在哪些版本修复了,成本也比较高
某个版本发生大的改动或者重构,之后的 Bug Fix PR Cherry-pick 就无法自动化,人工操作的成本会很高
兼容性问题
当我们维护很多个版本后,有时候不得不做一些不兼容的改动:
之前的行为本身是错误的或者不合理的
想完全删除某些旧代码
查询层是无状态的,所以不兼容的改动一般可以绕过或改 SQL 解决。但是存储层和元数据层是有状态的,一旦有不兼容的问题会很麻烦。
不同语言的行为不一致
对于类似 StarRocks 这种多进程、多语言的数据库,在稳定性问题上还有一个额外的挑战是:
不同语言的一些标准库行为会不一样,这会导致在不同模块计算相同函数的结果不一致
之前遇到不同语言 Java 和 C++ 取余的结果不一致
这几年来,我遇到过挺多这样的 Case。
编译器的 Bug
一般编译器被各种项目大量使用,我们开发者遇到 Bug 的机会比较少。
不过在开发 StarRocks 向量化的过程中,我就遇到了一个编译器的 Bug:编译器进行向量化优化后,把 Boolean 值转成了 255:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97255
还有最近遇到的一个 C++ 正则标准库的 Bug:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164#c8
分布式相关问题
现在的数据库几乎都是分布式数据库,所以分布式系统遇到的问题,一般数据库都会遇到。比如分布式系统的常见挑战:单点故障,部分失败,不可靠的网络,不可靠的时钟。
硬件故障
数据库的存储引擎必须保证数据能被正确地存储和读取,但是我们在实际环境中不可避免会遇到各种硬件故障:磁盘坏掉,磁盘数据错误,服务器宕机,机房断电,网络异常等。
数据导入
数据格式的多样性:CSV,JSON,Parquet,ORC,ARROW 等
数据源的多样性:Local File,Kafka,Hive,数据湖,传统数据库等
导入时支持 Transform (表达式计算)
数据导出
导出格式的多样性
导出位置的多样性:客户端,本地,分布式存储等
全量导出和部分导出
BI 工具:数据可视化
市面上流行的 BI 工具多达几十种,每种 BI 工具深入使用后都或多或少会有些兼容性问题:Session 变量的兼容性,数据类型的兼容性,函数的兼容性等。要尽可能完美地兼容,必然有大量的人力投入。
数据迁移
要支持用户从传统数据库和各种竞品数据库顺滑迁移,就必须和各种数据库进行对接,这里面有大量琐碎的工作。
比如我们想将 Presto 的用户迁移到 StarRocks 上,我们就需要在 SQL 语法层进行兼容。
比如单机数据库上一些很容易实现的功能,在分布式数据库上就会相对困难,这时候如果为了功能对齐,就需要不少的工作。
安全包括:认证,鉴权,审计,加密,脱敏等。这里面每一项都有着大量的工作,对于金融、政府客户、安全体系要求很高,公有云上的安全要求则会更高。
在未来,毋容置疑,易用性会越来越重要,并将会是一款数据库的核心竞争力。数据库分析师需要知道的数据库知识会越来越少,需要进行的操作会越来越少。易用性主要体现在 3 个层次:
架构层面的易用性:比如自适应执行,Automatic Clustering,Automatic Index,Automatic Scale 等。
产品层面的易用性:比如接口定义和功能上是不是足够简洁清晰,一个命令可以导入各种数据源,各种数据格式的文件,一个命令可以查询各种数据源、文件等。
细节层面的易用性:文档是否完善,报错信息是否清晰易懂,可观测性是否足够好。
在你将这些点一个一个深入思考下去,肯定会理解打造一个强大且成熟的数据库是多么困难,那么这些难题如何解呢?
欢迎关注 StarRocks 微信公众号,后续我们继续探讨、揭秘!
关于 StarRocks
面世两年多来,StarRocks 一直专注打造世界顶级的新一代极速全场景 MPP 数据库,帮助企业建立“极速统一”的数据分析新范式,助力企业全面数字化经营。
当前已经帮助腾讯、携程、顺丰、Airbnb 、滴滴、京东、众安保险等超过 170 家大型用户构建了全新的数据分析能力,生产环境中稳定运行的 StarRocks 服务器数目达数千台。
2021 年 9 月,StarRocks 源代码开放,在 GitHub 上的星数已超 3600 个。StarRocks的全球社区飞速成长,至今已有超 200 位贡献者,社群用户近万人,吸引几十家国内外行业头部企业参与共建。
|