Files
2nd/10_Wiki/Topics/Domain_Programming/Coding/Messaging_Exactly_Once.md
T
Antigravity Agent c24165b8bc refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:05:56 +09:00

4.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
messaging-exactly-once Exactly-once 의미 — Idempotency + Outbox + Dedup Coding draft B conceptual 2026-05-09 2026-05-09
messaging
exactly-once
idempotency
vibe-coding
language applicable_to
TS / Kafka / SQL
Backend
exactly once
EOS
transactional producer
dedupe
effectively-once

Exactly-once

분산 시스템에서 진정한 exactly-once 는 거의 없다. 실용적 = at-least-once + idempotent consumer = effectively-once. Kafka transactional 은 read-process-write 안에서만.

📖 핵심 개념

  • At-most-once: 잃을 수 있음.
  • At-least-once: 중복 가능.
  • Exactly-once: 정확히 1번 — 분산 boundary 까지는 어렵.
  • Effectively-once = at-least-once + idempotent.

💻 코드 패턴

Idempotent consumer (가장 표준)

CREATE TABLE processed (
  event_id TEXT PRIMARY KEY,
  processed_at TIMESTAMPTZ DEFAULT NOW()
);
async function consume(msg: Message) {
  const id = msg.headers['event-id'];
  return db.transaction(async (tx) => {
    try {
      await tx.processed.insert({ eventId: id }); // unique violation = 이미 처리
    } catch (e) {
      if (isUniqueViolation(e)) return; // skip
      throw e;
    }
    await handleInTx(tx, msg.body);
  });
}

Producer idempotency (Kafka)

const producer = kafka.producer({
  idempotent: true,
  maxInFlightRequests: 5, // <= 5 권장
  retry: { retries: 10 },
});
// Kafka 가 자동 PID + sequence number 로 중복 제거 (single producer)

Transactional producer (Kafka, read-process-write)

const producer = kafka.producer({ transactionalId: 'projector-1', idempotent: true });
const consumer = kafka.consumer({ groupId: 'projector', readUncommitted: false });

await producer.connect();
const tx = await producer.transaction();
try {
  await tx.send({ topic: 'output', messages: [...] });
  await tx.sendOffsets({ consumerGroupId: 'projector', topics: [{ topic: 'input', partitions: [{ partition: 0, offset: '42' }] }] });
  await tx.commit();
} catch {
  await tx.abort();
}

EOS 안 = consumer offset 과 produced messages 가 한 트랜잭션.

Outbox + processed-table 조합

1. Producer: outbox 안에 같이 commit (atomic)
2. Publisher: outbox → broker (at-least-once)
3. Consumer: processed table 검사 + handle in tx

이게 분산 시스템에서 정직한 exactly-once.

외부 부수효과 — TX 후

// ❌ TX 안 외부 API: TX 롤백 시 부수효과만 남음
await db.transaction(async (tx) => {
  await tx.orders.insert(o);
  await stripe.charges.create(...); // 안 됨
});

// ✅ outbox + 후속 처리
await db.transaction(async (tx) => {
  await tx.orders.insert(o);
  await tx.outbox.insert({ type: 'ChargeRequested', ... });
});
// publisher 가 별도로 stripe 호출 + idempotencyKey

Dedupe 시간 윈도우 (가벼운)

const recent = new LRU<string, true>({ max: 100_000, ttl: 5 * 60_000 });

async function consume(msg) {
  if (recent.has(msg.id)) return;
  recent.set(msg.id, true);
  await handle(msg);
}

분산 환경에선 Redis 같은 외부 store 필요.

Once-and-only-once 가짜 / 진짜

가짜 (claim 만): SQS FIFO MessageDeduplicationId 5분 / Kafka idempotent producer (single producer). 진짜에 가까움: Kafka transactional EOS + processed table. 완벽 X: 외부 시스템 boundary (HTTP, email).

🤔 의사결정 기준

상황 보장
Read → process → write Kafka 안 Kafka EOS transactional
DB write + 외부 API Outbox + idempotency key
단순 큐 처리 Idempotent consumer (processed table)
Stream aggregations Kafka Streams EOS
일회성 알림 At-least-once + idempotent
정확히 한 번 진짜 보통 불가 — effectively-once 디자인

안티패턴

  • EOS 라고 가정 + 외부 부수효과: 깨짐.
  • Idempotency key 없는 dedupe: 같은 의미 다른 hash 통과.
  • Processed table 없음: 중복 처리.
  • Producer idempotent 만 + multiple producer: producer 간 중복.
  • Manual offset commit + before processing: 메시지 잃음.
  • SQS visibility timeout < 처리 시간: 같은 메시지 두 consumer.
  • Distributed lock 으로 dedupe: 복잡 + 실패 모드.

🤖 LLM 활용 힌트

  • 진짜 exactly-once 거의 없음 — effectively-once 디자인.
  • Idempotent consumer + outbox 가 표준.
  • Kafka EOS 는 read-process-write 안에서만.

🔗 관련 문서