MoreRSS

site iconTXY | 谭新宇修改

清华本硕,对分布式系统和性能优化感兴趣。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

TXY | 谭新宇的 RSS 预览

2024 年终总结:在清华 IoTDB 创业公司中构建起摩尔定律成长节奏

2025-01-23 11:17:22

前言

忙忙碌碌又是一年,2024 匆匆结束。回想这一年的成长和收获,除了个人能力的提升,在做人做事做选择等方面也有了更多的认识。可以说,自己并未虚度时光,过得十分充实。

临近除夕,总算抽出时间坚持自己之前的习惯来继续写年终总结。希望今年的总结不仅能继续鞭策自己寻找并实践摩尔定律的成长节奏,也能获得更多反馈来修正自己。

首先依然是自我介绍环节,我叫谭新宇,清华本硕,师从软件学院王建民/黄向东老师。目前在时序数据库 Apache IoTDB 的商业化公司天谋科技系统组担任内核开发工程师。我对分布式系统、性能优化等技术驱动的系统设计感兴趣,2024 年也一直致力于提升 Apache IoTDB 的集群易用性 & 鲁棒性、共识能力和写入性能等,并接手完成了若干具有挑战性的大项目。

接下来介绍一下我司:

天谋科技的物联网时序数据库 IoTDB 是一款低成本、高性能的时序数据库,技术原型发源于清华大学,自研完整的存储引擎、查询计算引擎、流处理引擎、智能分析引擎,并拓展集群管理、系统监控、可视化控制台等多项配套工具,可实现单平台采存算管用的横向一站式解决方案,与跨平台端边云协同的纵向一站式解决方案,可方便地满足用户在工业物联网场景多测点、多副本、多环境,达到灵活、高效的时序数据管理。

天谋科技由全球性开源项目、Apache Top-Level 项目 IoTDB 核心团队创立。公司围绕开源版持续进行产品性能打磨,提供更加全面的企业级服务与行业特色功能,并开发易用性工具,使得 IoTDB 的读写、压缩、处理速度、分布式高可用、部署运维等技术维度领先多家数据库厂商。目前,IoTDB 可达到单节点每秒千万级数据写入、10X 倍无损压缩、TB 数据毫秒级查询响应、两节点高可用、秒级扩容等性能表现,实现单设备万级点位、多设备亿级点位管理。

目前,IoTDB 能够为我国关键行业提供一个国产的、更加安全的、性能更加优异的选择。据不完全统计,IoTDB 已服务超 1000 家以上工业企业,在能源电力、钢铁冶炼、航空航天、石油石化、智慧工厂、车联网等行业均成功部署数十至数百套,并扩展至期货、基金等金融行业。目前已投入使用的企业包括华润电力、中核集团、国家电网、宝武钢铁、中冶赛迪、中航成飞、中国中车、长安汽车等。

值得一提的是,2024 年 IoTDB 的商业公司天谋科技营收同比增长近 300%,正式进入了指数增长的摩尔定律节奏。

2024

介绍完背景后,在这里回顾下 2024 年我们系统组的主要工作,可分为 TPCx-IoT 双版本登顶、共识遥遥领先、性能优化摩尔定律新时代、集群易用性 & 鲁棒性显著提升、海量项目支持和海量技术沉淀 6 个方面。

在 TPCx-IoT 双版本登顶方面,国际事务处理性能委员会(TPC)是全球最权威的数据库性能测评基准组织之一。TPCx-IoT(TPC Express Benchmark IoT)是业界首个能直接对比物联网场景下不同软件和硬件性能的基准,涵盖了性能和性价比两个维度。今年,TimechoDB 1.3.2.2 版本在开启和关闭 WAL 测试的两种配置下,分别在 TPCx-IoT 的性能和性价比两个维度均登顶。值得一提的是,原本性能第一的系统是开启 WAL 的,而性价比第一的系统则关闭了 WAL。做数据库的人都知道,是否开启 WAL 对写入性能和资源消耗有着巨大的影响。尽管如此,IoTDB 最终实现了即使开启 WAL,仍能在性能和性价比两个维度同时登顶。如果关闭 WAL,性能和性价比还能够进一步提升约 20%,这充分彰显了 IoTDB 在应对物联网高吞吐场景中的极致性能。如今回想今年与新豪一起完成的这一工作,个人感触颇深,这对我来说也是四年磨一剑的过程。还记得 2020 年下半年研究生刚入学时,我就第一次尝试用 TPCx-IoT 测试过 0.12 版本的 IoTDB 老分布式集群。当时的分布式架构存在较大问题,性能始终难以提升,那年我的外号也成了 “tpc”,因为我一个学期几乎都在做 TPCx-IoT 测试,遗憾的是最终没有得到理想结果。经过四年的彻底重构与迭代,IoTDB 自 1.0 版本推出了全新的分布式架构,并在分区、共识和写入性能等方面做了诸多创新与改进。这些进展不仅使 IoTDB 能够支撑更多的用户场景,也最终帮助我们在 TPCx-IoT 榜单上登顶,性能达到了老版本的近 6 倍,充分证明了技术创新始终是第一生产力。此外,随着商业化公司成立,本次登顶过程中,我们不再是单打独斗,获得了许多同事的全力支持,特别是在与 TPC 委员会沟通、撰写报告等方面。这里特别感谢鹏程、Chris、昊男和苏总等同事对我们组的支持与帮助。没有团队的默默付出与协作,我们也不可能在今年完成这一目标,让这把磨了四年的剑最终得以出鞘。

在共识遥遥领先方面,IoTDB 的 1.x 分布式版本参照 2020 OSDI 最佳论文 Facebook Delos 的思路抽象了一个支持不同共识算法的统一共识框架,允许用户在一致性、可用性、性能和存储成本等若干维度进行权衡。今年我们不仅将现有共识算法迭代的几乎稳定,更是创新的提出了一个新的性能遥遥领先的共识算法

  • 在强一致性算法 Ratis 方面,过去一年里我们组三位同学共为 Ratis 社区贡献了 35 个 commit,使得 Apache IoTDB 社区成为 Apache Ratis 社区仅次于原创团队的最大贡献社区。目前,Ratis 在 IoTDB 的通用场景下已经基本稳定,成员变更的稳定性也得到了显著提升。今年,宋哥成为了 Ratis 的 PMC,我成为了 Ratis 的 Committer,邓超也成为了 Ratis 的 Active Contributor。由于 IoTDB 每次发布正式版本不能依赖 Ratis 的快照版本,今年我们主动承担了多次 Ratis 社区的 Release Manager 角色,确保 IoTDB 每次发布正式版本时不依赖快照版本,从而符合 Apache 基金会的规范。今年,Ratis 共发布了 5 个版本,其中 4 次由我们组担任 Release Manager,Ratis-ThirdParty 发布了 3 个版本,其中 2 次由我们组担任 Release Manager。这标志着我们在 Ratis 维护方面已经积累了相当的能力和影响力。
  • 在基于操作复制的最终一致性算法 IoTV1 方面,过去一年湘鹏,宇衡和我已经将其打磨至几乎彻底稳定,成员变更的稳定性也得到了显著提升。近半年以来,IoTV1 相关的工单几乎为零,这标志着 IoTDB 共识算法稳定性的显著提升。今年,我们在理论上也迈出了重要一步,针对多主异步复制共识算法在成员变更时的一致性机制和保证边界进行了详细分析和完善,最终效果是在上万次 Region 迁移中副本数据依然保持一致。甚至测试组的同学也发出了疑问:“IoTV1 已经这么稳定了,为什么还要开发新的共识算法?”。这进一步说明了 IoTV1 的稳定性已经得到了团队的广泛认可。
  • 在基于状态复制的最终一致性算法 IoTV2 方面,过去一年我们结合物联网场景中写写冲突少的特点,与思成和俊植从 0 到 1 设计并实现了业界首个多节点多副本性能超越单节点单副本的共识算法。该算法不仅在鲁棒性上优于 IoTV1,解决了 WAL 堆积问题,还能在性能上超越现有常用的 Raft 算法 5-10 倍。在实现这一新共识算法的过程中,我们复用了流处理框架,推动了团队合力迭代,与苏总、哲涵、宇辰和振羽一起逐步提升了流处理框架的鲁棒性和稳定性。这一工作是我们在物联网场景下对共识算法的重要创新,也是我们在工程实践中发现的关键方向。我也首次完全自驱地联系伙伴(十篇 A 在投的张先生,以及我们组的新豪和俊植)共同撰写论文来沉淀我们组的工作成果,并最终得到了导师们的认可与支持。虽然没读博士,但也写了论文的两个章节过了一把博士的瘾。经过两个多月的努力,第一版论文已提交至系统方向的 A 类会议,现正等待评审。我非常期待 2025 年,IoTV2 能在学术与工程上同时与大家见面,这也是我们组今年最令人振奋、最反直觉的技术突破。

在性能优化摩尔定律新时代方面,今年我跟旭鑫、昊男、雨峰、湘鹏、荣钊、钰铭、江天学长,田原学长和振宇师兄等团队成员一起进行了多项盲测写入性能优化工作并取得了显著进展。我们不仅在很多特定场景下实现了性能提升数十倍的效果,还在通用场景下实现了写入性能翻倍的成就,这是 IoTDB 写入性能提升最大的一年。通过分布式架构、存储引擎和系统优化的组合拳,我们成功让 IoTDB 在通用场景下的盲测写入性能进入了摩尔定律的成长节奏(每 18 个月性能翻一倍或资源利用率减少一半)。以 2023 年为基准,2024 年我们已经实现了这一目标,2025 和 2026 年现有的技术储备也已经为继续沿着摩尔定律节奏提升奠定好了基础。在具体优化方面,我们做了很多关键工作,仅列举已做的开源部分如下:

  • 行列接口自动转换:优化场景是用户使用行式接口写入列式数据,系统能够自动检测并转换为列式向量化执行,从而提升写入性能。
  • WAL 压缩:优化场景是通用 I/O 瓶颈场景,通过 WAL 压缩,显著节约实时磁盘 I/O 带宽。
  • WAL 批量化:优化场景是设备多测点少的行式批量写入接口,能够显著降低 CPU 利用率和写入延迟。
  • 第一条写入调优:优化场景是空集群第一条写入耗时过长,通过优化显著提升了用户体验。
  • 表模型写入性能优化:优化目标是使 IoTDB 即将发布的表模型写入性能与树模型相似,将树模型优化思路接入表模型,从而提升写入效率。
  • 重启加速:优化场景是加速重启速度,使得重启时间不再与节点数据量挂钩,进而提升集群可用性。
  • 默认 DataRegion 数与硬件资源绑定:优化场景是针对更强硬件(如 16 核以上机器),自动根据硬件资源配置合理的共识组个数,从而高效利用 CPU 资源。
  • Memtable State of the Art 数组:优化场景是解决 Memtable 读写删并发互相影响的性能问题,我们通过设计符合时序场景的 State of the Art 数组结构,显著提升了存储引擎 Memtable 的并发性能。

在集群易用性 & 鲁棒性显著提升方面,我们也做了非常多的工作

  • Region 迁移和 DataNode 缩容是 IoTDB 动态扩缩容能力的基石,针对在迭代过程中遇到的分布式状态维护难(多个节点的内存和磁盘均维护状态)、沟通成本高(牵扯模块多)和复杂场景多(考虑若干故障场景)的问题,宇衡、湘鹏、珍姐和我在考虑众多因素后进行了详细设计和开发。为了保证最终交付给用户的功能质量,我们进入了测试的 Bug 拉锯战,这个过程堪比系统组的诺曼底登陆,无数精力都投入在鲁棒性的打磨上。经过近一年的打磨,现阶段的 bug 已经基本收敛,功能也逐步从不可用到基本可用,我们已在若干实际用户场景中实践了该功能,2025 年我们已经不再畏惧上万次迁移和 TB 级别迁移的场景。
  • 在易用性 & 鲁棒性方面,我也和宇衡、雨峰、文炜、湘鹏和荣钊一起进行了大量的完善,包括但不限于配置文件三合一、热更新加载参数缺失配置项恢复默认值、Set Configuration 集群更新配置语句、Region 重建/增删副本、Verify connection 检测网络连通性、CLI 激活 & 机器码缩短、Procedure 维护(10+ commit)、WAL 阻写默认阈值与磁盘大小绑定、CN 脑裂问题修复、节点启停流程双军问题工程完善、RTO/RPO 优化、多数据库负载均衡、多数据库创建保护机制、激活代码同步冲突处理等。这些工作显著提升了 IoTDB 的稳定性和用户黏性。

在 IoT-Benchmark 方面,今年我们梳理了其项目结构和 README,使其逐步向更通用的时序基准测试工具演进。过去一年钰铭作为主力带领我们迭代了近 100+ commit,包括 50+ 稳定性修复和易用性提升、以及 10+ 性能优化。

在持续集成与迭代体系方面,今年在王老师的指引下,我们引入了对第三方库的 SBOM 管理,并开始使用 NVD 扫描并持续追踪开源项目中的 CVE 问题,从而逐步提升了对第三方依赖漏洞安全问题的重视。此外,我们还开始统计 IoTDB 的代码量,以评估代码复用效果和项目的复杂度。与此同时,我们也意识到,随着产品功能和复杂度的不断增加,测试用例的指数级增长与产品迭代效率之间存在一定的 trade-off。结合每天晚上和周末 CI 机器几乎都在空闲的现状——每周 168 个小时中,只有大约 1/3 的时间 CI 机器在工作,其余 2/3 的时间处于闲置状态——我与钰铭开始探索将 CI 拆分为不同级别的测试体系,包括 commit、daily 和 weekly 级别的测试。我们在 commit 级别保留最为关键的 CI 测试,在 daily 和 weekly 测试中充分利用闲置的机器资源,补充更多的测试用例。同时,我们也将引入智能化策略,自动识别并追踪有问题的 commit,从而在开发效率与质量保障之间找到更好的平衡点。

在海量项目支持这块,我则个人负责了若干探索性项目并参与了很多实际项目

  • 在某一关键领域的大客户项目中,我担任了技术架构师和部分项目经理的角色,与乔老师、祥志、洪胤和高飞学长从需求调研到业务建模,再到推动内核迭代优化,几乎将自己毕生所学都倾注其中。庆幸的是,今年我们的工作得到了双方的认可。我们的工作包括但不限于:特定领域数据时序数据库一体化建模与应用的可行性验证,较原生系统现有资料数据性能提升超过 10 倍,下一代负载更大 10 倍的资料数据从不可写转为可写;我们还实现了该领域首个针对多维查询的异构多副本高可用方案,性能提升了一个数量级;结合具体场景,我们对 IoTDB 的 LSM 引擎进行了架构优化,使 0 层 TsFile 文件大小提升了 300 倍,系统从不可用变为可用;此外,我们还针对大文本数据进行了关键技术演进,包括零拷贝和内存池优化等。最终,我们还预留了若干内核优化,期待在 2025 年继续打磨完善。这个极具性能挑战的业务场景,不仅让我在高压下不断突破自己,也让我更加深刻地体会到系统设计的乐趣。
  • 除了预研项目外,我还通过项目工时表统计了自己今年 6-12 月共 7 个月参与的项目耗时,共计 257 小时,平均每个工作日 2 小时。具体支持的内容包括但不限于与竞品 PK 并取得胜利、撰写各种报告和文档、售前(包括纯英文售前)、oncall、写标书、评奖答辩和技术分享等。这些持续的项目和业务侧投入,使我能够始终接触产品一线,从一个更全局、具备发展眼光的视角去平衡不同工作的优先级,并理解一个 2B 创业产品需要关注的方方面面。在此,也特别感谢佳哥、红岩以及其他项目组的同事,帮助内核团队承担了大量的线上压力。

在海量技术沉淀这块,则基本是我们出于技术 & 业务双驱动完成的很多探索

  • 在技术推送方面,今年我们组协调产研团队共发布了 19 篇技术推送,其中我们组独立完成了 10 篇,包括《分布式三部曲》系列、监控系列、与 HBase/InfluxDB 的对比系列、TPC 系列等。我们公众号的推送视角也逐渐从纯技术视角转向了用户视角,开始更加关注公众号目标用户的实际需求。现在,我也在跟随旋哥一起审核并整理一些 FAQ 问题,期望能够解决更多开源用户的问题。此外,我还在知乎上宣传了 IoTDB 在分布式架构下的细致考虑,收获了不少关注和反馈。
  • 在学术成果方面,今年我们组产出了 1 篇软著、4 篇专利和 3 篇论文(2 篇在投),涵盖了我们组负责的负载均衡、共识算法、时序基准测试工具和 TPC 登顶等方面的工作。这些成果得益于去年王老师和东哥的要求,促使我们组不断沉淀并输出成果,在与同行交流的过程中激发了更多的创新点。
  • 在 JVM GC 探究方面,今年俊植和我一起举办了 GC 讨论班,我们对 JDK8/11/17 的默认 GC 算法 PS 和 G1 的原理和所有可调优参数都进行了研究和分享,我们也整理了相关 Cook Book 便于更多的同事能够参照流程图进行 GC 调优。我们也完善了 IoTDB 启动相关的 GC 参数,使得默认的 GC 参数是我们实践得到的最优选择。此外关于默认 GC 算法到底应该选择 PS 还是 G1 的问题,团队内部有很多争论和质疑,由于默认总需要选择一个 GC 算法,而不可能有任何一个 GC 算法在所有维度(例如吞吐,最大暂停延迟,稳定性和内存占用等)都能够超过其它 GC 算法。为了避免大家在这里耗费太多精力(例如“我发现某个场景 PS 更好”,“我发现某个场景 G1 更好”,“我觉得应该 xxx”),我们组结合过去两年对 GC 算法的研究和所有的对比案例整理了一个文档,得出了在我们所接触的所有场景里默认使用 G1 对于盲测更优,如有特定需要可调整为 PS 的方案。通过这种方式,我们平息了大家对这块的时间投入,能够让大家抽出更多的精力去专注于其他更重要的事情。
  • 在默认推荐 JDK 版本升级方面,23 年俊植和我曾尝试将 IoTDB 的默认推荐 JDK 版本从 JDK8 升级为 JDK17。然而,升级后冯老师发现写入、合并和导入导出等功能的耗时均有增加,经过近一个月的排查未果,我们暂时搁置了该问题。今年在调整 GC 算法默认参数时,我们意外发现,JDK17 性能下降的原因是 JDK15 之后默认关闭了偏向锁。我们在 JDK8 环境下关闭偏向锁也能复现类似的耗时增加现象。进一步排查后发现,IoTDB 内部的某些文件 IO 基础类过多使用了 synchronized,导致偏向锁取消时性能回退。通过优化这些基础类,我们解决了性能下降的问题,使 IoTDB 从 1.3.2 版本起默认推荐 JDK 17 部署。此举不仅让 IoTDB 的默认推荐 GC 算法从 PS 改为 G1,还为我们未来利用如 Vector API 等 JDK 高阶功能奠定了基础。
  • 在 JVM 非堆内存上涨问题方面,23 年我们团队已将 JVM 内存划分为堆内内存、堆外内存和非堆内存。今年,在某用户环境中,我们发现配置好 IoTDB 的堆内和堆外内存后,整个进程占用内存依然不断上涨,最终被 OOM-Killer 杀掉,说明非堆内存出现泄漏。通过结合 NMT 工具和 Oracle 官网文档对非堆内存进行分析,俊植和我提出了 IoTDB 内存配置的安全部署公式,虽然解决了线上内存不断增长的问题,但由于用户不愿进一步在生产环境中帮助我们确认原因,问题的根因排查暂时被搁置。幸运的是,由于我们开源了我们沉淀的 JVM 内存管理文档,一家广州创业公司的程序员联系到了我们,他们的 Java 服务也复现了该问题。在进一步沟通后,我们定位到这个场景的问题是由于 JVM 的默认内存分配器 glibc 缓存机制引起的,经过更换为 jemalloc 后,内存 RSS 稳定不再出现泄漏。这一经历也让我们更加深刻地认识到沉淀和分享技术的重要性。
  • 在访存瓶颈零拷贝优化方面,23 年我们发现,在一些大文本场景中,IoTDB 的 CPU、磁盘和网络均已经不再是瓶颈,反而是访存成为了瓶颈。今年,思屹和我系统整理了 IoTDB 的写入 RPC 请求从网卡到磁盘的端到端拷贝次数和访存次数,发现了 Thrift 框架中可优化的零拷贝部分。通过该优化,IoTDB 在 44KB 大文本场景下的写入吞吐提升了 35%。由于零拷贝技术需要完善控制对象生命周期,我们尽量平衡了性能收益与代码侵入性,首先优化了性能提升最大且生命周期控制最容易的客户端 Server 和共识 Server 部分。我们也为未来的扩展预留了接口,目前流处理组的宇辰已经开始尝试进一步引入客户端零拷贝来优化性能。
  • 在 JVM 内存池优化方面,针对大文本场景中的 GC 问题,思屹和我仔细梳理了 GC 触发的根本原因,并分析了 Java 与 C++ 在内存管理上的差异。C++ 允许开发者手动管理对象生命周期,并显式申请和释放内存,而 Java 则通过后台的可达性分析机制异步回收内存。由于这一机制,在处理大对象(如 byte[]、long[] 数组等)时,Java 在频繁的大内存申请与释放过程中容易引发较多的 GC,消耗大量 CPU 资源并影响程序性能。这本质上也是 C++ 和 Java 在开发者心智负担与性能之间的 Trade-off。因此,我们设计并实现了一个支持变长 byte[] 的内存池,提供了手动和自动两种接口,允许调用者选择是否手动管理生命周期来池化 byte[]。手动接口需要牺牲一定的开发者体验,要求实现引用计数机制来显式归还,但能获得更好的性能;而自动接口则基于虚引用机制实现内存的自动归还,性能相对较差,但仍优于不做池化的方式。该内存池还引入了基于 EMA 算法的主动驱逐策略和基于 JMX 的 GC 感知被动驱逐策略,确保性能提升的同时,不会引入新的稳定性问题。根据我们的测试,在 4KB 至 1MB 的大文本场景下,写入性能提升了 6% 到 71%,GC 从很严重降低到几乎没有。未来,我们计划将所有 JVM 大对象数组接入该对象池,从而消除大多数 GC 开销。
  • 在多 NUMA Node 机器性能优化方面,今年思屹和我在一台 192C 768G 内存的 4 NUMA Node 机器上进行了 IoTDB 性能提升的探索。我们首先尝试了单进程优化,发现 JDK 17 之后的 G1 垃圾回收算法已支持 NUMA 感知,但仅限于新生代,对于老年代的内存访问仍然存在较多的跨 NUMA Node 访问。对于 Java 来说,老年代内存可以通过第三方库如 Thread Affinity 进行核绑定,这要求访存和管存线程在同一个核上运行。然而,这种方式对内核侵入较大,因此我们推荐直接使用 JDK 17 以上版本自身的能力进行单进程优化。尽管单进程优化的空间有限,但我们发现多进程优化具有较大突破潜力。通过将每个 IoTDB 进程使用 numactl 命令绑定到一个 NUMA Node,我们能够以极小的代价显著提升性能。在该物理机上进行的 1 进程与 4 进程绑核的性能对比测试表明,读写性能最高可提升 1 倍,使用 Intel Vtune 工具观测到的跨 NUMA 带宽也显著降低。通过这一探索,IoTDB 在多 NUMA Node 机器上的高性能部署方案得到了进一步的优化和完善。
  • 在外部技术输出方面,今年宇衡在持续追踪一个影响 IoTDB 运行的问题时,发现了一个 GraalVM 编译器的 bug,在将其提报到 Oracle 社区后,得到了认可并快速修复。此外我在解决一个线上 WAL 堆积问题时,发现根因是 Thrift AsyncServer 的 Epoll 存在 bug,通过深入研究代码,我明确了具体原因并推动 Thrift 社区最终解决了该问题。这些反馈也引发了我的深思:对于一个追求普适性的复杂产品,稳定性和兼容性往往需要最优先考虑,那么那些出于性能优化驱动的新技术(如异步 server、异步 I/O、direct I/O、虚拟线程等)便需要谨慎使用。即便使用了,也应提供开关。因为只要延迟够用,吞吐基本可以通过横向扩展来提升,不必过度依赖那些看起来非常新颖但可能不够稳定的技术。特别是在当前国产化的背景下,如果这些技术没有在各种硬件和操作系统环境中做足够的测试和完善,线上问题一旦发生,oncall 的体验将非常痛苦,且会对用户和产研团队带来更大的负担。
  • 在更多的探索尝试方面,今年我们还有 6-7 个尚未合并到主分支的硬核技术尝试工作,具体细节暂不展开。期望这些工作能在 2025 年不断完善,为 IoTDB 2026 年通用场景盲测性能的摩尔定律节奏奠定基础。

今年,我在 Apache IoTDB 社区提交并被合并了 84 个 PR(去年 119 个),Review 了 509 个 PR(去年 387 个)。相比去年,今年我的大部分精力都集中在贴近业务和技术管理上,也对个人和团队如何最大化产出有了更多思考和感悟。今年 8 月,我受邀成为 Ratis 社区的 Committer,并成功拥有了 1k Github Follower,这让我更加认可自己在开源领域的专注。回顾过去一年,我觉得我们成功将团队的许多工作通过各种方式沉淀下来,并影响了许多人。希望我们能始终在这段青春年华中保持对技术的热情,专注于我们的工作继续前行。在这里,我特别感谢我的女朋友🍊 始终支持我的工作并帮助我疏解情绪,让我感受到生活的美好与幸福。她还带我见识了许多新事物,让我对人生的很多方面有了新的认识和思考。

一些感悟

介绍完充实的 2024,回顾 2023 年终总结,可以发现今年我们在去年四个维度的展望上都取得了不错的成绩

  • 做深:我们组输出了大量工程技术沉淀,也产出了 4 项专利、3 篇论文(2 篇在投)和 1 项软著。
  • 做广:上半年与存储引擎合作,下半年与流处理引擎合作。
  • 做好:我们显著提升了系统稳定性,降低了纯分布式模块的 oncall;通过若干内核功能优化,提升了易用性;同时,我们也通过分布式、存储引擎和系统优化组合拳打造了 IoTDB 通用场景盲测写入性能的摩尔定律节奏,并预留了三年的余量。
  • 做响:我们成功完成了 TPCx-IoT 登顶工作,并获得了央视报道;此外,我们小组的技术输出总阅读量达 2w+,提升了团队的知名度和影响力。

下面分享一下我今年的很多成长感悟,欢迎大家批评指正。

稳定性打磨工作如何评估时间

对于一个稳定性打磨的功能,如何评估其完善时间?在打磨 Region 迁移的稳定性时,我今年思考了很久。如果考虑无数硬件环境(如 4C16G 和 192C768G)、测试负载(实时写入、读写混合),再加上注入各种异常(如节点重启、网络分区、断电等),以及多个模块功能的组合(如多级存储、存储引擎、共识层、流处理引擎等),可以看出它们的组合是指数级扩展的。即使研发进行了完善的设计与实现,但提测后仅测试完善的开销就几乎永无止境。但如果一开始就定下工作周期为半年或一年,也难以做出可靠的过程管理来向上汇报。

在这种困境下,我们必须意识到场景是无限的,在精力有限的情况下,我们的目标是用最小的研发和测试代价解决尽可能多的 bug。最初,我们按照研发视角将功能的稳定性迭代分为 V1、V2、V3,期望逐步打磨到稳定状态。然而在实际测试中,我们发现测试视角与研发视角并不同频,导致测出的 bug 比较分散,尽管测试与研发一同打磨很久,仍难以向上汇报阶段性成果,因为每个模块都有不少 bug 被修复。这使得这个工作看起来像是无底洞,且容易受到质疑。其实,问题的根本在于缺乏多方共识的客观评价标准。

回顾整体流程,我认为可以在以下两个方面做得更好

  • 避免测试开销的指数级扩展:对功能中的核心模块,尽量做好抽象并补充完备的测试。在功能从 UT、IT、研发自测、测试自测、用户 POC 到用户线上等各个环节中,越早发现问题,整体时间成本就越低。更有趣的是,这种流程优化能潜移默化提升整个团队的效率。同样工作 8 个小时,高效与低效的差距,对于软件开发团队的影响会非常的大,这也是我未来需要持续反思与提升的地方。
  • 多方对齐:对于稳定性迭代工作,需要研发、测试和产品在功能、性能和测试场景等多个维度上提前对齐优先级,并将工作分配到 V1、V2、V3 版本中。对于每个小工作项,能够精确预估时间;对于大工作项,提供概要预估时间即可。这样即便出现延期,团队也能清楚了解目前功能的进展,哪些场景可以交付给用户,哪些还需改进。这会大大提高工作的透明度和效率。

软件工程没有银弹

今年有件让我深受感触的事,那就是发现大家对 IoTV2 共识算法的价值产生了质疑。从纯研发的视角来看,IoTV2 显然在创新性和性能上都显著超越了 IoTV1,是我们组过去几年最具创新性的工作之一,其他共识算法也都花了好几年才稳定,IoTV2 毕竟才诞生一年。但如果想在开发团队中获得更广泛的认可,就需要考虑大家关注的不同方面,包括稳定性、创新性、问题收敛程度、潜在收益与投入的平衡等。可以看出,这些维度之间往往存在矛盾,而且很难得出绝对客观的结论,很多东西也完全看未来的事在人为,因此很难在所有人中达成共识。这让我意识到,当一个软件项目和团队发展到一定阶段后,是否落实创新工作,往往会面临保守和激进的分歧,二者需要不断博弈与制衡,才能走向一个可行的方向。完全激进或者完全保守都会带来不可预知的风险。

回到 IoTV2,我们能够平息质疑的一个重要原因是我们做了共识层的抽象,能在一套接口下支持不同的共识算法,从而使得各个共识算法可以单独迭代。如果没有这个统一的接口,不管 IoTV2 作为业界第一个多副本超越单副本的共识算法有多么创新,仍然会面临无数质疑,甚至可能导致无法迭代。而对于竞品来说,除非照抄 IoTDB 整体共识层的设计,否则也很难平息内部质疑,全力推进这项工作。这为我们未来的扩展性设计提供了指导——良好的接口抽象能够使得系统的关键迭代从“不可能”变为“可能”。当然,软件工程没有银弹,即使我们通过共识层的抽象让 IoTV2 的迭代得以顺利进行,但代价就是翻倍的测试和打磨开销。总体而言,抽象得越好,复杂度封装得越到位,测试和打磨的开销也就越低。

个人的管理成本 ROI

在今年参与更多技术管理工作后,我渐渐关注到个人管理成本 ROI 这一概念,其本质是消耗尽可能低的 +1 管理成本(包括时间和资源等)完成更复杂的工作,并在有风险时及时汇报并提供辅助决策的数据。

总体而言,不同的人有不同的管理风格,同一个人对于不同的事情也会采取不同的管理方式。有时像《大明王朝 1566》中的嘉靖一样,只关注结果,不拘过程;有时又像《大决战》中的 101 一样,会关注每一个细节。其实,不论是哪种管理风格,最终目标都是完成工作,并没有绝对的好坏之分。

对于我们个人而言,我们控制不了别人,唯一能够不断改善的就是提升自己的管理成本 ROI。通过这种方式,我发现能够使得自己与他人的合作变得更加高效和愉快。类似的例子包括但不限于

  • 完成一个 PR 后,补充详细描述和充足的测试用例,并至少自己 Review 一遍,再让 +1 Review,这样 +1 只需花费极小代价即可合并 PR。
  • 对于自己负责的工作方向,如果涉及到非常多的琐碎事项,主动周期性汇总关键点并屏蔽细节与 +1 沟通,让其在最短时间内了解现状并能进一步向上汇报,不要让他消耗很多时间去整理细节。
  • 在几乎不消耗 +1 时间的情况下,完成复杂架构设计并获得原本 +1 需要沟通的人员认可,让 +1 仅需做最终决策。
  • 在发现项目潜在风险时,及时整理现状和信息与 +1 沟通,评估是否需要更多资源,确保项目整体风险可控。

这些经验很多是在与我们组俊植一起进步的过程中学到的。希望自己能像俊植一样,不断提升自己的管理成本 ROI,进而锻炼出更好的职业素养。

十倍程序员如何进一步提升

今年年中,我读了润基哥哥的十倍程序员文章,受益良多。对于十倍程序员的成长,本质上有两个方面:

  • 基于延迟的纵向扩展:这既包括以前做不到的事现在能做了,即延迟从无穷大变成了可量化的数字,也包括以前能做的事现在能更高效完成,即延迟更低。
  • 基于吞吐的横向扩展:这包含了能够带领团队在单位时间内并行产出更多成果,这中间需要避免单点瓶颈和负载不均衡现象,才能发挥出团队最大的力量。

从纯个人能力上来看,可以按照上述思路进行纵向和横向扩展,但今年我也意识到人力始终有限,战略上选择一个正确的方向才是事半功倍的关键。所以今年除了个人业务能力的提升,我还积累了很多做人做事的经验。对于很多事情的可行性,我不用再依赖他人的意见,而是能够自己进行主观判断。希望自己能在这个方向上继续沉淀,用靠谱的战略指引自己不断成长。

决定不做什么往往比决定做什么更难

其实这个感悟与战略类似,说一个应该做的事情只需要十秒钟,然而将这个事情具体落地可能需要十周甚至十个月的时间。人力始终有限,尤其对于一个软件团队来说,面对无数的输入和决策指引,客观上这些工作不可能面面俱到,必须进行战略性取舍。

决定做什么往往没有太多压力,因为人性中总有一种“即使失败了,没有功劳也有苦劳”的自我安慰。但如果要决定不做什么,则必须对自己的业务和竞争力有深刻理解,出于提高人效的角度思考且愿意承担政治责任,才能最终说服别人。这种决策十分困难且珍贵,但也正是许多高效团队能够成功的关键。

今年,我有幸在博士生组会中跟随王老师龙老师带领的实验室团队学习时序 AI 大模型的落地思路。虽然王老师龙老师一直强调我们现在已经做好了“存数”,接下来要把“用数”做好,但他们也明确通过案例分析告诉我们,哪些 AI 项目是靠谱的能够最终产生实际价值,哪些 AI 项目是不靠谱的做了也只是白白耗费精力不创造实际价值。这种战略定力和担当,让我深受触动。

人性的惯性是根据情绪和结果来评价过程

尽管我一直对历史很感兴趣,很多大道理早已听过,但今年在工作中,我从切身实践中获得了一个深刻的感悟:任何事、任何人都会有正负面的影响和评价,不存在一个完人,也不存在一个能够得到所有人认可的方案。

人性大多数时候是非常真实的,大家常常根据结果来判断过程。如果事情没有做成,就会有人列举一堆负面评价来解释为什么失败;如果事情做成了,又会有人说一堆正面评价来证明“早就看他行”。但实际上,成与不成,除了人的因素,还很大程度上取决于天时地利,而这些天时地利,作为非人为因素,反而会深刻影响最终大家的评价。此外,尽管我们都在强调要客观理智,但根据我的观察,大多数人,包括我自己,也曾在情绪驱动下做出一些非预期的行为,并且不断强调自己并没有被情绪左右。

意识到这些后,我明白了很多事情,要么不做,要做就尽己所能做到最好,不必过多顾虑他人的评价。只有这样,才能避免不必要的内耗和沟通成本,将精力集中在更重要的事情上,这样反而更有可能将事情做成。

刚柔并济才能实现可持续发展

在今年的工作中,我逐渐有了一些中庸之道的感悟。每个人都有不同的特点,要凝聚一个多样化的团队,需要更多的包容性和开放性。过于从自己的视角偏重某一维度,反而可能导致适得其反,产生“过刚易折”的效果。在做人做事时,面对大的目标和原则性问题时,我们要坚持“刚”;而对于那些不影响最终目标的小细节,则可以选择“柔”。此外,在与许多跨行业朋友的沟通中,我逐渐意识到,尽管我们都在努力工作,但很多事情还是需要天时地利。有时候,顺势而为、蛰伏等待也许是成功的关键。

因此,保持良好的工作心态,营造融洽的工作氛围,在自驱保证自我成长的基础上,不必过于纠结于远方的目标,而应专注当下刚柔并济,才能实现可持续发展,并与团队一起走得更远。

如何平衡个人输出和团队输出

今年对我个人的时间管理和抗压能力来说是极具挑战的一年。上半年临危受命接管存储组,scope 显著扩大,团队人数相比去年接近翻倍。由于一些原因,无法进一步进行分级管理,这对我个人精力提出了极大挑战。基本上,我每天都在不断地线程切换,盯着十几二十个事项。尽管我已经转变为“没有深入参与时间,只略微沟通过程便要结果”的最低投入策略,但由于我依然是单点瓶颈,很多进展缓慢的事情和无人处理的 bug 需要我来当“救火队员”。我的时间依然远远不够用,一旦我在个人处理的某个问题上阻塞了一两个小时,那基本上会造成四五个问题的连锁阻塞。高压状态下,这种情况对我个人的心态和情绪也产生了一定的影响。幸好,下半年江天学长挺身而出,接过了存储组的压力,帮助我们组的人数恢复到了一个相对合理的规模,让我有更多精力去探索和深度参与我们组的很多工作。

现在回想这段经历,我意识到一个人的合理管理半径不应超过 10 个人。在这个范围内,能够在个人输出和团队输出之间取得一个良好的平衡。此外,只有与团队一起成长、大家自驱地去做事,才能在不线性增加时间和精力投入的情况下,扩展管理半径。

与指数增长团队一起指数增长

即使是同一个人,在不同的年纪,对于金钱、工作氛围、健康和工作生活平衡等方面的追求都会有所不同。但一直以来,驱动我前进并屏蔽这些外部欲望的动力,始终是如何在单位时间内获得更多的成长。随着时间的推移,我逐渐意识到,能够承担越来越大的责任,并创造更多的价值,才是个人成长的核心所在。

固然我们可以在任何地方按照这个思路去追求自我成长,但只有在一个增量团队中,团队和个人的双指数增长才更容易实现。希望大家可以找到与自己 match 的指数增长团队。

来年展望

通过这一年,我们为 IoTDB 在技术上构建了摩尔定律的成长节奏。幸运的是,这些技术积累也立即在影响力和营收上得到了体现。希望在新的一年,无论是我个人还是团队,都能继续保持这种摩尔定律般的成长节奏,推动更多的技术创新和业务突破。

最后,在除夕之际,预祝大家新年万事如意,心想事成!愿每个人在新的一年中都能够事业有成,收获满满!

2023 年终总结:从清华 Apache IoTDB 组到创业公司天谋科技

2024-02-07 15:24:52

前言

兜兜转转又是一年,不知不觉 2023 已经结束。回想自己过去一年的成长与感悟,依然觉得是收获满满。今年工作之后闲余时间相比学生时代少了许多,到了除夕才有时间来写今年的年终总结。好在自己还是下定决心将这个习惯坚持下去,希望这些年终总结不仅能够在未来的时光里鞭策自己,也能够获得更多大家的反馈来修正自己。

首先依然是自我介绍环节,我叫谭新宇,清华本硕,师从软件学院王建民/黄向东老师。目前在时序数据库 Apache IoTDB 的商业化公司天谋科技担任内核开发工程师。我对分布式系统、可观测性和性能优化都比较感兴趣,2023 年也一直致力于提升 Apache IoTDB 的分布式能力、可观测性和写入性能。

接下来介绍一下我司:

天谋科技的物联网时序数据库 IoTDB 是一款低成本、高性能的时序数据库,技术原型发源于清华大学,自研完整的存储引擎、查询计算引擎、流处理引擎、智能分析引擎,并拓展集群管理、系统监控、可视化控制台等多项配套工具,可实现单平台采存算管用的横向一站式解决方案,与跨平台端边云协同的纵向一站式解决方案,可方便地满足用户在工业物联网场景多测点、多副本、多环境,达到灵活、高效的时序数据管理。

天谋科技由全球性开源项目、Apache Top-Level 项目 IoTDB 核心团队创立。公司围绕开源版持续进行产品性能打磨,提供更加全面的企业级服务与行业特色功能,并开发易用性工具,使得 IoTDB 的读写、压缩、处理速度、分布式高可用、部署运维等技术维度领先多家数据库厂商。目前,IoTDB 可达到单节点每秒千万级数据写入、10X 倍无损压缩、TB 数据毫秒级查询响应、两节点高可用、秒级扩容等性能表现,实现单设备万级点位、多设备亿级点位管理。

目前,IoTDB 能够为我国关键行业提供一个国产的、更加安全的、性能更加优异的选择。据不完全统计,IoTDB 已服务超 1000 家以上工业企业,在能源电力、钢铁冶炼、航空航天、石油石化、智慧工厂、车联网等行业均成功部署数十至数百套,并扩展至期货、基金等金融行业。目前已投入使用的企业包括华润电力、中核集团、国家电网、宝武钢铁、中冶赛迪、中航成飞、中国中车、长安汽车等。

2023

介绍完背景后,在这里回顾下 2023 年我们系统组的主要工作,可分为高扩展性、高可用性、可观测性、性能优化、技术支持和技术沉淀 6 个方面。

在高扩展性方面,我们主要做了以下工作:

  • 计算负载均衡: Share Nothing 架构面临的主要挑战之一是扩展性问题,扩缩容过程中需要迁移大量数据,这不可避免地消耗系统资源,进而影响现有的读写性能。为了解决这个问题,Snowflake 带头在业界推广了存算分离的架构设计,近年来的 Serverless 架构则进一步追求了更极致的弹性。尽管存算分离架构能够避免在扩缩容时迁移大量数据的问题,但它仍面临着冷启动问题。也就是说,当一个计算节点宕机后,从对象存储服务恢复宕机节点数据的过程可能会比较耗时,这对于对 SLA 要求极高的应用场景构成了挑战。那要如何解决这一问题呢?在 VLDB 2019 的论文中,ADB 介绍了其架构解决方案,其中一个值得注意的点是,它对本应无状态的 ReadNode 实施了热备份。虽然论文没有解释为何采取这种做法,但很明显,这种方案可以通过增加机器资源消耗来确保 SLA 指标,从而进一步说明了面对不同业务场景和问题时,不同架构可以找到更加适合的 trade-off。针对 Share Nothing 架构的扩展性问题,乔老师今年引导我们探讨了在时序场景中是否可能避免扩缩容时的数据迁移。我们发现,相比传统的 TP/AP 场景,时序场景有几个不同之处:首先,读写负载相对更加稳定可预测;其次,大部分情况下数据的时间戳会呈现正态分布,并随着时间不断递增。这为我们提供了结合场景进行优化的可能性。我们通过将数据划分为不同的时间分区,并在新的时间分区到来时进行实时负载均衡分配,从而实现了无需迁移数据即可达到计算资源均衡的效果,甚至在运行 TTL 时间后,还能进一步实现存储和计算资源的双均衡。回顾我们的设计,通过牺牲新节点立即提供服务的能力,我们避免了扩容时的数据迁移,这在大多数负载可预测的时序场景下取得了良好的效果。当然,对于一些特殊场景,我们也提供了手动 Region 迁移的指令,以便运维人员根据业务需求,在存储和计算资源的平衡时间上进行手动调整。
  • 分片分配算法:在今年上半年针对某用户的 12 节点 2 副本场景进行高可用性测试时,我们遇到了一个问题:当我们故意使一个节点宕机后,发现另一个节点出现了 OOM 现象。深入分析后,我们明白了问题所在:由于整个集群仅有 6 个副本集合,一个节点的宕机导致约 1/6 的 Region Leader 被迫迁移到了同一节点上,这导致了该节点过载,进而出现 OOM。其实这一问题是一个典型的分片分配问题。我们调研学习了来自 Stanford 的 ATC 2013 Best Paper Copysets 论文以及该作者两年后在扩缩容场景对 Copysets 算法的补充,并决定将该算法应用到 IoTDB 中。通过这一改动,客户场景中的节点散度从 1 增加到了 5.11,这意味着当单个节点宕机时,多个节点能够分摊待迁移 Leader 的压力,有效避免了 OOM 现象的发生。此外,集成 Copysets 算法还带来了其论文提到的对于数据丢失概率和副本恢复速度的提升。回顾这项工作,最让人印象深刻的是陈老师的深厚算法功底。在我们努力理解论文理论证明的过程中,陈老师补充了论文中遗漏的公式证明。当陈老师引入泊松过程的概念时,我们尚能跟上步伐;然而当陈老师引入指数型随机变量和连续马尔可夫链的概念时,我们只能赞叹:天不生陈老师,飞书 Latex 公式功能万古如长夜了。
  • 企业版激活:对于企业版软件实现可信授权,我们面临多项挑战:如何在不依赖网络的情况下部署,同时通过硬件绑定来防止许可证的滥用?如何设计一个系统,让激活次数不再受节点数量的限制,以提高整个集群的激活效率?我们还需要引入一系列的使用限制,包括许可证的有效期、节点数、CPU 核心数、序列号和设备数等等。此外,还需考虑如何防止各种潜在的破解尝试,比如回调系统时间、复制文件目录、在使用相同机器码的云平台上部署等,同时保证这些安全措施不会影响到商业用户的使用体验,例如支持非 root 用户激活、提供一键激活功能等。面对这些问题,我们逐一制定了解决方案并加以设计实现。在这个过程中,我和宇衡对各种 Corner Case 进行了深入的分析和讨论,这段经历让我受益匪浅。

在高可用性方面,我们主要做了以下工作:

  • IoTConsensus:在过去的一年里,我们针对基于异步复制思路的 IoTConsensus 共识算法,在性能、稳定性、鲁棒性和可观测性方面做出了显著提升。如今,在线上的大部分场景中,该共识算法已经被优化至接近实时同步的效果,基本上不会再出现因为同步速度跟不上写入速度而导致的 WAL 堆积现象。接着我们开始思考一个命题:在异步复制系统中,不考虑节点宕机等异常情况,是否能在任何写入负载下都保持同步速度与写入速度同步?通过对 MySQL binlog 异步复制等类似场景的观察,湘鹏和我通过排队论的论证和性能实测发现,这个假设是错误的。这一发现促使我们开始进一步探索和设计基于操作变更到状态变更的共识算法。尽管理论上 Leader 侧的 WAL 堆积问题似乎无解,但在实际工程应用中,我们找到了解决办法。我们不仅在多个方面迭代优化以减少 WAL 堆积的可能性,还特别总结了导致 WAL 堆积的八大潜在原因及其解决策略。目前,我们团队已有许多成员能够独立地诊断并解决这一问题,有效地消除了这一单点瓶颈。
  • RatisConsensus:今年,我们对 Apache Ratis 社区做出了显著贡献,包括引入了基于 Read-Index 和 Lease Read 的线性一致性读功能,以及若干状态机易用的 API。我们还提高了 Snapshot 传输的稳定性,并提交了超过 30 个 patch,涵盖了各种 bug 修复。除此之外,宋哥不仅多次担任 Ratis 社区的 Release Manager,近期还荣幸被邀请成为 Apache Ratis 社区的 PMC 成员。宋哥作为目前 Ratis 社区 Top3 活跃的开发者,已经时常被我们开玩笑称为 Ratis 社区 Vice PMC Chair 了。
  • 共识层:去年,IoTDB 共识层参考了 OSDI 2020 Best Paper Delos 的思路进行了设计和实现,支持了多种具有不同一致性和性能特性的共识算法。今年,我们在性能与一致性级别这两个维度上对其支持的不同共识算法进行了深入的对比分析,为 IoTDB 的实施及用户在选择共识算法时提供了重要参考。我们还广泛调研了多种数据库的共识算法实现,通过文档阅读、代码走读和性能实测等多种方法,从共识算法的功能和性能开销等多个角度进行了细致地对比,并取其精华,去其糟粕。此外,今年我们在一些内存紧张的特殊场景下,发现 IoTConsensus 可能会出现副本不一致的问题。经过排查,我们认识到问题并非出在共识算法本身,而是由于状态机执行的不确定性导致的。虽然理论上根据 RSM 模型,所有副本应当达到一致状态,但在实际工程实践中,许多问题都可能使得 RSM 模型不完全适用,比如 Leader 的磁盘写满而 Follower 的磁盘未写满,可能引发执行的不确定性。针对这一问题,我们咨询了曾在 OB 工作的剑神,并在知乎上发问探寻大佬们的解决思路。收到的许多反馈都倾向于“Fail Fast”的处理原则,这可能是因为对许多 TP 系统而言,一致性比可用性更为重要。然而,对于时序场景,可用性往往比短暂的不一致性更加重要。因此,我们认为在遇到此类问题时直接退出进程并不是一个合适的解决方案。为此,我们通过在共识层捕获此类异常并采取有限重试的策略,以避免让业务感知到这种现象,从而保证了系统的高可用性和一致性。

在可观测性方面,我们主要做了以下工作:

  • 监控面板:今年,我们借鉴了火焰图作者在《性能之巅》中的思路,从用户视角和资源视角出发,构建并完善了四个监控面板,共计近四百个 panel。这些面板的建设旨在提供全面的性能监控和分析能力,帮助我们更有效地诊断和解决性能问题。首先,我们设立了 Performance Overview 面板,该面板汇总了集群信息,不仅能帮助我们判断性能瓶颈是否存在于 IoTDB 中,还能进一步拆解并统计不同类型请求的延迟分布,从而精确定位到 IoTDB 内部读写流程的具体瓶颈环节。其次是 System 面板,它聚焦于系统资源,包括网络、磁盘、CPU、线程池利用率、JVM 内存和 GC 等多个维度的监控数据。这个面板为系统资源瓶颈的分析提供了丰富的数据支持,使我们能够从资源层面进行深入分析。接下来,我们还有包含集群节点状态、分区信息等的 ConfigNode 面板,以及涵盖存储、查询、元数据、共识和流计算等引擎监控的 DataNode 面板。这两个面板从不同角度提供了 IoTDB 集群的详尽状态和性能信息,为我们提供了全面的监控视图。在这个过程中,我们团队中也涌现出了包括吾皇,彦桑在内的多位 Grafana 艺术家。他们运用 Grafana 的高级功能,创造了许多既美观又实用的监控面板,所有这些都是各位艺术家精心设计的作品。
  • 监控模块:在过去一年中,随着 IoTDB 各模块可观测性的显著提升,监控指标数量从 100 多个增加到了 900 多个。尽管监控指标数量增加了近 10 倍,但监控模块在火焰图中的 CPU 开销却从 11.34% 下降到了 5.81%,实现了显著的开销节省。这一成就主要归功于俊植、洪胤和我对监控模块的持续迭代和优化。我们不仅对 IoTDB 自身的监控框架进行了大量优化,还结合了 Micrometer 和 Dropwizard 这两个 Metric 库,通过白盒调参或自研选择了对写入操作最友好的实现方式,并针对不同监控指标类型进行了精细化管理。此外,今年雨峰、洪胤和我还持续完善了线上 IoTDB 的巡检文档、告警文档以及面板快照的导出方法等,进一步提升了运维工作的效率和便捷性。通过整个团队一年的共同努力,我们的监控模块不仅大幅提高了问题排查和性能调优的效率,而且已经成为运维 IoTDB 不可或缺的工具。现在我也可以非常自豪地说,IoTDB 现在的可观测性水平已经接近 2022 年暑假我在 PingCAP 实习时体验到的 TiDB 的可观测性水平,在时序数据库中也处于领先地位,这对于我个人和我们组来说是一个巨大的成就。
  • 日志精简:今年,我们注意到 IoTDB 线上环境中日志打印量较大,这在一定程度上影响了问题排查的效率。随着监控面板的日益完善,许多原本需要通过日志记录的性能统计信息已经能够通过监控模块以更高的信息密度进行记录,这使得部分日志变得不再必要。因此,吾皇和我针对 36 个用户和测试场景进行了深入的日志挖掘分析,筛选出了 62 条出现频率较高的日志记录。经过与各模块负责人的逐一讨论,我们对其中 23 条日志进行了降级(例如从 info 降至 debug)或直接删除的优化处理。此外,团队内部就如何打印性能调优、系统关键行为、SQL 执行错误等异常情况的日志达成了共识。通过这次日志精简工作,在不同场景下我们总共减少了约 37% 到 74% 的日志打印量,取得了明显的效果。其实这项工作可大做可小做,但我们还是非常认真地编写了日志分析脚本进行分析,并进行了量化的数据统计和效果预估。完成这项工作后,有一次我和在北大读博做可观测性研究的张先生闲聊,居然发现我们的工作思路与他们领域内腾讯和中山大学在 2023 ICSE 上发表的顶会论文 LogReducer 非常相似。这种巧合让我感到非常有成就感。我们的工作不仅提升了 IoTDB 的运维效率,还与学术前沿领域的研究工作不谋而合,证明了我们的方向和方法是具有前瞻性和实际应用价值的。

在性能优化方案,我们主要做了以下工作:

  • 某知名测试场景性能调优及打磨:今年后半年,我和刚上博一对 IoTDB 几乎 0 基础的谷博共同投入到了某知名测试场景的瓶颈分析、性能调优和内核迭代中。在短短三个月的时间里,谷博迅速成长为一个具备系统思维和深度 IoTDB 调优能力的专家。我们的努力最终获得了显著成果,不仅在该测试场景中取得了第一名的成绩,还通过了第三方的评测。这一成就不仅证明了 IoTDB 1.x 架构的出色性能,也让我们对于 2024 年能够实现更进一步的成绩充满期待。
  • 写入性能优化预研:IoTDB 之前主要集中在列式写入接口的性能迭代,而对行式写入接口的关注不足。鉴于今年许多用户由于各种原因必须使用行式接口,我们迫切需要对行式写入接口进行深入的瓶颈分析和性能优化。借助于我们目前的可观测性能力,以及对各种性能分析工具(如 JProfile、Arthas)的熟练使用,旭鑫和我对可能的性能提升方案进行了大量的 demo 级别预研。针对典型场景,我们已经找到了 5 个主要的优化点,预计完成这些优化后性能将提升一倍以上。当然,性能优化是一项需要持续投入的工作。当把目前发现的主要优化点做进去后,我们也会基于新的 codebase,继续探索新的瓶颈和优化方案。在这个过程中,我们意识到最重要的是积累理论建模能力和系统思维。如何针对任何系统分析当前的瓶颈并提出有效的优化方案,成为了我们在这项工作中积累的最宝贵财富。

在技术支持方面,我们主要做了以下工作:

  • IoT-Benchmark 基准测试工具的发展:IoT-Benchmark 在过去一年中实现了显著的功能提升,特别是在写入能力(跨设备写入)、查询能力(align by device/desc/limit 查询)和元数据建模能力(支持不同 TagKey 层级设置 TagValue 个数)方面。通过持续的迭代更新(50+ commits),我们不仅增强了工具的功能和稳定性,还吸引了其他时序数据库社区的贡献者,如 CnosDB 的开发者就在最近为我们贡献了 CnosDB Client Driver 的代码。我们期待 IoT-Benchmark 能够成为时序数据库领域内公认的基准测试工具,为不同的时序数据库提供一个公平竞技的平台。
  • POC:今年我们组参与了 10+ POC 项目,覆盖了海、陆、空、天等多个领域,并成功部署上线了 95 节点的 IoTDB 集群,实现了 62.6 GB/s 的最大吞吐量和 0.8 以上的集群线性比。参与这些带有挑战性的项目并最终成功落地还是非常让人有成就感的。
  • DBA 宝典:在乔老师的带领下,我们逐步构建了面向 IoTDB 的 DBA 宝典。通过梳理异常排查方案和问题导图,我们为 33 个常见问题提供了原因分析和解决策略。DBA 宝典的存在大大降低了实施团队处理异常的难度,有效减轻了产研团队的 Oncall 负担。
  • Oncall:今年,我个人承担了组内 80% 以上的 Oncall 工作,这不仅是一次对个人能力的极大考验,也是一次成长和学习的机会。通过不断地思考和解决问题,我对 IoTDB 的各个模块有了更深入的了解,并明确了可观测性建设的推进思路。值得一提的是,尽管项目数量还在增加,我的 Oncall 效率已经显著提升,感受到的压力也在逐渐减轻,这与 DBA 宝典的不断完善和实施团队技术支持团队的建立息息相关。

在技术沉淀方面,我们主要做了以下工作:

  • 技术工具:今年我们梳理了常用的 JDK 和 Linux 命令,也用熟了问题排查工具 JProfile 和 Arthas。回想之前看一个 Runtime 的值还需要使用 UDF 去 hack,现在我们直接用 Arthas 就可以了,技术工具的进步极大地提升了我们的生产力。在性能调优方面,除了常见的 JProfile 线程耗时分析和 Arthas 火焰图,权博带领我们探索了 Intel vTune 工具,用于观测高性能机器上的跨 NUMA 访问比例和 CPU 前后端执行效率等。随着 IoTDB 性能优化进入深水区,需要不断将硬件性能进一步压榨,学会使用这些原本 HPC 才可能需要的工具也就非常重要了。
  • 论文讨论班:今年我们组组织了 6 次工程讨论班和 6 次论文讨论班,对 6 个方向的共 15 篇论文进行了分享介绍,其中一些论文已经提供了写入性能的优化思路并 demo 实测有效。这中间最让我印象深刻的还是旭鑫的存算分离讨论班,我们对若干友商的云服务版本进行了计价统计,发现某些号称云原生时序数据库的系统定价显著高于其他时序数据库,我猜测是因为系统架构用了 EBS 而非对象服务吧,那么高成本就只能让用户买单了。
  • JVM:今年我们对 JVM 有了一些深入的探索和技术沉淀。俊植和我细致调研了 Java 的内存分类和观测手段,通过使用 NMT 等工具,我们发现堆外内存分类居然有 19 种之多,这是我在外面的八股中从没看到的结论。在 GC 方面,俊植和我不仅完善了 GC 的可观测性指标,例如不同 GC cause 的次数和耗时以及 GC 占据 Runtime 的比例等等。我们还针对 JDK 8/11/17 的默认 GC 算法 PS 和 G1,分析学习其原理并列举其所有可调参数,搜索优质 GC 调优博客并积累 GC 调优经验。目前我们已经基本具备了对 GC 深度调优的能力,在 GC 严重场景通过调优甚至能带来 60%+ 吞吐的提升,今年我们也会不断细化沉淀这里的方法论并择机分享。在向量化 API 方面,今年旭鑫实测了 JDK21 的 Vector API,在部分场景下能够带来最大 13.5 倍的性能提升,这也是 IoTDB 未来进行性能演进的技术储备之一。
  • IoTDB 磁盘文件地图:今年我们参照 Oracle/IBM 等数据库绘制了 IoTDB 的磁盘文件地图。通过该地图,我们不仅发现了一些可以潜在优化的点,还理顺了不同模块落盘文件的逻辑关系。
  • 压缩算法性能测试:今年我们针对若干用户场景的真实数据进行了压缩算法的对比测试,发现大多数场景下 LZ4 相比 Snappy 有更好的压缩效果,这也促使了 IoTDB 默认压缩算法的更改。
  • 难点预研:今年我们组还针对多个复杂问题,如共识组数与集群性能、线程模型、集群滚动升级方案和大 Text 值类型访存瓶颈优化方案等进行了深入的调研和测试,虽然部分工作尚未得出最终结论,但已经为未来的深入研究奠定了基础。

今年我在 Apache IoTDB 社区提交并被合并了 119 个 PR, Review 了 387 个 PR。从 PR 数量上来说相比去年和前年有了显著提升,可能是由于更加专注于工作,并且 scope 也在不断扩大吧。此外我也于今年 9 月受邀成为了 Apache IoTDB 社区的 PMC 成员,感谢社区对我的认可。

因时间所限,我今年在知乎等社交平台的活跃度有所下降。但回顾这一年,我觉得我们团队完成了许多既有趣又深入的工作,并且几乎都有相应的文档沉淀下来。这些宝贵的积累完全可以与业界分享以交流学习。我期待在 2024 年,我们团队能够更频繁地分享我们的技术沉淀,并吸引更多对技术有兴趣的同学加入 IoTDB 社区或我们的实验室进行交流!

一些感悟

性能优化:体系结构和操作系统是基本功

在深入研究和优化数据库系统在各种硬件环境及业务负载下的性能过程中,我越发认识到掌握体系结构和操作系统知识是进行性能优化的基础。今年,我在这两方面补充了许多知识,并阅读了《性能之巅》的部分章节。然而,令人感到有些沮丧的是,随着知识的增加,我反而越来越感觉到自己的无知。但我仍然希望,在 2024 年能够跨越这段充满挑战的绝望之谷,登上开悟之坡。

对于有意向学习 CMU 15-418 课程的朋友,我非常期待能够一同学习和进步!如果有经验丰富的大佬愿意指导,我将不胜感激!

GC 算法:追求吞吐还是延迟?

今年,我们组深入研究了 JDK 的垃圾回收(GC)算法,包括但不限于 Parallel Scavenge(PS)、Concurrent Mark Sweep(CMS)、Garbage-First(G1)和 Z Garbage Collector(ZGC)。我们还对 IoTDB 在相同业务负载下采用不同 GC 算法的吞吐量和延迟性能进行了比较测试,结果表明在不同的负载条件下,各 GC 算法的性能表现排序也有所不同。

在 GC 算法的选择上,我们面临着内存占用(footprint)、吞吐量(throughput)和延迟(latency)三者之间的取舍,类似于 CAP 定理,这三者不可能同时被完全满足,最多只能满足其中的两项。通常情况下,高吞吐量的 GC 算法会伴随较长的单次 STW 时间;而 STW 时间较短的 GC 算法往往会频繁触发 GC,占用更多的线程资源,导致吞吐量下降。例如,PS GC 虽然只有一次 STW,但可能耗时较长;G1 的 Mixed GC 在三次 STW 中的 Copying 阶段可能造成几百毫秒的延迟;而 ZGC 的三次 STW 时间都与 GC Roots 数量有关,因此 STW 延迟可以控制在毫秒级别。

JDK GC 算法的发展趋势似乎是在尽量减少 GC 对业务延迟的影响,但这种优化的代价是消耗更多的 CPU 资源(JDK 21 引入的分代 ZGC 有望大幅降低 ZGC 的 CPU 开销)。在 CPU 资源本身成为瓶颈的场景下,使用 ZGC 和 G1 等 GC 算法的吞吐量可能会低于 PS。GC 算法目前的演进具有两面性,例如 Go 语言就由于其默认 GC 与 Java 相比 STW 时间较短而被赞扬,但其 CPU 资源消耗大也会被批评,我们需要根据不同的目标选择合适的 GC 算法。

然而,GC 算法朝低延迟方向的不断演进仍具有重要意义,因为吞吐问题可以通过增加机器进行横向扩展来解决,而延迟问题则只能依赖于 GC 算法的改进。因此,在调优时应该有针对性,分别针对吞吐和延迟进行优化,而不是同时追求两者。如果追求吞吐量,可以优先考虑使用 PS;如果追求低延迟,可以考虑使用 G1/ZGC,并为之准备额外的机器资源以支付低延迟的代价。

全局成本:C/C++ 相比 Java 性能更好?

今年,我参与了许多问题修复和优先级排序的工作,同时深入思考了编程语言对软件开发总成本的影响。

在 PingCAP 实习期间的一次闲聊中,有些同事提出 TiDB 应该用 Rust 或 C++重写,理由是用 Go 语言编写的性能较差。然而,我的 mentor 徐总认为,采用 Go 语言后显著减少了大家的 OnCall 次数,从而节约了大量研发成本。

从纯技术的角度看,C/C++ 在极限优化下确实能比 Java 更好地发挥硬件特性。但工程开发,尤其是内核开发,不仅仅是技术问题,它更多涉及到软件工程的广泛议题。现实中,我们经常面临着无休止的问题修复和需求实现,性能优化往往未能充分利用硬件能力。我认为,尽管开发团队采用的编程语言可能影响理论上的性能上限,但在大多数工程实践中,项目成功的关键并不仅仅在于将性能优化到极致。更重要的是,在有限资源下如何优先追求满足用户需求的产品特性、如何持续保证产品的稳定性和可维护性、如何提升系统的横向扩展能力、以及如何在现有代码基础上持续进行性能优化。我相信,这些因素比起编程语言的选择所带来的潜在收益要重要得多。

因此,除了少数极特别的场景(例如追求超低延迟 or 边缘端等),选择一个团队熟悉且学习成本较低的编程语言就足够了。

工程难题:不是所有技术问题都能够立即找到解决方案

今年,我们面对并快速解决了许多棘手的问题,但同时也遇到了一些难以快速找到原因的疑难杂症。这些问题涵盖了多个方面,例如 DataNode 进程在 OOM 后仍能响应心跳但无法处理新的读写请求(这是因为 JVM 在 OOM 后随机终止了一些线程,导致监听线程被终止无法响应新连接而心跳服务线程仍在运行),以及 Ratis consensusGroupID 编码错误导致的 GroupNotFound 错误(使用 Arthas 监控后问题消失,我们怀疑这是 JVM JIT 的 bug)等。

解决这些问题的过程加深了我们对于设计新功能时对各种异常场景的考虑,有效避免了许多未来可能发生的 Oncall 问题。

在面对问题和解决问题的过程中,我深刻体会到人的认知可以分为四个象限:已知的已知、已知的未知、未知的已知以及未知的未知。其中,最难以应对的是“未知的未知”。我一直在思考工程经验这四个字究竟意味着什么?现在我认为,工程经验的积累不仅意味着将更多的“已知的未知”转化为“已知的已知”,还需要将更多的“未知的未知”变成“已知的未知”,这样才能具有可持续性。

流程体系:软件开发团队的重中之重

今年,我深刻体会到了流程体系在构建一个可持续发展的软件开发团队中的重要性。我认识到只有拥有一流的团队,才能够开发出一流的软件。

在王老师软件工程理念的统筹指导和 Apache 基金会的支持下,我认为我们的产品流程体系已经相对健全,包括但不限于以下几个方面:

  • CI/CD:对不稳定的 UT 和 IT 进行持续的修复,确保代码质量和功能稳定性。
  • 代码质量静态检测:利用 Sonar 等工具持续提升代码质量,确保软件的健壮性。
  • Commit 级别的监控:针对不同的用户和测试场景,实现性能和资源使用量的监测,防止出现非预期的产品回退。
  • 定期封版和发版:对每一项 Release Note 进行逐项测试验证,通过多轮的 RC 版本,不断收敛测试范围,确保成功发布。
  • 定期的功能和技术评审会议:各模块的核心开发者共同参与,评估产品的功能和技术实现。
  • 发版问题同步会:确保团队成员对 RC 验证中发现的问题能够快速响应。
  • P0 项目支持任务同步会:对重要项目的支持任务进行同步和讨论。
  • 多层级技术支持团队(L0/L1/L2):根据问题的复杂度,提供分层次的技术支持。
  • 敏捷开发的支持工具:使用多维表格等工具,支持敏捷开发流程。
  • 论文讨论班:持续学习和探索行业内的最新研究成果。
  • 竞品功能和技术分析:分析竞争对手的产品,从而不断优化自身产品。
  • 安全漏洞感知和修复:及时发现和修补安全漏洞,保证产品的安全性。

通过在这样的团队中工作,我对如何打造一个可持续的软件工程体系有了更深地理解。

工作管理:一键生成总结是好是坏?

随着我们组负责的模块和同学数量的增加,我逐渐发现,仅仅通过飞书文档记录工作内容的做法,虽然实现了工作的“记录”,却缺乏了有效的“管理”。例如,我们组面临的任务琐碎而多样,大家都经常会忘记一些计划中的任务;同时,我们的业务需求变化迅速,虽然大家都在同时推进多项任务,但仍然跟不上需求的变化速度。这就要求我们能够及时调整任务的优先级,以便灵活应对并优先完成 ROI 最高的任务。此外,我们以前的月度总结并没有持续进行,我分析的原因是任务汇总本身就是一种成本,导致月度总结难以持续,从而失去了很多总结沟通的机会。

为了解决这些问题,我开始学习并使用飞书的多维表格来管理团队的任务。通过多维表格,我们不仅可以清晰地看到每位成员当前的工作任务,还可以在团队会议上根据业务需要灵活调整任务优先级,甚至能够一键生成甘特图来明确不同优先级任务的时间线。在进行每周和每月总结时,我们也能够通过筛选日期快速生成任务汇总。

一开始,多维表格似乎完美地解决了我们之前的问题。然而,随着时间的推移,我发现这种方式也存在缺陷。由于总结能够一键生成,我不再每周花费一小时来统计和规划我们的周报和下周计划,甚至我们的月度总结也鲜少举办。这反而导致我们的日常开发缺乏规划,显得有些随波逐流。在东哥的点拨下,我重新开始在飞书文档中记录周报,并且连续三个月组织了月度总结会。通过定时的每周和每月汇总与沟通,团队的工作变得更加有序和明确。现在如果让我去说上半年做了什么主要工作,我可能还需要看多维表格筛选半天,但如果问我后 3 个月做了什么,我只需要看每月的月度总结就可以了。

现在,我们通过多维表格来管理任务的优先级,同时利用飞书文档来汇总周报和月报。通过对我们组流程管理的持续迭代和优化,我意识到有时候追求速度反而会拖慢进度,而适当地放慢脚步思考反而能够使我们更加高效。

团队协作:分布式系统的高扩展性和高可用性

在技术方面,我最开始深入了解的就是分布式系统,我一直在学习如何实现系统的高扩展性和高可用性。随着时间的推移,我发现这些分布式系统的理念同样适用于团队协作中。

为了实现高扩展性,关键在于让所有团队成员并行工作,而不是仅依赖于“主节点”或关键个体,这要求每个成员都能独自完成任务并持续提高自己的工作效率,这样才能提升整个团队的整体性能。同时,团队还需要能够支持成员的动态调整,如新成员的加入和旧成员的离开,确保团队结构的灵活性和适应性。

为了满足高可用性,就需要在关键任务或数据上实施冗余策略,以防止暂时的不可用状态对团队工作造成影响。这可能意味着需要在某些区域投入额外的资源,确保信息、知识或工作负载能够在多个成员之间共享,保持一致性。

这一年来,我们团队负责的模块不断增加,但每个模块都至少有 3 位以上的成员熟悉,上半年我的感受是每天从早忙到晚,连半天假都请不了。但到后半年我感觉偶尔请一两天假也不会对外产生可感知的影响了,这代表了我们组的高可用性出现了显著提升。针对我们组负责的模块,我们维护了详尽的功能和技术设计文档,以及改进措施的追踪记录,这不仅加速了新成员的融入,也保持了团队知识的一致性。此外,我们通过引入自动化工具,如飞书激活解密机器人、各类测试脚本、木马清理脚本等,有效提升了团队的工作效率,体现了我们组在高扩展性方面的进步。

希望 24 年我们组能在高扩展性和高可用性方面继续取得显著进步,为实现更加高效和稳定的团队协作模式而不断努力。

时间管理:可观测性

今年我们组的主要工作之一便是打造 IoTDB 的可观测性,目前已经显著提升了问题排查和性能调优的效率,成为线上运维 IoTDB 的必备工具。回到时间管理上,我发现可观测性的很多理念也同样适用。

随着组内同学越来越多,scope 越来越大,沟通协调的成本已经不容忽视,我自己的时间越来越不够用,逐渐成为了单点瓶颈。在向东哥请教后,我开始按照半小时为单位记录自己每天的工作内容,并定期反思每半小时的工作是否满足了高效率。

通过整理自己工作日一天 24 小时的时间分配,我发现自己实际可用于工作的时间并不超过 11 小时,因为每天基本要包括睡眠 8 小时、起床和就寝的准备及洗漱时间 1 小时、通勤 1 小时、餐饮和午休 2 小时以及运动 1 小时(有时会被娱乐消遣取代)。11 月份的数据显示,我的平均工作时间约为 10 小时(没有摸鱼时间),已经接近饱和每天都十分充实。这促使我思考如何提升自己和团队单位时间的工作效率,比如在协调任务时明确目标和截止日期,实行更细致的分工以解决我作为单点瓶颈的问题等。通过这些措施,到了 12 月份,我的平均每日工作时间减少到了 9.5 小时,而感觉团队的整体产出反而有所提升。不过,到了 1 月份,由于一些新的工作安排尚未完全理顺,我的平均工作时间又回升到了 10 小时,这需要我持续进行优化。

总的来说,定期统计和评估自己的时间分配及其 ROI,我觉得对于提高工作效率具有重大意义。

心态变化:职业发展和生活的关系

在经历了半年学生生活和半年职场生活后,我对职业发展与生活的关系有了新的认识和感悟。之前我是那种职业动机极强以至于生活显得相对单调的人。对我来说,除了那些能带给我快乐的少数娱乐活动外,生活中的许多琐碎事务如做饭洗碗,都被视为时间的浪费,不如将这些时间用于创造更多的价值。在地铁和高铁上不学习,我也会感到是对时间的浪费。我认为既然职业发展对我而言十分重要且能从中获得快乐,那么我应该将所有可用的时间都投入其中。

然而今年我的心态发生了显著的变化。我逐渐意识到,即使职业发展很重要,即使我能从中获得快乐,它也只是生活的一部分。我开始挤出更多的时间来陪伴家人,也开始与各行各业的老朋友新朋友进行交流。我不再认为生活中的全部琐事是对时间的浪费。我更加注重如何在有限的工作时间内提升效率完成超出预期的工作,而不是简单地用更多的时间去完成这些工作。

这种心态的转变对个人来说不一定是坏事。如果我的心态没有这些变化,可能会投入全部可用的时间于职业发展中,但这样的状态不确定能够持续多久。如果我的心态发生了变化,那我可能会更加注重工作效率和生活体验感,也许能达到职业发展和生活的双赢。

总的来说,每个人在不同的年龄阶段对这种平衡的感悟都会有所不同。我目前的想法是,顺应我们不断成熟的心态,选择让我们感到最舒适的状态,这不仅能让我们的心理状态更加健康,也能更好地平衡职业发展和生活的关系。

任务分配:兴趣驱动,效率优先

马克思指出社会分工是生产力发展的结果和需要,这种分工具有历史的必然性。对于创业公司而言,追求指数型增长是生存和发展的关键,因为即使是线性增长,在激烈的市场竞争中也可能面临被淘汰的风险。如何实现这种增长,是一个复杂且多维的问题,我在这里只从任务分配的角度分享一些个人理解。

在创业团队中,自上而下的任务繁多,而自下而上每个成员的兴趣和专长也各不相同。如何最大化团队的价值?关键在于沟通和了解每个成员的兴趣点和擅长点,尽可能让他们大部分时间都在做自己感兴趣和擅长的工作。虽然总有一些额外的任务需要团队共同承担,但是优先保证成员大部分时间能够从事自己感兴趣且擅长的工作是非常重要的。只有这样,每个人才会带着兴趣和专长去挖掘提升效率的可能,从而可能产生指数级的复利效应,并最终影响整个团队的产出。在现有的权力结构体系下,无论是企业还是更广泛的社会,我觉得自上而下的人员任命也基本遵循这一原则。

基于这样的理解,我在分配我们组的任务时,尽可能根据我对团队成员的了解,分配给每个人感兴趣和擅长的任务,并与大家一起探索提升效率和价值的途径。这一年里,我一直在寻求任务分配的全局最优解,并坚信找到合适的人做他们感兴趣的工作,能够产生的复利远远超过随机或平均分配工作所能带来的效益。

个人发展:更广还是更深?

在创业团队的初期阶段,各方面的需求和缺口(技术,市场,运行,销售等等)很多。从公司的角度看,这就非常需要大家能够主动承担额外的职责。从个人的角度看,我们不论是承担更多的职责还是在自己所做的工作上做得更突出,都是对公司的贡献,也都能收获成长。然而人的精力总是有限的,一个人不可能完美地做完所有事情,总是要把有限的精力投入到有限的事情上。面对这样的环境,每个人都面临着如何在工作的广度和深度之间做出选择的问题。

对于这个问题,我今年有了一番思考和探索。个人觉得对于职场新人来说,寻找一个自己擅长且能从中获得乐趣和成就感的领域至关重要,并且需要与领导进行积极的沟通,以获得相应的支持和资源。每个人的选择可能不同,领导的任务就是在团队成员之间找到一个平衡点,不仅能够完成所有任务,还要尽量让每个人能在其擅长的领域内发挥最大的复利效应。

就我个人而言,我目前更倾向于追求工作的深度,希望能够深入学习并掌握我目前尚不擅长但团队需要的技术知识。通过专注于深度,我希望能够在专业领域内取得更大的进步,并为团队带来更具影响力的贡献。当然,这也并不意味着就完全抛弃广度,随着时间不断推移,我在广度上投入的精力也会越来越多。

协作理念:以人为本,真诚坦率

今年,通过阅读《跳出盒子——领导与自欺的管理寓言》和李玉琢老师的《办中国最出色企业:我的职业经理人生涯》,我对管理有了初步的理解和感悟。这两本书分别代表了不同的管理理念,一种强调以人为本,另一种则是以结果为导向的雷厉风行。对于我目前的心态而言,我认同后者的评价体系,但从个人性格上我自己的风格更像前者。

在日常的产品迭代和团队管理中,我始终认为把人放在第一位是非常重要的。通过团结所有可以团结的力量,关注每个成员的工作态度、能力、心理状态以及需求和期望,找到大家适合的方向,往往能比反复推动大家完成不情愿的工作更加高效。

当然,在工作过程中难免会遇到与某些人的争执和冲突。面对这些情况,我常采取的做法是换位思考。我会设身处地地想,如果我是对方,我是否也会做出同样的选择?如果答案是肯定的,那么这往往是角色之间的冲突,而非个人情感的问题,我就不会在情感层面上过多消耗精力。如果答案是否定的,我则会进一步探索解决分歧的方法。我是一个性格相对温和的人,我通常不倾向于与人争执,而是尽可能地通过和平的方式解决问题。今年,我几乎都是这样处理冲突的。

然而,我也逐渐意识到,过分的忍让并不会赢得他人的尊重和理解,反而会被得过且过。有些原则和理念是需要坚持的底线,绝不能妥协。希望在未来的一年里,我能够在保持真诚坦率的同时,也能够坚持自己的原则和底线。

人生成就:小赢靠智,大赢靠势

今年在工作之余也读了《新程序员》杂志,深入了解了很多大佬的成长经历,也获得了不少启发。一个很深刻的感悟还是江同志的一句话:一个人的命运啊,当然要靠自我奋斗,但是也要考虑到历史的进程。

自从 ChatGPT 爆火以来,周围已经涌现出许多彻底成功的案例,这些故事不仅激励着我,也让我对未来充满了好奇和期待。尽管对于自己未来的方向,我目前还没有一个清晰的规划,甚至只能对未来 1 到 2 年内的工作做出一些预测,3 年后会做什么我还没有确切的答案。

但在这样的不确定性中,我坚信的一点是,只要相信自己当前的工作富有意义和前景,并且能够在其中找到快乐,那么就值得坚持下去,全力以赴。关于未来命运将我们带往何方,或许可以交给时间和命运去安排。在这个快速变化的时代,保持学习和成长的心态,积极面对每一次机遇和挑战,可能就是我们能做的最好的准备了。

来年展望

经过一天多的思考,我终于完成了今年的年终总结。回顾这一年,我在技术和管理方面取得了一些进步,但同时也深刻意识到,在让企业成功的方方面面,我还有太多不了解不擅长需要学习的地方。

展望新的一年,我为自己和我们组设定了以下几点期望:

  • 做深:希望能够系统地学习体系结构、操作系统以及《性能之巅》中的相关知识,并将这些知识应用到实践中,不断提升 IoTDB 的技术水平和性能表现。
  • 做广:除了在分布式和可观测性方面的投入之外,希望能深入学习时序数据存储引擎和流处理引擎的知识,向优秀的同事和业界前辈学习。
  • 做好:持续努力提高 IoTDB 的稳定性、鲁棒性和易用性,确保它成为用户信赖的时序数据库。
  • 做响:寻找机会将我们团队的工作成果和经验分享给外部,与更多的同行进行技术交流,不断增强 IoTDB 的知名度和技术影响力。

最后,感谢您的阅读。欢迎各位读者批评指正。

在新的一年里,祝愿大家身体健康、家庭幸福、梦想成真。希望我们都能在新的一年中取得更大的进步!

2023 IoTDB 用户大会分享:如何用 IoTDB 监控工具进行深度系统调优

2023-12-07 18:03:00

背景

2023 年 12 月 3 日,IoTDB 一年一度的 用户大会 成功举办。

在本次大会中,我有幸作为讲师之一做了《优其效:如何用 IoTDB 监控工具进行深度系统调优》的分享,系统介绍了 IoTDB 这一年来在可观测性方面的进展,并展示了它如何显著提升我们的性能调优和问题排查效率。

本博客将通过文字和图片的方式展示我的分享内容。

我们在可观测性方面做的工作后续也会有更多的博客输出出来,敬请期待!

内容

大家好,我是来自天谋科技的谭新宇,接下来我为大家分享的主题是”如何用 IoTDB 监控工具进行深度系统调优”。

本次分享分为 5 个方面,首先我们将介绍数据库系统的用户服务和架构演进挑战,这些挑战的本质都是如何去提升效率;接着我们会对 IoTDB 可观测性的发展进行概览,主要包括 Logging,Metrics 和 Tracing 三个方面;然后我们会介绍一下 IoTDB 的监控模块,其构建主要参考了火焰图作者著作《性能之巅》的思路,即从负载视角和资源视角两个维度对系统进行观测;最后我们会概述一下 IoTDB 的 4 个监控面板并着重做一些性能调优和问题排查的典型案例分享。

首先先来分析一下数据库系统的用户服务和架构演进有着怎样的挑战。

对于用户服务,主要存在以下三个挑战:

第一是如何快速找到业务场景的瓶颈点?系统的性能存在木板效应,会受限于系统最慢的模块,比如某节点的 CPU 和磁盘还没有打满,但网卡已经被打满了,此时增加写入负载也不会获得任何性能上的提升。

第二是如何对业务场景进行针对性调优?不同硬件环境和业务负载的排列组合会使得很多默认参数并不是当下最优的值,针对这个问题,一种理想流派是像 ottertune 一样使用机器学习的方式去找到最优的参数组合,另一种更为实际的流派则是能够对系统进行白盒调优。

第三是如何形成可扩展的调优体系?对于性能调优这个工作,其实非常容易形成马太效应,即越会调优的人越容易被分配更多性能调优的工作,虽然他会越来越能调优,但这也容易形成单点瓶颈,导致调优工作横向扩展不起来,这样其实是不利于整个产研团队和实施团队的共同成长的。因此需要针对调优这项工作形成可复制的调优方法论,大家共享互补调优的知识,一起成长。

对于架构演进,也主要存在以下三个挑战:

第一是如何确定典型业务场景?性能优化需要结合场景谈论才有意义,而一个系统往往也会有很多用户场景,这就需要从中抽象出来通用普适的典型场景并总结他们的典型特征。比如硬件环境到底是 4 核 8G 还是 64 核 128G ,业务需求到底是追求低延迟还是高吞吐等等。

第二是如何进一步演进典型业务场景下的性能?任何系统在特定业务场景下都存在进一步性能演进的可能,我们需要在寻找瓶颈的过程中区分出来哪些是工程问题(比如 GC 参数调优,代码写的冗余),哪些是学术问题(比如针对 IoTDB 特定的时序场景,有些数据库原理的 trade-off 发生了变化,这个时候就可以结合场景做一些更针对的设计,IoTDB 近几年在 Fast 和 ICDE 等顶会上发表的论文都是沿着这个思路去设计的),区分出这两个问题之后就可以利用不同的思路去并行协作优化性能了。

第三是如何确保性能优化的 ROI 最大?对于一个系统怎么优化,收集一圈能够得到一大堆思路,到底哪些效果会好,哪些效果会差?我们需要能够精确评估一个优化的正向作用和负面影响,并能够量化排列优先级,这样才可能将有限的资源持续投入到 ROI 最大的性能优化上,坚持做最正确的选择。

分析完了挑战,其实我们也都清楚了可观测性对于解决这些挑战的重要性。那么接下来我们介绍一下 IoTDB 的可观测性发展概览。

随着分布式架构成为主流,可观测性这一名词逐渐被大家频繁提及。学术界一般会将可观测性分为三个更具体的方向进行研究,分别是 Logging,Metrics 和 Tracing。

Logging 的职责是记录离散事件,从而使得事后可以通过这些记录来分析程序的行为。

Metrics 的职责是将不同类型的消息分别进行统计聚合处理,从而能够对系统进行持续的监控和预警。

Tracing 的职责是记录完整的调用轨迹,这就包含了服务间的网络传输信息与各个服务内部的调用堆栈信息。

IoTDB 自诞生时就使用了 Logback 框架来管理日志,随着版本的不断迭代,目前已经将不同级别和模块的日志拆分成了不同的文件便于检索。

虽然这些日志很重要,但它所有的信息都是离散的。如果要对某一类的信息进行一些汇总聚合统计,比如统计一段时间的平均刷盘点数,就需要首先 cat 文件,接着再 grep 过滤出同一类型的日志,然后还要写脚本来计算次数,平均值之类的,这就非常繁琐。

很自然的这就需要引入 Metrics 了。

IoTDB 在 0.12 版本就开始设计开发 Metrics,但从 1.0 版本之后才开始投入大量精力打磨 Metrics,到了 1.3 版本 Metrics 已经基本打磨的差不多了。

我们用了 Micrometer 和 DropWizard 的算法库来作为监控指标的类型支撑,具体的存储可以导出到 Prometheus 或者 IoTDB 中,可视化目前主要是在用 Grafana 工具。

右边贴了一张我们监控面板的图,还是非常漂亮的,后面会进一步介绍。

有了 Metric 之后,我们可以统计同一类请求的聚合信息,例如平均值,P99 值等等。这其实已经能够解决 90% 以上的问题,但对于剩下 10% 的问题,比如海量小查询和一个大查询并发执行时,大查询的执行耗时会被吞并,从而无法体现在 Metrics 中。此时我们就需要具备单独观测一条请求完整调用链路耗时的能力。

为了满足这种需求,今年我们也启动了对 Tracing 工作的研究,我们用 OpenTelemetry,ElasticSearch 和 Grafana 搭建了 Tracing 系统。

比如右图对于 show region 请求的调用链路,我们可以在 Grafana 中展示这个请求跨进程通信时不同进程内部调用栈的详细耗时信息,这对于慢查询等场景的性能排查效率会有显著提升。

总体而言,IoTDB 的可观测性能力在今年发生了质变。我们有信心也非常欢迎我们的用户朋友前来体验。

接下来我会着重介绍一下 IoTDB 的监控模块:

对于监控模块而言,它的灵魂就是他拥有哪些监控指标。

这里我们参照火焰图作者著作《性能之巅》的思路。从负载分析和资源分析这两个相反的角度去互补推进监控指标体系的建设。

对于自顶向下的负载视角:

我们对客户端写入 IoTDB 的流程进行了拆解。对于每个 IoTDB 的连接,当它将请求交给到 IoTDB 执行时,该连接被视为忙碌状态;当它在客户端攒批等待或者向服务端传输时,该连接被视为闲置状态。通过这种区分,我们能够对瓶颈是否在 IoTDB 内部有一个评估。比如每次连接繁忙都是 10ms,之后却要闲置 5 分钟,那基本瓶颈就不在 IoTDB 端了。

如果发现连接繁忙的时间要更大,要如何进一步去寻找瓶颈呢。我们将 IoTDB 的写入请求延迟进行了拆解,将写入流程分成了若干阶段,并对一般情况下更为耗时的阶段进行了更细粒度的拆分,从而能够确保发现瓶颈出现在哪个模块。比如调度执行阶段一直存在远程转发,那就需要去排查客户端的分区缓存是否失效。

总之,通过这种自顶向下的分析,我们能够找到系统当前的瓶颈是在哪些模块。

对于自底向上的资源视角:

我们主要从 4 个维度进行了考虑:

在磁盘方面,我们希望我们要比 Linux 的常用磁盘监控命令 iostat 更为丰富,比如除了磁盘利用率,吞吐 iops 之外,我们还想统计进程级别的磁盘读写情况和 page cache 的使用情况。

在网络方面,我们希望我们要比 Linux 的常用网络监控命令 sar 更为丰富,比如除了网络吞吐和收发包的速度之外,我们还想统计进程级别的连接个数等等。

在 CPU 方面,我们不仅要统计操作系统和进程的 CPU 利用率,还想统计 IoTDB 进程内部不同模块不同线程池的 CPU 利用率,也还想统计进程内部线程池的关键参数。

在 JVM 方面,我们不仅要对堆内堆外的内存大小做观测,对不同状态的线程个数做观测,还想对 GC 做更细致的观测。

总之,通过这种自底向上的分析,我们能为很多模块的瓶颈原因提供思路

那到了 1.3.0 版本,我们前面提到的监控指标都已经实现了,那么监控模块对于性能的影响到底大不大呢,线上敢不敢打开呢?

其实这块我们也在持续的做性能优化,尽管 IoTDB 监控指标的个数已经从 1.0.0 版本的 134 涨到了 1.3.0 版本的 905,增加了接近 8 倍,但监控模块 CPU 的开销反而从 11.34% 降低到了 5.81%,减少了近 50% 。其对于读写性能的影响也从 7% 以内降低到了 3% 以内。

因此,大家可以放心的开启监控模块,它对于系统运维的收益绝对远远大于这一点点性能损耗。

基于这些监控指标,接下来我们简单介绍一下 IoTDB 的监控面板:

主要分为四个监控面板:

分别 Performance Overview,System,ConfigNode 和 DataNode 面板。

下面将给出这些面板的示例:

对于 Performance Overview 面板:

它汇总统计了集群的基本信息,例如集群大小,总时间序列个数,总写入吞吐等等。

它还以延迟拆解的方式展示了客户端写入不同阶段的耗时统计,辅助定位瓶颈存在于哪个模块。

任何一个子面板我们都写了详细的注释,比如左图这个面板就展示了不同接口的耗时统计。

同时我们也可以在一个面板中同时查看多个节点的监控数据,便于定位相同时间不同节点的状态。

对于 System 面板,它提供了 CPU, JVM, 磁盘和网络维度的监控数据,在用于定位系统资源是否为瓶颈时非常管用。

对于 ConfigNode 面板,它也汇总统计了集群的基本信息,还提供了元数据及数据分区 Leader 分布等维度的监控。在用户定位集群扩展性能力时非常有用,比如是否所有的节点上都分配了读写流量,是否有节点宕机等等。

对于 DataNode 面板,它汇总了单节点引擎内部的细致监控,如存储,查询,元数据,共识和流处理引擎等等。在判断模块内部瓶颈原因时非常有用。

现在 IoTDB 有接近上千的监控指标,这些指标很难在今天短短的分享中介绍完,那接下来我就分享 5 个典型案例来展示一下 IoTDB 监控模块的能力:

第一个案例是在某高吞吐量场景下如何去确认瓶颈所在。

当业务链路较为复杂时,如果整体的性能不达标,用户其实是不太好去确认到底瓶颈是在业务上还是在 IoTDB 中。

那现在 IoTDB 的监控模块则是可以帮忙定位瓶颈是否在数据库中。

比如对于一个 Flink 实时消费 Kafka 数据来写入 IoTDB 的用户场景,业务链路上有 128 节点的 Kafka 集群,96 节点的 Flink 和 IoTDB 集群。

由于集群规模较大,部署测试调优运维的成本都较高。当时跑通整个链路后业务给我们的反馈就是 IoTDB 写入性能不够,IoTDB 集群总写入吞吐仅为 15GB/s,扩展性很差等等。

那当我们进行瓶颈排查之后发现锅并不在 IoTDB 而是在业务上层。

比如我们发现每个连接平均控制 4s 才会繁忙执行请求 20ms,每个节点平均每秒才接受 3 个请求且系统资源利用率都非常低。

因此我们推动了业务侧进行排查,他们发现即使把 Flink 的 Sink 侧置为空写整体吞吐也才 20GB/s,最终他们找到了问题所在并对 Flink 侧进行了优化。

在业务调整进行复测,我们发现 IoTDB 集群的整体吞吐可以达到 62.6GB/s,相比之前提升了 4 倍以上的性能,集群的线性比最高也达到了 0.89。

如果没有监控模块指导我们去推动业务侧改造,我们还一门心思的在数据库内部找瓶颈,那最终的结果一定是事倍功半。

第二个案例是某车联网场景的写入性能尖刺调优。

由于 IoTDB 是 Java 写的,很多用户也会询问我们 GC 对 IoTDB 性能的影响。由于我们在内存中也做了不少的池化来自己管理内存,所以大部分场景下用户其实感知不到 GC 对性能的影响。只有极少数个别场景才会观测到,比如这个案例就是 GC 导致了写入性能尖刺。

那现在 IoTDB 的监控模块内嵌了 GC 调优分析器,其实是具备对 GC 深度观测和调优的能力的,接下来让我们一探究竟。

该场景的整体架构是一个 3c 12D 的 IoTDB 集群,也是 Flink 实时消费 Kafka 的数据写入 IoTDB 集群。

在写入压测过程中,我们发现 IoTDB 的写入吞吐能力基本符合预期,但是存在尖刺,有时吞吐会接近 0。进一步排查原因我们发现这是由于 JVM 每 20 分钟会触发一次 Full GC,每次 Full GC 都耗时 1 min 以上,那这样的 GC 其实是非常不健康的。

那对于 GC 应该如何调优呢?常见的流程是启动 JVM 时打开 GC 日志,测试一段时间后上传 GC 日志到特定的网站进行分析,其会将不同原因导致的 GC 进行耗时和次数的汇总,然后我们可以基于这些聚合后的高密度 GC 信息再分析应该如何调整 GC 算法参数。

在建设好可观测性之后,现在的 IoTDB 如何去做 GC 调优分析呢?

我们首先提供了 GC 耗时比例的新手指标,它表示了 GC STW 耗时占整个 JVM RunTime 耗时的比例,如果这个比例小于 5-10%,则说明 GC 对系统整体的吞吐影响不大,如果在延迟上没有额外要求,那一般就不需要再调优了。

如果这个比例大于 10-15%,则一般说明可以对 GC 进行进一步调优。我们这时提供了若干专家指标,比如我们对不同 GC 原因导致的耗时和次数进行了统计,还对于种种 GC 前后的内存申请,内存大小都做了统计,这都能作为我们进行调优 GC 的数据支撑。

在该用户环境下,我们的调优思路首先是将 GC 算法从 PS 换成了对大内存更为友好的 G1,接着又结合负载和 IoTDB 的特点进行了 GC 参数的调优,其核心思路也是延缓并发标记阈值,提升 MixedGC 吞吐,控制单次 GC 耗时软上限等。

那最终调优的结果呢是写入吞吐稳定,不再又 Full GC,同时写入性能也提升了接近 50%,还是比较可观的。

这个案例主要是说明一下 IoTDB 对 GC 的观测能力和调优能力。

第三个案例是某测试场景下的硬件瓶颈原因探究。

在一些 POC 阶段,当系统出现瓶颈时,如果将系统视为黑盒,其实是不知道如何升级硬件的收益最高的。

结合 IoTDB 的监控模块,我们可以量化算出升级硬件带来的潜在性能收益,用以选择收益最高的硬件升级方案。

比如在该测试场景下,我们用 2 个客户端机器去压测高配机器的单节点集群,发现系统地性能仅为 1.2GB/s,不符合我们对如此高配机器的想象。

那接着我们对系统资源进行了分析,发现 CPU 和网络都没有达到瓶颈,但磁盘的繁忙程度达到了 100% 成为了瓶颈,从而限制了整体吞吐,此时就需要升级磁盘才能进一步提升性能了。

这就是一个典型的木桶效应,在理想状况下,所有资源应该同时达到瓶颈,这样硬件资源才没有浪费;然而在实际情况中,往往会有个别资源先到瓶颈,从而限制整体性能。

在升级磁盘之后,我们发现磁盘和网络不再是瓶颈,写入吞吐也提升到了原来的 2.5 倍,此时 CPU 又成为了新的瓶颈。

通过该案例,可以说明 IoTDB 的系统资源监控可以帮助我们快速找到硬件瓶颈,从而用最小的成本达到最大的收益。

案例 4 是某周测场景的写入性能波动变大问题排查。

对于服务器的 CPU 利用率出现波动这类问题,其实是比较难排查的,因为他不一定持续,等到我们去排查的时候可能已经不波动了。

对于这类问题,IoTDB 监控模块内置了操作系统,进程,线程池和模块 CPU 利用率监控,我们可以首先确认该波动是不是 IoTDB 引起的,如果是则可以一更进一步给出调优建议。

比如该问题就是我们在日常的周测场景中发现 1.2.0 rc5 版本的写入吞吐相比之前的一个版本波动更大,这其实属于很细致的观察了,不一定对业务有什么影响。

但我们没有放弃这一次机会进行了原因探究。

首先我们排查了操作系统及进程的 CPU 监控:发现新版本的 CPU 利用率波动更大,它应该是造成写入性能波动更大的原因。

那更进一步我们直接用了我们的大杀器-线程池 CPU 利用率监控,发现新版本中后台执行的合并线程池利用率在 0-18% 进行大幅波动,而老版本则稳定在 8% 左右。

更进一步我们去排查了存储引擎 TsFile 层级监控:发现新版本的存储引擎合并速度更快,文件状态也更健康。

因此我们就检查那段时间合入的代码,发现新版本修复了之前合并模块 IO 大小预估偏大的问题,可能导致之前受 IO 限速不能执行的合并任务受现在能够被执行。

至此我们已经明确了该现象的根因,考虑到改进后文件合并的更为健康,因此我们也没有进一步修改默认的限速参数。

但对于写入性能波动有要求的场景,我们也可以进一步降低合并模块的限速,从而达到与之前版本近似的效果。

该案例主要是说明 IoTDB 对 CPU 利用率的观测和掌控能力。

最后一个案例则是某钢厂场景的写入性能周期性下降 5% 排查。

随着我们可观测性做的不断深入,很多用户也开始对我们的监控面板越来越感兴趣,每天就上来翻一翻。

那在翻的过程中如果发现监控面板中有一些不影响业务的异样,是否有必要继续深挖?我们欢迎并鼓励这样的行为。

该案例就是由于用户的深挖反而促进了我们内核迭代的进一步演进,从而达到了双赢的效果。

该场景的架构是一个 3c3d 的集群,客户端会定期攒批写入 IoTDB,也会定期查询单设备过去 1 天的全量数据。

在用户日常巡检监控面板时,他发现了一个有趣的现象,即每 7 天会出现一次持续一天的 5% 性能下降。

这个问题其实可大可小,如果没有我们的监控面板,业务都不会感知到这件事情的存在,但该用户跟我们进行了反馈,于是我们也进行排查。

我们首先排查了系统及进程 CPU 监控:发现写入性能下降 5% 之后 CPU 占用率增加 5%,那依然还是怀疑 CPU 利用率的升高应该是写入性能下降 5% 的根因。

然后我们排查了写入延迟拆解监控,发现写入性能刚下降时存在跨节点转发,这代表客户端缓存失效,同时也观测到写入性能下降时调度执行阶段 P99 耗时增加。这基本可以辅助确定写入性能下降时 IoTDB 切换了时间分区,导致数据需要被写到新的节点。

接着我们对查询进行了分析,尽管查询的逻辑数据量始终是最近一天的数据,但跨分片查询时,由于涉及到更多的 operator 算子和跨节点序列化反序列化开销,这也会对 CPU 造成更大的消耗。

至此该问题的原因便找到了,它也催生了 IoTDB 内核的两个后续优化,第一个是尽量使得同一设备的数据保留在一个分片中,这样即可以避免该现象出现,第二个则是线程池 CPU 利用率监控,有了它我们则可以更直观的观察到增加的 5% CPU 都是在查询线程池导致的,排查效率就更高了。

该案例主要说明了 IoTDB 对这种很细微的业务感知不到的波动也具有诊断和迭代能力。

最后对以上 5 个案例做一个总结:

IoTDB 的可观测性目标是高效定位遇到的一切性能问题,虽然还有很长的路要走,但大家已经能够看到我们这一年的质变。

据我们的经验而言,针对硬件环境和业务负载调优一般可以获得 50% - 1000% 的性能提升。

所以我们非常欢迎大家来试用我们的商业版 IoTDB,也非常欢迎大家在使用监控模块过程中对想不通的性能问题和我们沟通。

我们期待与用户一起获得业务和技术上的成长。

我的分享就这么多,谢谢大家!

2022 年终总结:记清华硕士的秋招之年

2023-01-19 12:00:33

前言

忙忙碌碌又是一年,终于到了 2022 年底。去年第一次写年终总结受到了不少的关注,我在这一年里也时常会重读自己的 2021 年终总结来鞭策自己。今年由于家里的特殊原因需要在医院过年,时间比较仓促,勉强抽出了一天的时间来简短写写年终总结。一方面是给 2022 年的自己一个交代,另一方面也是给 2023 年的自己一个警醒。希望我的经历和感悟能给大家一些启发。

首先依然是自我介绍环节,我叫谭新宇,清华本硕,现在清华大学软件学院 Apache IoTDB 组就读研三,师从王建民/黄向东老师,我对共识算法,分布式存储系统,时序数据库和分布式事务都比较感兴趣。

接着简单介绍一下我们组的工作:Apache IoTDB(物联网数据库)是一体化收集、存储、管理与分析物联网时序数据的软件系统。Apache IoTDB 采用轻量式架构,具有高性能和丰富的功能,并与 Apache Hadoop、Spark 和 Flink 等进行了深度集成,可以满足工业物联网领域的海量数据存储、高速数据读取和复杂数据分析需求。

2022

介绍完背景之后,在这里回顾下 2022 年的经历。

2 月,Apache IoTDB 社区的新分布式架构设计正在整个社区的努力下如火如荼的进行着,社区针对分布式时序数据库架构的方方面面都进行了广泛的调研和讨论,我也很幸运地参加了若干模块的调研设计,收获良多。对于分区方式,我们结合一致性哈希和查找表的优缺点,选择了一个更符合时序场景 trade-off 的分区方式,既不引入较大的存储成本,又具备一定的负载均衡灵活性,而且还留有足够的扩展性。对于扩展能力,我们将时序元数据与时序数据等同来看都做了多分片,使得集群拥有很强的横向扩展能力。对于查询引擎,我们调研了 Trino/Impala/Doris 等系统的 MPP 框架,又基于我们对时序场景的理解设计出了针对时序场景特殊优化的 MPP 框架和 Pipeline 执行引擎。对于共识算法,我们观察到不同的业务对于共识算法一致性和性能之间的 trade-off 有不一样的倾向,开始思考并着手设计一个能够支持不同共识算法的通用共识框架。

2 月下旬,在为 Talent Plan 社区提交若干代码修复并做了 3 次有关共识算法和分布式事务的公开分享之后,我荣幸的被 Talent Plan 社区接纳为了 mentor,期待以后能够继续和志同道合的小伙伴在 Talent Plan 社区沟通交流。

3 月,需要毕设开题的我结合 Apache IoTDB 新分布式架构对通用共识框架的需求,开始调研学术界和工业界在通用共识框架领域的相关工作。在调研中,我发现了 Facebook 的 Delos 框架不仅支持在一套接口下实现不同的共识算法,还能够支持生产环境动态变更共识算法,他们的工作也得到了学术界的认可,成为了 2020 OSDI 的 Best Paper。沿着他们的思路,在摒弃了当前 ROI 收益较低的生产环境动态变更共识算法后,我便开始着手设计通用共识框架的接口,该框架不仅需要先支持强一致性的 Raft 算法和弱一致性的异步复制共识算法,还需要为未来更好更丰富的共识算法接入留下扩展性(例如 2021 FAST 的 Best Paper 就是基于异步复制的思路在提升写性能的同时又在读时添加了一些约束,从而提供了跨客户端单调读一致性,基于 ZK 实现后相比 ZK 性能能够提升 1.8-3.3 倍)。由于这个思路在学术界和工业界都相对新颖,我的开题便较为顺利的被通过了。

3-4 月,由于毕设开题读了一大堆论文状态比较好,我开始在知乎上回答自己感兴趣领域的很多问题,并幸运的在最开始就得到了很多赞,这些对我的认可又成为了我进一步在知乎上学习和活跃的动力,从而形成了一个正反馈效应。正如去年年终总结的感悟中所提到的,这种正向反馈对于我自己的激励作用是非常大的。现在回过头来看,那段时间尽管我也有一些输出得到了很多赞,但收获最大的还是在此过程中阅读了知乎上分布式系统领域非常多的优秀回答,学到了很多技术知识,了解了很多思考维度。

4 月,忙完毕设开题的我开始面试暑期实习,尽管八股和项目由于日常的积累都没有什么问题,但由于实在提不起兴趣和动力刷题,所以在面试过程中还是多少有些磕磕绊绊。幸运的是最后投的几家公司除了微软之外总体都面的不错,最后思考再三后还是选择去 PingCAP 实习。一方面是 PingCAP 的三轮面试官(赵磊老师,徐锐老师,金鹏老师)给我的面试体验都非常好,另一方面则是我一直对 PingCAP 的很多体系(例如工程服务体系,架构演进体系,开源体系等等)非常敬佩和好奇,想要借此机会去学习感悟。

5-6 月,我专心投入到了 Apache IoTDB 新分布式框架的实现中。最主要的工作便是通用共识框架,这期间一方面抽象了其对上对下的通用接口,另一方面则是为其支持了若干共识算法。对于强一致性共识算法,我调研了 Java 实现的若干 Raft 库的成熟度,并最终得出只有 SofaJRaft 和 Apache Ratis 可以使用的结论。基于此结论,我和子阳探索了 Apache Ratis 的实现并将其集成到了我们的共识框架中,这使得 Apache IoTDB 的新分布式架构具备了强一致性的能力(这半年以来,子阳在 Apache Ratis 的稳定性和性能优化上投入了大量的精力,令人开心的是目前他已经得到了 Apache Ratis 社区的认可,成为了 Committer,可以说是双赢了)。对于弱一致性共识算法,我们结合时序场景写写冲突极少的业务特点,设计并实现了基于异步复制思路的弱一致性共识算法 IoTConsensus。我和乔老师,恺丰,海铭,金瑞,洪胤,厚亮和珍姐都参与了 IoTConsensus 的设计实现与迭代测试。IoTConsensus 做了非常多的工程优化,包括但不限于 Batching,Pipeline,Thrift AsyncClient/AsyncServer 等等,所以流程非常复杂。虽然实现和 debug 的过程非常艰苦,但我们在异步编程,内存控制,可观测性,debug 技巧等方面都有了显著的进步,可以说是既痛苦又有收获吧。此外,为了兼容统一的共识框架,避免单副本时共识框架的额外开销,我们还专门针对单副本的场景(只需要 scale out 而不需要 high availability 的场景)设计了极为轻量的 SimpleConsensus,避免出现 RaftLog 和存储引擎 WAL 双写的现象出现。在未来,我们还计划为 Apache IoTDB 的共识框架加入更多的共识算法实现,例如 SofaJRaft 和我们组今年中的 ICDE NB-Raft 等等。我非常期待 Apache IoTDB 共识框架的文档,功能,性能,稳定性和正确性等等都能够迅速成熟,甚至可以成为大家未来使用通用共识框架的范式,这样如果业务上对共识算法一致性和性能的 trade-off 有不同的需求,便可以直接使用我们已经封装好的共识框架而不用再去造轮子了。这里也非常欢迎对共识算法感兴趣的同学一起参与进来~

7-10 月,我在 PingCAP 进行了全职实习。在 PingCAP 的暑期实习是我个人体验最好的一次实习。在技术上,我不仅可以去学习公司内部海量的技术积累(Rust,分布式数据库,TiKV,调优培训等)和流程规范(如何建立可扩展的工程服务体系和可持续的架构演进体系 -> 可观测性 + 分级 Support && 业务场景持续打磨),也可以系统学习 TiKV 事务引擎演进的历史(乐观事务->悲观事务->大事务->Async Commit/1PC-> 悲观事务内存悲观锁等)来培养自己的产品思维,最后还可以基于这些成长去做一些深入的探索并取得了不错的成果。在生活上,同事们会非常耐心友善地回答我的种种问题,工程经验十分丰富的 mentor 徐锐老师也花了非常多的时间和我 one-one 沟通我的种种疑问来帮助我快速成长(技术问题。如何平衡技术驱动和业务驱动?如何评估架构演进和工程服务的 ROI 等等),金鹏老师和 HRBP 也会和我定期沟通最近的工作进展。PingCAP 是国内开源数据库和开源社区的佼佼者,非常推荐大家有机会前去实习,一定会不虚此行~

8-10 月,我海投了很多感兴趣的数据库团队,并以没有任何职业背景的身份参加了 41 场秋招面试。虽然非常忙碌,但这些面试尤其是终面加面环节对我技术的成长和产品思维的提升有很大的帮助。在面试过程中我也有幸认识了很多大佬,并幸运地拿到了不少 offer,在这里表达对相关面试官和 HR 的真诚感谢。

10 月中旬,我被评选为了 2022 软件学院科研科创年度人物,这令我诚惶诚恐。在我看来,这个奖项更适合论文发到手软的大佬。询问之后发现现在的评审规则中也包含了开源贡献,所以现在做系统做工程的同学也可以得到学院的认可。在这里也真诚感谢学院对我的认可。

10 月下旬,我和祥威洪胤子阳参加了 2022 TiDB Hackathon,并最终拿到了产品组的最佳校园奖。在这次 Hackathon 中,从技术上我们为 TiKV 做了 Parallel Apply 的优化 Demo;从产品上我们在高并发批量写入热点场景会有不错的收益。我们模拟了银行清算结算等跑批业务的极致情况,在 60 并发下,不同 BatchSize 的批量写入性能提升 89.4%~119.0%。TiKV CPU 利用率从 700% 左右提升至 1500% 左右。我们也尝试了通用的批量导入热点场景,对于 TPCC prepare,在 1024 BatchSize 下,不同并发批量写入性能提升 29.8%~36.0%,TiKV CPU 利用率也从 750% 左右提升至 1000% 左右。通过这次 Hackathon,结合 OB 4.0 版本自适应日志流的设计,我们对 Raft 和 Multi-Paxos 的异同有了更深刻的理解。总体而看,一个系统的架构设计就是要在关键模块的各种 trade-off 中做出纠结的选择,并且一旦做出了某个选择,就需要基于这个选择更进一步做非常多的产品化工作和优化打磨来提升这些技术对于用户的实际价值。例如,TiDB 选了 TSO 的时间戳获取方案并不代表就不能服务跨数据中心场景了,其也做了 Local/Global TSO 的产品化工作来满足部分用户场景的跨数据中心需求。CRDB 用了 HLC 之后并不仅仅体现在获取事务时间戳更快,其至少还基于 HLC 在 Strong/Stale Follower Read 这一块做了许多工作来减少跨域流量从而降低成本。对于 OB,其选择了 Multi-Paxos 而不是 Raft,并且更进一步在 4.0 架构中提出了单机分布式一体化架构来解决其他数据库很难彻底解决的写热点缓解,大事务支持和 1PC 比例增大等难点。当然,这些技术和产品化的工作短期内很难形成事实标准,也都能够在各自的用户场景产生价值,至于孰优孰劣就很难客观判断了。

11 月,我手上的秋招 offer 陆陆续续开奖了,这期间涉及到了非常复杂的心理变化。有期待,有失望,有惊喜,有反思,有迷茫,有透彻。对于最终选择去哪里,我纠结了许久,请教了很多朋友,前辈,同学,师兄,父母和导师的意见。其实选择哪条路都不会太差,主要还是需要剖析自己更喜欢怎样的工作方式,并且说服自己一旦做出选择后就不再患得患失。此外王老师东哥乔老师都对我的就业方向和未来发展路径给予了中肯的建议,最终我选择了 Apache IoTDB 的商业化公司天谋科技继续做 IoTDB。我非常感谢研究生期间 IoTDB 这个大平台对我的帮助,也希望自己未来能够继续在这个大平台发光发热。

12 月,Apache IoTDB 的 1.0 版本发布了,这次新版本凝聚了整个社区的智慧和力量,标志着 Apache IoTDB 从此彻底拥有分布式,为后续产品快速发展奠定了良好基础。这次分布式 1.0 架构的发布也是促使我决定留下的重要原因之一,因为我觉得这个架构有太多可以做的工作可以去做,一些积累的技术宅已经还清,我相信未来 Apache IoTDB 一定能够在工业物联网的时序场景大放异彩。想通以后,我也和洪胤子阳湘鹏组成了系统优化小组,不断扩大我们的 scope 去和 IoTDB 一块成长了~

今年我在 Apache IoTDB 社区 Review 了 187 个 PR,提交并被合并了 48 个 PR。今年的工作有很多新颖的东西,希望明年能够继续保持下去。

今年我在知乎上写了几十篇博客和回答,粉丝数和点赞数相比去年同期有了接近 10 倍的增长。Github 上的 Follower 数和个人仓库 Star 数也有了 6 倍多的增长。希望自己明年还能继续坚持输出有价值有意思的知识。

今年我看了权谋剧的巅峰之作《大明王朝 1566》,尽管剧中演绎的是封建社会,但其中一些社会运行的本质规律放到今天也依然适用,我自认为受益良多,非常推荐大家去看。此外我也读了 IT 人的必读书籍《浪潮之巅》,了解了信息革命以来很多著名公司的兴衰更迭及背后的哲学原因。天下大势,浩浩汤汤,顺之者昌,逆之者亡。不论是公司,组织还是个人,自己的努力不可或缺,但只有抓准大势站上浪潮之巅,才有可能干一番大事。

一些感悟

介绍完了 2022 年的经历,在这里谈谈自己这一年的新感悟,这些感悟不一定适用于每个人,但都是我个人在今年得到成长的诀窍。

没有不写 bug 的人,但要成为追求不写 bug 的人

这一年在日常的开发过程中修复了无数的 bug,一些是陈年老 bug,一些则是自己在开发过程中引入的新 bug。这期间我一直在思考为什么会有这么多的 bug?什么时候能够修复完所有的 bug?现在我有了更明确的感悟:

对于一个 bug,从设计->编码->UT->IT->压力测试->混沌测试->发版测试->用户 POC 环境->用户生产环境这套全链路的流程中,越晚被发现,则修复的成本就越高。世界上没有不写 bug 的人,也不存在没有 bug 的系统,我们需要尊重这一客观规律。出现 bug 不可怕,但不对其进行反思改进就非常可怕了。虽然没有不写 bug 的人,但要成为追求不写 bug 的人,因为只有具备了这个追求,才会在日常的开发过程中注重 bug 产生原因的积累,并通过种种技术手段来规避 bug 的产生。正是因为对 bug 的反思,学术界和工业界才产生了很多对系统稳定性至关重要的工作(形式化验证,确定性模拟器,混沌测试,持续集成等等)。一个工程师的成长必然伴随着对种种异常情况的考虑,知识边界被拓宽后自然写出 bug 的概率就会越来越低。

新的系统,新的模块,新的引擎,新的算法需要一段时间的稳定性打磨,没有用户去用就很难成熟,但如果用户去用了,团队也一定要利用好这次机会,让产品的稳定性和工程师的能力一起成长,进入一个正反馈循环才有可能让产品越来越好。

性能优化与代码可维护性需要有一个平衡

一般而言,复杂的性能优化往往会导致代码维护成本的上升。例如事件驱动的并发编程模型具有更高的自主性,性能上限相比完全被 Runtime 接管的协程可能会高一点,但带来的回调地狱问题又可能会大幅增大代码复杂度,导致代码维护成本大幅上升。从全局的 ROI 评估来看,这样的工作不一定收益很高。

今年我在开发 IoTConsensus 过程中就有点过于追求性能优化,加了一堆异步逻辑和工程优化,虽然优化完后性能还不错,但这种代码上的复杂度也使得大家在 debug 时非常痛苦,新人也比较难一下子看懂主要逻辑,只能靠进一步完善文档细节并做代码走读才有可能让新人逐渐上手。

在未来的工作中,我也会吸取这次的教训更关注性能优化与代码可维护性之间的平衡。

有太多能力可以提升,不要只看到技术

今年在实习和秋招面试过程中有幸和非常多的技术大佬有过交流,在他们身上技术能力只是一个值得学习的维度,其他维度的能力还包括管理,演讲,分享,社交,营销,商业感知,为人处世等等。

要想进一步实现职场的抱负,综合能力是十分重要的。总之,有太多领域的太多能力可以去提升,技术只是其中比较重要的方面之一,最好不要仅仅把自己拘泥在技术上。

不断扩展自己的 scope,锻炼培养自己的综合能力,便能在单位时间内获得更大的成长拥有更丰富的阅历,从而可能在机遇出现时顶住压力抓住机会。

技术是满足业务的手段,一定要带有产品思维

技术最终是要为业务服务的。没有业务侧的需求,技术最终也难以发挥实际价值走向成熟。

在做日常的功能开发和性能优化时,最好能够带着产品思维去思考问题:自己所做的工作在整个产品中处于怎样的一个位置?做了这个工作之后整个产品会有什么样的不同?一旦能够培养出来这种端到端的产品思维,自己也会更明确工作目标并更有动力去做事成长。

针对不同硬件环境和业务负载的排列组合,有太多有意思的工作可以去做

刚入门数据库的时候一直比较好奇,像 Oracle 这样的数据库厂商是如何每年都有那么多新的工作可以做?为什么我就想不到做那些呢?

通过在 Apache IoTDB 实验室的成长和在 PingCAP 的实习,我理解了业务驱动对于产品的意义。只要有用户和业务的支持,就会有源源不断的需求出来。对于数据库系统而言,在不同的硬件环境和业务负载下,有太多的功能和性能优化可以去做,几乎不存在技术上没有事情可做的情况。当然,任何组织任何个人所能调度的资源都是有限的,如何评估当前所有工作的 ROI,如何在有限的资源上持续的做紧急重要且正确的事,也是产品成败存亡的关键。

好的系统需要在天花板高的架构设计上进行日复一日的持续打磨

OceanBase 的杨传辉老师提过一个观点:每个系统设计时都需要考虑架构、稳定性和性能,这三者之间的关系是什么?一个经典的规律是“把稳定的系统做高效,远比把高效的系统做稳定更容易”。最难的是从 0 到 1 把系统做稳定。有了稳定的系统,接下来逐步优化性能往往会比较顺利,直到遇到系统架构的性能天花板。因此,系统架构设计之前,首先要考虑清楚系统的目标和性能天花板,接着基于正确的架构把系统做稳定,最后优化性能。

在 PingCAP 的暑期实习期间我很明显的一个感受就是,TiDB 的可观测性做得非常好,同事们都比较明确特定业务场景下当前 TiDB 的短板在哪里,也明白自己工作的端到端价值,从而能够持续打磨 TiDB 在特定业务场景下的性能。大家的工作并行汇聚起来便能够促使 TiDB 每个版本都能够在很多业务场景下有不错的进步。

在现在的我看来,Oracle 最有价值的东西其实是在当前的市场规模下能够接触到最多最复杂的业务场景,从而能够持续的对系统进行打磨,这是其他任何单机数据库都没法做到的,自然也就无法在单机数据库领域打败 Oracle 了。

在系统初期,调研设计出一个性能天花板高的系统架构非常重要。在系统搭建完成基本稳定之后,在业务场景下进行持续的打磨就更为重要了。比如 Apache IoTDB 1.0 的分布式架构性能天花板我自认为就很高,目前性能也基本打磨到了和 0.13 版本一致的地步,下一步就是在特定业务场景下进行持续打磨做稳定性和性能的进一步提升了。

提出问题比解决问题更为重要

好奇心是人类进化创新的驱动力。发现当前存在的问题,才有可能去进一步改进解决。爱因斯坦早在《物理学的进化》中就说过:”提出一个问题比解决一个问题更为重要。 因为解决一个问题也许是一个数学上或实验上的技巧,而提出新的问题,新的可能性,从新的方向看旧问题,则需要创造性的想象力,而且标志着科学的真正进步”。

对于数据库系统,在特定硬件环境和业务负载下,性能瓶颈到底在何处?若想优化当前最大的性能瓶颈,它到底是一个工程问题还是学术问题?如果是工程问题,需要投入多少资源去完成?会有多少收益?如果是学术问题,它的 trade-off 到底是什么?有什么更符合当前业务场景的可能性吗?一旦有了这样思考问题的方式,就会发现技术上其实存在很多可能。

个人的努力不可或缺,但平台和机遇往往更为重要

在看了《大明王朝 1566》 和《浪潮之巅》后,越发认识到时势造英雄的客观规律。个人的努力能够决定个人的发展下限,但平台和机遇才能够决定个人的发展上限。很多时候,玄而又玄的天下大势和因缘际会反而可能会在若干年后造就一番佳话。

就目前而言,认识到平台的重要性,学会顺势而为,在职责范围内乐于沟通踏实做事,可能是个人职业生涯短期的最优解了吧。

没有绝对的成功,以个人最舒服的姿态和最擅长的方式去活一生就已足够

对于个人而言,绝对的成功很难定义,技术和商业也仅仅是很小的一部分。在认识到时势造英雄的客观规律后,我愈发觉得精神上的富足对于一个人一生的幸福最为重要。有一句话叫做”但行好事,莫问前程”,没有必要去和其他人比来比去,任何人都是比上不足比下有余,能够以个人最舒服的姿态和最擅长的方式去活一生就已足够,剩下的交给命运即可。

多站在别人的角度思考问题

在日常工作中,很多时候会涉及到任务的分工与合作,这就需要频繁地沟通。

每个人都有完全不同的成长环境,不同的成长环境造就了不同的三观以及思维方式,进而产生出不同的职业动机和职业规划,最终使得对待工作的态度和动力也会有所差别。这些差异无关对错,但有时候过于从自己角度思考问题并进行沟通反而会适得其反。

在与他人沟通时,如果能够站在别人的角度去思考问题,懂得换位思考,则很多矛盾在沟通过程中就可以被缓解甚至解决。当然,有些天然对立的矛盾是没办法彻底解决的,只能选择一个平衡点进行缓解。

来年展望

今年的年终总结写的比较仓促,但也算是勉强写完了。

新的一年就主要聊聊与 Apache IoTDB 有关的技术展望吧:

  • 共识框架:功能,性能,稳定性,正确性和文档稳定推进,希望能够吸引感兴趣的同学加入进来~
  • 可观测性:深受 TiDB Performance Overview 面板的影响,下一步计划大幅加强 Apache IoTDB 的可观测性,并基于此来建立可扩展的工程服务体系和可持续的架构演进体系。
  • Java 全异步执行框架:暑期在学 Rust 时就已经被其 async/await 的无栈协程设计所折服。对于 Java 来说,自带的有栈协程框架 Loom 刚刚 GA,离正式生产环境用到还很久远。事件驱动的回调机制尽管性能很好,但回掉地狱的出现又会极大的影响代码可读性和可维护性。这一块在调研很多 Java 实现的大数据栈后并没有查到相关的资料。幸运的是,通过对 CompletableFuture 和 Thrift AsyncServer/AsyncClient 接口和源码的探索,对于如何基于 Thrift 的 NIO 机制来提升性能又利用 CompletableFuture 的编排能力提升代码的可读性可维护性,我已经有了一些想法和探索,期待明年能够结合这两者的优点进行一些 Java 数据库下 SOTA 线程模型的探索论证和实现测试,并形成系统性的博客向社区输出。

最后,感谢您的阅读,希望这篇总结能够对您有所帮助。

在除夕这天,预祝大家新年身体健康,阖家幸福,心想事成~

2022 互联网求职经验分享

2022-12-15 10:21:29

背景

在前前后后忙活了接近四个月后,我的秋招终于结束了。

这篇 回答 分享了我几乎所有技术面的面经,本博客将结合本人在今年秋招中的经验和在院系就业分享会中的分享内容介绍一下实习和秋招各个环节的注意事项,希望能够帮助到对分布式数据库内核研发岗感兴趣的同学。

注:本文内容仅代表个人看法。

内容

2022 TiDB Hackathon 产品组最佳校园奖总结

2022-12-08 20:28:07

背景

2022 年 10 月,2022 TiDB Hackathon Possibility at Scale 成功举办。

作为暑期实习在贵司事务组的 intern,在对 TiKV 的 codebase 有一定了解后,我兴致勃勃地拉了实验室的同学报名参加了此次 Hackathon,并且最终拿到了产品组的最佳校园奖,虽然没有拿到更大的奖项,但已经玩得十分开心了。

非常感谢 Hackathon 期间队友,mentor,组织者和评委对我们组的帮助和认可。

本文将简单介绍我们组的工作和思考以做回顾。

以下是我们项目的一些相关资料,欢迎点击了解:

注:本文仅代表个人看法。

工作

本小节将沿着答辩 PPT 的思路介绍我们的工作。

我们的名字是热点清零队。我们的项目是无畏写热点,我们希望能够解决 TiKV 写热点问题的最后一公里。

我们的团队成员均来自清华大学软件学院。今年 hackathon 的主题是探索 scale 的可能性,我们的主要工作是解决热点 Region 在单机多核上的扩展性问题。在极致场景下我们提升了 TiDB 接近 1.2 倍的吞吐。

从用户视角来看,写热点问题会给他们带来多少困扰呢?比如某用户就提出,批量写场景下 TiKV 的 CPU/IO 都没用满,但写入依然很慢,这些现象给用户带来了不便和困扰。我们统计了 AskTug 论坛上写满 tag 帖子的原因分布,大致如右上图所示,可以看到写热点问题占比第一。

那现在的 TiDB 是如何解决热点问题的呢?其整体思路都是通过划分 region 来均匀承担负载,从而能够扩展。如右图所示,理想情况下随着 region 的分裂,热点会被稀释,进而线性扩展起来。然而,在当前上层数据分片的语义下,对于聚簇索引,唯一自增索引等场景,写热点问题难以避免。

当分裂 region 不能均分负载时,实际情况会如右上图所示,尽管分裂出了多的 region,但写热点始终集中在个别 region 上。在这种场景下,往往会遇到单节点存算资源并没有被充分利用的情况。其实不论上层怎么去分裂 region,单 region 热点始终是写热点问题的最后一公里,无法回避。

那让我们看看在 TiKV 中单 region 的执行路径。总体来说是需要在 StorePool 中进行 commit,在 ApplyPool 中进行 apply。然而在现有的实现中,不论是 StorePool 的持久化还是 ApplyPool 的写 memtable,并行度都是 1,因而不具备在单机上的扩展性。

那如果我们想要在单机上扩展热点 region,那可以从存储资源和计算资源两个方面来入手。对于存储资源,可以利用 IO 并行来提高吞吐,比如将单 region 不同批的日志用异步 IO 并行化处理,去年的 TPC 项目已经做过尝试,取得了不错的效果。对于计算资源,理论上也可以采用多核并行来提高吞吐,这也是我们的尝试,即去做 Parallel Apply

Parallel Apply 的整体思路是将无依赖的日志并行处理,从而提升整体吞吐。2018 年 VLDB 和 2021 年 JOS 已经对 Parallel Apply 的可行性和正确性进行了证明。

那在 TiKV 上落地 Parallel Apply 会有哪些难点呢?我们主要遇到了 4 个问题,比如如何保证依赖顺序的正确性,数据正确性,语义正确性和 index 正确性等等,以下分别进行介绍

我们的整体思路是在 ApplyPool 以外额外引入一个 ParallelApplyPool,并在 StorePool 中判断路由,进而使得单 region 的日志存在并行的可能性。同时在实现过程中,为了避免锁导致的线程切换,我们的共享状态均使用了原子变量。

对于刚刚提到的难点,我们简单介绍一下解决方案。

对于如何保证具有依赖的日志执行顺序正确?在 Leader 侧和在 Follower 侧需要有不同的处理方案。

  • 在 Leader 侧,得益于上层事务语义的约束,我们不需要引入依赖检测结果便可以无约束的并行 apply 普通日志,因为他们的 key 范围必定不会重叠。
  • 在 Follower 侧,最简单可以串行执行来保证正确性,更进一步也可以考虑基于拓扑排序的依赖检测机制来并行 apply 日志从而满足依赖关系。

对于如何保证 leader 切换或重启时数据依然正确?我们则很简单的使用了 Raft 的 term 变量,当且仅当日志的 term 为当前 term 时才考虑路由到 ParallelApplyPool 并行处理。这样的机制保证了 Leader 切换或者节点重启时系统不会将本不能并行处理的日志并行化处理,从而导致数据不一致。

对于如何保证 admin 等特殊日志的执行依然符合串行语义?即在 admin 日志执行之前其前面的所有日志均需要已经执行,在 admin 之后的日志执行前必须保证该 admin 日志已经执行。这个具体实现非常复杂,简单来说,我们在 Parallel Apply Pool 和 ApplyPool 中用原子变量共享了一些状态,对于单 region,StorePool 会将无冲突的普通日志在 Parallel ApplyPool 中并行执行,当出现 admin 日志时,当 StorePool 未感知到其执行完时,所有的日志都会被路由到 ApplyPool 中串行执行,当 admin 日志的 apply 结果返回 StorePool 后,之后的普通日志可以被继续路由到 Parallel Apply Pool 中并行执行。在 ApplyPool 中,我们还用原子变量来保证了只有 Parallel Apply Pool 中有关该 region 的所有普通日志都已执行完才去执行 Admin 日志。这些工作使得 admin 等特殊日志的执行依然符合了串行语义。在实际测试中,大部分普通日志都能够在 Parallel Apply Pool 中并行处理。,这也是我们在写热点场景性能提升的根源。

对于如何保证 applyIndex 的更新,我们发现不需要在磁盘上实时更新 applyState,这主要与底层 KV 引擎的幂等语义有关。我们在内存中维护了可能存在空洞的 applyIndex,当其连续时才推进 StorePool 中的 applyIndex,这也与现有的代码实现了兼容。

我们模拟了银行清算结算等跑批业务的极致情况,在 60 并发下,不同 BatchSize 的批量写入性能提升 89.4%~119.0%。TiKV CPU 利用率从 700% 左右提升至 1500% 左右。

我们也尝试了通用的批量导入热点场景,对于 TPCC prepare,在 1024 BatchSize 下,不同并发批量写入性能提升 29.8%~36.0%,TiKV CPU 利用率也从 750% 左右提升至 1000% 左右

在测试过程中,我们也更深刻地体会到了木桶效应。在 apply 是瓶颈的热点场景下,我们能取得很好的效果。但是对于 apply 不是瓶颈的场景,尽管这个阶段可能会加速,但根据阿姆达尔定律,最终的整体收益也不明显。

展望未来,我们认为我们的工作拼好了 TiKV 解决写热点问题的最后一块拼图,给出了解决写热点问题的终极形态。在多节点上,我们可以用 Split Region 的方式来在多节点上 scale。在单机上,我们可以用 Parallel Redo log 甚至是 Multi-Paxos 来更好地利用磁盘资源,也可以用 Parallel Apply 来更好的利用 CPU 资源,最终也能够在单机上彻底 scale。

同时在引擎演进方面,我们解决了不相关事务的日志回放顺序依赖问题,为 TiKV 更好的 CPU Scheduling 和极致性能做了铺垫。比如未来可以结合乱序确认乱序 commit 和 TPC 策略来对资源进行更精细的控制。

思考

本小节将更随心的介绍一些我们的思考。

在这次 Hackathon 中,从技术上我们为 TiKV 做了 Parallel Apply 的优化;从产品上我们在高并发批量写入热点场景会有不错的收益。

上小节已经介绍了基于 region split 的方式来缓解热点问题的局限性,那么如何解决这种局限性呢?这就要回到一个圣战问题了:Raft 和 Multi-Paxos 有什么区别?

如同我在 RFC 中介绍的一样,相比 Multi-Paxos,Raft 不能乱序 ack,不能乱序 commit,不能乱序 apply,因而有同学认为 Raft 不如 Multi-Paxos。

dragonboat 的作者对此观点进行了 反驳,其主要有两个观点:

  • 乱序并行 apply 不如拆分出更多的 raft 组来并行。
  • 对于一个通用的共识库,不能乱序 apply 是受限于 RSM 模型本身的限制,并不是 Raft 本身的问题。对于特定场景,乱序 apply 可以达到一定效果,但并不是一个通用性的优化。

对于第一个观点,从 TiKV 的角度来看,目前默认的 region 大小为 128M,尽管理论上 raft 组数拆的越多,单 raft 组内的串行化 apply 对性能的影响就越小。然而,raft 组过多带来的其他问题也会接踵而来,比如在 TiKV 实际测试中观察到过多的 region 和过大的 LSM Tree 都会导致性能的回退,因而未来 TiDB 的 Dynamic Region 工作计划一方面调大 region 大小到 512MB 至 10GB 从而减少 region 个数,另一方面则是拆分 RocksDB 实例。由此可见,影响 region 大小的因素并不只有 raft 串行化的问题。在未来,一方面 TiKV 的 region 会比现在更大,因此单 raft 组内的串行化问题会更加明显从而可能成为瓶颈;另一方面拆分 RocksDB 实例后 split 也不会再像现在这么轻量,因而实时动态的 split 负载均衡策略相比现在也会趋于保守。总体来看,在 TiKV 内实现乱序 apply 在未来是一个非常有可能的性能优化方向。

对于第二个观点,的确从通用的共识库的角度出发,乱序 apply 并无太大意义。但从 TiKV 中内嵌的共识算法来看,由于共识层之上的事务层已经定了一次序,因而共识层的重复定序在有些 case 下是没有意义,此时的乱序并行 apply 更可能提升热点场景的性能。

事实上,如果不支持乱序 apply,那共识算法的乱序 ack 和乱序 commit 可能没太大意义,因为整个共识组的瓶颈受限于最慢的模块。如果只能顺序 apply,那即使乱序 commit 了一批日志,如果这些日志之前存在空洞,那么这批日志也只能在内存中等待而不能被 apply。然而如果支持了乱序 apply,那结合乱序 ack 和乱序 commit 就更可能提高共识组的吞吐上限。比如一旦支持乱序 commit,那可以使用多个 IO depth 来持久化不同批的日志,这样每次 IO 的大小减少了,也可能能够减少每次 IO 的平均时间。此外支持乱序 commit 后也可以将前面存在空洞但确保与空洞日志无依赖关系的一批乱序 commit 日志提前 apply 处理,进而抬高 apply 的瓶颈天花板。

总体来看,Parallel Apply 能够解决 region 写热点在多核上的扩展性问题,Multi-Paxos 能够解决 region 写热点在现代硬盘上的扩展性问题。因此,Multi-Paxos + Parallel Apply 理论上能够解决写入热点在现代硬件上的扩展性问题。预计能够在写热点场景更充分的利用硬件资源,从而提升性能。

在写 Hackathon RFC 的时候我更多的关注了对于写热点场景下 Raft 与 Multi-Paxos 的区别,然而对于其他场景,我意外的发现 Raft 和 Multi-Paxos 也能体现不同的价值。

在 OB 社区 4.0 版本的 介绍 中,专门提到了一个很大的变动:自适应日志流,即将分片与共识解耦。在不考虑 leader 打散情况下,每个租户在一个进程中可以有多个分片但只会有一个共识组。

基于传统 Raft 的方案是很难去做出这样的架构设计的,但 Multi-Paxos 可以做到。

在收益方面,除了完全控制共识组个数和增大 1PC 比例以外,OB 4.0 更是基于自适应日志流架构重构了事务引擎,从而对大事务有了更好的支持,并基于这些工作产品化包装出了单机分布式一体化架构。这个架构的缺点就是灵活扩缩容和实时负载均衡会更难做,但如果考虑云上的部署形态,这些新的问题又可以通过其他方案来缓解。

总体而看,一个系统的架构设计就是要在关键模块的各种 trade-off 中做出纠结的选择,并且一旦做出了某个选择,就需要基于这个选择更进一步做非常多的产品化工作和优化打磨来提升这些技术对于用户的实际价值。例如,TiDB 选了 TSO 的时间戳获取方案并不代表就不能服务跨数据中心场景了,其也做了 Local/Global TSO 的产品化工作来满足部分用户场景的跨数据中心需求。CRDB 用了 HLC 之后并不仅仅体现在获取事务时间戳更快,其至少还基于 HLC 在 Strong/Stale Follower Read 这一块做了许多工作来减少跨域流量从而降低成本。对于 OB,其选择了 Multi-Paxos 而不是 Raft,并且更进一步在 4.0 架构中提出了单机分布式一体化架构来解决其他数据库很难彻底解决的写热点缓解,大事务支持和 1PC 比例增大等难点。当然,这些技术和产品化的工作短期内很难形成事实标准,也都能够在各自的用户场景产生价值,至于孰优孰劣就很难客观判断了。

仅就技术而言,如同我在之前有关 共识算法综述博客 开头中介绍的一样,对于 Raft 和 Multi Paxos 孰优孰劣这一圣战问题,我的主观看法是对于普通 KV,很可能区别不大。对于结合共识和事务模块的 NewSQL 数据库,Multi-Paxos 能够在整体上为一些难点问题提供一点不一样的思路(例如增大 1PC 比例,缓解写热点问题,大事务支持等等),可能有更高的性能天花板。

总结

本文简要总结了 2022 TiDB Hackathon 产品组最佳校园奖热点清零队的工作,并简要分享了本人对 Raft 和 Multi-Paxos 异同的看法,希望能够引起更多的讨论。

感谢您的阅读~