[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user