Polling asks a Solana RPC node a question on your schedule; a WebSocket subscription has the node tell you when something happens on the chain's schedule. Polling misses whatever occurred between two calls. Streaming misses whatever occurred while you were not connected. Neither is faster in a way that matters until you decide which of those two blind spots you can live with.
The practical answer for most live volume work is both, wired together deliberately: a subscription for the panel that has to feel current, and HTTP calls for the number that gets recorded. This note explains why that pairing keeps appearing, and what each half costs when you run it alone.
Pull and push are not two speeds
A poll is a request with coordinates. You asked getBlock for a specific slot, or getSignaturesForAddress for a specific account with a specific cursor, and the response is scoped to exactly that. If the call fails you know it failed. If you want yesterday, you ask for yesterday. The transport carries no state, which is precisely why it can answer questions about the past.
A subscription is a standing interest. You told the node which events you care about, and it sends them until something breaks the arrangement. There are no coordinates in a notification beyond what the payload happens to contain, no acknowledgement travelling back, and no memory on either side of what has already been delivered. The transport is stateful in the connection and stateless in the data.
That structural difference produces the two failure modes. A poller's blind spot is bounded and known: it is exactly one interval wide, and you chose the interval. A subscriber's blind spot is unbounded and unknown: it is however long the connection was broken, and nothing in the protocol reports it. The first is a design parameter. The second is an incident you have to detect yourself.
What polling really costs
The cost of polling is request rate, and request rate grows with the product of how many things you watch and how often you look. The arithmetic below is illustrative, using round numbers to show the shape of the growth rather than to describe any real workload.
Suppose you watch 40 pools and call getSignaturesForAddress once per pool. At a five second interval that is 40 divided by 5, or 8 requests per second, which is 480 per minute. Tighten the interval to one second and the same watch list costs 40 requests per second, or 2,400 per minute. Add 60 more pools at that interval and you are at 100 requests per second before you have fetched a single transaction body.
Then add the follow-up. Every interesting signature needs a getTransaction call to learn what moved, so on a busy launch the request count is driven by market activity rather than by your schedule, which defeats the main advantage of polling. Systems designed around a fixed poll cost are frequently surprised at exactly the moment the data becomes valuable.
The interval is a coverage statement, not a preference
Choosing a five second poll is choosing to say nothing about the interior of any five second window. That is fine for a running total that gets reconciled later, and it is not fine for anything claiming to show individual trades as they happen. Write the interval into the page that displays the number, so the reader knows what resolution they are looking at.
What a subscription really costs
The obvious cost is that you now run a connection. Sockets need heartbeats, reconnect logic, resubscription after reconnect, and a supervisor that notices when the process is technically alive but functionally deaf. None of that is difficult, and all of it is the part people skip.
The less obvious cost is backpressure. A poller naturally rate-limits itself because it only asks when it is ready. A subscriber receives at whatever rate the chain produces, and during a busy period that rate can exceed what your consumer can decode. What happens next depends on your client library: an unbounded queue that grows until the process dies, a bounded queue that drops events, or a slow read that causes the server to disconnect you. All three look like a data problem and are actually a capacity problem.
The third cost is the one that produces wrong numbers rather than outages. A subscription confirms with an id, and after a reconnect that confirmation must be re-established. An open socket with no live subscriptions is the worst state a pipeline can be in, because every health check passes and no data arrives, and the panel reads as a quiet market.
Side by side
| Property | HTTP polling | WebSocket subscription |
|---|---|---|
| Who decides timing | You do, on a fixed schedule | The chain does, as events occur |
| Blind spot | One interval, known in advance | Any disconnection, unknown until detected |
| Can request history | Yes, within the node's retention | No, live events only |
| Cost driver | Requests per second | Events per second plus follow-up fetches |
| Behaviour under load | Cost rises, coverage unchanged | Coverage at risk if the consumer falls behind |
| Failure visibility | Errors are explicit per call | Silence, which is ambiguous by construction |
| Ordering | You control it by asking in order | Delivery order, which you should not assume |
| Natural role | Reconciliation, backfill, audit | Live panels, triggers, alerting |
Read the last row first. Once you accept that polling is a reconciliation tool and subscriptions are a triggering tool, most of the arguments about which is better dissolve, because the two are answering different questions about the same chain.
A reconnect procedure that works
A reconnect is not a connection problem, it is a data problem wearing a connection problem's clothes. The socket comes back in seconds; the hole it left stays forever unless you fill it. The sequence below is the one worth implementing before your first production incident rather than after it.
- Record the last processed slot on every write. Not on a timer, and not in memory only. The marker has to survive the crash that created the gap, or your recovery starts from a lie.
- Detect the drop with a heartbeat you control. Subscribe to slots as a liveness channel and treat a missing update within your expected interval as a dead connection, regardless of what the socket layer reports.
- Reconnect with exponential backoff and jitter. Without jitter, every consumer of a provider that just restarted reconnects in lockstep and creates a second outage on top of the first.
- Resubscribe and wait for confirmation ids. Do not mark the pipeline healthy until every subscription has been acknowledged. An open, unsubscribed socket is silent in exactly the way a quiet market is.
- Backfill the range over HTTP. Fetch from your stored slot to the first slot the live stream delivered, and run those blocks through the same decoder so the results are identical in shape.
- Verify the seam and alert on a mismatch. The last backfilled slot and the first streamed slot must be adjacent in your store. If they are not, you have a second gap and you would rather know now.
Steps five and six are the ones that get dropped under time pressure, and they are the only two that actually protect the number. The full recovery design, including how to tell a real gap from a skipped slot, is in data gaps and backfill.
Rate limits bite the pull side first
Every RPC endpoint enforces limits, and the limits are the real constraint on a polling design long before bandwidth is. The public Solana endpoint publishes per-IP limits and states plainly that it is not intended for production applications; the current values are on the official RPC documentation and are worth reading there rather than copying from an article, because they change.
Commercial providers price differently from each other, sometimes per request, sometimes on a weighted credit system where an expensive method such as a full block fetch counts for far more than a cheap one. That weighting matters more than the headline number: a plan that looks generous in requests can be consumed quickly by a block-fetching poller, while a subscription-heavy design uses a different budget entirely.
The behaviour to design for is what happens when you exceed the limit. A rejected request that your code retries immediately turns a soft limit into a hard outage. Respect backoff signals, cap concurrency at the client, and treat throttling as an expected condition rather than an error, because at peak market activity it is the normal state rather than the exception.
The hybrid nearly everyone ends up with
The pattern that survives contact with production has three parts. A subscription supplies immediacy. A scheduled HTTP job supplies completeness. A single deduplication key makes it safe for both to write to the same store.
- Live path. A log or program subscription triggers on activity for the addresses you care about, and the trigger drives a fetch of the full transaction.
- Truth path. A job walks confirmed blocks or signature cursors on a fixed cadence, a few slots behind the head, and writes the same events with the same key.
- Idempotent store. Every event is keyed by transaction signature plus instruction index, so a row written twice is a row written once.
- Reconciliation. A periodic comparison of the two paths tells you the health of the stream, because a growing divergence is the earliest signal that the live path is missing things.
That last bullet is the quiet benefit. Running both paths does not only protect the data, it measures the stream. Without a second opinion you have no way to know whether your subscription is delivering everything, and the failure is invisible by design. A serious pipeline, whether it belongs to an analytics desk or to a Solana trading volume bot reporting on its own activity, needs some form of second opinion before any of its totals mean anything.
Three profiles and the right answer for each
A single token panel
Watching one token across a handful of pools is the case where a subscription is clearly correct. The event rate is manageable, the watch list is small enough to resubscribe cheaply, and the value of the product is immediacy. Add a slow reconciliation job that walks the same pools every minute, and you have covered the gap risk with almost no extra work.
A broad market scanner
Watching everything is the case where per-address subscriptions stop scaling and a stream with a broad filter, or a Geyser-based gRPC product, becomes the right shape. Here the bottleneck moves to your decoder, and the design question changes from how to receive events to how to drop the ones you do not need before they cost you anything.
A reporting pipeline
If the output is a figure somebody will quote, immediacy is worth very little and completeness is worth everything. Poll confirmed blocks a few slots behind the head, reconcile against finalized before publishing, and skip the socket entirely. Adding a stream to this profile adds failure modes without improving the product.
Pitfalls that look like market conditions
Each of the following produces a screen that looks like a calm market. That is what makes them dangerous: the system does not appear broken, it appears to be reporting nothing happening.
- Open socket, no subscriptions. The reconnect succeeded and the resubscribe silently did not. Health checks pass.
- Idle timeout at an intermediary. A proxy or load balancer closes a connection that has been quiet, and your client never notices because it was not sending anything either.
- Consumer lag. Events arrive faster than you decode them and the queue is being dropped or the server is disconnecting you as a slow reader.
- Filter drift. You subscribed to a pool address that is no longer the active pool for the pair, so the filter is correct and matches nothing.
- Commitment mismatch. You are consuming at finalized while comparing against a chart running at confirmed, and concluding your feed is broken when it is only stricter.
- Silent throttling. Your polling job is being rate limited and your error handling treats a throttle response as an empty result.
The single control that catches most of these is a liveness signal independent of the data you care about. Subscribe to slots, expect an update on a known cadence, and alert when the cadence breaks. It costs almost nothing and it converts the ambiguous silence into an explicit fault.
Decision checklist
- Write down your acceptable blind spot in seconds. If it is smaller than your poll interval, you need a subscription.
- Write down whether a missed event is acceptable. If it is not, you need an HTTP reconciliation path regardless of what else you build.
- Estimate requests per second at peak, not at rest, and check that number against your provider's weighting rather than its headline limit.
- Decide your deduplication key before you write ingestion code, so both paths can share a store safely.
- Implement the slot heartbeat first. It is the cheapest component and it is what makes every other failure legible.
- Test a forced disconnection in staging and confirm the backfill actually closes the seam, rather than assuming it does.
- Record which commitment level each path consumes, and never compare two figures produced at different levels.
If the subscription side is where you are heading, the next note takes the most common subscription apart in detail: what log subscriptions deliver, what they truncate, and why a log line is a hint rather than a ledger entry. If you are heading towards a managed feed instead, webhooks and push data covers the delivery guarantees you inherit.
Questions this desk keeps getting
Is a WebSocket subscription always faster than polling?
For an event you are already subscribed to, yes, because you are not waiting for your next scheduled request. But the comparison is not really about speed. A poll has a bounded, predictable cost and a known coverage window; a subscription has lower delay and no coverage guarantee at all. If you replace a poll with a subscription and change nothing else, you have traded a known lag for an unknown gap.
How often should I poll a Solana RPC node?
Slowly enough that you stay inside your provider limits under peak activity, and fast enough that your window is acceptable. The target slot duration on Solana is 400 milliseconds, so any interval above that means you are reconstructing several slots per cycle rather than watching them. Most read-only dashboards land between two and ten seconds; anything tighter usually indicates a subscription was the right tool.
What happens to events while my WebSocket is disconnected?
They are lost from your point of view. Solana PubSub has no acknowledgement, no cursor and no replay buffer, so the node does not know or care what you missed. This is why every serious streaming consumer keeps a slot watermark and an HTTP backfill path, and why a reconnect that does not close the gap is a silent data-loss bug rather than a recovery.
Can I just use both at the same time?
That is what most working systems do, and it is the sensible answer rather than a compromise. The subscription drives anything that has to feel live, and scheduled HTTP calls decide what is finally counted. The one rule is that both paths must write through the same deduplication key, otherwise the belt-and-braces design becomes a double-counting design.
Does a subscription reduce my request usage?
It changes the shape of the usage rather than removing it. You stop paying per poll and start paying per delivered notification plus whatever follow-up calls you make to fetch full transactions. On a quiet address that is a large saving. On a busy one during a launch it can be more expensive than polling, because activity, not your schedule, now sets the rate.
Why does my stream go quiet without an error?
Usually one of four reasons: an intermediary silently dropped an idle connection, the subscription was never confirmed after a reconnect, the filter no longer matches anything because the address you watch changed role, or the market really is quiet. Only a heartbeat distinguishes them. Without one, a broken pipeline and a calm market produce the same screen.
Should I subscribe per address or subscribe broadly and filter locally?
It depends on how many addresses you have and what the endpoint allows. Per-address subscriptions are precise and easy to reason about but multiply connections and subscription counts. Broad subscriptions with local filtering move the cost to your own bandwidth and CPU, and are the only workable pattern once the watch list is large enough that per-address bookkeeping becomes its own failure source.