---
id: backend-grpc-streaming-deep
title: gRPC Streaming — bidirectional / server / client
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [backend, grpc, streaming, vibe-coding]
tech_stack: { language: "TS / Go", applicable_to: ["Backend"] }
applied_in: []
aliases: [gRPC streaming, bidi, server stream, client stream, Protocol Buffers, Connect-RPC, Buf]
---

# gRPC Streaming Deep

> gRPC 의 큰 차별점. **4 mode: unary, server stream, client stream, bidi**. HTTP/2 + Protobuf.

## 📖 핵심 개념
- HTTP/2 multiplexing.
- Protobuf binary (작은).
- Type-safe (proto → 매 언어).
- Bidi 가 WebSocket 식.

## 💻 코드 패턴

### Proto definition
```protobuf
syntax = 'proto3';

service ChatService {
  rpc Send(Message) returns (Ack);                    // unary
  rpc Subscribe(Filter) returns (stream Message);     // server stream
  rpc Upload(stream Chunk) returns (FileInfo);        // client stream
  rpc Chat(stream Message) returns (stream Message);  // bidi
}
```

### Server (TS, Connect-RPC)
```ts
import { ConnectRouter } from '@connectrpc/connect';
import { ChatService } from './gen/chat_connect';

export default (router: ConnectRouter) =>
  router.service(ChatService, {
    async send(req) {
      return { ack: true };
    },
    
    async *subscribe(req) {
      for (let i = 0; i < 100; i++) {
        yield { text: `msg ${i}` };
        await sleep(1000);
      }
    },
    
    async upload(req) {
      let total = 0;
      for await (const chunk of req) {
        total += chunk.data.length;
      }
      return { size: total };
    },
    
    async *chat(req) {
      for await (const msg of req) {
        yield { text: 'echo: ' + msg.text };
      }
    },
  });
```

### Client (TS)
```ts
import { createPromiseClient } from '@connectrpc/connect';

const client = createPromiseClient(ChatService, transport);

// Server stream
for await (const msg of client.subscribe({ topic: 'news' })) {
  console.log(msg.text);
}

// Client stream
async function* chunks() {
  for (const chunk of file) yield { data: chunk };
}
const result = await client.upload(chunks());
```

### Connect-RPC (modern)
```
gRPC + gRPC-Web + Connect protocol 동시.
- Browser 에서 직접 (no envoy).
- HTTP/1.1 + HTTP/2 둘 다.
- TS-friendly.

→ gRPC 의 modern.
```

### Buf (toolchain)
```yaml
# buf.gen.yaml
version: v2
plugins:
  - remote: buf.build/connectrpc/es
    out: gen
  - remote: buf.build/bufbuild/es
    out: gen
```

```bash
buf generate
buf lint
buf breaking --against '.git#branch=main'
```

→ gRPC 의 npm.

### Bidi streaming
```ts
// Server
async *chat(reqs) {
  for await (const msg of reqs) {
    // 매 client message 받음.
    yield { reply: process(msg) };
  }
}

// Client
async function* messages() {
  yield { text: 'hi' };
  await sleep(1000);
  yield { text: 'how are you?' };
}

for await (const reply of client.chat(messages())) {
  console.log(reply);
}
```

→ Real-time bidirectional. Chat / game.

### Server stream use case
```
- Server-sent updates.
- Live data feed.
- Search results (incremental).
- Log streaming.
```

### Client stream use case
```
- Large upload (chunks).
- Sensor data submission.
- Telemetry batch.
```

### Cancellation
```ts
const ac = new AbortController();
setTimeout(() => ac.abort(), 5000);

for await (const msg of client.subscribe({}, { signal: ac.signal })) {
  // ...
}
```

→ Stream 중단.

### Error handling
```ts
try {
  for await (const msg of stream) {
    // ...
  }
} catch (e) {
  if (e.code === Code.Cancelled) ...;
  if (e.code === Code.DeadlineExceeded) ...;
}
```

### Deadline
```ts
const r = await client.send(req, { timeoutMs: 5000 });
```

### Interceptor (middleware)
```ts
const authInterceptor: Interceptor = (next) => async (req) => {
  req.header.set('Authorization', `Bearer ${token}`);
  return next(req);
};

const transport = createConnectTransport({
  baseUrl: '...',
  interceptors: [authInterceptor],
});
```

### vs REST
```
REST:
- HTTP/1.1.
- JSON (verbose).
- Stateless.
- 큰 ecosystem.

gRPC:
- HTTP/2 multiplexing.
- Protobuf (작은).
- Streaming native.
- Type-safe (codegen).

→ Internal microservice = gRPC.
Public API / web = REST / GraphQL.
```

### vs WebSocket
```
WebSocket: bidi 만, schema 없음.
gRPC bidi: typed schema + RPC.

→ gRPC 가 strict schema. WS 가 free-form.
```

### vs GraphQL Subscription
```
GraphQL Subscription: query 식 + WS.
gRPC streaming: RPC 식 + HTTP/2.

→ Different model. 같은 use case 가 가능.
```

### Performance
```
gRPC 가 REST 보다 7-10x 빠름 (binary + multiplex).
Streaming = 큰 throughput (multiple parallel).
HTTP/2 head-of-line blocking 가 약간 (TCP level).

→ Internal high-performance.
```

### gRPC-Web
```
Browser 가 native gRPC 안 됨.
- gRPC-Web (Envoy 가 transcode).
- 또는 Connect (native).

→ Connect 가 modern (envoy 없음).
```

### Production
```
- Envoy (proxy).
- gRPC LB (HTTP/2 LB).
- Auth interceptor.
- Tracing (OpenTelemetry).
- Monitoring (latency p99, error rate).
```

### Common pitfall
```
- gRPC LB 가 HTTP/2 aware 안 = sticky.
- Stream 의 backpressure 무시.
- Deadline 없음 = hang.
- Compression 가 default off (큰 payload 가 비효율).
- Schema breaking change.
```

## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Internal microservice | gRPC |
| Streaming | gRPC bidi |
| Web client | Connect / gRPC-Web |
| Public API | REST + OpenAPI |
| Real-time chat | gRPC bidi 또는 WS |
| File upload | gRPC client stream |

## ❌ 안티패턴
- **gRPC + REST 둘 다 같은 service**: complexity.
- **No deadline**: hang.
- **gRPC + non-HTTP/2 LB**: sticky.
- **모든 거 unary (streaming 안 사용)**: gRPC 의 가치 ↓.
- **Schema breaking change**: client 깨짐.

## 🤖 LLM 활용 힌트
- Connect-RPC 가 modern (browser-friendly).
- Buf 가 toolchain.
- 4 mode (unary, server, client, bidi).
- Internal microservice 의 default.

## 🔗 관련 문서
- [[Backend_gRPC_Patterns]]
- [[Backend_GraphQL_Federation]]
- [[Backend_WebSocket_Production]]
