Solana RPC latency: what we broke, what we fixed, and how many milliseconds our gateway adds

For six days a routing rule sent some recent-block reads to a slow node. This is the postmortem, the fix, the before/after numbers per network, and the phase histogram we now use to show how much time our own code adds to each request.

Endpoints and plans for the networks in this post: Solana RPC endpoint · Robinhood Chain RPC endpoint · Base RPC endpoint · BNB Smart Chain RPC endpoint.

Every RPC provider says its endpoint is quick. It is less common to say how much time your own software adds to a request, or to publish the week it went wrong. This post does both. From 2026-10-22 to 2026-10-28 a part of our Solana RPC traffic was slower than it should have been, because of a routing mistake we made. We found it, fixed it, and used the occasion to measure, phase by phase, where the milliseconds in a request actually go.

All numbers below are our own production measurements unless they are marked as replay. Each one says what it is (p50 or p95), what it covers and over which window. They are not promises for every request, region or minute.

The regression: recent getBlock on a slow node

Recent-block getBlock reads are among the heaviest calls on Solana: a full block can be several megabytes of JSON. We serve them from a node cohort, and inside it from the nodes tagged for history reads. Those nodes answer a recent block in roughly 100 ms.

One node in the cohort was much slower for this method, taking 0.6–3.2 s per block. Its history tag had kept it in a defined role. Once that tag was removed, a fallback rule of ours — "if no history node fits, use the light pool" — quietly put the slow node back into rotation for exactly the calls it was worst at.

Nothing failed. Every request returned a correct block. That is why it took us six days: error rates, availability and health checks were all green. What moved was the tail. On the affected traffic, getBlock p95 rose from about 356 ms on 2026-10-21 to about 3.5 s on 2026-10-27/28. If your indexer or bot read recent blocks during that week, some of those reads were slow because of us, not because of Solana. We are sorry about that.

How we fixed it on 2026-10-28

Two changes went out on 2026-10-28:

  • Strict history routing. Block and transaction reads now go only to nodes tagged for history. If none fits, the request goes to our history tier instead of falling back to a general pool.
  • A slow-host guard. A host that takes 1 s or more to answer a block or transaction read is excluded for that method for 30 minutes. One slow node can no longer hold the tail of a method hostage while it looks healthy on every other check.

On the affected traffic, getBlock p95 went from 6.3 s to 172 ms, comparing the 20-minute windows before and after the release. The 6.3 s is the p95 of the 20-minute window just before the fix, which was worse than the daily figure. Since then, p95 has held at about 180 ms.

The second finding: heavy calls leaving the Frankfurt edge

While checking the fix we found a related problem on 2026-10-29. Our Frankfurt edge sends heavy calls to the EU region when it is busy. A conservative egress limit kept its load indicator out of green almost all the time, so heavy calls such as getBlock and getTokenAccountsByOwner were sent on to EU far too often. There they hit slow history nodes.

We retuned the limit and made the history tags automatic. The generator that builds the node list now measures each node and tags it for history only if its full getBlock is within 2× of the fast history tier. A tag can no longer be correct on paper and wrong in practice.

In our probe, a recent getBlock through the Frankfurt edge went from about 1.9 s to about 48 ms. On the EU region, getBlock p50 went from 849 ms to about 76–104 ms, measured on 2026-10-29 before and after the change.

How we measure the milliseconds we add

The hard question behind "is this RPC slow?" is whose time it is. A provider can be quick at its own work and still pick a slow node, or the reverse. So we added a per-request phase histogram. Every request is split into four phases:

PhaseStartsEndsWhose time
Upstream TTFBwe send the request to the nodefirst byte of the answer arrivesthe node, and the path to it
Upstream bodyfirst byte arriveslast byte arrivesthe node and response size
Our processingupstream bytes arrivebytes are handed to the client connectionours
Client writewe start writingthe client has the responsethe network to you

The phase that belongs to us is "our processing". Here is what it showed after the 10-28 release:

  • Solana HTTP on the Frankfurt edge: about 0.3 ms. A replay of a 2.6 MB block measured +0.1–0.3 ms of our time.
  • EVM networks: about 1–5 ms.
  • EU region, all networks: at or below 3.6 ms for the large majority of methods.

We give these as ranges rather than one percentile, because at this size the spread between methods matters more than any single figure. The point they make is plain: the seconds in the regression were not our CPU. They were node choice.

Two details keep our processing small. First, we stream: nothing waits for the whole upstream body before sending it on. Second, compressed upstream bodies now pass straight through for large history responses. In a benchmark, CPU per request on a large getTransactionsForAddress response fell from 9.75 ms to 0.49 ms. Response sanitizing on EVM chains is now 3–4× cheaper in CPU. We verified that its output is byte-identical to the old one on 174 real responses.

Latency-aware upstream selection: before and after

The phase histogram made one thing obvious: most of the time in a request is upstream TTFB, so the biggest lever is which node gets the request. On 2026-10-29 (02:34Z on the North America region in Montreal, 02:54Z on the EU region in Strasbourg) we released latency-aware upstream selection. Every network now prefers the healthy node with the best measured response time for that method, instead of rotating evenly. A BSC batch now stays on one node instead of being split across several, because a split batch is only as quick as its slowest part.

Production p50 on the EU region, 10-minute window before and after the release:

Network / methodp50 beforep50 after
Robinhood Chain (all RPC)158 ms33 ms
Base eth_getTransactionReceipt106 ms35 ms
Solana getSignaturesForAddress383 ms38 ms
Solana getTokenAccountsByOwner236 ms68 ms

Replay benchmarks of the same change, not production numbers: a BSC batch of 10 went from 89 to 20 ms, TRON getnowblock from 188 to 30 ms, and Ethereum on the Montreal region from 160 to 0.5 ms, because the local node is now preferred there.

Monitoring so this pages us in minutes

The regression lasted six days because nothing we watched looked at the tail per region. That is fixed. We now alert when Solana p95 in any region stays above 1.5 s for 15 minutes, and on the health of our history tier, so a repeat of last week pages us within minutes instead of going unnoticed for days. Public uptime per network is on the status page.

When this is the wrong approach

Shaving milliseconds off our side only matters if our side is where you lose time. Often it is not:

  • Your client is far from every region. If your bot runs on another continent, the client-write phase dwarfs anything above. Run it near our Frankfurt edge or one of our regions, or use a stream.
  • You poll for new blocks. Polling getBlock in a loop adds your polling interval to every block. For fresh data, a stream that pushes blocks or transactions as they appear is the right tool: Yellowstone gRPC for account and transaction updates, or the Shred stream when you need transactions before they execute. Our streaming overview compares the channels, and the racing block feeds post shows the same "earliest copy wins" idea applied to a feed.
  • You read finalized history. If you wait for finality anyway, a few hundred milliseconds of read time rarely change the outcome; batch size and throughput limits matter more. Those are per plan on pricing.

What comes next

The same "our-side milliseconds" work is under way for streams: shreds, gRPC and WebSocket. We will publish those results in a follow-up post, with the same rules: labelled percentiles, stated windows, replay marked as replay.

FAQ

How much latency does Triport add to a Solana RPC request?

Our processing, from the upstream response arriving to the bytes reaching your connection, is about 0.3 ms for Solana HTTP on the Frankfurt edge, measured with the phase histogram after the 2026-10-28 release. Most of the time in a request is the node's own answer time.

Why was Solana getBlock slow between October 22 and 28?

A routing fallback sent some recent-block reads to a node that took 0.6–3.2 s per block. Strict history routing and a slow-host guard fixed it on 2026-10-28.

What is latency-aware upstream selection?

Each network prefers the healthy node with the best measured response time for the method, instead of rotating across nodes evenly. It went live on 2026-10-29.

Where can I check current status?

On the status page, which shows uptime per network.

Sources

  • Triport production metrics, Frankfurt edge and the EU and North America regions, 2026-10-21 to 2026-10-29: per-method getBlock p95 before, during and after the regression; 20-minute windows around the 2026-10-28 fix; 10-minute p50 windows around the 2026-10-29 release.
  • Triport per-request phase histogram (upstream TTFB, upstream body, our processing, client write), Frankfurt edge and EU region, after the 2026-10-28 release.
  • Triport replay benchmarks, 2026-10-29: BSC batch of 10, TRON getnowblock, Ethereum on the Montreal region, a 2.6 MB Solana block, and a large getTransactionsForAddress response.
  • Solana getBlock reference, read 2026-10-29: parameters, response size and history depth.
  • Triport streaming overview, read 2026-10-29: the real-time channels and when to use them instead of polling.