存储 (StableDB)
Stable v1.8.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 sidecar 中。
当节点必须回答比其最早保留的 MemIAVL 快照更早的历史状态查询时,您需要 VersionDB。这对于归档节点和暴露长期查询的 RPC 服务尤为重要。
升级注意事项
Stable 主网在区块 36,976,000 激活了 v1.8.0。文档中的升级替换了二进制文件和两个配置文件,没有进行状态导出或快照重置。
v1.8.0 配置文件增加了新存储和提案路径使用的设置。备份 config.toml 和 app.toml,安装 v1.8.0 模板,然后恢复每个特定于节点的设置。
归档节点操作员还必须恢复其以前的修剪设置。分发的 app.toml 模板使用默认修剪,并且修剪的历史记录无法从本地节点恢复。
延伸阅读
有关更多技术深度探讨和实现细节,请参阅:

