打造一款强大成熟的数据库有多难?

时间:2022-12-08 18:12:45

作者:康凯森,StarRocks PMC,负责查询方向的研发(本文转自其个人博客“编程小梦”)

从 2015 年开始,我在美团先后维护和研发过 Apache HBase、Apache Kylin、Apache Druid 和 Apache Doris,对大数据系统和 OLAP 数据库有了深入理解。

2020 年开始,我加入了 StarRocks 社区。我们先后打造了业界领先的向量化执行器、CBO 优化器、Pipeline 并行引擎、支持高效 Update 的存储引擎和极速数据湖分析。支持存储分离的 Cloud Native 版本也即将面世。

一路走来,随着 StarRocks 项目攻克一个又一个技术难题,解决一个又一个用户需求和难题,服务着越来越多的用户,我对如何打造一个强大且成熟的数据库有了更深的理解。在这篇文章中,我会和大家介绍打造一个强大且成熟的数据库都有哪些难点。

 

#01

本质矛盾

 

无论是一个年轻的社区团队,还是一家大公司的数据库部门,打造一款强大且成熟的数据库都会有下面两个本质矛盾:

有限人力和无限工作之间的矛盾

各种各样的用户需求,无止境的性能优化,永远也修不完的 Bug,一个又一个 Killer Feature 的打造,时刻不停的代码重构,增加不完的测试 Case,不间断的技术调研和系统设计。这些工作都需要大量人力,但无论哪家公司,人力总是不足的,优秀的数据库人才更是稀缺。

飞速增长与成熟稳定的矛盾

如果数据库的代码不大变,面对的应用场景不大变,我们让一个数据库变稳定会容易很多。但是如果一个数据库的代码在飞速变化和增长,应对的应用场景不断丰富和变化,让一个数据库稳定下来就会很难。

 

#02

定位和独特性之难

 

一款数据库想要在产品和商业上取得成功,想在很多竞品中脱颖而出,就必须有清晰的定位和自己的独特性。一款数据库的独特性就是自己的价值点,比如目前 StarRocks 的价值点就是极速统一。

一旦定位和独特性定义清楚,便意味着产研资源重心和方向的倾斜。而一款数据库想要让自己的独特性成为 Killer Feature,就必须超越所有竞品。这就意味着,这个数据库团队必须有足够强大的创新能力,足够强大的工程能力,才能做到其他数据库公司一定时间内无法做到的。

 

#03

架构之难

 

一个数据库的架构决定了一个数据库未来的上限,决定了一个数据库未来支持更多用户需求的难易程度。如果架构设计有误,后面的很多功能和优化成本就会很高,甚至无法实现。不过在当下,云时代数据库的架构逐渐趋同,想要有足够的独特性和明显的优势还是比较困难。

 

#04

功能之难

 

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

  • 用户使用一个系统稳定后,升级的动力会越来越弱,所以越靠后的版本得到用户大规模验证的周期也会更长

 

#05

性能之难

 

CBO 优化器 Plan 的稳定性和正确性

  • 统计信息的变化会导致查询 Plan 发生剧变

  • 某些优化就是会导致部分查询变快, 部分查询变慢

  • 选择度估计和基数估计一般都假设了数据的均匀分布,但这和实际情况并不相符

  • 统计信息收集如何做到相对准确又不耗费大量的系统资源

单核性能优化

单核性能优化是一个没有止境的过程,我在 StarRocks 技术内幕:向量化编程精髓 一文中已经解释了单核优化的关键点和方法论,所有算子和函数的深度优化是需要数十人年的事情。

多核扩展性优化

StarRocks 单核性能登顶 ClickBench 后,我们在优化多核性能上投入了大量精力。相比单核,突破多核扩展性的瓶颈要更困难些,目前已经做了大量优化、还远远不够。不同类型的查询在高并发下会遇到不同的瓶颈:Lock,线程池,RPC,调度问题,NUMA,CPU Cache,内存管理,IO 异步等。

多机扩展性优化

多机的扩展性的常见瓶颈点主要包括:

  • RPC 的扩展性

  • 元数据的扩展性

  • 如何解决数据倾斜

  • 如何通过调度策略解决热点,充分调用整个集群的算力

  • 优化器要确保每个算子,函数都可以生成分布式的执行计划,不会有单点执行的瓶颈

存储引擎的优化

存储引擎需要在导入性能和查询性能之间进行权衡,一般情况下,导入时候做的事情越多,查询时候的代价就更低些:

  • 更新能力的持续优化

  • 导入能力的持续优化

  • 压缩和编码

  • Compaction 策略的持续优化

  • 事务能力的优化

  • 内存使用上的优化

  • 各个级别 Cache 能力的优化

针对特定场景的性能优化一般会增加系统复杂性

数据库一般都是面向通用场景开发,但是在特定场景下可以获取更多信息和上下文,这时就可以进行更多的针对性优化。但这样也会带来问题,比如系统的代码逻辑更加复杂,测试和维护的难度更高。

如何保证性能不退化

当系统复杂之后,多人协作经常会出现的问题是,一个人之前精心写的一段对性能影响很大的代码,被后人不小心改掉了。或者是优化 A 优化了某些场景的性能,但是却导致之前优化 B 的优化失效了。

如何能 Cover 不同的硬件环境

数据库这个复杂的软件是构建在硬件之上的,硬件是决定性能的基础。但是 CPU 有不同的型号,网络带宽有高有低,磁盘的吞吐和 IOPS 也有很大的差别,有可能一些软件层面的优化只对某些硬件环境生效。

 

#06

稳定性之难

 

一款数据库的成熟,主要体现在稳定性上,而打造一个稳定的数据库有如下难点:

SQL 是声明式的

  • 机器可以生成成千上万行的 SQL,SQL 各种算子和函数的组合是无法穷尽的,要保证任何一条 SQL 没有 bug,是极其困难的

  • SQL 里面的 Null 和 Nullable 是比较令人烦恼的,不仅对性能有很大影响,对正确性也有较大影响

成百上千的用户场景

每个用户的硬件配置,环境信息,应用特点不一样,都可能引发不同的问题。

各种各样的集群规模

很多时候,只有当集群规模、数据量、并发量到一定程度,一些稳定性和扩展性的问题才会暴露出来。如何在产品发布之前,在有限的资源下,通过测试暴露这些问题也是一个难点。

功能的组合

很多时候,一个功能单独 Work 没有问题,但是多个功能相互影响时,一些 Bug 才会暴露出来。比如节点下线,触发数据的均衡和复制,又会触发数据版本的问题,进而触发查询的问题。比如导入、查询、Compaction、系统任务对 IO 资源的共同影响,这些复杂功能组合时的测试难度会更大。

函数的预期行为没有标准

比如 Date 类型和数字的比较,字符串和数字的比较,应该是直接报错还是隐式转换,隐式转换的公共类型转成啥。这些其实都没有统一的标准,每个数据库都有自己的实现。关键问题是即使改成最合理的表现,一些用户可能会因为习惯自己熟悉的数据库的表现,觉得当时最合理的表现是不合理的。

还有聚合函数溢出后行为,Decimal 运算精度的确定,函数一些异常行为是抛异常还是转 Null。我们不仅要兼容用户期望的行为和正确性,还要兼顾正确性和性能。

多版本维护

由于快速迭代和发展,我们必然要维护很多版本,这给稳定性提出了更大的挑战:

  1. 定位,复现问题成本会更高,比如研发得用指定版本的代码部署环境,复现问题

  2. 解决问题后,也必须给所有版本 Cherry-pick,这时候很容易遗漏某个版本

  3. 要确定某个 Bug 到底在哪些版本修复了,成本也比较高

  4. 某个版本发生大的改动或者重构,之后的 Bug Fix PR Cherry-pick 就无法自动化,人工操作的成本会很高

兼容性问题

当我们维护很多个版本后,有时候不得不做一些不兼容的改动:

  1. 之前的行为本身是错误的或者不合理的

  2. 想完全删除某些旧代码

查询层是无状态的,所以不兼容的改动一般可以绕过或改 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

分布式相关问题

现在的数据库几乎都是分布式数据库,所以分布式系统遇到的问题,一般数据库都会遇到。比如分布式系统的常见挑战:单点故障,部分失败,不可靠的网络,不可靠的时钟。

硬件故障

数据库的存储引擎必须保证数据能被正确地存储和读取,但是我们在实际环境中不可避免会遇到各种硬件故障:磁盘坏掉,磁盘数据错误,服务器宕机,机房断电,网络异常等。

 

#07

生态之难

 

数据导入

  • 数据格式的多样性:CSV,JSON,Parquet,ORC,ARROW 等

  • 数据源的多样性:Local File,Kafka,Hive,数据湖,传统数据库等

  • 导入时支持 Transform (表达式计算)

数据导出

  • 导出格式的多样性

  • 导出位置的多样性:客户端,本地,分布式存储等

  • 全量导出和部分导出

BI 工具:数据可视化

市面上流行的 BI 工具多达几十种,每种 BI 工具深入使用后都或多或少会有些兼容性问题:Session 变量的兼容性,数据类型的兼容性,函数的兼容性等。要尽可能完美地兼容,必然有大量的人力投入。

数据迁移

要支持用户从传统数据库和各种竞品数据库顺滑迁移,就必须和各种数据库进行对接,这里面有大量琐碎的工作。

比如我们想将 Presto 的用户迁移到 StarRocks 上,我们就需要在 SQL 语法层进行兼容。

比如单机数据库上一些很容易实现的功能,在分布式数据库上就会相对困难,这时候如果为了功能对齐,就需要不少的工作。

 

#08

安全之难

 

安全包括:认证,鉴权,审计,加密,脱敏等。这里面每一项都有着大量的工作,对于金融、*客户、安全体系要求很高,公有云上的安全要求则会更高。

 

#09

易用性之难

 

在未来,毋容置疑,易用性会越来越重要,并将会是一款数据库的核心竞争力。数据库分析师需要知道的数据库知识会越来越少,需要进行的操作会越来越少。易用性主要体现在 3 个层次:

  1. 架构层面的易用性:比如自适应执行,Automatic Clustering,Automatic Index,Automatic Scale 等。

  2. 产品层面的易用性:比如接口定义和功能上是不是足够简洁清晰,一个命令可以导入各种数据源,各种数据格式的文件,一个命令可以查询各种数据源、文件等。

  3. 细节层面的易用性:文档是否完善,报错信息是否清晰易懂,可观测性是否足够好。

 

#10

难点如何解决

 

在你将这些点一个一个深入思考下去,肯定会理解打造一个强大且成熟的数据库是多么困难,那么这些难题如何解呢?

欢迎关注 StarRocks 微信公众号,后续我们继续探讨、揭秘!

 

关于 StarRocks 

面世两年多来,StarRocks 一直专注打造世界*的新一代极速全场景 MPP 数据库,帮助企业建立“极速统一”的数据分析新范式,助力企业全面数字化经营。

当前已经帮助腾讯、携程、顺丰、Airbnb 、滴滴、京东、众安保险等超过 170 家大型用户构建了全新的数据分析能力,生产环境中稳定运行的 StarRocks 服务器数目达数千台。 

2021 年 9 月,StarRocks 源代码开放,在 GitHub 上的星数已超 3600 个。StarRocks的全球社区飞速成长,至今已有超 200 位贡献者,社群用户近万人,吸引几十家国内外行业头部企业参与共建。