TerarkDB 完整开源及我的一些思考

TerarkDB 完整开源及我的一些思考

1. 关于 TerarkDB 开源

把 TerarkDB 开源的想法持续很久了,因为各种原因一直没能促成,最近终于把代码发布到 Github 上,算是了解了一桩心事,非常期望大家能够支持和帮助 TerarkDB 持续演进和成长,未来它的开发也会以开源社区为主。

当然,也顺便求 watch、star、fork 三连。


在 2016 年的时候我发过一篇 PR 稿,其中用词较为夸张,目前再看深感惭愧,并没有在实践中达到最好效果:

虽然获得了不少朋友的支持,但我们很多“设想”并没有被实际需求验证,目前主要的几个同学一起加入了字节跳动,进一步深度优化 TerarkDB 以适应实际的场景需求。

2. TerarkDB 的现状

目前 TerarkDB 在字节跳动内部一些重要业务上上线了不少集群,面临的业务场景非常多样和复杂,在打磨的过程中也确实做了不少取舍(毕竟目标是通用存储引擎)。

2.1. 场景定位

目前的主要定位依然是做成替代 RocksDB 的通用存储引擎,但在业务负载非常低的场景下,使用 RocksDB、TerarkDB 甚至 LevelDB 其实并不会看到显著的性能差异(比如 WAL 写入量只有 10MB/s,并且大部分都是天然的顺序写入)。

所以对于用户来说,如果已经使用了 RocksDB,那么可以尝试对其进行性能瓶颈做分析,看瓶颈是在哪里,搜索一下 RocksDB 是否针对这个瓶颈做了优化(也许只需要调整几个参数就行了)。

TerarkDB 的核心优势主要体现在重负载业务下延迟和吞吐有显著提升,当然如果你当前的 RocksDB 总实有奇怪的剧烈延迟抖动,不管复杂如何,可以先替换一下看看,相信大部分情况下会有改善(可能是 RocksDB 的部分 bug 导致的)。

综合来说,如果任何一个场景下你发现 TerarkDB 的表现不如原版的 RocksDB,那说明我们的工作没有做到位,请务必提交一个 issue 并且把复现场景告诉我们。

2.2. KV 分离

很多 LSM 的改进工作都包含了 KV 分离,TerarkDB 也做了自己的实现,这项工作主要是应对 Value 比较大的场景,比如一些做块存储的业务喜欢把 4KB~16KB 的块直接存放在存储引擎中,启用了 KV 分离对写放大的降低大有裨益。

2.3. TerarkZipTable 和 BlockBasedTable

目前 TerarkDB 在大量业务实践中总结后,默认推荐使用的 SST 格式依然是默认的 BlockBasedTable,而不是 TerarkDB 早期提供的 TerarkZipTable。TerarkZipTable 包含了 NLT索引和PA-Zip 压缩,事实上它的压缩能力依然是非常出众难觅对手的,但是它也引入了两个比较严重的问题,我们未来会进行重构:

  • 几乎所有的操作都通过 mmap 进行
    • 本身 mmap 并没有什么太大的问题,但对于实例比较大数据库而言,单个 TerarkDB 实例可能有 2TB 甚至更多,SST 文件非常多,mmap 过多后无法保证内存被及时回收,部分场景下有可能触发 OOM
  • CPU 消耗过高
    • 对于负载比较轻的业务而言其实问题不大(比如 MyRocks,由于上层开销过大,打到引擎层的负载很难触碰到瓶颈),但对负载比较重的业务来说,本身单机的 CPU 就需要预留水位线,底层 CPU 消耗过高对业务是个致命问题

当然, 如果用户的场景是一次批量写入后,后续全部都是随机读,那还是推荐尝试使用的。

2.4. 下一步计划

  • TerarkFS 裸盘封装层
    • 目前我们为了绕过文件系统的开销,针对裸盘(尤其是混合介质,比如 Optane SSD + TLC/QLC SSD 或者直接使用 ZNS)开发了专门的 IO 层,在 PoC 上能够显著的降低读写延迟和提升吞吐能力
  • Remote / FaaS Compaction
    • FaaS Driven 我相信未来会是很多业务的默认使用方式,并且对于大企业而言,可以充分的池化 CPU 资源,节省综合成本,对于负载较高的业务也可以把计算力卸载出去(比如 TerarkZipTable 开启后,就可以把本地计算放到 FaaS 或远程节点,降低对本地的压力,即保障了数据压缩,又保障了服务能力)
  • RecordBasedTable
    • 在重度负载的情况下,使用有限的 CPU 获得较高的压缩率
  • 新的 TerarkZipTable
    • 在各类复杂场景下,重新优化 CPU 的使用,以尽量低的 CPU 消耗获得最高压缩率
    • 重点会关注在 Remote Compaction 资源的使用上

3. 一些分享

过去几年研究了很多存储系统,也和很多人交流了业务场景、技术趋势,有一些思考也趁此机会分享给大家,这些思考也是支持我们做 TerarkDB 的基本思想,其中有些理解可能是有偏差的,希望大家斧正。

3.1.开源的意义

比较功利的来说是:防止闭门造车,防止我们低头不断地把一个轮子打磨的越来越圆,但没有从更多的用户视角去看待产品的发展是否符合大的社会趋势和技术趋势。

比较公益的来说是:我们也希望能够做一个国际化的产品,能够让大洋彼岸的小伙伴们参与到中国人的开源事业上来。

3.2. 系统边界

当我们构建一个系统的时候,到底应该设计到什么程度?这个问题所覆盖的场景包括:

  • 分布式数据库中, 数据的安全性到底是单机保证,还是上层分布式系统保证?
  • 贯穿整个 I/O 链路的数据,每一层是不是都要单独做 CRC 校验?
  • 用户要求超低延迟以支持他们能在上层任性的使用同步调用模型,是否应该支持?
  • 等等

以 CRC 校验为例,数据库在写入数据的时候一般会做一次,存储引擎会再做一层,文件系统又做一层,当然 SSD 盘上也会做一层,网络传输当然也会有自己 CRC/FCS 校验,好不容易折腾下来一点压缩率,被这些各类校验轻松膨胀 10%,是否有必要呢?

除此之外,曾经也有同学和我讨论过使用 QAT 加速芯片进行数据压缩或者 CRC 计算+校验,那么问题也来了,我们是否能信任 QAT 返回的数据?或者说返回的 CRC 编码和数据同时损坏了,但恰好损坏了的 CRC 和损坏了的数据又能对上,怎么办?

更可怕的是,有些问题出现的概率极低,但要处理这种特殊情况(比如增加校验位的位数)可能会使让整体的系统性能下降 1%~5%,愿不愿意付出这个成本?

对于这类问题我的看法很简单,那就是 构建共识 + 预设灾难必然发生

以构建一个文件系统来讲,构建共识是指我们清晰的告诉我们的用户(如某分布式数据库)系统不提供 CRC,请上层数据库自行解决,这个很容易理解,也通常比较容易达成共识。

预设灾难必然发生是指,我们天然应该假设最坏情况一定会发生,即我们没有做 CRC 校验,但是上层校验后发现数据损坏了,我们如何应对,如果我们即没有做校验,又没有应对策略,那责任显然无可推卸。

如果上层数据库发现某条数据损坏了,我们的文件系统应对上层提供机制供其恢复数据(如从其他数据库节点或者日志中拉取对应的数据恢复),然后对付出的代价进行核算,如果代价可控,方案简单,那直接承受损失即可(比如仅仅是恢复数据需要花费两个小时,同时由于是五副本架构对业务几乎不造成影响),但如果损失不可预估(比如可能导致某业务不可用),那补齐这个校验付出的额外成本即便很高,也要加上。

依据此项原则,在上下层链路的共同设计下,可以做到综合成本和性能的均衡。

回到本节刚开始的问题,如何确定系统设计的边界?

我们必须在提供任何一项功能之前,扪心自问我们的服务对象是谁,我们的目标是什么,这个功能是否让我们离目标更进一步,我们是否能和使用者达成共识?最重要的是,我们首先要假设一定会出现问题,出问题的后果有多严重,损失是否能接受,以及如何善后。

如果功能设计之初没有考虑这些额外的问题,只考虑了如何实现以及性能是否足够好,产品本身的走向会越来越困惑,未来功能逐步复杂和庞大化,最后逐渐腐败、难以维护,不懂得取舍和专注核心方向的产品,用户也难以对其抱有长期信心。

3.3. 硬件发展

在数据存储系统的设计中,如何看待硬件的发展呢?如何把未来硬件的趋势提前设计到产品的规划中呢?

在构建存储系统的过程中,硬件发展带来的性能提升往往胜过过去很长时间的算法和工程优化,这时常让我感觉很沮丧,经常感觉同学们的很多的工作成果被浪费掉了(在新的硬件上,某些传统优化丧失了意义)。

我看到很多很有天赋的人所做的工程努力,在硬件能力的提升面前显得不值一提,花了大半年优化了一些数据结构和产品架构,新硬件的替换带来的收益简单、直接而且暴力。

以傲腾为首并且已经量产的持久化内存(PMEM,Persistent Memory)未来毫无疑问会是数据中心服务器的标配,目前很多用户的使用方法是简单的进行内存替换,降低内存成本,这固然是一种不错的商业考量,但对数据库和存储系统而言,利用 PMEM 的字节寻址特性对元数据进行管理,利用慢速设备(TLC/QLC SSD)进行数据管理,成本即没有增加太多,性能反而可以10倍以上提升,不禁反问自己是否我们的基础软件设计方法论产生了问题?是否大量的工作都被浪费掉了?

再以 ZNS 为例(个人认为 ZNS 的成功几乎是确定性的),各类存储系统,尤其是 LSM 花费了大量的精力进行写放大的优化,但即便是 LSM 写放大降低到 5,SSD 自身的写放大通常也有 3 倍,综合写放大还是 15 倍之多。但通过支持 ZNS 的设备,轻而易举的可以把 LSM 的 GC 和 SSD 的 GC 合二为一,既能够让 SSD 厂商把主控的任务减轻 50% 以上,降低 SSD 综合成本,又能大幅度降低综合写放大(盘上写放大几乎消失),同时没有了盘上 GC,整体吞吐能力和延迟又能获得大幅度优化。

所以,我们对待硬件的态度应该更加积极一些,虽然大家都常说硬件没有几年时间,数据中心不敢大规模使用,但作为开发者我们可以主动推动这个过程往更加快速的状况迭代,我们的态度也能影响厂商的投入、公司的决策,对产品研发的负责人来说,预测未来硬件的发展趋势是非常重要的能力。

3.4. 层出不穷的"黑科技"

最近几年是不是黑科技有点多?

数据库系统的发展近几年日新月异(当然主要是PR比较多,实际上的创新乏善可陈),除了专业会议 SIGMOD、VLDB 的一些噱头十足的 Paper 外,知乎、HackerNews、TechCrunch 也充斥着各种“震惊体”,Oracle平均每个月被干趴下一次(当然,我也犯过类似的错误)。

如今,我其实并不大相信所谓的“黑科技”,在确定的存储和计算资源的前提下,大部分产品很难玩出花活儿了,很多“性能测试”的场景往往是精心选取的,加了很多特效,实际生产环境复杂多变的程度会把各种“巧妙”的优化效果抹杀。

我认为真正的黑科技,一定是有历史可循,从一步一步缓慢的工程改进中慢慢获得当前的成果,横空出世的黑科技有必要被格外仔细的审查和研究。以性能测试为例,很多测试报告间接扼要,结论夸张炫酷(是的,我们犯过类似的错误),但其实性能测试是一个非常复杂的系统性工程,关联的影响要素多到要以周为单位才能完成一轮严肃的测试。举例来说,对数据库系统的测试除了要定义数据规模、数据样本、CPU、内存、磁盘外,还需要考虑存储类型(SATA SSD?NVMe SSD?HDD),甚至考虑是设备是低功耗运行还是标准功耗运行,IO模型如何(WAL 强制刷盘还是批量刷盘),是否开启压缩,压缩粒度多少、读写线程是隔离还是混合、测试前是否清空了缓存,文件系统是否开启了 Journal、大家的校验级别如何等等。

以我们和 Intel 联合测试的 P4510 TLC SSD 的性能为例,对磁盘本身的性能测试就折腾了整整一周,除了每轮测试要对磁盘进行全盘清空、然后覆盖写多轮让其达到稳态外,还需要单独调节标准功耗、低功耗模式,不同的线程模型、不同的 IO Depth 等等,试想这些不同条件叠加下,上层数据库等存储系统的性能测试只会更加复杂,如果想要获得“黑科技”性质的测试报告,经过精心的对各层面参数进行调节,两个相同版本的 RocksDB 也可以对比出巨大的差异。

所以,简单来说,除了各家公司产生的性能报告只能作为参考,用户尝试理解其原理后,分析对自己的实际使用场景是否有价值,最后进行应用及优化才是最重要的。

3.5. LSM 模型

近两年来很多人提起 LSM 会马上提起读写放大严重的问题,同时还会提出 PCIe NVMe SSD 的随机写和顺序写性能差距较小,LSM 这种为 HDD 顺序写和随机写性能差距过大的场景设计的结构,是否已经过时了呢?

我个人的看法是,LSM 的新时代才刚刚开始。

先谈谈写放大的问题,被拿出来对比最多的是 B+ 树系列,但实际上我们稍微分析一下,如果一个 B+ 树的 page size 是 16KB,恰好更新了 1KB 的 KV,那么写放大其实已经是 16 倍了,当然 MySQL 等数据库都会做 delta page 聚合写请求大幅度降低写放大,但请求比较均匀的情况下,写放大还是很容易达到 5~10 倍的,这和很多调参优化后的 LSM 写放大 7~10 倍相比,其实是同一个数量级的,在我看来并不足以成为一个关键的批判因素。

再谈谈硬件设备的性能,目前主流的 NVMe TLC SSD 写带宽打满到 1GB/s 理论上是没有太大问题的,但能否打满带宽和写IO的模型有极大的关联性,较差的 IO 模型(比如多线程大量的随机小IO)写带宽可能 100MB/s 就满载了,较好的写模型(如单线程、64KB顺序IO),可以轻松打满 1GB/s。在这种情况下, LSM 借助其后台 Compaction 只需要离线 IO 的优势,可以将后台 I/O 模型构建的比较完美后再进行 IO 下发,而且大部分操作都是单线程顺序写即可,对比 B+ 树而言,优势不言而喻。当然,目前的 RocksDB / TerarkDB 版本暂时还没有做此优化,TerarkDB 会在下一版本(明年初)通过和 Intel 合作的一个裸盘 IO 封装层来达到此目的(TerarkFS),敬请期待。

另一个趋势是,互联网大厂们都希望底层的硬件提供商尽可能不要做太多事情,把调度逻辑暴露出来,由这些厂商自己进行封装,以达到最佳的性能。比如早些年百度和宝存合作的 Open Channel 就属于此类范畴(当然,据说这个项目效果欠佳),由于 OC 对上层业务逻辑设计的要求过高,业内其实并没有很好的商用案例,其主要作者 matias 目前已经加入西部数据,重新开始推广 ZNS 了(这个是我个人非常看好的一个方向)。ZNS 将底层的 GC 彻底不做了,据了解只进行 zone 级别的 wear-leveling、简单的 zone 级别的 mapping table 管理、NAND 颗粒的条带化、数据校验等基本工作,其他的上层数据存储业务可以直接控制,而 LSM 天然的对这种场景具有亲和性,如果 ZNS 获得成功,LSM 将会是首先获益的。

3.6. 未来的数据库系统

这个话题比较大,也是我思考最多的问题之一。

我个人比较喜欢的架构是计算和存储完全分离的数据库架构,而不是 Spanner 系的 share-nothing 架构,这是因为我认为存储系统进行单独的优化能够带来更大的想象空间。如果面向未来,我们的分布式共享存储可能是这个分布:

单机存储(热数据) -> 分布式块存储(温数据) -> 边缘存储(冷数据)-> 区块链存储(归档数据)

而统一的存储系统即可以支撑延迟敏感的缓存业务(如作为 Redis 只使用单机内 DRAM 和 PMEM),也可以支撑归档数据的长期冷存储,还可以支持 HTAP/HSAP 等实时分析的数据库系统。

统一的存储架构优点显著:

  • 架构简洁优美,便于维护
  • 在其上可以根据业务需要提供各类不同的访问协议、提供不同粒度的一致性服务能力
  • 池化存储,大幅度节省资源开销
  • 充分利用各类存储设备的特点,针对性的优化存储性能
    • 不会因为后台数据处理抢占 CPU 而影响计算节点的业务服务能力
    • 更加轻便的可以将各类计算任务 offload 到专用计算卡上(参考 AWS Nitro Cards)

4. 总结

这篇文章的主要目的还是希望大家能够继续关注 TerarkDB 的发展,作为国产存储引擎,发展过程比较坎坷,希望未来能够做的更好一些。

如果有参与这个项目加入我们的小伙伴,请给我发私信吧

编辑于 2020-12-24 16:14