我在2020年年底开始担任Bitbucket Cloud的工程主管,此前我曾担任过团队的高级工程经理之一。能够领导这个团队是我的荣幸,我为我们所做的努力工作感到自豪,并且每天都在继续努力,使Bitbucket成为世界级的产品,使团队能够为世界各地的数百万人构建、测试和部署软件。
这是个多事之秋,过去几周也不例外。
最近的性能事件
早在10月,我们的前任工程主管Robert Krohn分享了一篇博文,以提供有关最近影响客户的重大事件的透明度,以及分享团队所交付的一些令人印象深刻的性能改进。今天,我想为最近在三月底和本月发生的一些事件做同样的事情,这些事件对我们的客户和我们的工程团队来说都是特别困难的。幸运的是,我也有另一套改进措施要分享,以及继续投资于我们服务的可靠性和速度的计划。
人们会问,最近的这些事件与过去的事件有什么不同,比如我的前任所解释的事件。这是一个区分可靠性和性能的好机会。虽然这两个关注点是高度相关的,但它们并不总是一起的。举个例子,将昂贵的工作负载从高性能无弹性的基础设施转移到新的基础设施,牺牲一些速度以获得可扩展性,可以提高可靠性,同时增加执行时间。
在过去的一年里,我们对Bitbucket云的大部分投资都集中在可靠性上--投资于更好的监控和警报,转向更可扩展的架构等。最近,我们的客户经历了严重的性能下降,导致页面加载非常缓慢,Git操作出现挂起或超时。虽然我们的服务在这些事件中没有出现中断,但这种性能下降对试图使用Bitbucket的开发者来说是非常具有破坏性的,突出了将性能和可靠性作为平等伙伴的重要性。
扑灭大火
上周,当团队深入调查我们的性能下降问题时,一个关键的发现出现了:到目前为止,最大的贡献者是Bitbucket的文件系统层,这是我们基础设施中负责促进应用程序访问客户的源代码的部分。下图中的蓝色区域代表了这一点,显示了上周一和周二我们响应时间的峰值。

其他每一个性能因素,如数据库、缓存和外部网络请求,与文件系统操作相比都相对稳定。(你可以看到,其他颜色的区域没有显示出像蓝色区域那样多的变化)。
关于我们最终发现的这些性能问题的根本原因的彻底解释,将不得不等待未来的博文。虽然调查被证明是具有挑战性的,但这一早期信号帮助我们立即开始行动,制定计划以减轻对客户的影响。
- 通过将尽可能多的工作从文件系统卸载到基础设施的低延迟部分(如缓存)。
- 通过识别依赖文件系统的昂贵操作,我们可以完全停止这些操作(毕竟,最快的代码是不运行的代码)。
缓存包文件
我们的文件系统吞吐量的最大来源之一一直是Git流量:开发人员克隆仓库,拉取队友的修改,以及推送自己的代码。这些操作的负载被CI工具(如Pipelines、Bamboo或Jenkins)提高了一个数量级。客户通常将这些工具配置为轮询Bitbucket的变化,有时是按照积极的时间表(例如每分钟!),这可能会产生大量的文件系统I/O。
在过去的几周里,我们的工程师推出了一项优化,在不接触文件系统的情况下,对仓库克隆和获取的数据包进行缓存和服务,这使得吞吐量几乎减少了一半。

来自Git的文件I/O通过SSH流量

通过HTTPS传输的Git的文件I/O
虽然这一改变没有直接提高Git操作的性能,但它极大地降低了我们文件系统层的负载,从而使所有执行文件系统操作的端点的响应时间更快。
自定义libgit2 ODB后端
虽然我们的Git服务一直是我们文件系统吞吐量的最大贡献者,但我们的网站和API也不逊色。渲染源代码、提交信息,尤其是计算版本之间的差异,都是昂贵的I/O绑定操作。
支持Bitbucket网站和API层的服务是用Python实现的,并使用一个自定义库来处理对磁盘上的存储库的所有访问。这个库使用libgit2 C库,它支持可插拔的自定义后端,包括ODB(对象数据库)后端--允许消费者指定本地文件系统以外的数据源来查找Git对象。
几个月前,我们的一些工程师实现了一个概念验证,即在libgit2和文件系统之间引入一个高性能的缓存层。在过去的几个月里,我们在一个暂存环境中启用了这个定制的后端,在决定将其投入生产之前,我们偶尔会发现一些小问题和优化机会。在过去的一周里,我们为所有用户启用了这个后端,使吞吐量减少了30%以上。

来自网站流量的文件I/O
预热和共享差异缓存
Bitbucket网站最昂贵的事情之一是计算和渲染差异。差异在Bitbucket中无处不在:审查一个拉动请求,查看一个提交,以及比较两个分支都需要计算差异。即使是不涉及显示差异的功能,有时也需要计算它,例如生成diffstat信息(文件版本之间添加、更改和删除的文件和行的摘要)或检测冲突。
我们已经在很多地方对差异进行了缓存,以加快响应速度,但这种缓存是临时性的:计算差异的不同代码路径会独立进行缓存,甚至缓存后端本身在所有地方也不一致;一些缓存的差异存储在Memcached中,另一些存储在共享目录的临时文件中。
我们的工程师已经开始对这些代码路径进行了一些重大的优化,这些优化整合了缓存(减少了资源的使用),并利用已知的访问模式来确保缓存在被检查之前被 "加热"。这里有一个例子:拉动请求视图利用幕后的差异来检测源和目标分支之间的冲突。通过更新用户界面,将对这些冲突的请求推迟到差异呈现之后--这确保在检查冲突的时候,差异已经被缓存了--我们的工程师能够将冲突API端点的缓存命中率提高到接近100%,从而使响应时间大幅下降。

拉动请求冲突API的响应时间
面向未来的重建工作
虽然我对我们的工程团队在过去几周内所做的所有改进感到兴奋,但我也从经验中知道,软件不会自我维护(无论我多么希望它这样做!),这包括性能。如果不加以控制,随着时间的推移,响应时间将呈上升趋势,资源使用量将攀升,而可靠性和性能自然会受到侵蚀。
为了保持和发展我们最近的性能工作,我们将需要增加我们在这些领域的投资。这种投资将采取以下三种形式:
- 除了我们的团队几个月来一直在内部跟踪的可靠性SLO之外,我将与我们的每个团队合作,正式确定互补的绩效SLO,以便我们对保持高绩效的服务负责。
- 我们已经在向一个新的所有权模式过渡,在这个模式下,我们的工程团队被授权关注一系列广泛的绩效指标并采取适当的行动。这应该会提高我们采取主动措施的速度,以及更有效地应对未来类似的事件,当一个全系统的基础设施问题影响到跨越多个团队拥有的服务能力时。
- 最后,我们将利用这个机会确定我们可以开始建立的自动化,以提供护栏,并建立一个更好的防御,以防止未来的性能瓶颈。这将包括功能测试,以检测性能回归的常见来源,以及程序性断路器,以自动化事件响应,并尽可能减少我们的TTR(恢复时间)。
对透明度的承诺
最后但同样重要的是,我打算继续让你了解我们所面临的挑战,以及我们为提高Bitbucket的性能所做的投资。我们理解您对Bitbucket Cloud的信任,我们不会把这种信任视为理所当然。增加透明度是我相信我们可以加深这种信任的关键方式之一,同时提供一个有趣的,也许是有教育意义的视角,看看每天在幕后进行的一些工程行动,使Bitbucket云成为全球数百万专业团队的首选软件协作和交付平台。