[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: messaging-nats-rabbitmq-comparison
|
||||
title: NATS / RabbitMQ / SQS — 큐 비교
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [messaging, nats, rabbitmq, sqs, vibe-coding]
|
||||
tech_stack: { language: "TS / NATS / AMQP", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [NATS JetStream, RabbitMQ, AMQP, SQS, message broker]
|
||||
---
|
||||
|
||||
# NATS / RabbitMQ / SQS
|
||||
|
||||
> Kafka 만이 답이 아니다. **NATS = 가볍고 빠름, RabbitMQ = AMQP routing, SQS = AWS managed**. 처리량 / ordering / 영속성 / 운영 부담 4축으로 선택.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- NATS Core: pub/sub fire-and-forget.
|
||||
- NATS JetStream: 영속, replay, exactly-once.
|
||||
- RabbitMQ: AMQP, exchange + routing key + queue.
|
||||
- SQS: AWS managed FIFO 또는 standard.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### NATS JetStream
|
||||
```ts
|
||||
import { connect } from 'nats';
|
||||
|
||||
const nc = await connect({ servers: 'nats://nats:4222' });
|
||||
const js = nc.jetstream();
|
||||
const jsm = await nc.jetstreamManager();
|
||||
|
||||
// Stream 생성
|
||||
await jsm.streams.add({ name: 'ORDERS', subjects: ['orders.*'], storage: 'file', max_age: 7 * 24 * 3600 * 1e9 });
|
||||
|
||||
// Publish
|
||||
await js.publish('orders.created', JSON.stringify({ id, userId }));
|
||||
|
||||
// Consumer
|
||||
const sub = await js.consumers.get('ORDERS', 'order-projector');
|
||||
const iter = await sub.consume();
|
||||
for await (const m of iter) {
|
||||
await handle(JSON.parse(m.string()));
|
||||
m.ack();
|
||||
}
|
||||
```
|
||||
|
||||
### RabbitMQ (AMQP)
|
||||
```ts
|
||||
import amqp from 'amqplib';
|
||||
|
||||
const conn = await amqp.connect('amqp://rabbit:5672');
|
||||
const ch = await conn.createChannel();
|
||||
|
||||
await ch.assertExchange('orders', 'topic', { durable: true });
|
||||
await ch.assertQueue('orders.created.projector', { durable: true });
|
||||
await ch.bindQueue('orders.created.projector', 'orders', 'orders.created');
|
||||
|
||||
// Publish
|
||||
ch.publish('orders', 'orders.created', Buffer.from(JSON.stringify(order)), {
|
||||
persistent: true,
|
||||
messageId: eventId,
|
||||
});
|
||||
|
||||
// Consume
|
||||
await ch.prefetch(10);
|
||||
ch.consume('orders.created.projector', async (msg) => {
|
||||
if (!msg) return;
|
||||
try {
|
||||
await handle(JSON.parse(msg.content.toString()));
|
||||
ch.ack(msg);
|
||||
} catch {
|
||||
ch.nack(msg, false, false); // DLX 로
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### RabbitMQ DLX (dead-letter exchange)
|
||||
```ts
|
||||
await ch.assertQueue('orders.created.projector', {
|
||||
durable: true,
|
||||
arguments: {
|
||||
'x-dead-letter-exchange': 'orders.dlx',
|
||||
'x-message-ttl': 60_000,
|
||||
'x-max-retries': 5,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### AWS SQS (standard)
|
||||
```ts
|
||||
import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
|
||||
|
||||
const sqs = new SQSClient({});
|
||||
|
||||
await sqs.send(new SendMessageCommand({
|
||||
QueueUrl: queueUrl,
|
||||
MessageBody: JSON.stringify(order),
|
||||
MessageAttributes: { eventId: { DataType: 'String', StringValue: id } },
|
||||
}));
|
||||
|
||||
// Long-poll
|
||||
const r = await sqs.send(new ReceiveMessageCommand({
|
||||
QueueUrl: queueUrl,
|
||||
WaitTimeSeconds: 20,
|
||||
MaxNumberOfMessages: 10,
|
||||
}));
|
||||
for (const msg of r.Messages ?? []) {
|
||||
await handle(JSON.parse(msg.Body!));
|
||||
await sqs.send(new DeleteMessageCommand({ QueueUrl: queueUrl, ReceiptHandle: msg.ReceiptHandle! }));
|
||||
}
|
||||
```
|
||||
|
||||
### SQS FIFO (ordering)
|
||||
```ts
|
||||
await sqs.send(new SendMessageCommand({
|
||||
QueueUrl: fifoUrl, // .fifo 끝
|
||||
MessageBody: ...,
|
||||
MessageGroupId: order.userId, // 같은 group 안 ordering
|
||||
MessageDeduplicationId: eventId, // 5분 dedupe
|
||||
}));
|
||||
```
|
||||
|
||||
### NATS Core (fire-and-forget, 빠름)
|
||||
```ts
|
||||
nc.subscribe('telemetry.*', { callback: (err, msg) => handle(msg.data) });
|
||||
nc.publish('telemetry.cpu', JSON.stringify({ host, cpu }));
|
||||
```
|
||||
|
||||
영속 X — 받지 못하면 잃음. 모니터링 / IoT 같은 use case.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 요구 | 추천 |
|
||||
|---|---|
|
||||
| 큰 처리량 + 영속 + replay | Kafka |
|
||||
| 가벼운 + Quick start | NATS JetStream |
|
||||
| Routing 복잡 (topic + header) | RabbitMQ |
|
||||
| AWS only | SQS (+ EventBridge / SNS) |
|
||||
| 매우 작은 (1대) | Redis Streams |
|
||||
| Realtime fire-and-forget | NATS Core / Redis pub/sub |
|
||||
| Lambda trigger | SQS / EventBridge |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **모든 곳 Kafka**: 운영 부담 큼. 단순 큐 = SQS / RabbitMQ.
|
||||
- **NATS Core 영속 가정**: subscribe 안 됐으면 잃음.
|
||||
- **Prefetch 무제한 (RabbitMQ)**: 한 consumer 가 모두 가져감.
|
||||
- **Visibility timeout 짧음 (SQS)**: 처리 중 다시 visible → 중복.
|
||||
- **Manual ack 없음**: 처리 실패해도 ack — 메시지 잃음.
|
||||
- **DLQ 없음**: 실패 메시지 영원 재시도 또는 잃음.
|
||||
- **FIFO 가정 standard SQS**: ordering 보장 X.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- 상황별 다름: 영속/replay = Kafka, 가벼움 = NATS, AWS = SQS, 복잡 routing = RabbitMQ.
|
||||
- DLQ + idempotency 항상.
|
||||
- prefetch / visibility 적정.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Messaging_Kafka_Patterns]]
|
||||
- [[Backend_Job_Queue_Patterns]]
|
||||
- [[Messaging_Exactly_Once]]
|
||||
Reference in New Issue
Block a user