存储 (StableDB)
Stable v1.4.0 使用 MemIAVL 替代了基于 LevelDB 的状态存储。MemIAVL 将一个状态快照存储在内存映射文件中,并在顺序写入预警日志中记录每个新区块的更改。
磁盘 I/O 为何成为瓶颈
每个区块都会改变应用程序状态。节点必须完成两个相关任务:
- 状态提交:计算并提交新状态的根哈希。
- 状态持久化:将更改的数据写入存储,以便节点以后可以恢复。
以前的 LevelDB 后端存储将这些任务耦合在一起。它还在后台压缩期间重写数据,以重新组织存储的记录,从而提高后续读取效率。
一次逻辑上的状态更新可能导致 10-50 倍的物理磁盘写入。这称为写入放大。执行必须等待一个由随机写入和压缩主导的存储路径。
MemIAVL 如何改变存储
内存映射文件 (mmap) 允许操作系统通过内存地址暴露文件内容。节点可以通过指针读取状态,而操作系统将所需的页面加载到其页面缓存中。
MemIAVL 结合了两种结构:
- 快照:持久化状态树的内存映射表示。
- 写入预警日志 (WAL):快照后所做原始更改的仅追加记录。
写入路径
每个区块按顺序将其更改集追加到 WAL 中。MemIAVL 不会将更新路由到 LevelDB 或触发压缩。因此,写入放大从 10-50 倍大约下降到一次写入。
MemIAVL 还将树节点分为两类:
PersistedNode表示已存储在快照中的未更改子树。MemNode表示当前更新在内存中更改的子树。
当 MemIAVL 计算下一个根哈希时,它会停止在未更改的 PersistedNode 子树处。它仅哈希包含新 MemNode 数据的路径。
读取路径
当前状态读取遵循指向内存映射快照的指针。经常访问的页面保留在操作系统的页面缓存中,因此节点避免了为每个树节点进行单独的数据库查找。
历史状态和 VersionDB
MemIAVL 服务于当前状态和节点保留的快照。一个配套的 VersionDB 将历史版本存储在 RocksDB 副车中。
当节点必须回答比其最早保留的 MemIAVL 快照更早的历史状态查询时,您需要 VersionDB。这对于需要公开长期查询的归档节点和 RPC 服务尤为重要。
迁移注意事项
现有节点在采用 v1.4.0 时必须迁移其 LevelDB 状态。推荐的方法是本地快照导出和恢复。
基准测试显示,标准恢复每个验证器平均停机时间为 19.4 秒。并行恢复方案将平均时间缩短至约 2.6 秒。
验证器可以逐个以轮询方式进行迁移。保持至少三分之二的投票权在线,可以在迁移期间让网络继续生成区块。
进一步阅读
有关更多技术深度解析和实现细节,请参阅:

