⚡ 新消息:ElysiumTestnet 发布QuicknodeTestnet Kinetiq 在 HyperEVM 上的 EVM 链的端点
获取一个endpoint通过 SOC 2 II 类认证 · ISO 27001
Solana“读取层的重构:深入解析Quicknode 的内存缓存”
Quicknode 在内存中重建了Solana 的读取层。getProgramAccounts、getLargestAccounts 等函数的运行速度比原版Agave快10倍至1000倍。

Solana 开发人员在使用返回大量数据或需要扫描全部账户集的方法时会遇到障碍。 getProgramAccounts 被限速或禁用。 getLargestAccounts 永远不会返回。其根本原因在于系统架构。Agave RPC 节点是针对写入操作进行优化的系统,对耗时较长的读取方法进行限流是必要措施,而非策略选择。应用程序最终只能通过重试逻辑、缓存层和轮询间隔来弥补这一缺陷,而这些措施的存在纯粹是为了绕过区块链提供商的基础设施限制。
尽管整个生态系统现在才开始着手解决这一问题,但Quicknode 早已构建好了Solana 生态系统真正需要的读取层。经过两年的迭代开发,采用专为该系统设计的内存架构,其性能比Agave的默认设置快10倍至1000倍以上。
如果您是Quicknode 用户,则无需额外申请这些权益。您的endpoint 已为您提供这些权益。
Agave RPC 节点旨在处理事务、达成共识并推进账本。它们维护的账户状态是该过程的副产品,而非针对读取优化的存储。该系统没有查询规划器,没有原生二级索引,且读写 I/O 未作分离。
硬件扩展无法解决这个问题。读取查询和共识工作在同一台机器上争夺同一块 CPU,因此增加硬件配置意味着需要承担验证者级别的成本(768 GB 内存、高速 NVMe 存储、高带宽网络),却无法消除资源竞争。
增加更多 RPC 节点会加剧这一问题:额外的节点会给Solana 的流言网络和涡轮网络增加负载,从而减慢那些真正需要状态传播的验证者的状态传播速度。通过验证者架构对读取层进行扩展,会直接导致写入层性能下降。
数据分页会带来一致性风险。当新数据块出现在页面之间时,响应所基于的账户状态会在读取过程中发生变化。遍历大型结果集的应用程序在读取完成之前,收到的数据位置就已经发生了偏移。
Quicknode 我们很早就意识到了这个问题,因此将读取工作负载从验证器中分离出来,并通过专门构建的基础设施来处理这些工作负载。目前处理流量的架构是这些努力的第三个版本。每个版本都更接近数据本身,去除了那些只会增加延迟却无实际价值的抽象层,相关工作仍在持续进行中。
第 1 轮:关系型数据库
第一次迭代采用了传统方法:将账户状态和账本数据导入关系型数据库,构建索引,并在此基础上进行查询。这立即解决了资源争用问题。验证者可以专注于达成共识,而读取操作则由另一个独立系统处理。 但在Solana 这种规模下,关系型数据库带来的开销不容忽视:查询规划、行级锁定、写前日志(WAL)管理、自动真空(autovacuum)以及基于磁盘存储固有的延迟。对于每400毫秒生成一个区块的区块链而言,每次读操作的微秒级延迟在每天数百万次请求中会产生累积效应。随着Solana 流量的增长,磁盘I/O逐渐成为需要突破的下一个瓶颈。
第 2 轮:专用高性能数据库
下一轮迭代从数据模式本身着手。equipe 将数据模型进行了简化和去规范化,以适应Solana 的实际访问模式,摒弃了已不再物有所值的关系型结构。这种重构为迁移至高性能NoSQL数据库铺平了道路——该数据库专为写入密集型数据摄取和低延迟的点读操作而设计,避免了关系型方法带来的查询规划、行锁定和预写开销。
吞吐量和延迟都得到了显著提升,而且在大规模运行关系型数据库时常出现的运营难题(如自动 vacuum 卡顿、锁竞争激增、WAL 膨胀)也一并消失了。
第 3 版:完全内存内架构
接下来这一步的动机则有所不同。基于磁盘的系统,无论调优得多么完善,都面临着一个难以逾越的瓶颈:每次读取最终都会涉及一种比 RAM 慢几个数量级的存储介质。要突破这一瓶颈,读取路径本身必须摆脱对磁盘的依赖。
当前的架构完全消除了读取路径中的磁盘操作。AccountsDB 的状态存储在内存中,并由专门为Solana 的特定查询模式量身定制的手工构建数据结构进行索引。这里没有查询规划器、没有 B 树遍历、也没有页面缓存未命中:只需直接访问内存中已预先索引的数据即可。
向全内存架构的转变是一个经过深思熟虑的决定,旨在优化一个目标:原始读取性能。
LedgerDB(用于存储每个槽位的区块、交易和元数据的历史记录)缓存遵循相同的设计理念,但采用了不同的存储策略。Solana 的账本体积过大,无法完全加载到内存中,因此采用了分层设计:链尖数据始终保存在内存中,而历史数据则由经过针对该工作负载调优的高性能分布式数据库提供。
Quicknode Solana Cache 是一个独立的二进制文件,无需任何外部依赖即可提供所有链尖方法。只有历史账本查询会调用外部数据库。
AccountsDB(用于存储每个账户的当前状态)缓存完全驻留于内存中,而使其运行迅速的设计选择绝非偶然。四种机制协同工作,消除了读写路径上所有可避免的延迟源。
领先于链条
数据新鲜度是一种性能属性,它在任何读取请求到达之前就已经开始发挥作用。 缓存直接从shred-stream数据源摄取数据,接收账户更新的时间比典型Agave RPC节点(该节点需等待完整数据块生成)早200-400毫秒。这些更新通过低延迟写入路径(采用near 无锁设计的内存写入)传入,因此这一时间优势得以在端到端过程中保持,而非被摄取开销所抵消。
这种综合效果在尖峰时段可量化:在生产流量下,缓存中最新的可观察槽位始终比配置相似的 Agave RPC 节点领先 1-2 个槽位。对于交易系统、MEV、清算引擎和实时仪表盘等需要查询“当前状态如何”的工作负载而言,这正是基于实时状态采取行动与基于已过时的数据采取行动之间的区别。
自定义数据结构
用于支持账户查询、令牌查询和程序账户过滤的索引,是基于基准测试选定的低级数据结构构建的——这并非最通用的选择,而是针对系统实际遇到的访问模式测得速度最快的方案。共享状态在尽可能的情况下采用最少的锁定机制,确保并发读取者互不干扰。
针对硬件进行优化
该系统在硬件利用率方面进行了深度优化:将数据摄取、索引构建和查询处理分配到专用 CPU 核心,以最大限度地减少上下文切换开销;并根据各组件的具体内存分配特征,为每个组件选择相应的内存分配器,从而在高负载下减少内存碎片。
预计算
在可能的情况下,系统会在读取账户状态时预先计算并维护派生视图:供应汇总、排序后的账户列表、经过过滤的程序缓存。这将 CPU 开销从对延迟敏感的读取路径转移到了以吞吐量为导向的写入路径。查询绝不会触发计算,而是直接读取已存在的结果。17 毫秒 getSupply 基准测试中的响应正是该机制在实际中的体现:当查询到达时,聚合结果已经存在。
getProgramAccounts 它执行一项听起来很简单的任务:返回该程序拥有的、符合这些筛选条件的所有账户。难点在于很难对其进行有效的索引。该程序可能是已部署的数千个程序中的任意一个,而每个程序都有自己的账户模式。
这些过滤器通常是 memcmp 在特定字节偏移量处执行操作,这些偏移量只有在已知程序的内部数据布局时才有意义。A memcmp 在 SPL Token 中,偏移量 32 表示一种含义,而在 DEX 程序中则表示完全不同的含义。
没有一种放之四海皆准的索引策略能适用于所有情况。索引 getProgramAccounts 要高效地实现这一点,需要了解每个程序的账户结构。Quicknode 的索引器通过两种协同工作的策略解决了这个问题:
原生索引程序
对于一小部分流量巨大且机制清晰的项目,Quicknode 会随着账户的更新,持续构建和维护专门的原生索引。这些项目占据了Solana 流量的主体:SPL Token(旧版)、SPL Token-2022 以及 Stake 项目。
账户结构已知,客户使用的过滤模式也已知,而Quicknode 设计的索引正是为了直接利用这些具体特性。无论程序管理着多少百万个账户,这些索引都能在不到一毫秒的时间内处理查询,并且它们覆盖了绝大多数的 getProgramAccounts 流量,因为最热门的查询都集中在少数几个程序上。
动态索引程序
对于其他所有情况,例如每个定制的 DeFi 协议、上周部署的每个程序、每个小众用例,索引器都会采用动态策略。 当调用者首次为某个程序请求过滤器组合时,缓存会自动在运行时创建一个新索引。该首次请求将以未优化的方式运行。索引器必须先扫描该程序的账户并构建索引,才能做出响应,因此调用者需要承担预热缓存的一次性成本。即便如此,这种冷启动路径的性能仍优于Agave。
此后每次对同一过滤器的请求,都将从一个实时、增量维护的缓存中获取,该缓存会随着链上账户的变化而保持最新状态。flow 通过与原生索引程序相同的流式处理管道进行更新,因此缓存视图永远不会与实际情况产生偏差。
从那时起,无论有效载荷大小如何,或程序流行程度如何,延迟实际上都保持恒定。一个返回十个账户的过滤器与一个返回一百万个账户的过滤器,其响应速度处于同一数量级,因为相关工作已经完成。响应直接从预先组装好的内存中流式传输,而不是针对每个请求重新计算。
新程序在部署当天即可获得支持。无需进行模式集成,无需手动配置,也无需等待工程师equipe 添加支持。第一个查询新程序的客户端将触发索引生成,此后所有客户端均可从中受益。
原生索引可为绝大多数实际流量提供最佳性能。动态索引器确保没有任何程序或过滤模式不受支持。
p50
节目 | 账户 | Quicknode 缓存预热 TTFB | Agave Direct TTFB | Quicknode 对阵龙舌兰 |
|---|---|---|---|---|
Jupiter v6 | 60 | 0.2 毫秒 | 2.0 毫秒 | 速度提升10倍 |
Marinade Finance | 1K | 2.5 毫秒 |
p99
节目 | 账户 | Quicknode 缓存预热 TTFB | Agave Direct TTFB | Quicknode 对阵龙舌兰 |
|---|---|---|---|---|
Jupiter v6 | 60 | 0.3 毫秒 | 6 毫秒 | 速度提升20倍 |
Marinade Finance | 1K | 4 毫秒 |
getProgramAccounts 这是 AccountsDB 层面上最棘手的问题,因此它拥有专属的优化策略。其他所有操作(点查询、聚合、令牌查询、块和槽元数据)均从同一内存状态中处理,并遵循相同的设计原则:尽可能预先计算、读取路径上无锁,且通过单次查询而非扫描即可完成。
因此,在并发负载下,AccountsDB 的每个方法(而不仅仅是那些主要方法)都能以内存访问的速度运行。没有任何受支持的方法会在流量增加或账户集扩大时,因负载而导致性能下降。
以下数据来自与同一网络上一个现成的Agave验证器的基准测试。性能差异源于架构设计,而非硬件性能更优所致。
对于简单的点查询, getAccountInfo, getBalance,以及 getTokenAccountBalance,Quicknode 的索引器与 Agave 的性能相当,响应时间在 2-4 毫秒之间。这些方法的热点路径是单次索引读取。响应时间主要取决于网络往返时间,而非随账户集规模增长的工作量。在需要聚合、排序视图或验证器未维护的二级索引的方法上,性能表现会有所差异。
方法 | Quicknode 缓存 p50 | 龙舌兰 p50 | Delta |
|---|---|---|---|
getTokenLargestAccounts (USDC) | 4 毫秒 | 97,535 毫秒 | 快了 24,384 倍 |
getTokenLargestAccounts (BONK) | 4 毫秒 | 8,463 毫秒 | 快了 2,116 倍 |
getLargestAccounts |
有三项结果尤为突出:
getTokenLargestAccounts 对于 USDC:Agave 需要近 100 秒。每次请求都会迫使验证器遍历整个 SPL 代币程序,按铸币方进行过滤、排序,并返回排名前 20 的结果,且没有缓存结果,也没有排序索引。Quicknode 的索引器维护着按铸币方排序的集合,该集合会随着余额的变化进行增量更新。
getLargestAccounts 在 Agave 上无法在五分钟内完成。验证器会在每次请求时,从头开始对整个账户集重新计算一个已排序的聚合结果。Quicknode 的索引器将其维护为一个运行中的索引。
getTokenAccountsByDelegate 在 Agave 上不支持此功能。Quicknode 的索引器可在个位数毫秒内完成处理。
延迟数据反映了单个请求的速度。并发情况则有所不同,但同样重要。当单个 Agave getTokenLargestAccounts 该查询耗时97秒,该请求占用了服务槽,并导致其后所有查询都陷入停滞:一个缓慢的请求会引发连锁反应,导致所有并发调用者的延迟骤增。
Quicknode的索引器不存在该问题mode 。每个查询都由预先构建的内存索引处理,因此没有任何单个请求的运行时间会长到影响并发流量。在持续负载下,AccountsDB 缓存每秒可处理数万次请求,同时将 P50 延迟控制在 10 毫秒以内,其吞吐量由硬件容量决定,而非账户集大小或查询复杂度。
并非所有方法的响应时间都低于10毫秒。对原生索引集之外程序的冷请求,在首次调用时需承担11至40秒的初始化成本,期间订阅关系建立并填充过滤后的缓存。此后对同一过滤器的每次请求均从温缓存中获取数据。
大规模的质押计划查询体现了中端场景。Marinade的8.1万个质押账户在Quicknode 的索引器上约1秒即可完成查询,而在Agave上则需6.7秒。索引查找速度依然很快,但在如此大的数据量下,组装和序列化响应确实需要消耗大量的CPU资源。
在 Raydium V4 这种规模下(约 70.5 万个账户,约 869 MB 的 JSON 数据),响应遇到了另一种瓶颈:首次字节传输时间为 1.5 秒,而完整传输所需时间更长,这受限于通过网络传输 869 MB 数据的物理限制。
各方面性能提升都十分显著。每种方法的运行速度都比原生 Agave 验证器快 10 到 1000 倍。在单方法负载测试中,单个实例可持续处理 350,000 getTransaction 每秒请求数,延迟低于 40 毫秒,且 120,000 getAccountInfo 每秒请求数,P50值低于20毫秒。
AccountsDB 和 LedgerDB 缓存合计覆盖了约 95% 的生产环境 RPC 流量,以及约 90% 的Solana JSON-RPC 规范。
今天所呈现的成果是两年迭代的结晶。这并非最终形态。Quicknode 对Solana 读取层的投入是持续不断的。该架构将不断提速,方法覆盖范围将持续扩大,而弥合开发者需求与基础设施交付能力之间差距的工作也不会停止。
Solana读写工作负载本质上是截然不同的问题。它们理应采用截然不同的架构。Quicknode 耗时两年从零开始构建了Solana 生态系统真正需要的读取层,且未在性能上做出任何妥协。对这一基础设施的持续改进并非可选事项,而是Quicknode Solana endpoint 早已提供的服务。
在Quicknode 上创建一个Solana endpoint ,并开始在专为该工作负载量身打造且持续优化的读取层上进行构建。
Quicknode 成立于 2017 年,致力于为开发者和企业部署机构级区块链基础设施。凭借 99.99% 的运行时间和对 76 条以上区块链的支持,各团队能够毫无妥协地构建和扩展链上应用程序。
将最新的工程见解、产品更新和 Web3 资讯直接发送至您的收件箱。
12 毫秒
速度提升4.8倍 |
张量 | 30K | 0.7 毫秒 | 213 毫秒 | ~304倍快 |
逆戟鲸漩涡 | 91K | ~2.0 毫秒 | 3,515 毫秒 | 约1,760倍快 |
Phoenix DEX | 109K | ~2.6 毫秒 | 783 毫秒 | ~301倍快 |
Drift Protocol | 273K | ~6.4 毫秒 | 3,469 毫秒 | ~542倍快 |
SPL 名称服务 | 341K | ~8.7 毫秒 | 6,764 毫秒 | ~777倍快 |
Pump.fun | 507K | ~15 毫秒 | 27,958 毫秒 | ~1,864倍更快 |
Raydium V4 | 705K | ~18.5 毫秒 | 10,224 毫秒 | ~553倍快 |
15 毫秒
速度提升3.8倍 |
张量 | 30K | 1.2 毫秒 | 234 毫秒 | 约195倍快 |
逆戟鲸漩涡 | 91K | ~2.3 毫秒 | 3,668 毫秒 | 速度提升约1,595倍 |
Phoenix DEX | 109K | ~2.8 毫秒 | 845 毫秒 | ~302倍快 |
Drift Protocol | 273K | ~7.0 毫秒 | 3,720 毫秒 | ~531倍更快 |
SPL 名称服务 | 341K | ~8.9 毫秒 | 7,571 毫秒 | ~851倍快 |
Pump.fun | 507K | ~16 毫秒 | 29,718 毫秒 | 速度提升约1,857倍 |
Raydium V4 | 705K | ~20 毫秒 | 11,120 毫秒 | ~556倍快 |
7 毫秒
>300,000 毫秒(超时) |
>快42,857倍 |
getSupply | 17 毫秒 | 6,231 毫秒 | 快367倍 |
getTokenAccountsByDelegate | 4 毫秒 | 不支持(错误) | Agave 无法提供此服务 |
getProgramAccounts:所有者持有的 Token-2022 | 8 毫秒 | >15,000 毫秒 | 速度提升约1,875倍 |
getProgramAccounts:Orca Whirlpool(9.1万个账户)(温和) | 167 毫秒 | 3,515 毫秒 | 快21倍 |
getProgramAccounts:Pump.fun(50.7万个账户)(预热中) | 795 毫秒 | 27,958 毫秒 | 快35倍 |