Solana区块链以顺序、仅追加的账本存储数据。这对数据完整性和交易吞吐量有益,但代价是:查询历史数据非常低效且速度极慢。
复杂操作通常涉及从多个来源过滤、聚合或联合数据。在这些情况下,直接查询Solana对于大多数实际应用来说是不切实际的。
为了解决这个问题,大多数企业构建了Solana历史数据的私有索引。
本指南涵盖了完整的生命周期:使用 getTransactionsForAddress 和其他档案 RPC 方法回填历史数据,选择数据库,并通过实时流保持索引最新。
何时使用
构建索引的场景:
- 您的产品需要快速过滤查询,而直接 RPC 调用速度太慢(例如,钱包的代币账户和余额,或交易对的完整历史)
- 您需要过滤、聚合或连接链上数据,或将其与链下数据结合(CEX 价格、KYC、标签)
- 您正在计算盈亏、持有人分析或 NFT 销售历史等需要预处理、可查询数据的项目
- 您为众多用户提供服务,无法承受链上请求的延迟
如果您只需标准钱包或资产数据,托管 API 可能比运行自己的索引更简单。请参阅下面的互补选项。
索引 Solana 数据意味着什么?
索引是从 Solana 区块链查询数据并将其存储在后端数据库中(例如,PostgreSQL, ClickHouse),以便能够轻松响应客户请求,而不需要直接使用 Solana RPC 调用 查询区块链。
索引器通常做四件事:
- 回填历史数据: 使用档案 RPC 方法查询所有历史数据。
- 流处理新数据: 处理网络确认的新区块。
- 解析和转换数据: 从确认的区块中提取相关数据(例如,交易、状态变化等)。
- 将数据组织到数据库中: 使用新数据更新索引。
为什么大多数公司构建 Solana 索引?
公司建立Solana索引是因为他们的业务依赖于提供快速实时访问特定目的的区块链数据,这是本地RPC所不提供的(例如NFT销售历史)。
公司还利用自定义索引将链下数据(例如,中心化交易所价格,KYC信息等)与他们的链上数据相结合。
钱包示例
例如,如果一个Solana钱包需要快速返回用户的代币账户和余额,直接使用getTokenAccountsByOwner和getTokenAccountBalance查询Solana太慢,可能导致产品无法使用。因此,钱包通常会维护他们自己的客户地址、代币和账户余额的索引。
交易示例
类似地,加密交易公司可能希望记录在特定交易对(例如,SOL-USDC)或市场上发生的所有交易活动,以便回测其交易算法。
直接查询区块链以获取这些数据对于任何实际的交易分析来说都太慢。相反,量化交易者可能会选择为SOL-USDC市场建立索引,并使用像LaserStream这样的实时流产品随时更新最新交易。
过滤示例
想象一下,用户想要在他们的前端应用程序中过滤特定条件的交易(例如,按代币类型、转账金额、日期或钱包地址)。
如果没有索引器,您的应用需要扫描上百万条交易和数十万个区块,逐个检查是否符合过滤条件。
这个过程对现代产品用户体验来说太慢了。
盈亏示例
要计算交易者的盈亏(PnL),您需要:
- 找到给定时间段内与他们钱包相关的每笔交易
- 筛选掉兑换交易并标记为买入或卖出
- 确定用户在每次兑换中支付了多少费用
- 获取每次交易时各代币的历史价格数据
- 汇总每笔交易的盈亏以计算交易者的总盈亏
实时计算这一切是不切实际的,需要一种更快速、更具扩展性的解决方案。
通过索引,所有这些信息已经被处理并存储在可查询的数据库中。现在,计算交易者的PnL变成了一次API调用,并立即提供。
让我们看看用于回填Solana索引并保持其最新的三种方法。
第一步:获取历史数据
建立Solana索引的第一步是获取您关心的所有历史数据。
主要有三种方法可以做到这一点:
- getTransactionsForAddress(推荐)
- getSignaturesForAddress 和 getTransaction
- getBlock
方法1:getTransactionsForAddress(推荐)
getTransactionsForAddress RPC方法允许您获取区块链数据任意部分的完整交易详情。由于其强大的过滤功能,您不会浪费时间获取不需要的数据,并且由于其反向搜索功能,您可以按时间顺序获取交易。
使用此方法的步骤
- 确定您需要的数据时间范围,并相应设置过滤器
- 将
transactionDetails设置为full以获取所有交易详情
- 如果需要,将
tokenAccounts过滤器配置为包括相关的代币账户交易
- 使用
paginationToken分页获取结果
- 每次迭代时,提取所需数据并存储到您的数据库中
使用getTransactionsForAddress的好处
使用gTFA endpoint的主要优势是速度和简便性。通过基于插槽和时间的过滤器、代币账户支持、反向搜索和分页,您可以在Solana历史的任何时间获取所需的任何数据,所有这些只需一次调用,无需复杂的循环或重试逻辑。与getSignaturesForAddress不同,它还可以包括涉及地址拥有的相关代币账户的交易。
如果您的索引专注于代币和本地 SOL 的移动(支付、分类账、余额对账),getTransfersByAddress 返回已解析的、可对账的转账行,而不是完整的交易,这可以为您节省解析步骤。
方法2:getSignaturesForAddress 和 getTransaction
在发布 gTFA 之前,查询历史数据的标准方法是递归地循环签名,使用 getSignaturesForAddress (从最新到最旧),然后调用 getTransaction 提取完整的交易详情。
使用此方法的步骤
以下是使用此方法的基本步骤:
- 调用
getSignaturesForAddress
- 存储此次调用最后接收到的交易的签名
- 对于下一次调用
getSignaturesForAddress,将 before 参数设置为此签名
- 根据需要重复此循环
- 对于以这种方式检索到的每个交易签名,调用
getTransaction 获取其完整交易详情
- 将相关数据插入您的数据库
此方法的缺点
不幸的是,要使用此方法,您需要:
- 从最新的交易开始并向后处理
- 为每笔交易额外进行一次 RPC 调用
- 构建一个线程安全的队列来处理并发处理
- 构建重试和退避逻辑,以防止数据丢失和达到速率限制
- 不包括地址拥有的关联代币账户的交易
虽然这种方法有效,但更复杂、灵活性较差,并且消耗更多的 credits。要查看包括代币账户在内的完整钱包历史,请使用 getTransactionsForAddress。
方法3:使用 getBlock
当您目标区块中的高百分比交易与您的分析相关时,getBlock 方法最为有效,例如索引 频繁使用的 Solana 程序 的交易,如 DFlow 的聚合器、Pump.fun 程序或 Solana 的 Token 程序。
使用此方法的步骤
使用 getBlock 查询历史数据的基本过程包括:
- 决定要查询的时间范围
- 将这段时间转换为槽号
- 依次(向前或向后)获取相应的区块
- 对每个区块,筛选与您的索引相关的交易
- 将其中的相关信息存储到您的索引中
对于大多数使用情况,此方法本质上是浪费的,因为您正在检索区块中的所有交易,而通常只有一小部分与您的分析相关。
仅在检查经常使用的程序的交易或地址筛选无法捕获目标数据时使用此方法。
步骤 2:将 Solana 数据同步到您的数据库
在获取历史数据后,您需要将其转化并高效地存储到数据库中。
您的存储选择应根据具体用例量身定制—没有一种解决方案适用于所有情况。正确的数据库取决于数据集的大小、延迟要求、查询模式和团队专业知识。
选项 1:SQL 数据库
推荐在像 PostgreSQL 这样的关系型数据库中存储 Solana 数据,适用于大多数用例。SQL 灵活、无处不在且易于学习。现代关系型数据库可扩展至超过 1 亿行以上,同时仍能提供 ACID 合规性、复杂连接和强大的二级索引的好处。
使用 SQLite 进行原型开发、本地开发或希望通过单文件数据库实现零配置时。它非常适合数据集保持在几个 GB 以下的情况。
对于需要数据复制、多个客户端并发访问或高级功能(如全文搜索和JSON操作符)的生产应用程序,使用PostgreSQL。
对于大多数生产级Solana索引器,PostgreSQL是我们的推荐选择。
实现示例:
例如,我们将展示如何在PostgreSQL数据库中存储代币转移。
首先,创建一个表:
然后,在经常查询的列上添加索引:
您还可以创建部分索引,以防频繁查询的数据只有一部分。
以下是如何为高价值转移创建索引:
在回填数据时,确保使用批量INSERT和预准备语句以获得最佳写入速度。
选项 2:列式数据库
列式数据库针对分析查询、聚合和高容量时间序列数据进行了优化。如果您需要索引数十亿笔交易,像ClickHouse或Cassandra这样的列式数据库是您的最佳选择。
当您需要对大型数据集进行实时分析查询时,使用ClickHouse——它针对快速读取、聚合和时间序列分析进行了优化。
当您需要极高的写入吞吐量、轻松的水平扩展和高容错能力时,使用Cassandra。这使其成为持续摄取大量Solana数据的理想选择。
实现示例:
我们将展示如何在ClickHouse数据库中存储代币转移。
为此,创建一个使用MergeTree表引擎的表。它被设计用于高摄取率,因此非常适合索引。
使用以下命令:
在此设置中,(token_mint, date)被设置为主键和排序键。ClickHouse将根据您的排序键在磁盘上排序数据。这对于查询单个token mint,并按日期范围缩小响应非常理想。
这是一个查询示例:
一个简单的实现是使用 Parquet 数据格式与 Amazon Athena。
交易签名和地址使用 FixedString(N) 数据格式存储,该格式存储精确的 N 字节。ClickHouse 会自动压缩数据,以减少 10-20 倍的存储成本,并提高查询性能。
为优化查询性能,使用物化视图来预先计算常见的聚合。
例如,您可以预计算每天的代币转移量,以供仪表板上的与容量相关的图表使用。
选项 3:数据湖
数据湖非常适合存储大量的原始和处理过的区块链数据,以作长期存档和分析查询。
一个简单的实现使用 Parquet 数据格式和 Amazon Athena。
Parquet 是一种面向列的数据文件格式,设计用于高效的数据存储和检索。
Amazon Athena 是一种交互式查询服务,允许您使用标准 SQL 分析存储在 Amazon S3 中的数据,而无需设置基础设施或将数据加载到单独的数据库中。
只有在需要查询大量非结构化数据时才推荐使用数据湖。在大多数用例中,我们推荐使用 SQL 数据库(选项 1)。
实现示例:
我们想创建一个代币转移的存档并查询它们。
首先,我们需要在 S3 中存储它们:创建一个名为 solana_index 的桶,并使用此键结构按时间分区您的代币转移数据:
使用Carbon和类似框架,以避免编写样板代码,并在数小时而不是数天内设置索引器。
每天的转移存储在相应日期文件夹中的单独 Parquet 文件中。
处理 Solana 的转移时,将其转换为 Parquet 格式并写入相应的 S3 对象。
稍后,您在 Athena 中创建一个表并将其连接到存储桶。这使您可以直接在存储桶中的数据上运行如下查询:
使用索引框架
使用Carbon和类似框架来避免编写样板代码,并在几小时内设置您的索引器。
关键特性:
- 为热门程序(Token 程序、DeFi 协议、Metaplex)预构建的解码器
- 可配置的数据源(RPC、LaserStream、Enhanced WebSockets)
- 内置支持回填和实时流
- 输出到多个存储后端(Postgres 开箱即用)
- 完全可定制:您可以设置自己的数据源、解码器和数据接收器
步骤 3:保持索引的更新
在回填历史数据后,您需要一个实时流解决方案来使您的索引与新的区块链活动保持同步。否则,您的索引会变得过时。
方法 1:LaserStream(推荐)
我们推荐LaserStream gRPC作为所有生产索引用例的默认选择。它专为可靠、超低延迟和容错的数据流而打造。
使用 LaserStream 的一些好处包括:
- 24 小时历史重放:如果您的索引器断开连接,LaserStream 会自动重放所有丢失的交易,从您断开的地方开始
- 自动重新连接:我们的LaserStream SDK(Rust、Go、JS/TS)无缝处理网络中断
- 节点故障转移:您的 LaserStream 连接同时从多个节点聚合数据,以确保最大运行时间
结合速度和可靠性,LaserStream 非常适合实时应用,如实时交易馈送、交易仪表板和即时余额更新。
如何使用 LaserStream 进行索引
使用 subscribe 方法来订阅区块链事件。
以下是一些最佳实践:
- 尽可能缩小你的过滤器:仅订阅您实际需要索引的数据,以尽量减少带宽消耗和处理需求。
- 使用
confirmed 承诺级别:这平衡了延迟和最终性。processed 级别可能过于不可靠,而 finalized 增加了约13秒的延迟
- 设置
failed: false 除非您特别需要跟踪失败的交易
- 排除投票交易 (
vote: false),因为它们与索引无关
让我们看一个例子。
使用以下订阅来索引所有新的代币转移:
方法2:使用 LaserStream WebSocket
LaserStream WebSocket —— LaserStream 的 WebSocket 变体,包括 Helius 专用 transactionSubscribe 扩展 —— 运行在与 LaserStream gRPC 相同的后端,是一种成本效益高的实时流媒体替代方案,当您不需要 gRPC 时使用。
您应该在以下情况下使用 LaserStream WebSocket:
- 您的应用可以容忍偶尔的数据间隙
- 实时更新很重要,但不是关键任务
- 您有现有的基础设施来检测和填补丢失的数据
- 预算限制显著,您需要最大限度地减少流媒体成本
- 您在承诺使用 LaserStream 之前正进行原型设计或测试
然而,选择 WebSockets 时需要考虑一些权衡:
- 速度:LaserStream WebSocket 运行在与 LaserStream gRPC 相同的后端,但 WebSocket 协议增加了 JSON 结构和每条消息的开销 — 为了同一数据的最低延迟,使用 LaserStream gRPC
- 可靠性:没有历史重放保证。如果您的 WebSocket 断开连接,您需要使用 RPC 方法手动检测和填补间隙
- 复杂性:需要额外的监控基础设施以确保数据的完整性
如何使用 WebSockets 进行索引
要更新存储所有 token 转移的索引,您可以这样订阅 transactionSubscribe:
构建一个稳健的 Solana 索引和回填数据需要解决三个核心挑战:
- 高效地获取历史数据
- 转换和存储数据以便快速检索
- 实时更新索引的 Solana 数据
通过我们新的先进归档系统、诸如 getTransactionsForAddress 的归档调用,以及业界领先的数据流解决方案如 LaserStream,构建 Solana 索引变得比以往更简单和实用。
补充选项
运行您自己的索引可让您完全掌控,但并非总是必要的。对于常见需求,托管的 Helius API 可以替代或补充自定义索引:
- DAS API — 查询 NFTs、可替代代币和压缩资产(元数据、所有权、余额,按所有者/集合/创建者)而无需自行索引资产数据。
- Wallet API — 提供钱包余额、历史记录、转移和身份的高级 REST 端点,带有 USD 值和更简单的响应格式。
许多团队使用这些 API 来管理投资组合、代币和钱包数据,并为托管 API 无法涵盖的特定数据保留自定义索引。
下一步