gRPC Streaming Patterns

Streaming lets one call carry many messages over time. The four communication types show the shapes. Patterns show how real teams use those shapes to solve everyday problems such as live feeds, uploads, and chat.

Pattern 1: Live Feed (Server Streaming)

The client subscribes once. The server pushes updates as they happen. Stock prices, match scores, and order tracking use this pattern.

Client ──Watch(AAPL)──▶ Server
Client ◀──price 190.1──  Server
Client ◀──price 190.3──  Server
Client ◀──price 190.2──  Server
func (s *server) WatchPrices(req *pb.WatchRequest, stream pb.PriceService_WatchPricesServer) error {
  ticker := time.NewTicker(time.Second)
  defer ticker.Stop()
  for {
    select {
    case <-stream.Context().Done():
      return stream.Context().Err()
    case <-ticker.C:
      if err := stream.Send(&pb.Price{Symbol: req.Symbol, Value: currentPrice(req.Symbol)}); err != nil {
        return err
      }
    }
  }
}

Pattern 2: Chunked Upload (Client Streaming)

Large files do not fit in one message, because receivers cap message size at 4 MB by default. The client slices the file into chunks and streams them. The server replies once at the end.

Client ──chunk 1──▶
Client ──chunk 2──▶   Server assembles the file
Client ──chunk 3──▶
Client ◀──"Upload complete, 3 chunks"── Server

Choose chunks between 32 KB and 1 MB. Smaller chunks add overhead. Larger chunks strain memory.

Pattern 3: Chat (Bidirectional Streaming)

Both sides send whenever they want. Each direction works independently, like two people on a phone call who can speak at the same time.

Alice ──"Hi"──────▶ Server ──"Hi"──────▶ Bob
Alice ◀──"Hello"── Server ◀──"Hello"──── Bob

Pattern 4: Heartbeat

Long streams can look alive while the network silently dies. Send a small heartbeat message every few seconds. The receiver treats a long silence as a broken connection and reconnects.

Pattern 5: Resume with a Cursor

Streams break. Mobile users walk into elevators. Include a cursor or sequence number in each message. The client remembers the last cursor and sends it when it reconnects, so the server resumes where the stream stopped.

message WatchRequest {
  string topic = 1;
  int64  last_seen_sequence = 2;
}

message Event {
  int64  sequence = 1;
  bytes  payload  = 2;
}

Flow Control and Backpressure

A fast sender can overwhelm a slow receiver. HTTP/2 solves this with flow control windows. The receiver grants a limited amount of buffer space, and the sender pauses when the window fills. The send call blocks, which slows the producer naturally.

Fast Server ══▶ [buffer filling] ══▶ Slow Client
                    │
        window full → Server Send() waits
        client reads → window opens → Server continues

Respect this mechanism. Never pile unlimited messages into memory queues to dodge blocking.

Keepalive

Keepalive pings test idle connections. Configure sensible intervals so that proxies and firewalls do not close quiet streams.

Choosing a Pattern

NeedPattern
Subscribe to updatesServer streaming with a cursor
Upload a large fileClient streaming in chunks
Interactive conversationBidirectional streaming
Detect dead connectionsHeartbeat or keepalive
Survive disconnectsCursor based resume

Ending a Stream Cleanly

Every stream needs a clear ending. A server finishes a stream by returning from the handler. A client signals the end of its messages by closing the send side. Both sides check the final status code. A missing end signal leaves connections open and wastes memory on both machines.

Client sends:  msg, msg, msg, CloseSend
Server reads:  msg, msg, msg, EOF
Server returns: final reply + status OK

Message Size Limits

Receivers reject any single message above the configured limit, which defaults to 4 MB. Raise the limit only when the design demands it. Splitting large payloads into many small messages keeps memory use predictable and plays well with flow control.

Summary

Streaming patterns turn the four call types into practical designs: feeds, uploads, chat, heartbeats, and resumable subscriptions. Watch the context, honor flow control, and design every stream to survive disconnects.