Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
30 Jun 2025 · 5 min read ·Article 22 / 110
Go

22 Implementing Server-side Streaming RPC

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

gRPC has become one of the most widely used communication protocols in the modern microservices ecosystem. Its role as a replacement for REST in many systems stems from the flexibility of the RPC (Remote Procedure Call) model, its speed, and its superior data streaming capabilities. One of the more powerful streaming models is Server-side Streaming RPC.

In this article, I’ll walk through the concept of Server-side Streaming RPC, the steps to implement it, and a case study using Go (golang). To make things easier to follow, there will be code examples, a request-response flow simulation, and a simple comparison table against the other RPC models.


What Is Server-side Streaming RPC?

In the Server-side Streaming RPC scenario, the client sends a single request to the server and receives a stream of responses incrementally from the server until it completes. In short, the server can send more than one message back to the client after receiving the initial request—similar to a “push notification” over HTTP/2.

Flow Diagram

Let’s visualize the flow using a mermaid diagram:

MERMAID
sequenceDiagram
    participant Client
    participant Server

    Client->>Server: Send Request (permintaan tunggal)
    loop Selama masih ada data
        Server-->>Client: Stream Response (berulang kali)
    end
    Server-->>Client: (Stream selesai / EOF)

When Should You Use Server-side Streaming RPC?

Some typical use cases include:

  • Sending data in large batches (reports, files, logs)
  • Streaming database query results incrementally
  • Push notifications about the status or updates of background processes
  • Event-driven communication (e.g., build pipeline progress)

The following table compares the RPC models in gRPC:

RPC TypeRequestResponseExample
Unary RPCOneOneGetUser() -> User
Server-side StreamingOneStream (many)ListLogs() -> LogEntry*
Client-side StreamingStream (many)OneUploadPhotos() -> UploadStatus
Bidirectional StreamingStreamStream (many, two-way)Chat() -> Message*/Message*


Case Study: Streaming Logs with gRPC in Go

We’ll build a simple gRPC service called LogService, where the client can request logs and the server responds with a stream of log data incrementally.

1. Defining the Protobuf

File: log.proto

protobuf
 1syntax = "proto3";
 2
 3package log;
 4
 5service LogService {
 6    rpc FetchLogs(LogRequest) returns (stream LogEntry) {}
 7}
 8
 9message LogRequest {
10    string filter = 1;
11}
12
13message LogEntry {
14    int32 id = 1;
15    string timestamp = 2;
16    string message = 3;
17}

2. Server Implementation

File: server.go

go
 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "log"
 7    "net"
 8    "time"
 9
10    "google.golang.org/grpc"
11
12    pb "path/to/your/generated/proto/log"
13)
14
15type server struct {
16    pb.UnimplementedLogServiceServer
17}
18
19func (s *server) FetchLogs(req *pb.LogRequest, stream pb.LogService_FetchLogsServer) error {
20    logs := []pb.LogEntry{
21        {Id: 1, Timestamp: "2024-06-05T10:00:00Z", Message: "Server started"},
22        {Id: 2, Timestamp: "2024-06-05T10:10:00Z", Message: "Process completed"},
23        {Id: 3, Timestamp: "2024-06-05T10:15:00Z", Message: "User login"},
24    }
25
26    for _, entry := range logs {
27        // Simulate logs being sent incrementally
28        if err := stream.Send(&entry); err != nil {
29            return err
30        }
31        time.Sleep(1 * time.Second) // Simulate streaming delay
32    }
33    return nil
34}
35
36func main() {
37    lis, err := net.Listen("tcp", ":50051")
38    if err != nil {
39        log.Fatalf("failed to listen: %v", err)
40    }
41    s := grpc.NewServer()
42    pb.RegisterLogServiceServer(s, &server{})
43    log.Println("gRPC server started at :50051")
44    if err := s.Serve(lis); err != nil {
45        log.Fatalf("failed to serve: %v", err)
46    }
47}

3. Client Implementation

File: client.go

go
 1package main
 2
 3import (
 4    "context"
 5    "log"
 6    "time"
 7
 8    "google.golang.org/grpc"
 9
10    pb "path/to/your/generated/proto/log"
11)
12
13func main() {
14    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
15    if err != nil {
16        log.Fatalf("did not connect: %v", err)
17    }
18    defer conn.Close()
19    c := pb.NewLogServiceClient(conn)
20
21    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
22    defer cancel()
23    stream, err := c.FetchLogs(ctx, &pb.LogRequest{Filter: "all"})
24    if err != nil {
25        log.Fatalf("Error on FetchLogs: %v", err)
26    }
27
28    for {
29        entry, err := stream.Recv()
30        if err != nil {
31            log.Printf("End of stream or error: %v", err)
32            break
33        }
34        log.Printf("LogEntry: [%d] %s - %s", entry.Id, entry.Timestamp, entry.Message)
35    }
36}

4. Simulating the Client-Server Interaction

Let’s illustrate the request and response simulation:

MERMAID
sequenceDiagram
    participant Client
    participant Server

    Client->>Server: FetchLogs({filter:"all"})
    Server-->>Client: LogEntry #1
    Note over Server,Client: (1 detik kemudian)
    Server-->>Client: LogEntry #2
    Note over Server,Client: (1 detik kemudian)
    Server-->>Client: LogEntry #3
    Server-->>Client: [EOF]

Challenges and Best Practices

1. Error Handling:
A stream can break due to the network or application errors. Make sure error propagation is clear on both sides; use the gRPC status codes (codes.Canceled, codes.Unavailable, etc.).

2. Deadline/Timeout:
The client must set a deadline so the stream doesn’t hang indefinitely.

3. Scalability:
Streaming is well suited for large data volumes, but keep an eye on buffer limits, the goroutine pool, and flow-control settings.

4. Versioning:
Maintain backward compatibility in your stream messages, especially when clients and services span multiple versions.


Conclusion

Server-side Streaming RPC is a powerful gRPC feature that’s ideal for sending large amounts of data or when the response can’t be delivered all at once (for example, progress reports, large query results, update notifications, and so on).

By understanding the server-side streaming implementation pattern, engineers can:

  • Save resources (since the data isn’t held as one large batch)
  • Improve client responsiveness (data can be processed as soon as part of it arrives)
  • Make more optimal use of the HTTP/2 communication channel

If you want to be better prepared for real-time or big-data requirements in your microservices backend, server-side streaming is a must-have skill to master.


References:

If you found this article helpful, don’t forget to share it and give it a star!
Let’s discuss: what kind of streaming RPC implementations have you built—and what were the challenges? Share them in the comments! 🚀

Related Articles

💬 Comments