Geyser gRPC - Best Practices

This guide covers essential patterns for building robust, production-ready applications with Geyser gRPC. Solana Stream SDK supports Geyser gRPC.

Getting started

  • Start simple: begin with slot subscriptions to get familiar with streaming before adding complex filters.
  • Use filters wisely: subscribe only to what you need to reduce bandwidth/CPU.
rust
SubscribeRequestFilterTransactions {
    vote: Some(false),   // Exclude vote txs
    failed: Some(false), // Exclude failed txs
    ..Default::default()
}

Connection management

  • Automatic reconnection: expect network issues; reconnect with exponential backoff.
  • Ping/pong handling: Yellowstone gRPC servers send ping. Always respond with pong or the server can close the connection (common cause of ~30s disconnects).
rust
if matches!(update.update_oneof, Some(UpdateOneof::Ping(_))) {
    subscribe_tx.send(SubscribeRequest {
        ping: Some(SubscribeRequestPing { id: 1 }),
        ..Default::default()
    }).await?;
}
  • Gap recovery: use from_slot after reconnecting to avoid data loss (duplicates are acceptable).
rust
subscribe_request.from_slot = if tracked_slot > 0 {
    Some(tracked_slot) // Optionally subtract a small buffer to dodge reorgs
} else {
    None
};

Architecture patterns

  • Separate ingress and processing with channels to decouple network IO from business logic and add backpressure.
rust
let (tx, rx) = mpsc::channel::<SubscribeUpdate>(10_000);
// ingress task reads stream and tx.send(...)
// processing task consumes rx and does business logic
  • Use bounded channels: choose capacity based on processing speed and data-loss tolerance (smaller = less memory, more drops; larger = more memory, fewer drops).

Performance and resiliency

  • Monitor processing latency and log when thresholds are exceeded.
  • Batch DB writes; flush on batch size or interval.
  • Use async IO for external calls; offload heavy compute to worker tasks/threads.
  • Reuse subscription requests to reduce allocations.

Error handling

  • Distinguish error types (stream vs processing vs channel).
  • Repair an invalid request before retrying it; use a pause-aware exponential backoff only when a response calls for retry.
  • Log/metric dropped updates to track slow-consumer conditions.

Repair before retry and pause-aware backoff

Keep the first gRPC error before a reconnect loop replaces it. Record its status and message, plus any x-erpc-error-code, x-erpc-error-reason, x-erpc-doc-url, x-erpc-retry-after-seconds, and x-erpc-retry-at metadata that are present. An early connection close can look like a generic transport error, so it may not include every trailer.
Repair an invalid request, rejected filter shape, or IP-registration mismatch before retrying. For an ERPC pause, wait for the returned deadline; it is more specific than a local backoff. Use a bounded exponential backoff for availability failures and other retryable responses. Do not repeatedly retry an unchanged request merely because it returned a non-zero status.

Resource use and processing capacity

Shared protections keep one workload from consuming delivery capacity needed by other ERPC customers. A stream can become slow because of effective bandwidth, CPU, memory, the consumer queue, database writes, or external calls. Sufficient resources must match the workload; a slow receiver does not by itself identify which part is constrained.
Measure the peak data rate at the consumer. Convert MB/s to Mbps with MB/s × 8, then leave headroom above the measured peak. There is no fixed unmeasured minimum. Keep receive work short, hand updates to a bounded queue, and scale or reduce the subscription if processing cannot keep up. A bounded queue limits memory use; it cannot resolve a sustained slow consumer.

Data management

  • Handle duplicates when using from_slot (time-bounded cache or DB constraints).
  • Pick commitment per use case:
    • processed: fastest dashboards, may roll back
    • confirmed: good default for most apps/indexers
    • finalized: when absolute certainty is required

Dynamic subscriptions

Use the bidirectional stream to update subscriptions at runtime (hot-swap filters, expand coverage) without reconnecting.

Production checklist

  • ✅ Auto reconnection with exponential backoff
  • ✅ Gap recovery using from_slot
  • ✅ Ping/pong handling (avoid 30s disconnects)
  • ✅ Separate ingress and processing with bounded channels/backpressure
  • ✅ Error logging and metrics/alerting
  • ✅ Processing latency tracking
  • ✅ Duplicate handling and filter optimization
  • ✅ Batch writes (if persisting)
  • ✅ Graceful shutdown and health checks

Additional resources