Solana infrastructure, included with every server. LEARN MORE>

OpenInfra.shopeninfra.sh
Engineering9 min read

When a Solana App Outgrows WebSockets

OpenInfra Team·August 6, 2026
When a Solana App Outgrows WebSockets

WebSockets solve an important problem: they stop an application from repeatedly asking whether something changed.

For many Solana products, that is exactly the right model. A wallet watches a signature. A dashboard follows a few accounts. A service listens for program logs and sends a notification. The application opens a connection, subscribes to the changes it cares about, and responds when an update arrives.

That is a real-time feature. It is not necessarily a data pipeline.

The boundary matters. As the number of accounts, transactions, and downstream decisions grows, an application can remain connected while no longer remaining current. The question changes from “did an update arrive?” to “can this service receive, process, store, and recover every relevant update without falling behind?”

This is the point at which a Solana application may have outgrown WebSockets—not because WebSockets failed, but because the workload changed shape.

WebSockets are the right starting point

WebSockets turn a repeated read into a subscription. Instead of polling an account every few seconds, the application asks to be notified when that account changes. Instead of refreshing program logs on a timer, it receives the next matching log when it is emitted.

The result is simpler than polling. There are fewer unnecessary requests, shorter stale windows, and a clearer connection between a chain event and the feature that reacts to it. For a bounded set of subscriptions, it is also easy for a small team to understand and operate.

Good first workloads

WebSockets fit well when the application is watching a known, manageable set of state changes:

  • A wallet following signatures and selected token accounts.
  • A dashboard displaying changes for a small set of addresses.
  • A support tool listening for logs from a known program.
  • A notification service reacting to activity on specific accounts.
  • A product feature that needs a live update, but not a complete historical feed.

The common feature is bounded responsibility. The application knows what it is watching, each update is inexpensive to handle, and a brief interruption can be repaired without rebuilding a large part of the product’s state.

There is no benefit in replacing this design with a heavier streaming system simply because that system can handle more volume. The protocol should match the work.

Connection health is not data health

The first operational mistake is treating an open WebSocket as proof that everything is fine.

The connection can be alive while the consumer is slow. Messages can be arriving while a queue grows behind a database write. A process can reconnect successfully while still missing the account changes that occurred during the disconnect. A subscription can be too broad, forcing the application to receive and discard work that it never needed.

These are data-health problems, not only connection-health problems.

An application that depends on live updates should measure the time from receiving an event to finishing the work it triggered. It should know whether its backlog is growing, whether reconnects occurred, and whether it can identify the last update it fully applied. A green connection indicator cannot answer those questions.

The first sign is usually lag

Lag is the difference between what the application has processed and what it needs to have processed to be current.

In a dashboard, that may be visibly stale information. In an indexer, it may be thousands of transactions waiting to be decoded and written. In a trading or risk service, it may mean a decision was made from older state than the system intended.

Lag often appears gradually. The application works at average load, then becomes inconsistent during a busy period. Restarting it clears the immediate symptom, but the same queue returns at the next burst. Developers add more subscriptions or more worker processes without knowing whether the incoming data, the processor, or the database is actually the bottleneck.

At that point, pause and identify the work performed for each event. A consumer may need to deserialize a payload, load additional state, apply business logic, write records, notify another service, and update a checkpoint. Multiplying the number of live updates multiplies that entire path.

More subscriptions are not automatically scale

When a product needs more live information, opening more WebSocket subscriptions can feel like the natural extension. Sometimes it is. But it can also turn the system into a collection of independent connections whose overlap, failure handling, ordering, and recovery become difficult to reason about.

More subscriptions can mean more duplicate work. The same transaction may matter to several account watchers. A broad program-log subscription can create a large volume of messages that downstream code must filter after receiving them. Reconnect logic can multiply quickly when every product feature owns its own connection state.

The important question is not “Can we open another subscription?” It is “Are we now consuming a sustained feed of accounts, transactions, or blocks?” If the answer is yes, the workload may need a streaming interface designed around filters and continuous consumption.

When the stream becomes the workload

An indexer is a clear example. It does not need one account update to update a screen. It needs every relevant account, transaction, or block record to build a durable representation of a program’s activity. A market-data service has a similar requirement. So does an analytics pipeline that turns chain events into reports, alerts, or a queryable product database.

These systems need more than delivery. They need controlled ingestion.

Controlled ingestion means the service can state what it subscribes to, how it filters, where it stores the raw input, what it has fully processed, and how it repairs a gap. The stream is no longer a feature attached to an application. It is a core input to the application.

Yellowstone gRPC is built for sustained consumption

Yellowstone gRPC is suited to consumers that need a continuous filtered stream of Solana accounts, transactions, and blocks.

The meaningful difference is not only transport speed. gRPC uses structured binary messages and supports server-side filtering, allowing the application to subscribe to the data it actually needs before it arrives at the consumer. That matters when the input volume is high enough that client-side filtering, message handling, and connection management become part of the bottleneck.

For a production consumer, filtering is an architectural control. It reduces the amount of work that enters queues, passes through parsers, reaches storage, and must be replayed after a recovery. It also makes ownership clear: the consumer can define exactly which records it is responsible for.

Workloads that commonly fit gRPC

  • An indexer following account and transaction activity for a program.
  • A trading system maintaining a live view of markets or positions.
  • An analytics pipeline consuming transaction or block data continuously.
  • A data service providing a derived view of on-chain activity to other applications.
  • A risk system that must inspect a defined class of events as they occur.

Moving to gRPC does not make the downstream system optional. It still needs bounded queues, idempotent writes, durable checkpoints, and a plan for reconnects. It gives that system a more appropriate input when the stream is sustained.

Design the consumer before changing the endpoint

Changing transport without changing the consumer only moves the bottleneck. Before moving a workload, decide what a healthy consumer looks like.

Start with the smallest useful filter. Write down the programs, accounts, transaction types, or block data that are relevant. Decide whether the application needs raw records, derived records, or both. Choose the stable keys that let a record be processed more than once without creating corruption. Then decide which slot or other marker proves that the consumer has fully completed work through a point in the stream.

This is the checkpoint. It should only advance after the associated records have been successfully stored or applied. If a process stops halfway through a batch, the checkpoint gives recovery code a known place to start instead of asking it to infer progress from logs or memory.

Keep RPC in the recovery path

A live stream tells the consumer what is happening now. RPC provides a confirmed reference point.

After a restart or disconnect, the consumer should not assume it is current simply because a connection has resumed. Read the confirmed state or relevant historical range around the last durable checkpoint. Fill the gap, safely process any duplicate records, then continue the live stream.

This is especially important when records trigger real actions. An application that sends an alert twice may be inconvenient. An application that records a trade, calculates a balance, or makes an automated decision from a gap can create a much more serious error.

The useful pairing is simple: use gRPC for continuous live consumption and RPC for verification and recovery.

A practical migration path

There is no need to replace every subscription at once. Start with the part of the system where sustained volume is already visible.

Keep WebSockets for product features that watch a small, user-driven set of accounts. Introduce a gRPC consumer for the central indexer or data pipeline. Let that consumer build durable state and expose a clear internal interface to the rest of the product. Over time, features that were individually subscribing to the chain can read the indexed state or subscribe to a narrower application-level event instead.

This often makes the product easier to operate. The chain-facing ingestion responsibility becomes concentrated in one service built for it, while the rest of the application consumes data in forms that match its own needs.

The practical rule

Use WebSockets when the application needs timely updates for a manageable set of live changes.

Move to Yellowstone gRPC when it needs to continuously consume filtered account, transaction, or block data, and staying current is part of the service’s job. Keep RPC available to verify and recover confirmed state.

The connection type is not a badge of scale. It is a decision about the shape of the workload. A dependable Solana application uses the simplest path that can reliably process the information it is responsible for.

Review the choice as the product changes

The right path today may not be the right path six months from now. A product can begin with a handful of account subscriptions, add a successful real-time feature, and eventually discover that the same information is now needed by analytics, alerts, APIs, and internal operations. That is a change in responsibility, not merely in traffic.

Review the design when a new consumer is added, when a stream becomes a source for business decisions, or when recovery work is no longer simple enough to describe. Keep the original WebSocket features where they remain a good fit. Move only the sustained chain-facing workload into a consumer designed for it.

Get Started

Stop paying for RPC.

Every server includes the full Solana stack. See how the economics work in practice.