
Solana 长期 RPC 交易历史基于 BigTable 的六个月交易数据存储方案设计与实现【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana本篇指南解读 Solana 仓库中的长期 RPC 交易历史方案docs/src/implemented-proposals/rpc-transaction-history.md它说明了为什么验证节点本地 RocksDB 账本无法支撑六个月以上的交易历史查询、为什么选择 Google BigTable 作为外部存储、四张核心数据表block/tx-by-addr/tx/entries的行键设计与 GC 策略以及通过solana-ledger-tool完成数据灌入、通过 tonic/gRPC 访问层的完整实现链路。读完本文你能理解该方案的表结构、数据写入与查询路径并能在本地模拟器或生产环境中部署验证。一、问题背景为什么需要外部数据存储RPC 节点需要对外提供至少 6 个月的交易历史服务而验证节点本地 RocksDB 账本实际只能保留数天的历史对下游用户明显不足。同时6 个月的交易数据量在物理上无法合理地塞进验证节点本地的 RocksDB 账本中因此必须引入一个外部数据存储。方案的核心定位是验证节点本地 RocksDB 账本继续作为主数据源查询时先查本地本地没有再回落到外部数据存储fall back to the external data store。受此方案影响的 RPC 端点共 7 个getFirstAvailableBlockgetConfirmedBlockgetConfirmedBlocksgetConfirmedSignaturesForAddressgetConfirmedTransactiongetSignatureStatusesgetBlockTime方案提出时给出了五条系统级设计约束这也是后续所有技术选型的依据数据规模可达 TB 级且不可变immutable——存储与检索的数据量会迅速膨胀到太字节级别且写入后不再修改对 SRE 运维负担要尽量轻——例如需要 SRE 持续监控、再平衡节点的 SQL 数据库集群是明确不期望的方案数据必须支持实时检索——以分钟或小时计的批量查询是不可接受的易于在全球复制——便于把数据部署到各区域的 RPC 端点附近降低访问延迟与外部存储的对接必须简单可靠——不能依赖风险较高、使用率低的社区支持库。基于以上约束方案最终选择了 Google 的 BigTable 产品作为数据存储。二、Table Schema四张表的行键设计与数据生命周期一个 BigTable 实例承载全部交易数据按查询路径拆分成不同的表以支持快速检索新数据可以随时拷贝进实例而不影响已有数据所有数据不可变一般约定是每个 epoch 完成后上传一次当前 epoch 的数据但对数据 dump 的频率没有硬性限制旧数据的清理通过配置实例表的数据保留策略GC policy自动完成数据到期后直接消失无需维护清理任务。由于清理是自动按时间过期完成的数据写入的顺序变得重要如果 epoch N-1 的数据在 epoch N 的数据之后才写入较旧的 epoch 数据反而会存活得比新数据更久。文档指出这种乱序删除除了会在查询结果中产生空洞holes外不会产生其他负面影响。另外这种基于保留期的清理方式实际上允许存储无限量的交易数据唯一的限制是金钱成本。需要说明的是该表布局只为现有的 RPC 端点服务未来新增 RPC 端点可能需要扩充 schema甚至需要遍历所有交易来构建必要的元数据。2.1 实例初始化四张表与保留策略仓库中的 init-bigtable.sh 脚本定义了实例solana-ledger中预期的四张表脚本对每张表执行建表、建列族、设 GC 策略三步见 init-bigtable.shinstancesolana-ledger for table in blocks entries tx tx-by-addr; do ( set -x ${cbt[]} createtable $table ${cbt[]} createfamily $table x ${cbt[]} setgcpolicy $table x maxversions1 ${cbt[]} setgcpolicy $table x maxage360d ) done要点表名共四张blocks、entries、tx、tx-by-addr分别对应提案文档中的 Block / Entries / Transaction Signature Lookup / Account Address Transaction Signature Lookup 四类数据每张表只有一个列族xGC 策略为maxversions1每个 cell 只保留最新版本加maxage360d数据保留 360 天后自动过期这正是六个月交易历史目标在基础设施层面的落地方式。2.2 Block 表block按 slot 检索整块该表保存给定 slot 的压缩后的块数据。行键取 slot 的 16 位小写十六进制表示保证在按行键列表检索时最早的已确认块的 slot 一定排在最前面。例如 slot 42 的行键是000000000000002a行数据压缩后的StoredConfirmedBlock结构体。StoredConfirmedBlock的内容结构与 confirmed_block.proto 中的ConfirmedBlock消息一一对应前一个块哈希previous_blockhash、块哈希blockhash、父 slotparent_slot、交易列表transactions、奖励rewards、块时间block_time与块高度block_height。其中每笔交易的元数据TransactionStatusMeta见 confirmed_block.proto包含费用、前后余额、内部指令、日志、token 余额变化、reward、return_data以及自 v1.10.35 / v1.11.6 起可用的compute_units_consumed字段——这保证了长期历史数据同样能提供完整的交易状态明细。2.3 地址交易签名索引表tx-by-addr按地址倒序取最新交易该表保存影响某个给定地址的所有交易服务于getConfirmedSignaturesForAddress这类按地址查签名列表的端点。行键base58 address/slot 取反码的十六进制 slot前补 0 到 16 位行数据压缩后的TransactionByAddrInfo结构体。对 slot 取一补码ones compliment的效果是按行键顺序列出时影响该地址的最新 slot 的交易永远排在最前——这与 Block 表用原始 slot 保证最旧在前正好互为镜像两张表分别优化了从最旧开始扫和从最新开始扫两种遍历方向。行数据结构见 transaction_by_addr.proto每条TransactionByAddrInfo包含签名signature、交易错误err、交易在块内索引index、可选memo和块时间block_time。其中的TransactionError枚举完整覆盖了交易级错误码如ALREADY_PROCESSED、BLOCKHASH_NOT_FOUND、SIGNATURE_FAILURE等见 transaction_by_addr.proto以及指令级错误码保证历史错误交易也能被完整还原。文档还特别指出Sysvar 地址不建索引但 Vote、System 这类高频程序会被索引它们几乎在每个已确认 slot 都有行——这正是 TB 级数据量来源之一。2.4 交易签名索引表tx签名到块与索引的映射该表把交易签名映射到它所在的已确认块以及块内索引服务于getSignatureStatuses、getConfirmedTransaction等按签名查询的端点。行键base58 编码的交易签名行数据压缩后的TransactionInfo结构体。2.5 Entries 表entriesslot 内 entry 摘要entries表的支持自 v1.18.0 起加入。该表保存 slot 内各 entry 的摘要数据服务于getConfirmedBlock的jsonWithEntries编码。行键与block表的行键相同行数据压缩后的Entries结构体即一个 entry 摘要列表。其 schema 与 entries.proto 对应每个Entry包含index、自上一个 entry 以来的哈希数num_hashes、entry 哈希hash、交易数num_transactions以及起始交易索引starting_transaction_index。三、访问层tonic 原始 protobuf 的 gRPC 客户端文档在 Accessing BigTable 一节明确BigTable 提供 gRPC 端点在 Rust 侧使用tonic加原始 protobuf API 访问——因为当时不存在更高层的 BigTable Rust crate。这意味着解析 BigTable 查询结果更复杂一些但不是大问题。仓库中 storage-bigtable/src/bigtable.rs 就是这一访问层的实现连接建立BigTableConnection::new设置BIGTABLE_EMULATOR_HOST时直连本地模拟器http://明文通道project 固定为emulator见 new_for_emulator否则走https://bigtable.googleapis.comTLS 根证书从 crate 内置的 pki-goog-roots.pem 加载并支持可配置的连接超时表前缀统一为projects/{project}/instances/{instance}/tables/通过read_only参数决定申请bigtable.data还是bigtable.data.readonlyOAuth scope只读 RPC 节点只申请只读权限支持BIGTABLE_PROXY环境变量为 gRPC 流量配置正向代理隧道内仍走 TLS见 bigtable.rs 与 storage-bigtable/README。单元格读写写入时数据会先压缩再落盘。put_bincode_cells 把 serde 结构体 bincode 序列化后经compress_best压缩以列名bin写入列族xput_protobuf_cells 则把 prost 消息编码后以列名proto写入。读取侧的 deserialize_protobuf_or_bincode_cell_data 会先尝试按 protobuf 解码失败再回退 bincode使同一张表能兼容新旧两种序列化格式——这对滚动升级期间新旧客户端混跑是必要的。范围查询与过滤get_row_keys 与 get_row_data 通过ReadRows请求配合行范围StartKeyClosed/EndKeyClosed、rows_limit和链式行过滤器实现CellsPerRowLimitFilter(1)只取每行最少单元格、CellsPerColumnLimitFilter(1)只取每个 cell 的最新版本、StripValueTransformer(true)在只列举行键时直接剥离单元格值以减少传输量。这些过滤器正是实时检索约束在查询路径上的体现。可靠性所有高层操作如 put_bincode_cells_with_retry都包了指数退避重试且把 table not found 这类 gRPC NotFound 识别为不可重试的永久错误to_backoff_err。四、数据灌入solana-ledger-tool 的 BigTable 子命令文档规定实例数据的持续灌入将按epoch 节奏进行通过一个新的solana-ledger-tool命令把给定 slot 范围内的 RocksDB 数据转换成实例 schema同样的流程还会手动运行一次以回填backfill现有账本数据。该命令在仓库中落地为 ledger-tool/src/bigtable.rs 的bigtable子命令模块核心操作包括uploadupload起始 slot 未指定时取blockstore.get_first_available_block()结束 slot 未指定时取blockstore.max_root()即默认回填本地账本内全部可用根上块上传并非一次性提交整个 slot 范围而是以max_num_slots_to_check * 2为步长分段推进starting_slot逐段向后滚动直到ending_slot避免长时间占用单个上传任务底层调用solana_ledger::bigtable_upload::upload_confirmed_blocks配合ConfirmedBlockUploadConfig完成块数据到 BigTable 的写入提供--force-reupload参数见 bigtable.rs强制重传用于覆盖/修复历史上传。first-available-blockfirst_available_block查询外部存储中最早的可用块用于运维侧验证数据下限与getFirstAvailableBlock端点行为blockblock按 slot 从 BigTable 拉取ConfirmedBlock以 Base64 编码输出支持--show-entries时同时从entries表读取 entry 摘要一并展示——这正是 RPC 端getConfirmedBlock数据路径的人工验证入口delete-slotsdelete_slots删除指定 slot 的数据在只读配置read_only即 dry-run下仅演练不落盘与 GC 自动过期形成互补的手工修正手段。五、开发与生产环境的部署方式storage-bigtable/README 给出了完整的两类环境操作方式开发/测试环境BigTable 模拟器后台运行gcloud beta emulators bigtable start启动模拟器运行$(gcloud beta emulators bigtable env-init)建立BIGTABLE_EMULATOR_HOST环境变量运行 init-bigtable.sh 在模拟器上建好四张表开始开发/测试——访问层代码检测到BIGTABLE_EMULATOR_HOST后会自动切换到模拟器连接bigtable.rsinit-bigtable.sh也会自动改用emulatorprojectinit-bigtable.sh。生产环境将标准的GOOGLE_APPLICATION_CREDENTIALS环境变量指向服务账号凭据项目中应包含名为solana-ledger、并已用init-bigtable.sh初始化过表结构的 BigTable 实例根据操作模式申请bigtable.data读写用于灌入或bigtable.data.readonly只读用于 RPC 查询scope如需走正向代理按HTTP_PROXY的方式导出BIGTABLE_PROXY即可。六、方案要点小结从提案文档到仓库实现该方案的关键设计可以概括为主从数据源本地 RocksDB 账本为主BigTable 为六个月历史的回落层两者分工明确四表按查询路径切分blockslot 正序行键最旧在前、tx-by-addrslot 补码行键最新在前、tx签名为行键、entriesslot 内 entry 摘要每张表都直接对齐一个 RPC 端点的扫描模式利用 BigTable 行键有序性免去二级索引不可变 自动过期数据只增不改GC 策略maxversions1,maxage360d自动回收运维面接近零负担理论存储上限只受成本约束轻量对接tonic 原始 protobuf 直连 gRPC序列化上 bincode/protobuf 双格式兼容配合压缩与指数退避重试保证读写稳健epoch 节奏灌入solana-ledger-tool bigtable的 upload 子命令按 epoch 持续增量上传、可一次性 backfill并配套 first-available-block / block / delete-slots 等运维子命令形成完整的数据生命周期闭环。相关源码与文档入口提案文档docs/src/implemented-proposals/rpc-transaction-history.md访问层实现storage-bigtable/src/bigtable.rs实例初始化脚本storage-bigtable/init-bigtable.sh存储 proto 定义storage-proto/proto/confirmed_block.proto、storage-proto/proto/transaction_by_addr.proto、storage-proto/proto/entries.proto数据灌入命令ledger-tool/src/bigtable.rs部署说明storage-bigtable/README.md【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考