在此博客文章中,我们将介绍 Solana 的水平缩放状态架构 Cloudbreak。
概述:RAM、SSD 和线程
在不分片的情况下缩放区块链时,仅缩放计算是不够的。用于跟踪帐户的内存很快会成为大小和访问速度方面的瓶颈。例如:众所周知,许多新式链使用的本地数据库引擎 LevelDB 在一台机器上最多能支持约 5,000 TPS。这是因为虚拟机无法通过数据库抽象来利用对帐户状态的并发读写访问。
一个简单的解决方案是在 RAM 中维护全局状态。但是,期望消费级计算机具有足够的 RAM 来存储全局状态是不合理的。另一个选择是使用 SSD。尽管 SSD 将每字节的成本降低了 30 倍或更多,但它们仍比 RAM 慢 1000 倍。以下是最新 Samsung SSD 的数据表[1],Samsung SSD 是市场上最快的 SSD 之一。
一次性交易需要读取 2 个帐户并写入 1 个帐户。帐户密钥是加密的公共密钥,并且完全是随机的,不存在实际意义上的数据局部性。一个用户的钱包将有许多帐户地址,每个地址的位与任何其他地址完全无关。由于帐户之间不存在局部性,因此不可能将它们存储在内存中,使它们彼此接近。
仅使用单个 SSD 以简单的单线程方式实现帐户数据库,每秒可实现最多 15,000 次的唯一读取,从而每秒最多可支持 7,500 个交易。新式 SSD 支持 32 个并发线程,因而可以支持每秒 370,000 次读取,也就是每秒约 185,000 个交易。
Cloudbreak
Solana 的指导性设计原则是进行软件设计时,要使其不影响硬件,从而实现 100% 的硬件利用率。
要组织帐户的数据库并实现 32 个线程的并发读写,这不是一件容易的事。LevelDB 之类的普通开源数据库会产生瓶颈,因为它们不会针对区块链环境中的这一特定问题进行优化。Solana 不使用传统的数据库来解决这些问题。相反,我们使用操作系统所利用的一些机制。
首先,我们利用内存映射文件。内存映射文件的字节会映射到某个进程的虚拟地址空间。当一个文件经过映射后,其行为将与所有内存一致。内核可能会在 RAM 中缓存一些内存数据,但物理内存量受磁盘大小而非 RAM 的限制。显然,读写仍然受磁盘性能的限制。
第二个需要在设计上考虑的要点是顺序操作比随机操作快得多。这不仅适用于 SSD,也适用于整个虚拟内存堆栈。CPU 特别适合预取以顺序方式访问的内存,而操作系统则特别适合处理顺序式的页面错误。为了利用这种行为,我们将帐户数据结构大致分解如下:
帐户和分叉的索引存储在 RAM 中。
帐户存储在不超过 4MB 的内存映射文件中。
每个内存映射仅存储来自建议的单个分叉的帐户。
映射随机分布在尽可能多的 SSD 上。
使用”写入时复制”语义。
写入被附加到同一分叉的随机内存映射中。
每次写入完成后,索引都会更新。
由于帐户更新采用”写入时复制”方式且会附加到随机 SSD 上,因此在实现并发交易时,Solana 可以获得顺序写入和跨多个 SSD 对写入进行水平缩放的好处。读取仍然是随机访问,但是由于任何给定的分叉状态更新都分布在许多 SSD 中,因此读取最终也会实现水平缩放。
Cloudbreak 还执行某种形式的垃圾收集。当分叉在回滚之后最终完成,且帐户完成更新后,无效的旧帐户将被作为垃圾回收,内存将被释放。
该架构至少还有一个很大的好处:可以通过跨 SSD 水平缩放的顺序式读取为任何给定的分叉计算状态更新的 merkle 根。这种方法的缺点是失去了数据的通用性。由于这是一个采用自定义布局的自定义数据结构,因此我们无法使用通用数据库抽象来查询和操作数据。我们必须从头开始构建所有内容。幸运的是,现在已经完成了。
Cloudbreak 基准测试
帐户数据库位于 RAM 中时,我们可以看到吞吐量与 RAM 访问时间相匹配,同时可以随着可用核心的数量进行缩放。但如果帐户数增至 10m,则该数据库不再适合位于 RAM 中。然而,我们可以看到,单个 SSD 上每秒的读写性能仍接近 1m。
参考链接
[1]
数据表: https://www.samsung.com/semiconductor/global.semi.static/Samsung_NVMe_SSD_970_PRO_Data_Sheet_Rev.1.0.pdf