Files
2nd/10_Wiki/Topics/Coding/Backend_NATS_JetStream.md
T
2026-05-10 22:08:15 +09:00

7.6 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
backend-nats-jetstream NATS JetStream — modern messaging Coding draft B conceptual 2026-05-09 2026-05-09
backend
messaging
vibe-coding
language applicable_to
TS / Go
Backend
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)

# Download
nats-server -js
# JetStream enabled.

Connect (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)

// 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)

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)

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

// 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
nc.subscribe('orders.user.*');     // all users
nc.subscribe('orders.>');          // all orders
nc.subscribe('orders.user.alice'); // specific

Queue group (load balance)

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 종류

m.ack();           // 성공
m.nak();           // 다시 deliver
m.term();          // 영원히 X
m.working();       // 진행 중 (timeout 연장)

Replay

// 옛 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

deliver_policy: 'by_start_sequence',
opt_start_seq: 12345,

→ Specific sequence 부터.

KV store

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

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)

nats-server -c cluster.conf
# cluster: routes = [...]
cluster {
  port: 6222
  routes = [
    nats://node1:6222
    nats://node2:6222
    nats://node3:6222
  ]
}

→ 3+ node = HA.

Replication

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)

leafnodes {
  remotes = [{ url: "nats://main-cluster:7422" }]
}

→ Edge / IoT 의 작은 NATS 가 main cluster 와 연결.

Encryption / Auth

# 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 설정

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.
nats stream list
nats stream info ORDERS
nats consumer list ORDERS

LLM agent

// 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 친화.

🔗 관련 문서