[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
---
|
||||
id: backend-nats-jetstream
|
||||
title: NATS JetStream — modern messaging
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [backend, messaging, vibe-coding]
|
||||
tech_stack: { language: "TS / Go", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [NATS, JetStream, pub/sub, KV store, persistent stream, NATS subject]
|
||||
---
|
||||
|
||||
# NATS JetStream
|
||||
|
||||
> Kafka 보다 simple + 빠름. **NATS = pub/sub + request-reply. JetStream = persistent stream + KV + ObjectStore**. Single binary, multi-cluster, edge.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Subject: routing key (`orders.user.123`).
|
||||
- Wildcard: `orders.user.*` (1 token), `orders.>` (rest).
|
||||
- JetStream: persistent.
|
||||
- Core NATS: ephemeral pub/sub.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Setup (single binary)
|
||||
```bash
|
||||
# Download
|
||||
nats-server -js
|
||||
# JetStream enabled.
|
||||
```
|
||||
|
||||
### Connect (TS)
|
||||
```ts
|
||||
import { connect, JSONCodec } from 'nats';
|
||||
|
||||
const nc = await connect({ servers: 'nats://localhost:4222' });
|
||||
const jc = JSONCodec();
|
||||
|
||||
// Pub
|
||||
nc.publish('orders.created', jc.encode({ id: '1', total: 99 }));
|
||||
|
||||
// Sub
|
||||
const sub = nc.subscribe('orders.>');
|
||||
for await (const m of sub) {
|
||||
console.log(m.subject, jc.decode(m.data));
|
||||
}
|
||||
```
|
||||
|
||||
### Request-reply (RPC)
|
||||
```ts
|
||||
// Server
|
||||
const sub = nc.subscribe('rpc.add');
|
||||
for await (const m of sub) {
|
||||
const { a, b } = jc.decode(m.data) as any;
|
||||
m.respond(jc.encode({ result: a + b }));
|
||||
}
|
||||
|
||||
// Client
|
||||
const r = await nc.request('rpc.add', jc.encode({ a: 2, b: 3 }), { timeout: 1000 });
|
||||
const result = jc.decode(r.data);
|
||||
// { result: 5 }
|
||||
```
|
||||
|
||||
→ Built-in RPC.
|
||||
|
||||
### JetStream (persistent stream)
|
||||
```ts
|
||||
const jsm = await nc.jetstreamManager();
|
||||
|
||||
await jsm.streams.add({
|
||||
name: 'ORDERS',
|
||||
subjects: ['orders.>'],
|
||||
retention: 'limits', // 또는 'workqueue', 'interest'
|
||||
max_msgs: 1_000_000,
|
||||
max_age: 7 * 24 * 3600 * 1e9, // 7 days
|
||||
});
|
||||
|
||||
// Publish
|
||||
const js = nc.jetstream();
|
||||
await js.publish('orders.created', jc.encode({ id: '1' }));
|
||||
```
|
||||
|
||||
### Consumer (durable)
|
||||
```ts
|
||||
const consumer = await js.consumers.get('ORDERS', 'order-processor');
|
||||
|
||||
const messages = await consumer.consume();
|
||||
for await (const m of messages) {
|
||||
const data = jc.decode(m.data);
|
||||
await process(data);
|
||||
m.ack();
|
||||
}
|
||||
```
|
||||
|
||||
→ Kafka consumer 와 비슷. Durable.
|
||||
|
||||
### Push vs Pull
|
||||
```ts
|
||||
// Pull (default, robust)
|
||||
const consumer = await js.consumers.get('ORDERS', 'pull-c');
|
||||
await consumer.consume();
|
||||
|
||||
// Push (server pushes to subject)
|
||||
await jsm.consumers.add('ORDERS', {
|
||||
durable_name: 'push-c',
|
||||
deliver_subject: 'inbox.push-c',
|
||||
});
|
||||
```
|
||||
|
||||
→ Pull 가 modern default.
|
||||
|
||||
### Subject hierarchy
|
||||
```
|
||||
orders.user.123 # specific
|
||||
orders.user.* # any user (1 token)
|
||||
orders.> # any orders.* deep
|
||||
```
|
||||
|
||||
```ts
|
||||
nc.subscribe('orders.user.*'); // all users
|
||||
nc.subscribe('orders.>'); // all orders
|
||||
nc.subscribe('orders.user.alice'); // specific
|
||||
```
|
||||
|
||||
### Queue group (load balance)
|
||||
```ts
|
||||
nc.subscribe('jobs', { queue: 'workers' });
|
||||
```
|
||||
|
||||
→ "workers" group 의 1 instance 만 받음 (load balance).
|
||||
|
||||
### Retention
|
||||
```
|
||||
limits: max_msgs / max_age / max_bytes 까지 유지.
|
||||
workqueue: ack 즉시 삭제 (작업 queue).
|
||||
interest: subscriber 있는 동안 유지.
|
||||
```
|
||||
|
||||
### Ack 종류
|
||||
```ts
|
||||
m.ack(); // 성공
|
||||
m.nak(); // 다시 deliver
|
||||
m.term(); // 영원히 X
|
||||
m.working(); // 진행 중 (timeout 연장)
|
||||
```
|
||||
|
||||
### Replay
|
||||
```ts
|
||||
// 옛 message 다시 read
|
||||
const consumer = await jsm.consumers.add('ORDERS', {
|
||||
durable_name: 'replay-c',
|
||||
deliver_policy: 'by_start_time',
|
||||
opt_start_time: '2026-05-01T00:00:00Z',
|
||||
});
|
||||
```
|
||||
|
||||
→ Time-based replay.
|
||||
|
||||
### Sequence-based
|
||||
```ts
|
||||
deliver_policy: 'by_start_sequence',
|
||||
opt_start_seq: 12345,
|
||||
```
|
||||
|
||||
→ Specific sequence 부터.
|
||||
|
||||
### KV store
|
||||
```ts
|
||||
const js = nc.jetstream();
|
||||
const kv = await js.views.kv('config');
|
||||
|
||||
await kv.put('theme', jc.encode('dark'));
|
||||
const entry = await kv.get('theme');
|
||||
console.log(jc.decode(entry.value));
|
||||
|
||||
// Watch
|
||||
const watch = await kv.watch();
|
||||
for await (const e of watch) {
|
||||
console.log(e.key, jc.decode(e.value));
|
||||
}
|
||||
```
|
||||
|
||||
→ Real-time config / state.
|
||||
|
||||
### Object store
|
||||
```ts
|
||||
const os = await js.views.os('files');
|
||||
|
||||
// Upload
|
||||
await os.put({ name: 'image.jpg' }, fs.createReadStream('./image.jpg'));
|
||||
|
||||
// Download
|
||||
const r = await os.get('image.jpg');
|
||||
const stream = r.data;
|
||||
```
|
||||
|
||||
→ S3 비슷. NATS native.
|
||||
|
||||
### Cluster (HA)
|
||||
```bash
|
||||
nats-server -c cluster.conf
|
||||
# cluster: routes = [...]
|
||||
```
|
||||
|
||||
```hcl
|
||||
cluster {
|
||||
port: 6222
|
||||
routes = [
|
||||
nats://node1:6222
|
||||
nats://node2:6222
|
||||
nats://node3:6222
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
→ 3+ node = HA.
|
||||
|
||||
### Replication
|
||||
```ts
|
||||
await jsm.streams.add({
|
||||
name: 'ORDERS',
|
||||
subjects: ['orders.>'],
|
||||
num_replicas: 3,
|
||||
});
|
||||
```
|
||||
|
||||
→ Stream 가 N 곳 복제. Raft consensus.
|
||||
|
||||
### Multi-cluster (super-cluster)
|
||||
```
|
||||
Region A cluster ↔ Region B cluster (gateway).
|
||||
- 매 region 가 자기 stream.
|
||||
- Cross-region 가 selective replicate.
|
||||
```
|
||||
|
||||
→ Multi-region 가 native.
|
||||
|
||||
### Leaf node (edge)
|
||||
```hcl
|
||||
leafnodes {
|
||||
remotes = [{ url: "nats://main-cluster:7422" }]
|
||||
}
|
||||
```
|
||||
|
||||
→ Edge / IoT 의 작은 NATS 가 main cluster 와 연결.
|
||||
|
||||
### Encryption / Auth
|
||||
```hcl
|
||||
# JWT-based (decentralized)
|
||||
operator: ./op.jwt
|
||||
resolver: MEM
|
||||
|
||||
# 또는 nkeys
|
||||
# 또는 mTLS
|
||||
```
|
||||
|
||||
→ Multi-tenant 친화.
|
||||
|
||||
### vs Kafka
|
||||
```
|
||||
Kafka:
|
||||
- Persistent log
|
||||
- 매우 큰 throughput
|
||||
- 큰 ecosystem (Connect, Streams)
|
||||
- ZooKeeper / KRaft 필요
|
||||
- 무거움
|
||||
|
||||
NATS JetStream:
|
||||
- Persistent stream + ephemeral pub/sub
|
||||
- 단일 binary
|
||||
- Cluster simple
|
||||
- KV / Object store native
|
||||
- Edge / multi-region 친화
|
||||
|
||||
→ 큰 enterprise = Kafka. 작은-중간 = NATS.
|
||||
```
|
||||
|
||||
### vs RabbitMQ
|
||||
```
|
||||
RabbitMQ:
|
||||
- AMQP
|
||||
- 복잡 routing (exchange, binding)
|
||||
- Mature
|
||||
|
||||
NATS:
|
||||
- Subject-based (simple)
|
||||
- 빠름
|
||||
- KV / Object store
|
||||
|
||||
→ 단순 = NATS. 복잡 routing = RabbitMQ.
|
||||
```
|
||||
|
||||
### vs Redis Pub/Sub
|
||||
```
|
||||
Redis: ephemeral.
|
||||
NATS: ephemeral + persistent.
|
||||
|
||||
→ Redis 가 cache + pub/sub 가 main. NATS 가 messaging 가 main.
|
||||
```
|
||||
|
||||
### Use case
|
||||
```
|
||||
- Microservice 통신 (sync request-reply)
|
||||
- Event stream (async)
|
||||
- IoT / edge
|
||||
- Real-time config (KV watch)
|
||||
- File / blob (Object store)
|
||||
- LLM agent 의 message (Temporal alternative)
|
||||
```
|
||||
|
||||
### Production 설정
|
||||
```hcl
|
||||
listen: 4222
|
||||
http: 8222
|
||||
|
||||
jetstream {
|
||||
store_dir: /data/nats
|
||||
max_memory_store: 1G
|
||||
max_file_store: 10G
|
||||
}
|
||||
|
||||
cluster {
|
||||
name: prod
|
||||
port: 6222
|
||||
routes: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
```
|
||||
NATS HTTP /varz, /connz, /streamz, /consumerz.
|
||||
Prometheus exporter 가 metric.
|
||||
NATS-CLI 가 admin.
|
||||
```
|
||||
|
||||
```bash
|
||||
nats stream list
|
||||
nats stream info ORDERS
|
||||
nats consumer list ORDERS
|
||||
```
|
||||
|
||||
### LLM agent
|
||||
```ts
|
||||
// Agent 가 NATS 으로 통신
|
||||
nc.subscribe('agent.task.>');
|
||||
nc.publish('agent.result.123', encode(result));
|
||||
|
||||
// 또는 KV 가 state
|
||||
await kv.put('agent.state.123', encode({ step: 'reasoning' }));
|
||||
```
|
||||
|
||||
→ Temporal / Cadence alternative for simpler use.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| 단순 pub/sub | Redis / NATS Core |
|
||||
| Persistent + edge | NATS JetStream |
|
||||
| 큰 throughput | Kafka |
|
||||
| 복잡 routing | RabbitMQ |
|
||||
| AWS native | SNS / SQS |
|
||||
| Cloud managed NATS | Synadia |
|
||||
| IoT / edge | NATS leaf |
|
||||
| Microservice RPC | NATS request-reply |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Subject 가 random**: organization 깨짐.
|
||||
- **No queue group + parallel sub**: 중복 처리.
|
||||
- **WorkQueue retention + multi-consumer**: 1 consumer 만 받음.
|
||||
- **No replication**: data lose.
|
||||
- **Persistent everything**: 큰 disk.
|
||||
- **Wildcard 폭발 (`>`)**: 모든 subject 받음.
|
||||
- **Auth 없음**: 외부 노출.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- NATS = simple Kafka alternative.
|
||||
- JetStream = persistent + KV + Object.
|
||||
- Subject 기반 routing 이 powerful.
|
||||
- Edge / multi-region 친화.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Messaging_Kafka_Patterns]]
|
||||
- [[Messaging_NATS_RabbitMQ_Comparison]]
|
||||
- [[Backend_WebSocket_Scaling]]
|
||||
Reference in New Issue
Block a user