---
title: "Alchemy 如何在 Solana 上构建最快的归档方法"
description: "了解让我们的归档方法成为目前市场上最快方案的架构。"
---

# Alchemy 如何在 Solana 上构建最快的归档方法

<ImageBlock
  src="https://media.alchemy.com/1771818629-blog-archival-methods-solana.png"
  alt="显示Solana归档数据的图表"
  width={5760}
  height={2700}
  priority
/>

去年年底，我们[发布了全新的 Solana 产品](https://www.alchemy.com/blog/solana-infrastructure)，其核心功能之一就是归档方法的性能：比市面上任何其他方案快 20 倍。

借助我们的 Solana 产品，开发者可以通过标准 JSON RPC 接口以前所未有的速度查询历史交易、区块和签名。实现这一点并不容易，这篇文章将介绍我们是如何做到的。

## 什么是 Solana 上的归档方法？

在 Solana 中，归档方法指的是检索历史区块链数据的 RPC 调用，例如 `getTransaction`、`getBlock` 和 `getSignaturesForAddress`。

这些归档端点是链上应用的基础。钱包、索引器、分析服务商、区块浏览器等都依赖这些历史数据来为用户提供更丰富的信息并构建连贯的体验。

然而，在 Solana 上获取归档数据素来困难。数据集庞大、关联性强，查询计算量也很大。对大多数开发者来说，延迟和数据不一致的问题往往首先且最明显地出现在这些端点上。

## Solana 归档方法的性能问题

目前大多数 Solana 基础设施运行在 Google Bigtable 之上，搭配基于验证节点的 RPC 节点。

这套技术栈部署简单（这也是许多团队选择它的原因），但并不适合规模化场景。它占用大量 CPU 和内存，处理大批量请求时表现吃力。在大规模查询时，这些方法会限制吞吐量，拖慢应用响应速度，更严重时还会在返回结果中丢失数据。

这就是为什么即便是口碑良好的服务商，在查询历史数据时也常常返回不完整或滞后的结果。对开发者来说，这些缺失的区块和缓慢的调用很快就会导致应用内体验的崩溃。

我们想解决这个问题。不仅仅是增加硬件（虽然我们也这么做了），而是彻底改进基础设施的架构。

## 我们在 Google Bigtable 上的优化困境

和许多 Solana 基础设施服务商一样，我们最初也是从 Google Bigtable 起步的，随着时间推移，我们发现了上述性能问题。而这些问题又因两个因素而加剧。

首先，是 Solana 区块的体积问题。Bigtable 在行数据约为 1KB 时性能最佳，而 Solana 的交易数据或账户状态通常要大得多。获取一个大型二进制对象需要从 Google 底层存储（Colossus）读取更多 I/O。

其次，是成本问题。云数据库价格不菲。Solana 上的很多数据（比如投票交易）本身价值不高，将这些数据长期保存在云端在经济上并不划算。此外，Google Bigtable 按用量计费，这进一步加大了提供更高 RPS 的压力，也降低了灵活性。

尽管存在这些限制，我们仍然尝试对这套体系做了一些优化。例如，我们优化了交易获取流程。在 Google Bigtable 的默认配置下，当你用 `getTransaction` 请求一笔交易时，系统会先向 `tx` 表发起一次查询，根据签名找到区块 ID，然后拉取所有区块以返回这一笔交易。为了返回极小一部分数据，却要从远程云数据库中做这么多工作。

为了让这一常见请求更高效，我们引入了一张名为 `tx-full` 的新表，其中不仅存储区块的引用，还存储完整的交易数据。这大幅改善了响应时间，但由于数据中心地理位置较远，我们仍然受到延迟增加的困扰。

尽管我们努力适配默认技术栈，但开发进展依然很慢。哪怕是从完整 RPC 节点/验证节点执行一次简单的写入操作，都相当麻烦：每次变更都需要重启整个节点。这个过程可能需要 30 分钟到几个小时不等。无法让数据库在地理位置上更靠近，也无法控制其中的一些参数，这让一切变得更困难、成本也更高。经过一番权衡，我们决定重写整个技术栈，从数据摄入到 RPC 服务本身全部重来。

## 我们的方案？重写整个技术栈

这项工作的主要目标是缩短开发周期。这意味着不再使用既负责写入又负责提供数据服务的重型 RPC 节点。相反，我们将写入和提供数据的功能拆分成独立的小型服务，并切换到自托管的开源数据库。

新系统提供了以下能力：

- **硬件与软件协同优化：** 技术栈的每一层，从磁盘布局到内存访问，都经过调优，以最大化吞吐量、最小化计算开销。
- **多区域数据分布：** 归档数据在全球范围内分布，确保无论请求来自哪里都能实现低延迟访问。
- **三重验证摄入：** 每条记录写入两次，通过程序化方式验证准确性，并持续扫描以确保完整性。
- **自我修复管道：** 一旦检测到数据不一致，我们会自动重新摄入缺失的条目，并交叉核对每个区块最多 30-50 个相关地址，以保证数据完整性。

下面我们逐一介绍这套方案的各个组成部分，这些代码也[已开源](https://github.com/dexterlaboss/archival-rpc)（相关代码位于 [DexterLab](https://www.alchemy.com/dapps/dexterlab) 的仓库中，该团队[去年被 Alchemy 收购](https://www.theblock.co/post/354709/alchemy-acquires-solana-infrastructure-provider-dexterlab-as-it-continues-expansion-beyond-ethereum)）。

## ArchivalRPC

我们从零重建了归档服务，使其性能大幅提升，可以在几秒钟内启动或重启。相比之前使用完整 RPC 节点时需要 30 分钟到几个小时的启动时间，这是一次巨大的进步。

我们依然使用 Solana 官方库来序列化和反序列化数据，因此该服务始终能兼容后续变更。这意味着即使采用了全新的自研技术栈，我们也完全没有改变数据本身，确保所提供的数据在处理过程中不会被破坏。

作为依赖数据库的轻量级服务，它可以在按需扩容时非常便捷地实现（仅需几秒钟），从而应对大量流量。这对流量波动较大的应用尤其有用，单个数据库实例即可支持这一服务达到每秒 10 万到 20 万次请求，具体取决于数据库配置。

## ArchivalRPC 摄入器

这部分技术栈负责从多个来源拉取 Solana 数据并写入我们的数据库层。它既能处理批量导入（例如在引导新实例时使用的原始 gzip 区块归档），也能处理来自 Kafka 等系统的实时数据流。

我们还在摄入器中直接构建了精细化的控制功能，使我们能够精确选择某个实例应存储哪些数据类型（例如仅存储区块、仅存储签名、仅存储交易，或任意组合）。这种可选择性摄入模型减少了单点故障，提高了可靠性，也让我们能够根据不同使用场景灵活优化存储成本。

## Hbase

在提升了读写性能之后，我们把目光投向下一个挑战：云数据库的延迟问题。ArchivalRPC 从一开始就被设计为支持多种存储后端，这让我们能够顺利地探索其他方案。

我们最终选择了 HBase——一个开源的、可自托管的 BigTable 替代方案，因为它让我们能够将数据库与 RPC 服务器部署在同一位置，从而将存储延迟降到接近于零。这种实现方式也让我们能够沿用现有的数据模型和实现原则，在不引入架构风险的前提下获得性能收益。

但即使 HBase 有诸多优势，我们仍然遇到了一些问题。例如，为了处理 Solana 区块体积过大的问题，我们最初尝试在 Hbase 中使用 `BucketCache`，该功能可以配置使用堆外内存或高速 SSD 作为二级缓存层，让更多数据保持在“热”状态。

然而，我们最终发现 `BucketCache` 并不可靠。由于数据的随机性以及变化速度太快，它反而拖慢了系统性能。因此，我们最终转向了另一种处理大数据的方案：改变了区块的存储方式。我们不再将完整数据块存储在数据库中（数据库本就不适合处理这类大型数据结构），而是只在数据库中存储元数据，这些元数据指向 CAR 文件，而 CAR 文件可以存放在任意存储介质上，具有很强的可扩展性。

### 服务优化与多区域分布

要构建一个能够支撑百万甚至十亿级请求的方案，需要对整个技术栈进行优化：仅仅替换数据库架构并不足以达到我们想要的性能水平。这需要我们在不同硬件上尝试多种部署方式，并测试不同的架构。经过反复迭代，我们不断改进摄入和服务模块，最终达到了预期效果。

优化硬件和软件以处理大量请求是我们需要解决的一个挑战，另一个则是对抗延迟。对于负载较重的归档调用，我们发现要将延迟控制在合理范围内，必须将硬件扩展到多个区域，以确保全球范围内响应速度的一致性。

这种多区域部署也提升了我们的可用性，帮助我们维持更高的可靠性标准。服务中断是不可避免的，而跨地理区域的备用方案确保即便某个区域出现故障，我们依然能够继续为客户提供服务。同样的多区域配置也有助于我们扩展和分配流量：当某个区域接近容量上限时，我们可以在扩容部署的同时将流量路由到其他区域。

### 三重验证摄入

这个产品的大部分工作都是为了提升性能，但这只是问题的一半。Solana 归档方法长期存在的一个问题是数据经常会被整体丢失。我们必须提升系统的可靠性。

许多基于 Bigtable 的服务商在摄入管道扩容时会悄无声息地丢失区块、签名或交易。这类缺失源于验证节点默认数据摄入方式的特性：每次重启都可能导致数据写入出现断层。

而如果你依赖多个验证节点写入相同的数据，这也无法保证数据一致性，因为没有二次校验来确认数据是否被正确写入。作为使用这些基础设施的开发者，在下游出现问题之前，你根本无从得知数据已经缺失。

我们的基础设施从设计上就避免了这个问题。在写入数据之后，我们会运行二级管道来读取每一条记录，持续验证这些数据，并在发现不一致时自动修复。我们还会在初始引导完成后对数据库进行全面检查，并持续对实时写入进行近乎实时的检查。

我们打造的不只是更快的数据，而是值得信赖的数据。

### 自我修复管道

对于某些复杂数据，比如 `signatures`，回退方法是不可靠的。这意味着上述三重验证流程有时仍然不够。

为解决这个问题，我们使用程序化验证对每个区块最多 30-50 个地址的交易进行交叉核对。一旦系统检测到差异，我们的备份与恢复流程就会自动修复缺失的条目。

这一流程包括获取作为真实来源的区块，并从中提取所有地址-签名关系。为了优化在 HBase 中扫描的成本，我们会在大范围的连续 slot 中按地址累积这些映射关系，并执行一次扫描查询，而非逐个基于 key 的查询。然后将结果进行比对，从而识别出 SIGS 集群中的缺口。

一旦识别出这些缺口，我们会将该区块重新提交给索引管道（发送到适合该数据类型的 kafka topic）并重新索引。我们还增加了一致性检查流程，如果缺口未被填补，该流程会重复执行这一操作，并在故障状态持续存在时最终向团队发出警报。

正是这种冗余设计和自我修复机制，确保你可以放心依赖我们的方法，将其作为 Solana 上最完整、最一致的历史数据来源。

## 将这些工作转化为吞吐量

所有这些工作最终转化为了实实在在的处理能力：

- `getTransaction` 每区域每秒 100,000 次请求
- `getSignaturesForAddress` 每区域每秒 50,000 次请求
- `getBlock` 每区域每秒 2,000 次请求

我们将持续投入 Solana 产品的建设，并很快会扩展到更多区域，以获得更好的可靠性和延迟表现。这只是一个开始。

## 亲自试试

如果你想体验我们在 Solana 上的归档方法，可以[创建一个 Dashboard 账户](https://dashboard.alchemy.com/)免费开始使用，生成你的 API key。然后可以[在我们的文档中](https://www.alchemy.com/docs/reference/solana-api-quickstart)探索 Solana 相关端点。

## 常见问题

### 什么是 Solana 上的归档方法？

归档方法是用于检索历史区块链数据的 RPC 调用，例如 `getTransaction`、`getBlock` 和 `getSignaturesForAddress`。这些端点让开发者能够通过标准 JSON RPC 接口访问过去的交易、区块和签名。

### 为什么 Solana 的归档方法通常较慢？

大多数 Solana 基础设施运行在 Google Bigtable 上，该数据库占用大量 CPU 和内存，处理大批量请求时表现吃力。这套技术栈并未针对规模化场景设计，可能限制吞吐量、拖慢性能，甚至在响应中丢失数据。

### Alchemy 在 Solana 上的归档方法有多快？

Alchemy 的归档方法比其他方案快多达 20 倍，处理能力为 `getTransaction` 每区域每秒 100,000 次请求，`getSignaturesForAddress` 每秒 50,000 次请求，`getBlock` 每秒 2,000 次请求。

### Alchemy 如何确保数据的完整性和可靠性？

我们采用三重验证摄入机制，每条记录写入两次，通过程序化方式验证准确性，并持续扫描以确保完整性。自我修复管道会通过交叉核对每个区块最多 30-50 个相关地址，自动重新摄入缺失的条目。

### Alchemy 为 Solana 归档数据使用什么数据库？

我们使用 HBase，这是一个开源、可自托管的数据库，可以将数据与 RPC 服务器部署在同一位置，从而将存储延迟降到接近于零。它取代了 Google Bigtable，用以提升性能并降低成本。

### Alchemy 如何处理 Solana 区块体积过大的问题？

Alchemy 不再将完整区块存储在数据库中，而是只存储指向 CAR 文件的元数据，CAR 文件可以存放在任意存储介质上，具有很强的可扩展性。这种方式能更好地应对 Solana 区块体积过大的问题。

### Alchemy 为什么要从零重写整个归档技术栈？

使用完整 RPC 节点的默认技术栈重启需要 30 分钟到几个小时，导致开发进展缓慢且成本高昂。Alchemy 将数据写入和服务拆分为可在几秒内重启、并能按需扩容的小型服务。

### Alchemy 如何在全球范围内维持低延迟？

归档数据在全球多个区域分布，确保无论请求来自哪里都能实现低延迟访问。这种多区域部署也提升了可用性，并有助于在高需求时段分配流量。
