藏川线前段

--- 摄于 2017 年 9 月 藏川线前段

同步性能优化(三)

上一篇的重点在于修复卡顿,而本篇的重点在于,在一切正常的情况下,让同步性能提升,作为一个程序,优化三把斧:

  1. 提高 cpu、内存利用率
  2. 提高带宽/io 等利用率
  3. 串行并行化

只要硬件上没有神秘之力加成,优化就并没有什么神秘的东西,总结一句话,尽可能把资源榨干。

优化策略

IBD concurrent download

相关 PR:

https://github.com/nervosnetwork/ckb/pull/1957

https://github.com/nervosnetwork/ckb/pull/1966

在上一篇,简单介绍了同步协议的基本流程,并没有涉及到内部的细节实现,而在优化的过程中,对工程实现细节的把控是很重要的。由于历史原因,在实现 IBD 同步的时候,0.30 以前的所有实现,在 IBD 状态下,有且仅有一个节点能够提供同步服务,造成这个的原因不是协议本身的限制,也就是说,这是可以开放并行下载的,理论上讲,并行下载会大幅减少网络等待时间,从而提高 cpu 利用率。

是什么导致了 IBD 状态下无法并行下载呢,这个原因很简单,因为 IBD 对许多消息进行了静默处理,导致节点缺失了对端的信息,只需要在接到对端的 get_headers 消息时加上一点点处理,即可通过 header_map 还原出对端的 best_known_header,进而可以向对端发起 get_blocks 的请求。

get_header 消息是通过指数级步进的方式对本节点的链信息进行采样,然后发给对端,我们又将该指数级步进的采样方式进行了一定的补充,使该样本能尽可能包含离 genesis block 更近的 header,这样,在 IBD 的时候,就可以尽快开启并行下载的模式。

Cache system cell

相关 PR:

https://github.com/nervosnetwork/ckb/pull/2006

ckb 执行验证的效率不仅是同步性能的瓶颈,也是链本身 TPS 的瓶颈。在之前,无论是什么 cell,在被依赖执行时,都需要从数据库中临时加载出来,然后执行,上面这个 PR 将 system cell 这些常用的依赖项缓存在内存中,执行时只需要读取内存,从而大幅降低了 io 开销,让整体验证过程性能有 15% 左右的提升。这个改动的收益场景是全局的。

Download scheduler

相关 PR:

https://github.com/nervosnetwork/ckb/pull/1999

这个 PR 的改动非常多,也因为思路的改变,至少实现了两版,并经过了长达近一个月的测试,才最终合入仓库,也因为改动太多,导致其本身还有可以优化的地方,但需要拆开,之后再提 PR 去优化这个 scheduler 的实现。

本身的实现并不复杂,更多的改动是去掉了之前因为时间关系遗留的历史问题,比如冗余的一个 block 可以同时向两个节点请求,孤块池中的 block 可以被重复请求,分离 blocking 消息处理点等等,还有就是根据节点的响应行为进行动态调节任务数,及时清退延时过高的节点,从而保证不会因为某个节点的不响应而卡主。同时做了一点点检查状态的小优化,让部分 O(n) 的检查降低到 O(20),即常数级。

后期优化点

重复下载和异步化下载验证

“孤块池中的 block 可以被重复请求”这个问题目前只在 IBD 状态下解决了,非 IBD 状态下还是会有这样的问题,这也是一个未来的优化点,那么为什么要保留这个东西呢?原因很简单,这是当前 Request/Response 验证块架构下的固有问题,这也涉及到另一个优化点,当前的设计里,验证和下载块是串行的,如果将其并行化(异步化),性能又会有不小的提高,与此同时也彻底解决重复请求的问题。这是后期优化的一个点。

优化 scheduler

Download scheduler 目前用的策略是每次 block 的接受做计算并根据固定参数动态调节任务数,这个方案只是一个临时的方案,它有两个问题:

  1. 计算频率太高了,会影响本身的性能
  2. 固定参数不够灵活,无法很好支持多变的环境

虽然从目前的测试数据来看,效果还算理想,但上诉的两个问题其实是可以改善的,并且也是可预期的未来就会碰到的。

首先第一个问题,如果翻看了 PR 的同学们就可以看到,有大量的 benchmark 数据,从数据看,不在非 IBD 禁用调度器的调整功能会导致 15% 的性能下降,这是为什么:

第二个问题,目前是全网的块交易数据并不多,需要同步的 block size 也小,以 10M 带宽做计算算出来的上下界固定值调整不会有特别大的问题,但后期可见的数据量提升、每个人带宽的不一致、连接节点数的不一致会让该调整界限在很多情况下失去应有的效果,进而退化为没有 scheduler 能力的节点。

那么应对的方案已经存在于我脑海里了,但还并没有形成 PR,这里我简单描述一下方案:

这样首先将计算任务延时为一个 gap 一次的排序和取中位数,其次通过中位数与上期的平均,让该调整界限不会因为极端值而大幅抖动导致意外地逐出节点。使用中位数而不是平均数的原因也是为了避免极端值的影响。

当然,上述方案还没有经过测试,最近几天都在被其他事情干扰,导致被拖延了,看上去做个简单的测试还是很容易的,假期过后差不多就开始这项工作了。

跳过验证

将上述所有东西都实现完之后,同步的性能几乎就是在拼用户 cpu 性能了,那么有没有更快的方式呢?有,那就是跳过验证,也就是在到达某个 block 之前的所有 block 都不校验了,认为它们是安全的,这样在没有验证的情况下,同步性能可以提升到网络能支持的极致。这个方案不少其他项目有支持,在本项目下,它的实现应该会在上诉优化都完成之后。

小结

其实整体的优化并没有什么黑魔法,就是非常普通的改善方案。

这篇应该是本次优化系列的最后一篇,之后写其他的又要想标题了,有点烦躁。

评论区

加载更多

登录后评论