<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그 - 위키 / distributed-systems</title><description>&apos;distributed-systems&apos; 카테고리 위키 페이지 최신 업데이트</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>[Distributed] Kafka Consumer Group: rebalancing, offset, lag</title><link>https://shinkeonkim.com/wiki/distributed-systems/kafka-consumer-group/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/kafka-consumer-group/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Consumer Group&lt;/strong&gt; = 같은 &lt;code&gt;group.id&lt;/code&gt; 를 가진 consumer 들이 &lt;em&gt;함께 한 topic 을 분담 소비&lt;/em&gt;. &lt;em&gt;partition 단위로 분배&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;partition-할당&quot;&gt;Partition 할당&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph T[Topic: orders, 6 partitions]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P0[p0] &amp;#x26; P1[p1] &amp;#x26; P2[p2] &amp;#x26; P3[p3] &amp;#x26; P4[p4] &amp;#x26; P5[p5]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph CG[Consumer Group]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1[Consumer 1] --&gt; P0 &amp;#x26; P1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C2[Consumer 2] --&gt; P2 &amp;#x26; P3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C3[Consumer 3] --&gt; P4 &amp;#x26; P5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;규칙:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;partition 수 ≥ consumer 수&lt;/em&gt; 면 모두 분배.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;partition 수 &amp;#x3C; consumer 수&lt;/em&gt; 면 &lt;em&gt;일부 consumer idle&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;한 partition → 한 consumer&lt;/em&gt; (group 안).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;rebalancing&quot;&gt;Rebalancing&lt;/h2&gt;
&lt;p&gt;consumer 추가 / 제거 / 죽음 → &lt;em&gt;partition 재할당&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C1 as Consumer 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C2 as Consumer 2 (new)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Coord as Group Coordinator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Coord: heartbeat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;Coord: join group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Coord: rebalance 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;C1: stop fetching (revoke)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Coord: ack + commit offset&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;C1: new assignment (p0, p1, p2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;C2: new assignment (p3, p4, p5)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;C1: resume fetching&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;C2: start fetching&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;eager-vs-cooperative&quot;&gt;Eager vs Cooperative&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Eager (옛 기본)&lt;/th&gt;&lt;th&gt;Cooperative (Kafka 2.4+)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Rebalance 시&lt;/td&gt;&lt;td&gt;&lt;em&gt;모든 consumer 정지&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;영향받는 partition 만&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;처리 중단&lt;/td&gt;&lt;td&gt;길음 (수십 초 가능)&lt;/td&gt;&lt;td&gt;&lt;em&gt;짧음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Throughput&lt;/td&gt;&lt;td&gt;하락 큼&lt;/td&gt;&lt;td&gt;적음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Cooperative rebalancing&lt;/em&gt; 이 &lt;em&gt;2026 권장 기본&lt;/em&gt;. &lt;code&gt;partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;static-membership-kip-345&quot;&gt;Static Membership (KIP-345)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;properties&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;group.instance.id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=consumer-1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;consumer 가 &lt;em&gt;동일 ID 로 재접속&lt;/em&gt; 하면 &lt;em&gt;partition 유지&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;배포 / 재시작&lt;/em&gt; 시 rebalance 회피.&lt;/li&gt;
&lt;li&gt;보통 &lt;em&gt;수십 초 ~ 수 분 안&lt;/em&gt; 의 짧은 재시작 가정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;offset-관리&quot;&gt;Offset 관리&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[Consumer] --&gt;|fetch| K[(Kafka)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|commit| Offset[&quot;__consumer_offsets&amp;#x3C;br/&gt;(internal topic)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note1[정기 commit 또는 명시적]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;모드&lt;/th&gt;&lt;th&gt;안전성&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;enable.auto.commit=true&lt;/code&gt; (기본)&lt;/td&gt;&lt;td&gt;&lt;em&gt;중복 또는 손실&lt;/em&gt; 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;명시적 sync commit&lt;/td&gt;&lt;td&gt;안전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;명시적 async commit + 마지막 sync&lt;/td&gt;&lt;td&gt;balanced&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;auto-commit-함정&quot;&gt;Auto commit 함정&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. fetch [100, 101, 102]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 처리 중 ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. 5초 후 auto commit (103 까지 commit)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. consumer crash (102 처리 미완)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. 재시작 → 103 부터 fetch → 102 손실!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;Auto commit 은 손실 또는 중복&lt;/strong&gt; 의 함정. &lt;em&gt;exactly-once&lt;/em&gt; 가 필요하면 명시적 commit + idempotent 처리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;consumer-lag&quot;&gt;Consumer Lag&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Last[&quot;partition 의 last offset (LEO): 1000&quot;] --&gt; Lag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Curr[&quot;consumer current offset: 800&quot;] --&gt; Lag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lag[Lag = 200&amp;#x3C;br/&gt;processing 못 따라옴]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;도구&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;kafka-consumer-groups.sh --describe&lt;/code&gt;&lt;/td&gt;&lt;td&gt;즉시 확인&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kafka Lag Exporter&lt;/td&gt;&lt;td&gt;Prometheus 메트릭&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Burrow (LinkedIn)&lt;/td&gt;&lt;td&gt;더 세련된 lag 분석&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;lag-기반-auto-scaling&quot;&gt;Lag 기반 Auto-Scaling&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lag[Consumer Lag] --&gt;|KEDA| HPA[Kubernetes HPA]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HPA --&gt; Scale[Consumer pod 수 증가]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://keda.sh/&quot;&gt;KEDA&lt;/a&gt; 가 Kafka lag 을 &lt;em&gt;Kubernetes HPA 지표&lt;/em&gt; 로 사용. &lt;em&gt;spike 자동 흡수&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;heartbeat--session&quot;&gt;Heartbeat / Session&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;설정&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;th&gt;기본&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;session.timeout.ms&lt;/code&gt;&lt;/td&gt;&lt;td&gt;이 시간 heartbeat 없으면 &lt;em&gt;죽었다고 판단&lt;/em&gt;&lt;/td&gt;&lt;td&gt;45s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;heartbeat.interval.ms&lt;/code&gt;&lt;/td&gt;&lt;td&gt;heartbeat 보내는 주기&lt;/td&gt;&lt;td&gt;3s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max.poll.interval.ms&lt;/code&gt;&lt;/td&gt;&lt;td&gt;poll 사이 최대 시간&lt;/td&gt;&lt;td&gt;5min&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;code&gt;max.poll.interval.ms&lt;/code&gt; 초과 = consumer &lt;em&gt;추방&lt;/em&gt;. 처리 시간이 길면 &lt;em&gt;명시적 늘림&lt;/em&gt; 또는 &lt;em&gt;워커 별도 스레드&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rebalancing 폭주&lt;/strong&gt; = consumer 자주 추가/제거 → 처리 정지 반복. cooperative + static membership.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;auto.offset.reset=latest&lt;/code&gt; + 다운&lt;/strong&gt; = 다운 사이 메시지 &lt;em&gt;손실&lt;/em&gt;. 보통 &lt;code&gt;earliest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;너무 큰 batch&lt;/strong&gt; = &lt;code&gt;max.poll.records&lt;/code&gt; 너무 큼 → 처리 시간 길어 추방.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 있는 consumer + rebalance&lt;/strong&gt; = partition 이 다른 consumer 로 가면서 &lt;em&gt;처리 진행 정보 손실&lt;/em&gt;. 상태는 &lt;em&gt;외부 store&lt;/em&gt; 에.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[kafka]]&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (consumer group 유사)&lt;/li&gt;
&lt;li&gt;[[message-broker-comparison]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>kafka</category><category>consumer-group</category><category>rebalancing</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Distributed] Kafka: 분산 로그, partition, consumer group</title><link>https://shinkeonkim.com/wiki/distributed-systems/kafka/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/kafka/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Apache Kafka&lt;/strong&gt; = &lt;em&gt;분산 commit log&lt;/em&gt;. &lt;em&gt;고처리량 (수백만 msg/s), 영속, 수평 확장&lt;/em&gt;. &lt;em&gt;event-driven 아키텍처&lt;/em&gt; 의 &lt;em&gt;de facto&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;핵심 개념: &lt;strong&gt;topic = partition 들 = 노드에 분산된 append-only log&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;토폴로지&quot;&gt;토폴로지&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P1[Producer 1] --&gt; T1[Topic: events]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P2[Producer 2] --&gt; T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph T1[Topic: events]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      Part0[&quot;partition 0&amp;#x3C;br/&gt;(broker A)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      Part1[&quot;partition 1&amp;#x3C;br/&gt;(broker B)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      Part2[&quot;partition 2&amp;#x3C;br/&gt;(broker C)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T1 --&gt; CG1[Consumer Group: analytics]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T1 --&gt; CG2[Consumer Group: search]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CG1 --&gt; C11[Consumer 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CG1 --&gt; C12[Consumer 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CG1 --&gt; C13[Consumer 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CG2 --&gt; C21[Consumer 1]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;partition-핵심-단위&quot;&gt;Partition: 핵심 단위&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;속성&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Append-only&lt;/td&gt;&lt;td&gt;끝에만 추가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Immutable&lt;/td&gt;&lt;td&gt;변경 불가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ordered&lt;/td&gt;&lt;td&gt;partition 안에서 순서 보장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Distributed&lt;/td&gt;&lt;td&gt;노드 간 분산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replicated&lt;/td&gt;&lt;td&gt;RF (Replication Factor) 만큼 복제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Offset&lt;/td&gt;&lt;td&gt;각 메시지의 &lt;em&gt;partition 내 ID&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;partition-수-선택&quot;&gt;Partition 수 선택&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{Partition 수}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;적음 (1-3)&quot;| Low[병렬성 낮음&amp;#x3C;br/&gt;순서 보장 쉬움]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;중간 (10-50)&quot;| Med[일반적]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;많음 (100+)&quot;| High[오버헤드 증가&amp;#x3C;br/&gt;리밸런싱 비용]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;Partition 수 = 한 consumer group 의 최대 병렬성&lt;/strong&gt;. 늘리는 건 가능하지만 &lt;em&gt;줄이는 건 불가&lt;/em&gt;. 보수적으로 시작.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;consumer-group&quot;&gt;Consumer Group&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph CG1[Consumer Group A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1[Consumer 1] --&gt; P0[part 0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C2[Consumer 2] --&gt; P1[part 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C3[Consumer 3] --&gt; P2[part 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph CG2[Consumer Group B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1[Consumer 1] --&gt; P0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1 --&gt; P1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1 --&gt; P2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;규칙&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;한 partition → 한 consumer (group 안)&lt;/td&gt;&lt;td&gt;순서 보장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;한 consumer → N partitions&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consumer 수 &gt; Partition 수&lt;/td&gt;&lt;td&gt;남는 consumer는 idle&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;다른 group → 독립 offset&lt;/td&gt;&lt;td&gt;fan-out&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;offset-관리&quot;&gt;Offset 관리&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;K: poll() (offset 100 ~)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    K--&gt;&gt;C: msgs [100, 101, 102]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;K: commit offset 103&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C,K: 다음 poll 부터 103 부터&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;모드&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Auto commit&lt;/td&gt;&lt;td&gt;주기적 자동. &lt;em&gt;최대 한 번 손실 또는 중복&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Manual sync commit&lt;/td&gt;&lt;td&gt;처리 후 동기 commit. 정확&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Manual async commit&lt;/td&gt;&lt;td&gt;비동기 commit. &lt;em&gt;최후 commit 실패 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;replication--isr&quot;&gt;Replication + ISR&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leader[&quot;Leader replica&quot;] --&gt; F1[&quot;Follower 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leader --&gt; F2[&quot;Follower 2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leader --&gt; ISR{&quot;ISR&amp;#x3C;br/&gt;In-Sync Replica set&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F1 --&gt; ISR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F2 -.지연.-&gt; ISR&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Leader 가 모든 write&lt;/em&gt; 수신.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Follower 가 동기 (또는 비동기) 복제&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ISR&lt;/em&gt; = &lt;em&gt;충분히 따라온 replica 집합&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Leader 다운 → &lt;em&gt;ISR 중 새 leader&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;설정&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;acks=0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;응답 안 기다림 (손실 가능)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;acks=1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;leader 만 (기본)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;acks=all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ISR 전체 (가장 안전)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;min.insync.replicas&lt;/code&gt;&lt;/td&gt;&lt;td&gt;최소 ISR 수. 미달 시 write 거절&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;code&gt;acks=all&lt;/code&gt; + &lt;code&gt;min.insync.replicas=2&lt;/code&gt; 가 &lt;em&gt;프로덕션 표준&lt;/em&gt;. &lt;em&gt;낮은 settings&lt;/em&gt; 는 운영 사고 단골.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;exactly-once-semantics-eos&quot;&gt;Exactly-once Semantics (EOS)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Producer&amp;#x3C;br/&gt;idempotent + transactional] --&gt; K[Kafka]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    K --&gt; C[Consumer&amp;#x3C;br/&gt;read_committed]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|transactional.id| TC[Transaction Coordinator]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|isolation.level=read_committed| K&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Producer idempotence&lt;/em&gt;: 재전송 시 &lt;em&gt;중복 제거&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Transactional producer&lt;/em&gt;: 여러 topic write 를 &lt;em&gt;원자적&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Consumer isolation.level=read_committed&lt;/em&gt;: aborted 트랜잭션 무시.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;strong&gt;완벽한 EOS 는 Kafka 안에서만&lt;/strong&gt;. &lt;em&gt;Kafka → DB → Kafka&lt;/em&gt; 등 &lt;em&gt;외부 시스템 포함&lt;/em&gt; 은 &lt;em&gt;outbox + idempotency&lt;/em&gt; 패턴이 추가로 필요. 자세한 건 [[outbox-pattern]].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;kraft-kafka-raft-zookeeper-제거&quot;&gt;KRaft (Kafka Raft, ZooKeeper 제거)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Old[옛 구조]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        K1[Kafka 브로커] --&gt; Zk[(ZooKeeper)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph New[KRaft 구조]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        K2[Kafka 브로커] -.-&gt; Quorum[메타데이터 controller&amp;#x3C;br/&gt;Raft 합의]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.8+ preview, 3.5+ production, 4.0+ ZK 완전 제거.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;운영 단순화&lt;/em&gt; + &lt;em&gt;확장성 (수백만 partition)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kafka-의-적합--부적합&quot;&gt;Kafka 의 적합 / 부적합&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;적합&lt;/th&gt;&lt;th&gt;부적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Event sourcing&lt;/td&gt;&lt;td&gt;작은 데이터 (RabbitMQ 가 단순)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;로그 수집&lt;/td&gt;&lt;td&gt;request-reply (가능하지만 어색)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Stream processing&lt;/td&gt;&lt;td&gt;1-N 즉시 fan-out (Pub/Sub 가 간단)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CDC (Change Data Capture)&lt;/td&gt;&lt;td&gt;짧은 retention task queue (Sidekiq 가 적합)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Partition 수 부족&lt;/strong&gt; = 병렬성 제한. 늘리는 건 가능하지만 &lt;em&gt;기존 키 분포 깨짐&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebalancing 폭주&lt;/strong&gt; = consumer 가 자주 join/leave → &lt;em&gt;모든 consumer 정지 후 재할당&lt;/em&gt;. cooperative rebalancing (Kafka 2.4+) 으로 완화.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hot partition&lt;/strong&gt; = key 선택 잘못 → 한 partition 만 트래픽. key 디자인 재검토.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;auto.offset.reset=latest&lt;/code&gt;&lt;/strong&gt; = consumer 가 죽었다 살아나면 &lt;em&gt;그동안 메시지 손실&lt;/em&gt;. 보통 &lt;code&gt;earliest&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[rabbitmq]] (대안)&lt;/li&gt;
&lt;li&gt;[[nats]] (대안)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (Streams 가 Kafka 비슷)&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[event-sourcing]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>kafka</category><category>message-broker</category><category>streaming</category><category>distributed</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Distributed] Message Broker 비교: Kafka / RabbitMQ / NATS / SQS / Redis Streams</title><link>https://shinkeonkim.com/wiki/distributed-systems/message-broker-comparison/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/message-broker-comparison/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;결정-트리&quot;&gt;결정 트리&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{사용 패턴}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|event log + replay| Kafka[Kafka]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|복잡 라우팅 + task queue| Rabbit[RabbitMQ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|마이크로서비스 가벼움| NATS[NATS]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|AWS managed + 단순 큐| SQS[SQS]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|Redis 위 fan-out / 짧은 큐| RedisS[Redis Streams]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|클라우드 native pub-sub| GCP[Google Pub/Sub]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;6가지-broker-매트릭스&quot;&gt;6가지 broker 매트릭스&lt;/h2&gt;








































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Kafka&lt;/th&gt;&lt;th&gt;RabbitMQ&lt;/th&gt;&lt;th&gt;NATS&lt;/th&gt;&lt;th&gt;SQS&lt;/th&gt;&lt;th&gt;Redis Streams&lt;/th&gt;&lt;th&gt;Google Pub/Sub&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;모델&lt;/td&gt;&lt;td&gt;log&lt;/td&gt;&lt;td&gt;exchange-queue&lt;/td&gt;&lt;td&gt;subject pub-sub&lt;/td&gt;&lt;td&gt;queue&lt;/td&gt;&lt;td&gt;log&lt;/td&gt;&lt;td&gt;topic pub-sub&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;영속&lt;/td&gt;&lt;td&gt;&lt;em&gt;항상&lt;/em&gt;&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;옵션 (JS)&lt;/td&gt;&lt;td&gt;항상&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;항상&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Throughput&lt;/td&gt;&lt;td&gt;&lt;em&gt;수백만/s&lt;/em&gt;&lt;/td&gt;&lt;td&gt;수만/s&lt;/td&gt;&lt;td&gt;수백만/s&lt;/td&gt;&lt;td&gt;거의 무한 (auto-scale)&lt;/td&gt;&lt;td&gt;수십만/s&lt;/td&gt;&lt;td&gt;수백만/s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;중간 (ms)&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;&lt;em&gt;마이크로초&lt;/em&gt;&lt;/td&gt;&lt;td&gt;수십 ms&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 복잡도&lt;/td&gt;&lt;td&gt;&lt;em&gt;높음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;&lt;em&gt;낮음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt; (managed)&lt;/td&gt;&lt;td&gt;낮음 (Redis 운영자라면)&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;라우팅&lt;/td&gt;&lt;td&gt;partition by key&lt;/td&gt;&lt;td&gt;exchange 패턴&lt;/td&gt;&lt;td&gt;subject 계층&lt;/td&gt;&lt;td&gt;FIFO/standard&lt;/td&gt;&lt;td&gt;consumer group&lt;/td&gt;&lt;td&gt;filtering&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Re-play (재처리)&lt;/td&gt;&lt;td&gt;&lt;em&gt;자유&lt;/em&gt; (offset)&lt;/td&gt;&lt;td&gt;한 번만&lt;/td&gt;&lt;td&gt;JS 가능&lt;/td&gt;&lt;td&gt;DLQ 만&lt;/td&gt;&lt;td&gt;가능 (XREAD)&lt;/td&gt;&lt;td&gt;가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;학습 곡선&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;비용 (자체호스팅)&lt;/td&gt;&lt;td&gt;비쌈&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;&lt;em&gt;저렴&lt;/em&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;저렴&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;비용 (managed)&lt;/td&gt;&lt;td&gt;Confluent&lt;/td&gt;&lt;td&gt;CloudAMQP&lt;/td&gt;&lt;td&gt;Synadia&lt;/td&gt;&lt;td&gt;&lt;em&gt;AWS&lt;/em&gt;&lt;/td&gt;&lt;td&gt;ElastiCache&lt;/td&gt;&lt;td&gt;&lt;em&gt;GCP&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;처리량--지연--운영비용-직관&quot;&gt;처리량 / 지연 / 운영비용 (직관)&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;시나리오별-추천&quot;&gt;시나리오별 추천&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;시나리오&lt;/th&gt;&lt;th&gt;추천&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Event sourcing (큰 로그)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;마이크로서비스 commands&lt;/td&gt;&lt;td&gt;&lt;strong&gt;NATS&lt;/strong&gt; 또는 RabbitMQ&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS Lambda + 큐&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SQS&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sidekiq 같은 Ruby job queue&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt; (list 기반)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;실시간 채팅 fan-out&lt;/td&gt;&lt;td&gt;&lt;strong&gt;NATS&lt;/strong&gt; 또는 Redis Pub/Sub&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CDC (DB → search)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt; + Debezium&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;트래픽 spike 자동 흡수&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SQS&lt;/strong&gt; (managed)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;빠른 mvp / startup&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Redis Streams&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;메시지-보장-비교&quot;&gt;메시지 보장 비교&lt;/h2&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Broker&lt;/th&gt;&lt;th&gt;At-most-once&lt;/th&gt;&lt;th&gt;At-least-once&lt;/th&gt;&lt;th&gt;Exactly-once&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;td&gt;EOS 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RabbitMQ&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;persistent + ack&lt;/td&gt;&lt;td&gt;외부 idempotency 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NATS Core&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NATS JS&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SQS&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;td&gt;FIFO + 5분 dedup&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis Streams&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;XACK 로&lt;/td&gt;&lt;td&gt;외부 idempotency&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Exactly-once 는 broker 내부&lt;/em&gt; 에서만 의미 있음. &lt;em&gt;외부 시스템 (DB, API)&lt;/em&gt; 까지 &lt;em&gt;완벽한 exactly-once&lt;/em&gt; 는 불가능. &lt;em&gt;idempotency + outbox&lt;/em&gt; 가 현실적 정답. 자세한 건 [[outbox-pattern]], [[idempotency-keys]].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;영속--재처리-비교&quot;&gt;영속 / 재처리 비교&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Kafka[Kafka&amp;#x3C;br/&gt;days~weeks retention&amp;#x3C;br/&gt;임의 offset replay]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Rabbit[RabbitMQ&amp;#x3C;br/&gt;queue 안의 메시지&amp;#x3C;br/&gt;소비 = 사라짐]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Nats[NATS JS&amp;#x3C;br/&gt;retention 정책&amp;#x3C;br/&gt;임의 시점 replay]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sqs[SQS&amp;#x3C;br/&gt;최대 14일&amp;#x3C;br/&gt;한 번 소비 = 사라짐]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream[Redis Streams&amp;#x3C;br/&gt;MAXLEN 만큼&amp;#x3C;br/&gt;임의 offset]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;마이그레이션-비용&quot;&gt;마이그레이션 비용&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;출발&lt;/th&gt;&lt;th&gt;도착&lt;/th&gt;&lt;th&gt;비용&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Sidekiq (Redis)&lt;/td&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;큼 (consumer group, exactly-once 재설계)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RabbitMQ&lt;/td&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;중간 (라우팅 → topic 재설계)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;NATS&lt;/td&gt;&lt;td&gt;작음 (둘 다 log)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;자체 호스팅&lt;/td&gt;&lt;td&gt;Managed&lt;/td&gt;&lt;td&gt;작음 (운영 측면 큰 이득)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;운영자-시점-체크리스트&quot;&gt;운영자 시점 체크리스트&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ 메시지 손실 허용?              (acks, persistent, ack 정책)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ 순서 보장 필요?                 (partition key, FIFO)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ 재처리 가능?                    (offset / position)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ DLQ + parking lot              (실패 메시지 격리)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ Backpressure                   (prefetch, max-in-flight)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ Monitoring                     (lag, dead messages, throughput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ Consumer auto-scaling          (lag 기반)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[kafka]], [[rabbitmq]], [[nats]]&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;li&gt;[[aws-sqs]] (별도)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>message-broker</category><category>kafka</category><category>rabbitmq</category><category>nats</category><category>sqs</category><category>comparison</category><author>koa (김신건)</author></item><item><title>[Distributed] NATS: 가벼움, JetStream, subject 라우팅</title><link>https://shinkeonkim.com/wiki/distributed-systems/nats/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/nats/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;NATS&lt;/strong&gt; = &lt;em&gt;극도로 가벼운 메시징 시스템&lt;/em&gt;. core NATS = &lt;em&gt;at-most-once Pub/Sub + request-reply&lt;/em&gt;. JetStream = &lt;em&gt;영속 / at-least-once / exactly-once&lt;/em&gt; 까지.&lt;/p&gt;
&lt;p&gt;특징:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 바이너리&lt;/strong&gt; (Go), 메모리 ~10MB.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;마이크로초 latency&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;수백만 msg/s/노드&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;edge / IoT / 마이크로서비스&lt;/em&gt; 친화.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;두-모드-core-vs-jetstream&quot;&gt;두 모드: Core vs JetStream&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Core NATS&lt;/th&gt;&lt;th&gt;JetStream&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;영속&lt;/td&gt;&lt;td&gt;없음 (in-memory)&lt;/td&gt;&lt;td&gt;&lt;em&gt;있음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;신뢰성&lt;/td&gt;&lt;td&gt;at-most-once&lt;/td&gt;&lt;td&gt;at-least-once / exactly-once&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;매우 낮음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용&lt;/td&gt;&lt;td&gt;telemetry, signal&lt;/td&gt;&lt;td&gt;event log, queue&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;subject-계층&quot;&gt;Subject 계층&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;orders.created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;orders.paid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;orders.shipped.us&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;orders.shipped.eu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;inventory.low.warehouse-a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;와일드카드&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;단어 1개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;나머지 모두&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;subscribe orders.*    → orders.created, orders.paid (1단 차이만)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;subscribe orders.&gt;    → orders.created, orders.paid, orders.shipped.us, ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;request-reply-core-nats-의-강점&quot;&gt;Request-Reply (Core NATS 의 강점)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Requester&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant N as NATS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S as Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S-&gt;&gt;N: SUB shop.getItem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R-&gt;&gt;N: PUB shop.getItem (reply=INBOX.abc)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N-&gt;&gt;S: deliver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S-&gt;&gt;S: 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S-&gt;&gt;N: PUB INBOX.abc {결과}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N-&gt;&gt;R: deliver&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;RPC&lt;/em&gt; 가 &lt;em&gt;Pub/Sub 위에 자연스럽게&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;load balancing&lt;/em&gt; = 같은 subject 의 &lt;em&gt;queue group&lt;/em&gt; 으로.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Queue group: 여러 service 가 같은 subject 구독, 한 명에게 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nats.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;shop.getItem&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, { queue: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;shop&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;jetstream-메시지-흐름&quot;&gt;JetStream 메시지 흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Producer] --&gt;|PUB orders.&gt;| JS[(JetStream Stream)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    JS --&gt; Consumer1[Push consumer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    JS --&gt; Consumer2[Pull consumer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    JS --&gt; Consumer3[Durable consumer]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Consumer 타입&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Push&lt;/td&gt;&lt;td&gt;broker 가 능동 push&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pull&lt;/td&gt;&lt;td&gt;consumer 가 poll&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ephemeral&lt;/td&gt;&lt;td&gt;임시 (재접속 시 처음부터)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Durable&lt;/td&gt;&lt;td&gt;진행 상태 영속&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;nats-kv--object-store&quot;&gt;NATS KV / Object Store&lt;/h2&gt;
&lt;p&gt;JetStream 위 &lt;em&gt;추가 추상&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// KV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; kv&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jsm.views.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kv&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;config&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kv.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;feature.x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;enabled&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; val&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kv.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;feature.x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Object Store&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; os&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jsm.views.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;images&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; os.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;logo.png&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, readableStream);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Redis 같은 KV&lt;/em&gt;, &lt;em&gt;S3 같은 object store&lt;/em&gt; 가 NATS 안에 통합. &lt;em&gt;작은 인프라&lt;/em&gt; 친화.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;nats-vs-kafka-vs-rabbitmq&quot;&gt;NATS vs Kafka vs RabbitMQ&lt;/h2&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;NATS&lt;/th&gt;&lt;th&gt;Kafka&lt;/th&gt;&lt;th&gt;RabbitMQ&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;바이너리 크기&lt;/td&gt;&lt;td&gt;&lt;em&gt;10 MB&lt;/em&gt;&lt;/td&gt;&lt;td&gt;수백 MB&lt;/td&gt;&lt;td&gt;수십 MB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 복잡도&lt;/td&gt;&lt;td&gt;&lt;em&gt;낮음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;처리량&lt;/td&gt;&lt;td&gt;매우 높음&lt;/td&gt;&lt;td&gt;매우 높음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;&lt;em&gt;최저&lt;/em&gt;&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;영속&lt;/td&gt;&lt;td&gt;옵션 (JS)&lt;/td&gt;&lt;td&gt;항상&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replication&lt;/td&gt;&lt;td&gt;빌트인 (JS)&lt;/td&gt;&lt;td&gt;빌트인&lt;/td&gt;&lt;td&gt;mirror queue&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;학습 곡선&lt;/td&gt;&lt;td&gt;&lt;em&gt;낮음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;edge--iot-패턴&quot;&gt;Edge / IoT 패턴&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Edge[&quot;Edge devices (수천 ~ 수백만)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        E1[Sensor 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        E2[Sensor 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        EN[Sensor N]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Edge --&gt; NEdge[NATS edge cluster]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NEdge --&gt; Leaf[Leaf node]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leaf --&gt; NCloud[NATS central cluster]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NCloud --&gt; Apps[Apps / Analytics]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Leaf node&lt;/em&gt; = &lt;em&gt;edge → central&lt;/em&gt; 의 경량 게이트웨이.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;bandwidth 최적&lt;/em&gt;: 같은 메시지 한 번만 전송.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;오프라인 buffer&lt;/em&gt; + &lt;em&gt;재연결 시 sync&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;적합--부적합&quot;&gt;적합 / 부적합&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good[적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G1[마이크로서비스 사이 메시징]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G2[Edge / IoT]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G3[Real-time telemetry]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G4[&quot;작은 영속 큐 (JS)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad[부적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B1[&quot;복잡한 라우팅 (Topic exchange 같은)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B2[수개월 retention 의 거대 로그]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Core NATS 의 &lt;em&gt;손실 가능성 무시&lt;/em&gt;&lt;/strong&gt; = subscriber 없으면 &lt;em&gt;조용히 사라짐&lt;/em&gt;. JS 로 가야.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JS replica 1&lt;/strong&gt; = 노드 다운 시 손실. RF 3 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wildcard 과도 사용&lt;/strong&gt; = &lt;code&gt;&gt;&lt;/code&gt; 가 &lt;em&gt;모든 메시지&lt;/em&gt; 받음 → 클라이언트 부하.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subject 표준화 없음&lt;/strong&gt; = 작은 팀에서는 OK, 큰 팀에서는 &lt;em&gt;충돌 / 중복&lt;/em&gt;. 네임스페이스 가이드 필요.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[kafka]], [[rabbitmq]]&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;li&gt;[[grpc]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>nats</category><category>message-broker</category><category>lightweight</category><category>edge</category><category>distributed</category><author>koa (김신건)</author></item><item><title>[Distributed] RabbitMQ: exchange, queue, routing key</title><link>https://shinkeonkim.com/wiki/distributed-systems/rabbitmq/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/rabbitmq/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt; = &lt;em&gt;AMQP 0.9.1&lt;/em&gt; 기반 &lt;em&gt;traditional message broker&lt;/em&gt;. &lt;em&gt;exchange → queue 라우팅&lt;/em&gt;, &lt;em&gt;workload distribution&lt;/em&gt;, &lt;em&gt;publish-subscribe&lt;/em&gt;. Kafka 보다 &lt;em&gt;작은 단위 메시지 라우팅에 강함&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;구조&quot;&gt;구조&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Producer] --&gt; Exch[Exchange]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Exch --&gt;|routing key 매칭| Q1[Queue 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Exch --&gt;|routing key 매칭| Q2[Queue 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Exch --&gt;|routing key 매칭| Q3[Queue 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt; C1[Consumer 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt; C2[Consumer 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt; C3[Consumer 3]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;컴포넌트&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Producer&lt;/td&gt;&lt;td&gt;메시지 발행자&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Exchange&lt;/td&gt;&lt;td&gt;&lt;em&gt;routing 규칙&lt;/em&gt;. 4 종류&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Queue&lt;/td&gt;&lt;td&gt;메시지 저장소&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Binding&lt;/td&gt;&lt;td&gt;exchange ↔ queue 연결 + key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consumer&lt;/td&gt;&lt;td&gt;소비자&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;exchange-4-종류&quot;&gt;Exchange 4 종류&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Direct[Direct&amp;#x3C;br/&gt;routing_key 정확 일치]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Topic[Topic&amp;#x3C;br/&gt;*.error.* 와일드카드]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Fanout[Fanout&amp;#x3C;br/&gt;모든 queue 로 broadcast]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Headers[Headers&amp;#x3C;br/&gt;header attribute 매칭]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-direct&quot;&gt;1. Direct&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;publish key=ORDER.PAID → queue:paid (binding key=ORDER.PAID)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;publish key=ORDER.SHIPPED → queue:shipped (binding key=ORDER.SHIPPED)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-topic&quot;&gt;2. Topic&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;publish key=order.paid.us → matches &quot;order.*.us&quot;, &quot;order.paid.*&quot;, &quot;#&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;와일드카드&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;단어 1개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;#&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0+ 단어&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;3-fanout&quot;&gt;3. Fanout&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;모든 binding 된 queue 로 무조건 복사&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Pub/Sub fan-out&lt;/em&gt; 의 정통.&lt;/p&gt;
&lt;h3 id=&quot;4-headers&quot;&gt;4. Headers&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;header { type: &quot;order&quot;, region: &quot;us&quot; } 매칭&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;메시지-보장&quot;&gt;메시지 보장&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;Broker: publish&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker-&gt;&gt;Disk: persistent? → disk write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker-&gt;&gt;P: confirm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;P: confirm 받음 → 안전&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker-&gt;&gt;C: deliver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Broker: ack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker-&gt;&gt;Broker: ack 받음 → 제거&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;설정&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;persistent&lt;/code&gt; 메시지&lt;/td&gt;&lt;td&gt;broker 재시작 후에도 보존&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;durable&lt;/code&gt; queue&lt;/td&gt;&lt;td&gt;broker 재시작 후 queue 유지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;publisher confirms&lt;/code&gt;&lt;/td&gt;&lt;td&gt;broker 의 &lt;em&gt;받음&lt;/em&gt; 확인&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;consumer ack&lt;/code&gt;&lt;/td&gt;&lt;td&gt;처리 완료 확인&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mandatory&lt;/code&gt; flag&lt;/td&gt;&lt;td&gt;routing 안 되면 &lt;em&gt;unroutable&lt;/em&gt; 반환&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
모든 &lt;em&gt;4가지 켜야&lt;/em&gt; 실제 &lt;em&gt;at-least-once&lt;/em&gt; 보장. 하나라도 빠지면 &lt;em&gt;손실 가능&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;dlq-dead-letter-queue&quot;&gt;DLQ (Dead Letter Queue)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1[primary queue] --&gt;|reject 또는 ttl 만료| DLX[Dead Letter Exchange]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DLX --&gt; DLQ[DLQ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DLQ --&gt; Inspect[운영자 검사]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;trigger&lt;/th&gt;&lt;th&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;reject (requeue=false)&lt;/td&gt;&lt;td&gt;DLQ 로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TTL 만료&lt;/td&gt;&lt;td&gt;DLQ 로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;queue 길이 한도 초과&lt;/td&gt;&lt;td&gt;DLQ 로&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자동 retry 패턴:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1[primary] --&gt;|fail| DLQ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DLQ --&gt;|ttl 5s| Retry1[retry-1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Retry1 --&gt;|fail| DLQ2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DLQ2 --&gt;|ttl 30s| Retry2[retry-2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Retry2 --&gt;|fail| Final[parking lot]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;rabbitmq-vs-kafka&quot;&gt;RabbitMQ vs Kafka&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;RabbitMQ&lt;/th&gt;&lt;th&gt;Kafka&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;모델&lt;/td&gt;&lt;td&gt;큐 + exchange 라우팅&lt;/td&gt;&lt;td&gt;append-only log&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;처리량&lt;/td&gt;&lt;td&gt;&lt;em&gt;수만 ~ 수십만/s&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;수백만/s&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;&lt;em&gt;낮음&lt;/em&gt; (ms)&lt;/td&gt;&lt;td&gt;좀 더 큼&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;영속&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;항상&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;재처리&lt;/td&gt;&lt;td&gt;한 번 소비 후 &lt;em&gt;사라짐&lt;/em&gt;&lt;/td&gt;&lt;td&gt;offset 으로 &lt;em&gt;임의 재생&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;라우팅&lt;/td&gt;&lt;td&gt;&lt;em&gt;유연&lt;/em&gt; (exchange 패턴)&lt;/td&gt;&lt;td&gt;단순 (topic + partition)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용&lt;/td&gt;&lt;td&gt;task queue, request-reply&lt;/td&gt;&lt;td&gt;event log, stream&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;rabbitmq-streams-kafka-흉내&quot;&gt;RabbitMQ Streams (Kafka 흉내)&lt;/h2&gt;
&lt;p&gt;3.9+ 의 &lt;em&gt;Stream&lt;/em&gt; 큐 타입. &lt;em&gt;append-only log&lt;/em&gt; + replica + 재생 가능.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 또는 declare 시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;queue.declare(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;&quot;my-stream&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arguments={&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;x-queue-type&quot;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;stream&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kafka 와 비슷한 흐름&lt;/em&gt;. RabbitMQ 안에서 &lt;em&gt;durable log + fan-out 임의 시점&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;persistent&lt;/code&gt; 없이 신뢰&lt;/strong&gt; = broker 재시작 시 모든 메시지 손실.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;prefetch 너무 큼&lt;/strong&gt; = 한 consumer 가 &lt;em&gt;수천 메시지 lock&lt;/em&gt; → 다른 consumer 가 굶음. &lt;code&gt;prefetch=10~100&lt;/code&gt; 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DLQ 없이 reject&lt;/strong&gt; = 메시지 영구 손실 또는 무한 재시도.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;auto_ack=true&lt;/code&gt;&lt;/strong&gt; = consumer 다운 시 처리 안 한 메시지가 &lt;em&gt;ack 됨&lt;/em&gt; (손실).&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[kafka]] (event log)&lt;/li&gt;
&lt;li&gt;[[nats]] (가벼움)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>rabbitmq</category><category>amqp</category><category>message-broker</category><category>queue</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Pattern] API Gateway: BFF, 라우팅, auth, rate limit</title><link>https://shinkeonkim.com/wiki/distributed-systems/api-gateway/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/api-gateway/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; = &lt;em&gt;클라이언트와 마이크로서비스 사이의 단일 진입점&lt;/em&gt;. 라우팅, auth, rate limit, transformation, monitoring 의 &lt;em&gt;cross-cutting 책임&lt;/em&gt; 을 모음.&lt;/p&gt;
&lt;h2 id=&quot;역할&quot;&gt;역할&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client[Mobile / Web / IoT] --&gt; GW[API Gateway]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW --&gt;|/users/*| US[User Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW --&gt;|/orders/*| OS[Order Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW --&gt;|/payments/*| PS[Payment Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW -.cross-cutting.-&gt; Auth[Auth]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW -.cross-cutting.-&gt; RL[Rate Limit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW -.cross-cutting.-&gt; Log[Logging]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW -.cross-cutting.-&gt; Cache[Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW -.cross-cutting.-&gt; Transform[Request/Response transform]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;책임-카탈로그&quot;&gt;책임 카탈로그&lt;/h2&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;책임&lt;/th&gt;&lt;th&gt;예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;라우팅&lt;/td&gt;&lt;td&gt;path / header / host 기준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;인증 / 인가&lt;/td&gt;&lt;td&gt;JWT 검증, OAuth, API key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rate limiting&lt;/td&gt;&lt;td&gt;per-API key, per-IP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Transformation&lt;/td&gt;&lt;td&gt;request/response 변환 (gRPC↔REST)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;캐싱&lt;/td&gt;&lt;td&gt;응답 캐시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Load balancing&lt;/td&gt;&lt;td&gt;백엔드 분산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Circuit breaker&lt;/td&gt;&lt;td&gt;백엔드 다운 시 차단&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Logging / metrics&lt;/td&gt;&lt;td&gt;중앙 집계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WAF&lt;/td&gt;&lt;td&gt;SQLi, XSS 차단&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TLS termination&lt;/td&gt;&lt;td&gt;인증서 관리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Versioning&lt;/td&gt;&lt;td&gt;URL / 헤더 기반 분기&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;bff-backend-for-frontend&quot;&gt;BFF (Backend For Frontend)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    iOS[iOS App] --&gt; iOSGW[iOS BFF]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Web[Web App] --&gt; WebGW[Web BFF]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Android[Android App] --&gt; AnGW[Android BFF]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    iOSGW &amp;#x26; WebGW &amp;#x26; AnGW --&gt; Services[(공통 services)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;각 클라이언트 종류마다 별도 gateway&lt;/em&gt;. 클라이언트 별 &lt;em&gt;응답 형식 / 페이지네이션 / 압축&lt;/em&gt; 차이 흡수. &lt;em&gt;모바일 = 작은 응답, 웹 = 큰 응답&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;api-gateway-vs-service-mesh&quot;&gt;API Gateway vs Service Mesh&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;API Gateway&lt;/th&gt;&lt;th&gt;Service Mesh&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;위치&lt;/td&gt;&lt;td&gt;&lt;em&gt;외부 ↔ 내부 경계&lt;/em&gt; (north-south)&lt;/td&gt;&lt;td&gt;&lt;em&gt;내부 ↔ 내부&lt;/em&gt; (east-west)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;책임&lt;/td&gt;&lt;td&gt;인증, transformation, public API&lt;/td&gt;&lt;td&gt;mTLS, retry, circuit, observability&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;클라이언트 인지&lt;/td&gt;&lt;td&gt;직접 호출&lt;/td&gt;&lt;td&gt;sidecar 자동&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;예&lt;/td&gt;&lt;td&gt;Kong, AWS API Gateway, Envoy&lt;/td&gt;&lt;td&gt;Istio, Linkerd&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;둘 다 운영하는 경우&lt;/em&gt; 가 흔하다. &lt;em&gt;gateway 가 north-south&lt;/em&gt;, &lt;em&gt;mesh 가 east-west&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;도구-비교&quot;&gt;도구 비교&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;도구&lt;/th&gt;&lt;th&gt;종류&lt;/th&gt;&lt;th&gt;강점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Kong&lt;/td&gt;&lt;td&gt;OSS gateway&lt;/td&gt;&lt;td&gt;plugin 생태계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tyk&lt;/td&gt;&lt;td&gt;OSS gateway&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Envoy&lt;/td&gt;&lt;td&gt;Proxy&lt;/td&gt;&lt;td&gt;gRPC, L7, mesh 토대&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS API Gateway&lt;/td&gt;&lt;td&gt;Managed&lt;/td&gt;&lt;td&gt;Lambda 통합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cloudflare Workers&lt;/td&gt;&lt;td&gt;Edge&lt;/td&gt;&lt;td&gt;글로벌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Apigee&lt;/td&gt;&lt;td&gt;Enterprise (Google)&lt;/td&gt;&lt;td&gt;엔터프라이즈&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Apollo Router&lt;/td&gt;&lt;td&gt;GraphQL&lt;/td&gt;&lt;td&gt;federation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;패턴-graphql-federation-gateway&quot;&gt;패턴: GraphQL Federation Gateway&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt; Router[Apollo Router]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt; A[User subgraph]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt; B[Order subgraph]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt; C[Payment subgraph]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 건 [[graphql]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gateway 가 &lt;em&gt;모든 비즈니스 로직&lt;/em&gt; 흡수&lt;/strong&gt; = monolith 의 &lt;em&gt;재림&lt;/em&gt;. cross-cutting 만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단일 gateway 의 &lt;em&gt;bottleneck&lt;/em&gt;&lt;/strong&gt; = HA + auto-scaling 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth 검증 &lt;em&gt;백엔드 마다&lt;/em&gt; 다시&lt;/strong&gt; = gateway 가 검증 → 백엔드는 &lt;em&gt;trust&lt;/em&gt;. 단 &lt;em&gt;zero-trust&lt;/em&gt; 환경에서는 백엔드도 검증.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limit 의 &lt;em&gt;분산 상태&lt;/em&gt;&lt;/strong&gt; = 단일 노드 카운터 = race. 중앙 store (Redis) + sliding window.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[microservices-vs-monolith]]&lt;/li&gt;
&lt;li&gt;[[Service Mesh]] (mTLS, sidecar)&lt;/li&gt;
&lt;li&gt;[[Load Balancer]]&lt;/li&gt;
&lt;li&gt;[[JWT]], [[OAuth2]]&lt;/li&gt;
&lt;li&gt;[[graphql]] (federation)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>api-gateway</category><category>microservices</category><category>backend</category><category>infrastructure</category><author>koa (김신건)</author></item><item><title>[Pattern] CQRS: Command / Query 분리, read model</title><link>https://shinkeonkim.com/wiki/distributed-systems/cqrs/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/cqrs/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CQRS (Command Query Responsibility Segregation)&lt;/strong&gt; = &lt;em&gt;쓰기 모델&lt;/em&gt; 과 &lt;em&gt;읽기 모델&lt;/em&gt; 을 &lt;em&gt;분리&lt;/em&gt;. 같은 데이터를 &lt;em&gt;다른 형태&lt;/em&gt; 로 저장 / 조회.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
CQRS 는 &lt;em&gt;쓰기 / 읽기 모델 분리&lt;/em&gt; 라는 &lt;em&gt;추상 패턴&lt;/em&gt;. &lt;em&gt;반드시 다른 DB&lt;/em&gt; 일 필요는 없다. &lt;em&gt;대부분 시작 단계는 같은 DB + 다른 모델&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;동기&quot;&gt;동기&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Trad[전통 CRUD]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Trad --&gt; ProbW[복잡한 비즈니스 로직 + 정규화 DB]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Trad --&gt; ProbR[복잡한 join 으로 화면 그리기]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CQRS[CQRS]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CQRS --&gt; Write[Write: 정규화, 트랜잭션, 비즈니스 규칙]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CQRS --&gt; Read[Read: 화면 친화, denormalized, 빠른 조회]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;CRUD 의 &lt;em&gt;읽기 / 쓰기 사용 패턴이 매우 다른&lt;/em&gt; 워크로드 (대시보드, 추천, 검색) 에서 효과적.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;기본-구조&quot;&gt;기본 구조&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd[Command&amp;#x3C;br/&gt;CreateOrder, ConfirmOrder] --&gt; CH[Command Handler]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CH --&gt; WM[(Write Model&amp;#x3C;br/&gt;정규화, 트랜잭션)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WM --&gt;|event| EH[Event Handler / Projection]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    EH --&gt; RM[(Read Model&amp;#x3C;br/&gt;denormalized, optimized)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query[Query&amp;#x3C;br/&gt;GetOrderSummary] --&gt; QH[Query Handler]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    QH --&gt; RM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Command Side&lt;/th&gt;&lt;th&gt;Query Side&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;입력&lt;/td&gt;&lt;td&gt;비즈니스 의도 (Command)&lt;/td&gt;&lt;td&gt;정보 요청 (Query)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;검증&lt;/td&gt;&lt;td&gt;비즈니스 규칙&lt;/td&gt;&lt;td&gt;권한만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;결과&lt;/td&gt;&lt;td&gt;변경 사실 (성공/실패)&lt;/td&gt;&lt;td&gt;데이터&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;모델&lt;/td&gt;&lt;td&gt;정규화 (3NF)&lt;/td&gt;&lt;td&gt;denormalized&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB&lt;/td&gt;&lt;td&gt;RDB 가 일반&lt;/td&gt;&lt;td&gt;다양 (RDB, ES, Redis)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;강&lt;/td&gt;&lt;td&gt;보통 &lt;em&gt;eventual&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;예시-전자상거래&quot;&gt;예시: 전자상거래&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Write Model (정규화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders (id, user_id, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_items (id, order_id, product_id, qty, price);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; products (id, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Read Model (denormalized, 화면 친화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_summary_view (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  order_id, user_name, total, item_count,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  status_label, created_at, items_json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;읽기 1 쿼리:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_summary_view &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ?;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;쓰기 - 정상 트랜잭션 + event 발행:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_items ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; outbox (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;OrderCreated&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후 &lt;em&gt;event handler&lt;/em&gt; 가 &lt;em&gt;order_summary_view&lt;/em&gt; 갱신.&lt;/p&gt;
&lt;h2 id=&quot;event-sourcing-과-자주-같이&quot;&gt;Event Sourcing 과 자주 같이&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd[Command] --&gt; Agg[Aggregate]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Agg --&gt;|emit event| ES[(Event Store)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ES --&gt;|project| V1[(Read View 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ES --&gt;|project| V2[(Read View 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ES --&gt;|project| V3[(Read View 3)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query --&gt; V1 &amp;#x26; V2 &amp;#x26; V3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 건 [[event-sourcing]].&lt;/p&gt;
&lt;h2 id=&quot;read-model-종류&quot;&gt;Read Model 종류&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Read Model&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Materialized view (DB)&lt;/td&gt;&lt;td&gt;같은 RDB 안&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ElasticSearch / OpenSearch&lt;/td&gt;&lt;td&gt;검색 + aggregation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis&lt;/td&gt;&lt;td&gt;작은 hot data&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cassandra&lt;/td&gt;&lt;td&gt;시계열 / 대용량&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GraphQL cache&lt;/td&gt;&lt;td&gt;API 응답 캐시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;클라이언트 IndexedDB&lt;/td&gt;&lt;td&gt;offline-first&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;eventual-consistency&quot;&gt;Eventual Consistency&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User-&gt;&gt;API: POST /orders (command)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API-&gt;&gt;WriteDB: INSERT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WriteDB--&gt;&gt;API: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API--&gt;&gt;User: 200 OK (order_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over API,ReadDB: 비동기 projection 진행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User-&gt;&gt;API: GET /orders/123 (query)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API-&gt;&gt;ReadDB: SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ReadDB--&gt;&gt;API: NOT FOUND? (projection lag)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;방금 만든 주문이 안 보이는&lt;/em&gt; UX 함정. 해결책:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;짧은 lag&lt;/em&gt; (수 ms) 보장 + UI 가 &lt;em&gt;낙관적 표시&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;write 후 즉시 같은 transaction 으로 read&lt;/em&gt; 시 &lt;em&gt;write DB 우회&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;작성자 본인은 강 일관성, 다른 사람은 eventual&lt;/em&gt; (read-your-writes)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;언제-cqrs-안-쓸-때가-더-많다&quot;&gt;언제 CQRS? (안 쓸 때가 더 많다)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{비즈니스 도메인 복잡?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|아니오| Simple[CRUD 충분]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|예| Q2{읽기 / 쓰기 패턴 매우 다름?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|아니오| Simple&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|예| Q3{팀이 ES + 분산 시스템 이해?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|아니오| Risk[학습 비용 큼 → 천천히]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|예| CQRS[CQRS 시작]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;대부분의 앱은 CQRS 가 &lt;em&gt;과한&lt;/em&gt; 솔루션&lt;/em&gt;. &lt;em&gt;진짜 도움 되는 곳&lt;/em&gt; (큰 e-commerce, 트레이딩 시스템, 큰 SaaS 대시보드) 가 아니면 &lt;em&gt;모듈 모놀리스 + 일부 read model&lt;/em&gt; 정도면 충분.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;단순-cqrs-가벼운-형태&quot;&gt;단순 CQRS (가벼운 형태)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd[Command Handler&amp;#x3C;br/&gt;POST /orders] --&gt; Write[(DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Write --&gt;|trigger / event| Mat[Materialized View&amp;#x3C;br/&gt;갱신]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query[Query Handler&amp;#x3C;br/&gt;GET /orders/dashboard] --&gt; Mat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;같은 DB.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;materialized view&lt;/em&gt; 또는 &lt;em&gt;summary table&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;별도 인프라 없이 &lt;em&gt;80% 의 효과&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;모든 곳에 CQRS&lt;/strong&gt; = 단순 CRUD 도 분리. 복잡도 폭증.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Projection 동기 실행&lt;/strong&gt; = read model 갱신이 &lt;em&gt;write 막음&lt;/em&gt;. async + idempotent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-your-writes 무시&lt;/strong&gt; = 사용자가 &lt;em&gt;방금 한 일을 못 봄&lt;/em&gt; UX.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read model 재구성 어려움&lt;/strong&gt; = event log 없으면 &lt;em&gt;replay 불가&lt;/em&gt;. ES + CQRS 가 자주 묶이는 이유.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[event-sourcing]]&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[saga-pattern]]&lt;/li&gt;
&lt;li&gt;[[microservices-vs-monolith]]&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]] (read model 캐시)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>cqrs</category><category>architecture</category><category>pattern</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Pattern] Event Sourcing: 상태 대신 이벤트 누적</title><link>https://shinkeonkim.com/wiki/distributed-systems/event-sourcing/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/event-sourcing/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt; = &lt;em&gt;현재 상태 대신 &lt;em&gt;과거 이벤트 시퀀스&lt;/em&gt; 를 저장&lt;/em&gt;. 현재 상태 = &lt;em&gt;event 모두 replay 한 결과&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;전통: state = { balance: 100 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ES:   events = [Deposited(50), Deposited(60), Withdrawn(10)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      state = events.reduce(apply) = { balance: 100 }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-약속&quot;&gt;핵심 약속&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;과거 모든 변화 기록&lt;/em&gt;. 감사 / 디버깅 / 시간 여행 자유.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;읽기 모델 자유&lt;/em&gt; (어떻게든 projection).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;replay 로 새 view 생성&lt;/em&gt; (옛 데이터에서 새 통찰).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;비파괴&lt;/em&gt;. 옛 이벤트는 &lt;em&gt;불변&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;구조&quot;&gt;구조&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd[Command&amp;#x3C;br/&gt;Deposit 50] --&gt; Agg[Aggregate]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Agg --&gt;|emit| Event[&quot;Deposited(50)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Event --&gt; Store[(Event Store&amp;#x3C;br/&gt;append-only)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|project| V1[(Balance View)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|project| V2[(Audit View)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|project| V3[(Search View)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;event-store&quot;&gt;Event Store&lt;/h2&gt;
&lt;p&gt;이벤트 = 불변 사실. 형식:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;event_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;evt_abc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aggregate_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;account_42&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aggregate_type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Account&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;event_type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Deposited&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;payload&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;amount&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;currency&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;USD&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;metadata&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;correlation_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;occurred_at&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:00:00Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Store&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;log + retention&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EventStoreDB&lt;/td&gt;&lt;td&gt;ES 전용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PostgreSQL&lt;/td&gt;&lt;td&gt;append-only 테이블 + outbox&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DynamoDB&lt;/td&gt;&lt;td&gt;partition key = aggregate_id&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Axon&lt;/td&gt;&lt;td&gt;Java 프레임워크&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;replay-재계산&quot;&gt;Replay (재계산)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Store as Event Store&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Store: load events for aggregate_id=42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store--&gt;&gt;App: [e1, e2, e3, e4, e5]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;App: state = e1.apply(empty)&amp;#x3C;br/&gt;state = e2.apply(state)&amp;#x3C;br/&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over App: state 복원 완료&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;snapshot&quot;&gt;Snapshot&lt;/h2&gt;
&lt;p&gt;이벤트 수 만이면 &lt;em&gt;replay 가 느림&lt;/em&gt;. 주기적 &lt;em&gt;snapshot&lt;/em&gt; 저장:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store[(Event Store)] --&gt; Snap[(Snapshot Store)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note1[&quot;Snapshot at version 1000:&amp;#x3C;br/&gt;{ balance: 5000 }&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Load[Load aggregate] --&gt; Snap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Snap --&gt;|version 1000 부터| Replay[event 1001 ~ 현재 replay]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; load_aggregate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    snapshot &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; snapshot_store.latest(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_store.from_version(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, snapshot.version)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    state &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; snapshot.state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        state &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e.apply(state)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; state&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;cqrs-와의-자연-결합&quot;&gt;CQRS 와의 자연 결합&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd --&gt; Write[Write side]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Write --&gt; Store[(Event Store)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|stream| P1[Projection 1] --&gt; View1[(Read View 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|stream| P2[Projection 2] --&gt; View2[(Read View 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt;|stream| P3[Projection 3] --&gt; View3[(Read View 3)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query --&gt; View1 &amp;#x26; View2 &amp;#x26; View3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 건 [[cqrs]].&lt;/p&gt;
&lt;h2 id=&quot;새-view-추가&quot;&gt;새 View 추가&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;기존 데이터에서 새 인사이트&lt;/em&gt; 가 필요할 때:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Dev-&gt;&gt;Code: 새 projection 함수 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Code-&gt;&gt;Store: from version 0 부터 replay&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store--&gt;&gt;Code: 모든 이벤트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Code-&gt;&gt;NewView: 새 view 채우기 (수 시간 ~ 일)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Code,NewView: 이후 실시간 stream 으로 유지&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
이게 &lt;em&gt;ES 의 가장 큰 매력&lt;/em&gt;. &lt;em&gt;과거에 안 묻던 질문&lt;/em&gt; 을 &lt;em&gt;나중에 데이터로&lt;/em&gt; 만들 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;적합--부적합&quot;&gt;적합 / 부적합&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good[적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G1[&quot;감사 추적 필수 (금융, 의료)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G2[과거 시점 상태 조회]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G3[&quot;복잡 비즈니스 (주문 흐름)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G4[CQRS 가 자연스러운 도메인]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad[부적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B1[단순 CRUD]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B2[&quot;GDPR 의 *잊혀질 권리* (이벤트 불변과 충돌)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B3[빠른 prototype]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;함정-이벤트-진화&quot;&gt;함정: 이벤트 진화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v1: Deposited(amount: int)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v2: Deposited(amount: int, currency: string)   ← currency 추가&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;옛 이벤트는 &lt;em&gt;currency 가 없다&lt;/em&gt;. 처리 시:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;전략&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Default value&lt;/td&gt;&lt;td&gt;옛 이벤트에 currency=“USD” 가정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Up-cast&lt;/td&gt;&lt;td&gt;load 시 옛 → 새 형태로 변환&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Lazy migration&lt;/td&gt;&lt;td&gt;처리 시점에 보강&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Re-write event (안 권장)&lt;/td&gt;&lt;td&gt;&lt;em&gt;불변 원칙 위반&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;gdpr-의-잊혀질-권리-문제&quot;&gt;GDPR 의 &lt;em&gt;잊혀질 권리&lt;/em&gt; 문제&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[GDPR: 사용자 데이터 삭제 요구]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Issue[ES: 이벤트는 불변 → 어떻게 지움?]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Issue --&gt; S1[&quot;1. Crypto-shredding: 개인정보 *암호화*, key 만 삭제&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Issue --&gt; S2[&quot;2. Event redaction: 특정 필드 null 처리&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Issue --&gt; S3[&quot;3. 별도 저장: 개인정보는 가변 DB, 이벤트는 reference&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;모든 도메인에 ES&lt;/strong&gt; = 단순 CRUD 까지 &lt;em&gt;복잡 모델&lt;/em&gt;. 진짜 도메인 가치 있는 곳에만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 schema &lt;em&gt;영구 보존&lt;/em&gt; 안 함&lt;/strong&gt; = 1년 뒤 옛 이벤트 &lt;em&gt;해석 불가&lt;/em&gt;. 스키마 진화 정책.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 크기 폭증&lt;/strong&gt; = 작은 이벤트 + 정기 snapshot. 큰 binary 는 reference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggregate 경계 잘못&lt;/strong&gt; = aggregate 가 &lt;em&gt;너무 크면&lt;/em&gt; concurrent write 충돌, 너무 작으면 일관성 사라짐.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[cqrs]]&lt;/li&gt;
&lt;li&gt;[[saga-pattern]]&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[kafka]] (이벤트 저장)&lt;/li&gt;
&lt;li&gt;[[Redis Streams]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>event-sourcing</category><category>cqrs</category><category>architecture</category><category>pattern</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Pattern] Idempotency Keys: 중복 요청 안전 처리</title><link>https://shinkeonkim.com/wiki/distributed-systems/idempotency-keys/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/idempotency-keys/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Idempotency&lt;/strong&gt; = &lt;em&gt;같은 요청을 N번 보내도 결과가 1번과 동일&lt;/em&gt;. 분산 시스템 / 결제 / API 의 &lt;em&gt;안전망&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
네트워크는 &lt;em&gt;항상 timeout / retry&lt;/em&gt;. 진짜 &lt;em&gt;exactly-once&lt;/em&gt; 는 불가능 → &lt;em&gt;at-least-once + idempotent&lt;/em&gt; 가 &lt;em&gt;현실적 정답&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;http-의-idempotent-method&quot;&gt;HTTP 의 idempotent method&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Idempotent&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;GET&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HEAD&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OPTIONS&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PUT&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DELETE&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;POST&lt;/td&gt;&lt;td&gt;&lt;em&gt;X (기본)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PATCH&lt;/td&gt;&lt;td&gt;&lt;em&gt;X (대개)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;POST 의 &lt;em&gt;비-멱등성&lt;/em&gt; 이 &lt;em&gt;결제 중복&lt;/em&gt; 같은 사고의 근원.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;idempotency-key-header&quot;&gt;Idempotency-Key Header&lt;/h2&gt;
&lt;p&gt;Stripe / Square / PayPal 등이 &lt;em&gt;표준화&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /v1/charges &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Authorization&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Bearer sk_...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Idempotency-Key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 7f4d8e2a-c-3a-4e-9f-1234567890ab&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;amount&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;currency&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;usd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;customer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cus_...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 응답:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1번째 호출: 200 OK (실제 처리)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2번째 호출 (같은 key): 200 OK (옛 응답 그대로)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;서버-구현&quot;&gt;서버 구현&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant API&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Store as Idempotency Store&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant DB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;API: POST + Idempotency-Key: abc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API-&gt;&gt;Store: GET key abc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt 처음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Store--&gt;&gt;API: not found&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API-&gt;&gt;Store: SET key abc IN_PROGRESS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API-&gt;&gt;DB: 실제 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        DB--&gt;&gt;API: 결과&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API-&gt;&gt;Store: SET key abc COMPLETED + 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API--&gt;&gt;Client: 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    else 옛 응답 있음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Store--&gt;&gt;API: COMPLETED + 옛 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API--&gt;&gt;Client: 같은 옛 응답 (실제 처리 없음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    else 진행 중&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Store--&gt;&gt;API: IN_PROGRESS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        API--&gt;&gt;Client: 409 Conflict (같은 key 중복)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idempotent_post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key, request, handler):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; store.get(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached.status &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached.response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached.status &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;in_progress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        raise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Conflict(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Same key in progress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    store.set(key, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;in_progress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; handler(request)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        store.set(key, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Exception&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        store.delete(key)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 또는 &quot;failed&quot; 로 표시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        raise&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;store-선택&quot;&gt;Store 선택&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Store&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Redis&lt;/td&gt;&lt;td&gt;빠름, TTL 자동&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PostgreSQL&lt;/td&gt;&lt;td&gt;트랜잭션 친화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DynamoDB&lt;/td&gt;&lt;td&gt;분산 + TTL&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;PostgreSQL 의 UNIQUE constraint&lt;/em&gt; + ON CONFLICT 가 &lt;em&gt;가장 단순&lt;/em&gt;. &lt;em&gt;결제 같이 강한 일관성&lt;/em&gt; 필요할 때.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idempotency_keys&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  request_hash &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BYTEA&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  response JSONB,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  status&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  expires_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idempotency_keys (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, request_hash, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (?, ?, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;in_progress&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CONFLICT (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) DO NOTHING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;RETURNING ...;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;request-hashing-재시도-안전&quot;&gt;Request Hashing (재시도 안전)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; request_hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(req):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sha256(req.method &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.body).hexdigest()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;같은 key + 다른 body = &lt;em&gt;수상&lt;/em&gt;. 거절 (400) 또는 &lt;em&gt;옛 응답&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;같은 key + 같은 body = &lt;em&gt;재시도&lt;/em&gt;. 옛 응답.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ttl-정책&quot;&gt;TTL 정책&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;기간&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;24시간&lt;/td&gt;&lt;td&gt;Stripe 표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7일&lt;/td&gt;&lt;td&gt;더 안전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;영구&lt;/td&gt;&lt;td&gt;결제처럼 &lt;em&gt;극한 안전&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;짧으면&lt;/em&gt; 재시도 보호 부족. &lt;em&gt;길면&lt;/em&gt; store 부담.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;분산--큐의-idempotency&quot;&gt;분산 / 큐의 idempotency&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Producer] --&gt;|publish + msg_id| Broker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker --&gt; C[Consumer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|이미 처리?| Inbox[(Inbox table)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inbox --&gt;|YES| Skip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inbox --&gt;|NO| Process[처리 + 기록]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 건 [[outbox-pattern]] 의 &lt;em&gt;Inbox&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;멱등한-작업-만드는-패턴&quot;&gt;멱등한 작업 만드는 패턴&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;작업&lt;/th&gt;&lt;th&gt;멱등으로 만드는 방법&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;이메일 발송&lt;/td&gt;&lt;td&gt;&lt;em&gt;deduplication store&lt;/em&gt; + 발송 전 체크&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB INSERT&lt;/td&gt;&lt;td&gt;UNIQUE constraint + ON CONFLICT&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;계좌 송금&lt;/td&gt;&lt;td&gt;transaction_id 컬럼 + UNIQUE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Webhook 수신&lt;/td&gt;&lt;td&gt;event_id + 처리 표시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API 호출&lt;/td&gt;&lt;td&gt;Idempotency-Key 헤더&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클라이언트가 &lt;em&gt;매번 새 key 생성&lt;/em&gt;&lt;/strong&gt; = idempotency 의미 &lt;em&gt;없음&lt;/em&gt;. retry 시 &lt;em&gt;같은 key 재사용&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;key 충돌&lt;/strong&gt; = UUID v4 사용. 클라이언트 라이브러리가 자동 생성.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL 너무 짧음&lt;/strong&gt; = 24시간 뒤 &lt;em&gt;재시도&lt;/em&gt; 시 &lt;em&gt;중복 처리&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;in-progress 처리&lt;/strong&gt; = 그동안 동일 key 가 또 오면 &lt;em&gt;대기 vs 거절&lt;/em&gt; 정책. 보통 409.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[REST API Design]]&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]]&lt;/li&gt;
&lt;li&gt;[[saga-pattern]]&lt;/li&gt;
&lt;li&gt;[[kafka]], [[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>idempotency</category><category>pattern</category><category>api-design</category><category>backend</category><category>distributed</category><author>koa (김신건)</author></item><item><title>[Pattern] Microservices vs Monolith: 언제 분리, 언제 통합</title><link>https://shinkeonkim.com/wiki/distributed-systems/microservices-vs-monolith/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/microservices-vs-monolith/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Monolith&lt;/th&gt;&lt;th&gt;Modular Monolith&lt;/th&gt;&lt;th&gt;Microservices&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;배포 단위&lt;/td&gt;&lt;td&gt;1개&lt;/td&gt;&lt;td&gt;1개 (모듈 명확)&lt;/td&gt;&lt;td&gt;N개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;데이터&lt;/td&gt;&lt;td&gt;단일 DB&lt;/td&gt;&lt;td&gt;단일 DB (스키마 분리)&lt;/td&gt;&lt;td&gt;DB per service&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;통신&lt;/td&gt;&lt;td&gt;함수 호출&lt;/td&gt;&lt;td&gt;함수 호출&lt;/td&gt;&lt;td&gt;네트워크 (HTTP/gRPC)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 복잡도&lt;/td&gt;&lt;td&gt;&lt;em&gt;낮음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;&lt;em&gt;높음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;스케일링&lt;/td&gt;&lt;td&gt;전체 함께&lt;/td&gt;&lt;td&gt;전체 함께&lt;/td&gt;&lt;td&gt;service 별 독립&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;장애 격리&lt;/td&gt;&lt;td&gt;약함&lt;/td&gt;&lt;td&gt;약함&lt;/td&gt;&lt;td&gt;강함 (이상적)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합 팀&lt;/td&gt;&lt;td&gt;&amp;#x3C; 30&lt;/td&gt;&lt;td&gt;&amp;#x3C; 100&lt;/td&gt;&lt;td&gt;100+&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;결정-트리&quot;&gt;결정 트리&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{팀 크기}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&amp;#x3C; 10| Mono[Monolith]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|10-50| Q2{성장 속도?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|느림| Mono&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|빠름| MM[Modular Monolith]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|50-200| MM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|200+| MS[Microservices]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    MM --&gt;|&quot;진짜 필요해지면&quot;| MS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;시작은-monolith-fowler-의-monolith-first&quot;&gt;시작은 Monolith (Fowler 의 “Monolith First”)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start[새 프로젝트] --&gt; Mono[Monolith로 시작]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Mono --&gt; Grow[성장 + 도메인 명확]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Grow --&gt; MM[Modular Monolith]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    MM --&gt; Carve[필요시 service 분리]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Carve --&gt; MS[Microservices 일부]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;대부분의 새 프로젝트는 monolith 로 시작&lt;/em&gt; 이 정답. &lt;em&gt;마이크로서비스로 시작&lt;/em&gt; 한 팀이 &lt;em&gt;2년 후 distributed monolith&lt;/em&gt; 가 되는 사례가 &lt;em&gt;압도적&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;microservices-가-진짜-도움될-때&quot;&gt;Microservices 가 &lt;em&gt;진짜&lt;/em&gt; 도움될 때&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{독립 배포 절실?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|예| C1[조건 1 ✓]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2{팀 별 자율성 절실?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|예| C2[조건 2 ✓]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3{스케일 차이 큼?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|예| C3[조건 3 ✓]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1 &amp;#x26; C2 &amp;#x26; C3 --&gt;|모두 ✓| Go[Microservices 도입]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;distributed-monolith-안티-패턴&quot;&gt;Distributed Monolith (안티 패턴)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[Service A] --&gt;|동기 호출| B[Service B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|동기 호출| C[Service C]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|동기 호출| D[Service D]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note1[하나 다운 = 전체 다운]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;증상:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 service 가 &lt;em&gt;함께 배포&lt;/em&gt; 되어야 함 (calling contract 변경 시)&lt;/li&gt;
&lt;li&gt;한 service 다운 = &lt;em&gt;전체 다운&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;분산의 비용 + monolith 의 결합&lt;/em&gt; 동시&lt;/li&gt;
&lt;li&gt;&lt;em&gt;코드 변경 영향이 service 경계 넘김&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;마이크로서비스의 최악의 결과&lt;/strong&gt;. &lt;em&gt;monolith 보다 모든 면에서 나쁨&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;service-boundary-가르는-기준&quot;&gt;Service Boundary 가르는 기준&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Best[좋은 boundary]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Best --&gt; B1[&quot;Domain (DDD bounded context)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Best --&gt; B2[&quot;Team ownership (Conway&apos;s Law)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Best --&gt; B3[데이터 모델 자연 분리]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad[나쁜 boundary]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; X1[&quot;기술 계층 (API / DB / UI)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; X2[&quot;유사 데이터 기준 (DB 테이블 1:1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; X3[&quot;CRUD 단위 (UserService, OrderService 의 진짜 분리는 어디서?)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conways-law&quot;&gt;Conway’s Law&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“시스템 구조는 그것을 만든 조직의 의사소통 구조를 닮는다”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Team1[&quot;Team A&amp;#x3C;br/&gt;(독립 의사소통)&quot;] --&gt; Service1[Service A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Team2[&quot;Team B&amp;#x3C;br/&gt;(독립 의사소통)&quot;] --&gt; Service2[Service B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Team3[&quot;Team C&amp;#x3C;br/&gt;(독립 의사소통)&quot;] --&gt; Service3[Service C]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Team1 -.협업.-&gt; Team2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service1 -.API.-&gt; Service2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;팀 경계와 service 경계의 일치&lt;/em&gt; 가 &lt;em&gt;Conway’s Law 의 실용적 활용&lt;/em&gt;. 한 service 가 &lt;em&gt;두 팀에 걸치면&lt;/em&gt; 운영 사고.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;inverse-conway-maneuver&quot;&gt;Inverse Conway Maneuver&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;반대로 service 구조부터 정해 → 팀 구조 맞추기&lt;/em&gt;. &lt;em&gt;큰 조직 재편&lt;/em&gt; 에서 사용.&lt;/p&gt;
&lt;h2 id=&quot;분리-비용-카탈로그&quot;&gt;분리 비용 카탈로그&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost[Microservices 의 추가 비용]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Net[네트워크 호출]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Obs[&quot;관측성 (Tracing 필수)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Auth[mTLS / Service Mesh]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Data[&quot;데이터 일관성 (Saga)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Deploy[K8s, CI/CD 복잡도]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Test[Contract / E2E 테스트]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cost --&gt; Skill[엔지니어 학습 비용]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;모듈-모놀리스-modular-monolith&quot;&gt;모듈 모놀리스 (Modular Monolith)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph App[Single deployable]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        U[User module]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        O[Order module]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P[Payment module]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U -.interface only.-&gt; O&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O -.interface only.-&gt; P&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App --&gt; DB[(단일 DB&amp;#x3C;br/&gt;스키마 분리)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;모듈 간 직접 호출 금지&lt;/em&gt;. &lt;em&gt;interface만&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;같은 DB, &lt;em&gt;다른 스키마&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;나중에 service 분리 쉬움&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대부분의 회사가 결국 여기서 멈춤&lt;/strong&gt; (충분).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;너무 일찍 분리&lt;/strong&gt; = 도메인 잘못 잡고 &lt;em&gt;분리 후 재경계&lt;/em&gt;. 가장 큰 후회.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DB 공유&lt;/strong&gt; = “Service 단위 분리” 가 &lt;em&gt;DB 결합&lt;/em&gt; 으로 &lt;em&gt;무의미&lt;/em&gt;. 각 service 자기 DB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분산 트랜잭션 (2PC)&lt;/strong&gt; = 마이크로서비스의 &lt;em&gt;영혼&lt;/em&gt;. Saga 패턴으로 대체.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shared library 의 &lt;em&gt;영원한 결합&lt;/em&gt;&lt;/strong&gt; = service A 가 라이브러리 v2 로 못 올라가면 &lt;em&gt;모든 service&lt;/em&gt; 정체.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[api-gateway]]&lt;/li&gt;
&lt;li&gt;[[saga-pattern]]&lt;/li&gt;
&lt;li&gt;[[cqrs]], [[event-sourcing]]&lt;/li&gt;
&lt;li&gt;[[Service Mesh]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-distributed-transaction]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>microservices</category><category>monolith</category><category>architecture</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Pattern] Outbox Pattern: DB + 메시지의 원자성</title><link>https://shinkeonkim.com/wiki/distributed-systems/outbox-pattern/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/outbox-pattern/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Outbox Pattern&lt;/strong&gt; = &lt;em&gt;DB 변경 + 메시지 발행&lt;/em&gt; 의 &lt;em&gt;원자성 보장&lt;/em&gt;. &lt;em&gt;이중 쓰기 (dual write) 문제&lt;/em&gt; 의 표준 해결.&lt;/p&gt;
&lt;h2 id=&quot;문제-dual-write&quot;&gt;문제: Dual Write&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant DB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Broker as Kafka/RabbitMQ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;DB: BEGIN&amp;#x3C;br/&gt;INSERT order&amp;#x3C;br/&gt;COMMIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB--&gt;&gt;App: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Broker: publish OrderCreated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over App,Broker: ✗ App crash, 또는&amp;#x3C;br/&gt;Broker timeout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker--xApp: 응답 없음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over DB,Broker: DB 는 INSERT 됨&amp;#x3C;br/&gt;이벤트는 안 감 → *불일치*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;시나리오&lt;/th&gt;&lt;th&gt;결과&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;DB OK + Publish OK&lt;/td&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB OK + Publish fail&lt;/td&gt;&lt;td&gt;&lt;em&gt;불일치&lt;/em&gt; (DB 만 변경)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB fail + Publish OK&lt;/td&gt;&lt;td&gt;&lt;em&gt;불일치&lt;/em&gt; (이벤트만 갔음)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;둘 다 fail&lt;/td&gt;&lt;td&gt;OK (취소)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;→ &lt;em&gt;두 시스템을 한 번에 일관&lt;/em&gt; 시키는 건 &lt;em&gt;불가능&lt;/em&gt;. &lt;em&gt;2PC&lt;/em&gt; 는 &lt;em&gt;비현실적&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;해결-outbox-테이블&quot;&gt;해결: Outbox 테이블&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App --&gt;|단일 트랜잭션| DB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB --&gt; Orders[(orders)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB --&gt; Outbox[(outbox)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Outbox -.relay.-&gt; Broker[(Broker)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders (id, ...) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (?, ...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; outbox (event_type, payload, aggregate_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;OrderCreated&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ?, ?);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 한 트랜잭션 = 원자성!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후 &lt;em&gt;별도 프로세스&lt;/em&gt; 가 outbox 를 읽고 &lt;em&gt;broker 로 publish&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;2가지-relay-방식&quot;&gt;2가지 Relay 방식&lt;/h2&gt;
&lt;h3 id=&quot;1-polling-publisher&quot;&gt;1. Polling Publisher&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Outbox[(outbox)] -.SELECT unpublished.-&gt; Relay[Relay process]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Relay --&gt;|publish| Broker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Relay --&gt;|UPDATE published=true| Outbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    rows &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; db.query(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SELECT * FROM outbox WHERE published=false LIMIT 100&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; row &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rows:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        broker.publish(row.event)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        db.execute(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;UPDATE outbox SET published=true WHERE id=?&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, row.id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;구현 간단.&lt;/li&gt;
&lt;li&gt;DB 부담 (polling).&lt;/li&gt;
&lt;li&gt;Latency ≈ polling 주기.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-cdc-change-data-capture&quot;&gt;2. CDC (Change Data Capture)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB[(DB)] --&gt;|WAL / binlog| CDC[Debezium / Maxwell]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CDC --&gt; Kafka[(Kafka)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Kafka --&gt;|topic| Consumers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DB 의 &lt;em&gt;transaction log (PostgreSQL WAL / MySQL binlog)&lt;/em&gt; 를 &lt;em&gt;실시간 캡처&lt;/em&gt;.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;도구&lt;/th&gt;&lt;th&gt;DB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Debezium&lt;/td&gt;&lt;td&gt;PG, MySQL, MongoDB, Oracle 등&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Maxwell&lt;/td&gt;&lt;td&gt;MySQL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS DMS&lt;/td&gt;&lt;td&gt;다양&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Striim&lt;/td&gt;&lt;td&gt;다양&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Outbox + CDC&lt;/em&gt; 는 &lt;em&gt;2026 시점 마이크로서비스 메시징의 표준&lt;/em&gt;. 더 이상 &lt;em&gt;애플리케이션 코드에서 publish&lt;/em&gt; 하지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;outbox-테이블-디자인&quot;&gt;Outbox 테이블 디자인&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; outbox&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  aggregate_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &apos;Order&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  aggregate_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &apos;order_42&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  event_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,             &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &apos;OrderCreated&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  payload JSONB &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  occurred_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOW&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  published &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BOOLEAN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  published_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_outbox_unpublished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; outbox(id) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; published &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;partial index&lt;/em&gt; 로 &lt;em&gt;unpublished 만 빠른 SELECT&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;처리-후-정리&quot;&gt;처리 후 정리&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Outbox 너무 큼?]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt; O1[옵션 1: published row 즉시 DELETE]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt; O2[옵션 2: 별도 archive 테이블]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt; O3[&quot;옵션 3: TTL (며칠 보관)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CDC 가 &lt;em&gt;binlog stream 위주&lt;/em&gt; 라 &lt;em&gt;published 컬럼 자체 필요 없음&lt;/em&gt;. Debezium 이 &lt;em&gt;INSERT 이벤트 그대로&lt;/em&gt; 전송.&lt;/p&gt;
&lt;h2 id=&quot;메시지-순서-보장&quot;&gt;메시지 순서 보장&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Tx1[Tx 1: INSERT outbox id=100] --&gt; Order&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Tx2[&quot;Tx 2: INSERT outbox id=99 (먼저 시작했지만 commit 늦음)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Tx2 --&gt; Order&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[&quot;DB ID 순 ≠ commit 순&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;concurrent transaction&lt;/em&gt; 때문에 &lt;code&gt;id&lt;/code&gt; 순서 ≠ &lt;em&gt;commit 순서&lt;/em&gt;. &lt;em&gt;strict 순서 보장&lt;/em&gt; 이 필요하면 &lt;em&gt;aggregate ID 별 partition&lt;/em&gt; 으로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;at-least-once--idempotency&quot;&gt;At-least-once + Idempotency&lt;/h2&gt;
&lt;p&gt;Outbox + relay 는 &lt;em&gt;at-least-once&lt;/em&gt;. &lt;em&gt;중복 발행&lt;/em&gt; 가능 (relay 가 publish 후 update 전 crash).&lt;/p&gt;
&lt;p&gt;→ &lt;em&gt;consumer 가 idempotent 처리&lt;/em&gt;. 자세한 건 [[idempotency-keys]].&lt;/p&gt;
&lt;h2 id=&quot;inbox-pattern-반대-방향&quot;&gt;Inbox Pattern (반대 방향)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Broker --&gt;|consume| Inbox[(inbox)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inbox --&gt;|단일 트랜잭션| DB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB --&gt;|이미 처리?| Skip[중복 skip]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;수신 측의 &lt;em&gt;멱등 보장&lt;/em&gt;. Outbox 와 &lt;em&gt;대칭&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;*&lt;em&gt;&lt;code&gt;UPDATE published=true&lt;/code&gt; 와 &lt;em&gt;publish 가 &lt;em&gt;별도 transaction&lt;/em&gt;&lt;/em&gt;&lt;/em&gt; = relay crash 시 중복 발행. &lt;em&gt;at-least-once 인정&lt;/em&gt; + consumer idempotent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbox 의 &lt;em&gt;순서 무시&lt;/em&gt;&lt;/strong&gt; = 다운스트림이 순서 가정. partition / sequence 명시.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbox 가 &lt;em&gt;너무 큼&lt;/em&gt;&lt;/strong&gt; = 정리 필요. 보관 정책.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SELECT FOR UPDATE SKIP LOCKED&lt;/code&gt;&lt;/strong&gt; 없이 &lt;em&gt;multi worker&lt;/em&gt; = &lt;em&gt;같은 row 동시 처리&lt;/em&gt; → 중복.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[kafka]], [[rabbitmq]]&lt;/li&gt;
&lt;li&gt;[[saga-pattern]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;li&gt;[[event-sourcing]]&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>outbox</category><category>messaging</category><category>cdc</category><category>distributed</category><category>pattern</category><author>koa (김신건)</author></item><item><title>[Pattern] Saga: 분산 트랜잭션의 보상 흐름</title><link>https://shinkeonkim.com/wiki/distributed-systems/saga-pattern/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/saga-pattern/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Saga&lt;/strong&gt; = &lt;em&gt;여러 service 의 로컬 트랜잭션&lt;/em&gt; 을 &lt;em&gt;연결한 긴 흐름&lt;/em&gt;. 한 단계 실패 시 &lt;em&gt;이전 단계의 보상 (compensating) 트랜잭션&lt;/em&gt; 으로 &lt;em&gt;논리적 롤백&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Saga 는 &lt;em&gt;ACID 트랜잭션이 아니다&lt;/em&gt;. 분산 환경에서 &lt;em&gt;2PC (XA)&lt;/em&gt; 가 비현실적이라 &lt;em&gt;eventually consistent + 보상&lt;/em&gt; 으로 대체.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;시나리오-주문&quot;&gt;시나리오: 주문&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant O as Order Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant P as Payment Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant I as Inventory Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S as Shipping Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;O: T1: 주문 생성 (pending)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;P: T2: 결제 요청&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P--&gt;&gt;O: 결제 완료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;I: T3: 재고 차감&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I--&gt;&gt;O: 차감 완료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;S: T4: 배송 요청&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;O: 배송 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;O: T5: 주문 confirmed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실패 시 보상:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;P: T2 결제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P--&gt;&gt;O: ✓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;I: T3 재고&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I--&gt;&gt;O: 실패 (재고 부족)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;P: C2: 결제 환불 (보상)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O-&gt;&gt;O: C1: 주문 취소 (보상)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;Compensating&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;T1 주문 생성&lt;/td&gt;&lt;td&gt;C1 주문 취소&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T2 결제&lt;/td&gt;&lt;td&gt;C2 환불&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T3 재고 차감&lt;/td&gt;&lt;td&gt;C3 재고 복원&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T4 배송&lt;/td&gt;&lt;td&gt;C4 배송 취소&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;두-스타일-choreography-vs-orchestration&quot;&gt;두 스타일: Choreography vs Orchestration&lt;/h2&gt;
&lt;h3 id=&quot;1-choreography-이벤트-기반&quot;&gt;1. Choreography (이벤트 기반)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O[Order] --&gt;|OrderCreated| P[Payment]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|PaymentSucceeded| I[Inventory]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I --&gt;|StockReserved| S[Shipping]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S --&gt;|Shipped| O&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|PaymentFailed| O&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I --&gt;|StockUnavailable| P&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;각 service 가 &lt;em&gt;event 듣고 자기 일&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;중앙 제어자 없음.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;유연&lt;/em&gt;, &lt;em&gt;낮은 결합&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;흐름 추적 어려움&lt;/em&gt; (어디까지 갔는지).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-orchestration-중앙-코디네이터&quot;&gt;2. Orchestration (중앙 코디네이터)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord[Saga Orchestrator] --&gt; O[Order]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O --&gt;|성공| Coord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord --&gt; P[Payment]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|성공| Coord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord --&gt; I[Inventory]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I --&gt;|실패| Coord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord --&gt;|보상| P&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord --&gt;|보상| O&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;중앙 상태 기계&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;흐름 &lt;em&gt;명확&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;결합도 약간 높음&lt;/em&gt; (orchestrator 가 모든 service 안)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;문제 추적 쉬움&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Choreography&lt;/th&gt;&lt;th&gt;Orchestration&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;결합도&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;약간 높음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;흐름 추적&lt;/td&gt;&lt;td&gt;&lt;em&gt;어려움&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;쉬움&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;디버깅&lt;/td&gt;&lt;td&gt;어려움&lt;/td&gt;&lt;td&gt;쉬움&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합&lt;/td&gt;&lt;td&gt;단순 흐름 (3-4 단계)&lt;/td&gt;&lt;td&gt;복잡 흐름 (5+ 단계)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 도구&lt;/td&gt;&lt;td&gt;event tracer&lt;/td&gt;&lt;td&gt;상태 기계 시각화&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;3-4 단계까지는 choreography&lt;/em&gt;, &lt;em&gt;그 이상은 orchestration&lt;/em&gt; 이 일반 권장. Netflix 도 &lt;em&gt;Conductor&lt;/em&gt; 라는 orchestrator 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;보상-트랜잭션의-함정&quot;&gt;보상 트랜잭션의 함정&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[보상이 *항상 가능* 한가?]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; X1[&quot;✗ 이메일 발송 (취소 불가)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; X2[&quot;✗ SMS / push 알림&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; X3[&quot;✗ 외부 API 호출 (paypal 환불 한도)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Sol1[&quot;→ 가능한 단계만 saga 안&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Sol2[&quot;→ 비가역 단계는 saga *마지막* 으로&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;비가역 작업&lt;/em&gt; (이메일, push, 외부 API) 은 &lt;em&gt;saga 가 commit 결정 후&lt;/em&gt; 실행. 그 전에 실행하면 &lt;em&gt;보상 불가&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;멱등성--idempotency-key&quot;&gt;멱등성 + Idempotency Key&lt;/h2&gt;
&lt;p&gt;각 단계 / 보상은 &lt;em&gt;멱등 (idempotent)&lt;/em&gt; 해야 한다. 자세한 건 [[idempotency-keys]].&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 보상 시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; compensate_payment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(saga_id, payment_id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; already_refunded(saga_id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                       # 멱등&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payment_service.refund(payment_id, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;saga_id)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;saga-상태-저장&quot;&gt;Saga 상태 저장&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Orch[Orchestrator] --&gt; Store[(Saga State Store)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; SQL[(PostgreSQL)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; Stream[(Kafka)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; SF[(Step Functions)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Store&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;PostgreSQL (DB)&lt;/td&gt;&lt;td&gt;트랜잭션 친화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kafka topic&lt;/td&gt;&lt;td&gt;event log 자연 통합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS Step Functions&lt;/td&gt;&lt;td&gt;managed orchestrator&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Temporal&lt;/td&gt;&lt;td&gt;워크플로 엔진&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Camunda&lt;/td&gt;&lt;td&gt;BPMN 표준&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;temporal--step-functions-워크플로-엔진&quot;&gt;Temporal / Step Functions (워크플로 엔진)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SDK[&quot;Application code&amp;#x3C;br/&gt;(workflow + activity)&quot;] --&gt; Temporal[Temporal Server]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Temporal --&gt; StateDB[(상태 DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Temporal --&gt; Workers[&quot;Workers&amp;#x3C;br/&gt;(activity 실행)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;워크플로 코드&lt;/em&gt; 가 &lt;em&gt;durable execution&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;retry / timeout / sleep&lt;/em&gt; 이 &lt;em&gt;언어 native&lt;/em&gt; 처럼.&lt;/li&gt;
&lt;li&gt;마치 &lt;em&gt;함수 호출 같은데 분산 + 영속&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;모든 분산 흐름 = saga&lt;/strong&gt; = 단순 &lt;em&gt;async event&lt;/em&gt; 가 충분한데도 복잡한 saga. &lt;em&gt;진짜 보상 필요한 흐름에만&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보상 &lt;em&gt;없거나 미구현&lt;/em&gt;&lt;/strong&gt; = “&lt;em&gt;해피 패스만 만들고 실패는 운영자 처리&lt;/em&gt;”. 1년 뒤 &lt;em&gt;데이터 불일치 폭증&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choreography 의 &lt;em&gt;순환 의존성&lt;/em&gt;&lt;/strong&gt; = A → B → C → A 같은 흐름. 디버깅 지옥.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 저장 안 함&lt;/strong&gt; = orchestrator 다운 시 &lt;em&gt;진행 잃음&lt;/em&gt;. event log 또는 DB 필수.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[distributed-systems-distributed-transaction]] (2PC)&lt;/li&gt;
&lt;li&gt;[[outbox-pattern]] (event publishing 보장)&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;li&gt;[[event-sourcing]]&lt;/li&gt;
&lt;li&gt;[[microservices-vs-monolith]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>saga</category><category>distributed-transaction</category><category>microservices</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Voice AI] 파이프라인 디커플링: Redis Streams / Kafka 적용</title><link>https://shinkeonkim.com/wiki/distributed-systems/voice-pipeline-decoupling/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/voice-pipeline-decoupling/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;대규모 음성 에이전트 = &lt;em&gt;STT/LLM/TTS 를 분리 배포 + 메시지 큐로 디커플링&lt;/em&gt;. 수평 확장 + 독립 실패.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;소규모 (수십 동시 통화)&lt;/em&gt; 는 &lt;em&gt;monolith pipeline (Pipecat 한 프로세스)&lt;/em&gt; 으로 충분. &lt;em&gt;수백 ~ 수천 동시&lt;/em&gt; 부터 디커플링 의미.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;monolith-vs-decoupled&quot;&gt;Monolith vs Decoupled&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Mono[&quot;Monolith&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        M1[&quot;Pipecat process&amp;#x3C;br/&gt;(STT + LLM + TTS 한 프로세스)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Decoupled[&quot;Decoupled&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1[&quot;Orchestrator (stateless)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D2[STT Workers]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D3[LLM Workers]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D4[TTS Workers]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Q1[(Queue: stt-tasks)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Q2[(Queue: llm-tasks)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Q3[(Queue: tts-tasks)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1 --&gt; Q1 --&gt; D2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D2 --&gt; Q2 --&gt; D3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D3 --&gt; Q3 --&gt; D4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D4 --&gt; D1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Monolith&lt;/th&gt;&lt;th&gt;Decoupled&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;운영 복잡도&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;수평 확장&lt;/td&gt;&lt;td&gt;동일 비율&lt;/td&gt;&lt;td&gt;&lt;em&gt;단계별 독립&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;독립 실패&lt;/td&gt;&lt;td&gt;전체 다운&lt;/td&gt;&lt;td&gt;&lt;em&gt;단계별 격리&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;지연&lt;/td&gt;&lt;td&gt;&lt;em&gt;최저&lt;/em&gt;&lt;/td&gt;&lt;td&gt;+ queue hop (~10ms)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;동시 통화&lt;/td&gt;&lt;td&gt;수십&lt;/td&gt;&lt;td&gt;&lt;em&gt;수천+&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;큐-선택&quot;&gt;큐 선택&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;큐&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Redis Streams&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;가장 단순, &amp;#x3C; 수만 RPS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Redis Pub/Sub&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;손실 OK, 알림만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;큰 throughput, 영속, replay&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;NATS JetStream&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;가벼움, edge&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AWS SQS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;managed, AWS only&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;음성 에이전트 = &lt;em&gt;Redis Streams&lt;/em&gt; 가 가장 흔함 (latency 낮음, 운영 단순).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;redis-streams-패턴&quot;&gt;Redis Streams 패턴&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Producer (STT worker)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; stt_worker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # stt-tasks 에서 받음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        msgs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.xreadgroup(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;stt-group&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stt-worker-1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stt-tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            count&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;block&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stream, items &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msgs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msg_id, data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; items:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                audio_url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; data[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;audio_url&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                transcript &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; deepgram.transcribe(audio_url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # 다음 단계로 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.xadd(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;llm-tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                    &quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: data[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                    &quot;transcript&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: transcript,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.xack(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stt-tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stt-group&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, msg_id)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 Streams 는 [[Redis Streams]].&lt;/p&gt;
&lt;h2 id=&quot;orchestrator-stateless&quot;&gt;Orchestrator (stateless)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VoiceOrchestrator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;Session 만 관리. 실제 처리는 worker.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; on_call_start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, call_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # Session 을 Redis 에 저장 (stateless 의 외부 상태)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.hset(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;call:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;call_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;mapping&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;started_at&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: time.time(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;status&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;active&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; on_audio_chunk&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, call_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, audio: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 큐에 던지고 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.xadd(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stt-tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;call_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: call_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;audio&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: audio,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; on_llm_result&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, call_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, text: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # TTS 큐로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.xadd(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;tts-tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;call_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: call_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: text,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; on_tts_audio&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, call_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, audio_chunk: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 사용자에게 stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.send_to_user(call_id, audio_chunk)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Orchestrator 인스턴스 자체는 &lt;em&gt;상태 없음&lt;/em&gt;. &lt;em&gt;모든 상태 Redis&lt;/em&gt;. 수평 확장 + 무중단 배포 자유.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;수평-확장&quot;&gt;수평 확장&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB[Load Balancer] --&gt; O1[Orchestrator 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt; O2[Orchestrator 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt; O3[Orchestrator 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    O1 &amp;#x26; O2 &amp;#x26; O3 --&gt; Redis[(Redis: 상태 + 큐)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Redis --&gt; STT_W[&quot;STT Workers (auto-scale)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Redis --&gt; LLM_W[&quot;LLM Workers (auto-scale)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Redis --&gt; TTS_W[&quot;TTS Workers (auto-scale)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;컴포넌트&lt;/th&gt;&lt;th&gt;스케일링 기준&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Orchestrator&lt;/td&gt;&lt;td&gt;동시 connection 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;STT Worker&lt;/td&gt;&lt;td&gt;stt-tasks lag&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LLM Worker&lt;/td&gt;&lt;td&gt;llm-tasks lag, GPU&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TTS Worker&lt;/td&gt;&lt;td&gt;tts-tasks lag&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;각자 다른 자원&lt;/em&gt;. STT 는 CPU, LLM 은 GPU, TTS 는 GPU 또는 API. 분리 = 효율적 자원.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;sticky-vs-stateless&quot;&gt;Sticky vs Stateless&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{Voice agent stateless?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;Yes (모든 state Redis)&quot;| Stateless[수평 확장 자유]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;No (메모리에 conversation)&quot;| Sticky[Sticky session 필수]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sticky&lt;/em&gt; = 같은 통화 = 같은 orchestrator. SFU + WebRTC 환경에서 &lt;em&gt;불가피할 때 있음&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;failure-recovery&quot;&gt;Failure Recovery&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Crash[Orchestrator 1 죽음]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Crash --&gt; Detect[Health check fail]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Detect --&gt; Drain[기존 통화 drain]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Detect --&gt; NewO[다른 orchestrator 가 이어받음]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NewO --&gt; Restore[Redis 에서 state 복원]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; take_over_call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(call_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    state &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.hgetall(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;call:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;call_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; state:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 이미 다른 orchestrator 처리 중?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    owner &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.set(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;call:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;call_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:owner&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.instance_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        nx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; owner:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 이어받음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.resume_call(call_id, state)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 &lt;em&gt;상태 관리&lt;/em&gt; 는 [[Redis Sorted Sets]] 의 session 패턴.&lt;/p&gt;
&lt;h2 id=&quot;sentinel--cluster&quot;&gt;Sentinel + Cluster&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Voice[Voice agents] --&gt; Sentinel[&quot;Redis Sentinel&amp;#x3C;br/&gt;(자동 failover)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sentinel --&gt; Primary[(Redis Primary)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Primary --&gt; Replica1[(Replica 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Primary --&gt; Replica2[(Replica 2)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;음성 = &lt;em&gt;Redis 단일 장애 = 모든 통화 끊김&lt;/em&gt;. Sentinel 또는 Cluster + sticky session 보호 필수.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;외부-api-복원력&quot;&gt;외부 API 복원력&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[복원력 패턴]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; T1[&quot;Timeout (짧게)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; T2[&quot;Retry + backoff (idempotent only)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; T3[Circuit Breaker]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; T4[&quot;Fallback (다른 vendor)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; T5[Health check]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;timeout&lt;/th&gt;&lt;th&gt;fallback&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;STT&lt;/td&gt;&lt;td&gt;5초&lt;/td&gt;&lt;td&gt;Deepgram → AssemblyAI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LLM&lt;/td&gt;&lt;td&gt;30초&lt;/td&gt;&lt;td&gt;GPT-4o-mini → Claude Haiku&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TTS&lt;/td&gt;&lt;td&gt;10초&lt;/td&gt;&lt;td&gt;Cartesia → Azure&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자세한 건 [[retry-with-backoff]], [[circuit-breaker]].&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Monolith → Decouple 전환 너무 일찍&lt;/strong&gt; = 운영 복잡도 폭증. 수십 동시 통화는 monolith.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 메모리 보관&lt;/strong&gt; = orchestrator restart = 통화 끊김. Redis 외부 상태.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis Streams MAXLEN 무제한&lt;/strong&gt; = 메모리 폭증. MAXLEN ~ 100K 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue lag 모니터링 없음&lt;/strong&gt; = 어디서 병목인지 모름. Prometheus + alert.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[voice-agent-architecture]]&lt;/li&gt;
&lt;li&gt;[[Redis Streams]]&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]]&lt;/li&gt;
&lt;li&gt;[[kafka]]&lt;/li&gt;
&lt;li&gt;[[circuit-breaker]]&lt;/li&gt;
&lt;li&gt;[[retry-with-backoff]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>voice-pipeline</category><category>queue</category><category>redis-streams</category><category>kafka</category><category>decoupling</category><category>scaling</category><author>koa (김신건)</author></item><item><title>[Distributed Systems] CAP Theorem과 PACELC</title><link>https://shinkeonkim.com/wiki/distributed-systems/cap-theorem/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/cap-theorem/</guid><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CAP Theorem&lt;/strong&gt; (Eric Brewer, 2000): 분산 시스템에서 &lt;strong&gt;3가지 중 2개만 동시에 보장 가능&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C (Consistency)&lt;/strong&gt;: 모든 노드가 같은 시점에 같은 데이터 반환 (강한 일관성)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A (Availability)&lt;/strong&gt;: 모든 요청이 응답 받음 (실패도 응답)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P (Partition Tolerance)&lt;/strong&gt;: 네트워크 분할이 있어도 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;현실: &lt;strong&gt;P 는 선택지 아님&lt;/strong&gt; (네트워크는 항상 깨질 수 있음). 따라서 실질적 선택은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CP&lt;/strong&gt;: 분할 시 일관성 우선 → 일부 노드 응답 안 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AP&lt;/strong&gt;: 분할 시 가용성 우선 → 일관성 일시 깨짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cap-의-한계와-pacelc&quot;&gt;CAP 의 한계와 PACELC&lt;/h2&gt;
&lt;p&gt;CAP 는 “분할 시” 시나리오만 다룸. &lt;strong&gt;정상 작동 시의 trade-off&lt;/strong&gt; 는?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PACELC&lt;/strong&gt; (Daniel Abadi, 2010):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P&lt;/strong&gt; artition 시: &lt;strong&gt;A&lt;/strong&gt; vailability vs &lt;strong&gt;C&lt;/strong&gt; onsistency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt; lse (정상): &lt;strong&gt;L&lt;/strong&gt; atency vs &lt;strong&gt;C&lt;/strong&gt; onsistency&lt;/li&gt;
&lt;/ul&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;시스템&lt;/th&gt;&lt;th&gt;PACELC&lt;/th&gt;&lt;th&gt;해석&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;MongoDB&lt;/td&gt;&lt;td&gt;PA / EC&lt;/td&gt;&lt;td&gt;분할 시 A, 정상 시 C&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cassandra&lt;/td&gt;&lt;td&gt;PA / EL&lt;/td&gt;&lt;td&gt;분할 시 A, 정상 시 L (낮은 latency)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HBase&lt;/td&gt;&lt;td&gt;PC / EC&lt;/td&gt;&lt;td&gt;항상 C&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DynamoDB&lt;/td&gt;&lt;td&gt;PA / EL&lt;/td&gt;&lt;td&gt;항상 가용성 / latency 우선&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PostgreSQL&lt;/td&gt;&lt;td&gt;PC / EC&lt;/td&gt;&lt;td&gt;단일 노드 강한 일관성&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;PACELC 가 더 실제적: 정상 시간이 훨씬 길기 때문.&lt;/p&gt;
&lt;h2 id=&quot;cp-시스템-예-hbase-mongodb-default&quot;&gt;CP 시스템 예: HBase, MongoDB (default)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Node A (Primary)] ←── network OK ──→ [Node B (Secondary)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                replication&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            consistent read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Node A] ←── network 분할 ──→ [Node B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓ leader election 발동&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   write → A 만 받음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   B 의 read → 실패 또는 stale 반환 거부&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;분할 시 minority partition 은 응답 거부 → &lt;strong&gt;가용성 손실&lt;/strong&gt;, 일관성 보장.&lt;/p&gt;
&lt;p&gt;장점: 사용자가 항상 최신 데이터를 봄.
단점: 일부 사용자가 아예 응답 못 받음.&lt;/p&gt;
&lt;h2 id=&quot;ap-시스템-예-cassandra-dynamodb&quot;&gt;AP 시스템 예: Cassandra, DynamoDB&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Node A] ←── 분할 ──→ [Node B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;write A → A 의 view 만 업데이트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;write B → B 의 view 만 업데이트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;both nodes 응답 OK → 가용성 보존&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;나중에 분할 복구:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A 와 B 의 view 가 다름 → conflict resolution (LWW, vector clock 등)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;장점: 사용자가 항상 응답 받음.
단점: 일시적으로 다른 사용자가 다른 결과 봄 (eventual consistency).&lt;/p&gt;
&lt;h2 id=&quot;consistency-spectrum&quot;&gt;Consistency Spectrum&lt;/h2&gt;
&lt;p&gt;CAP 의 “C” 는 strong consistency. 실무는 더 세밀한 spectrum:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Strong consistency     ─── 가장 엄격&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Linearizability        (실시간 순서 보장)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sequential consistency&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Causal consistency     (인과 관계만 보장)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Eventual consistency   ─── 가장 느슨&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;대부분 AP 시스템 = eventual. CP 시스템 = strong (or linearizable).&lt;/p&gt;
&lt;h3 id=&quot;tunable-consistency-cassandra-패턴&quot;&gt;Tunable consistency (Cassandra 패턴)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONSISTENCY LEVEL = ONE / QUORUM / ALL / EACH_QUORUM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;ONE: 1개 노드 응답이면 OK (빠르지만 stale 위험)&lt;/li&gt;
&lt;li&gt;QUORUM: 과반수 (N/2 + 1)&lt;/li&gt;
&lt;li&gt;ALL: 모든 replica 응답 (느리지만 가장 일관)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;write QUORUM + read QUORUM = strong consistency 보장 (N=3 면 2+2 = 4 &gt; 3).&lt;/p&gt;
&lt;h2 id=&quot;실무-선택-기준&quot;&gt;실무 선택 기준&lt;/h2&gt;
&lt;h3 id=&quot;금융--결제-cp&quot;&gt;금융 / 결제: CP&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;계좌 잔액은 절대 stale 안 됨&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ HBase, PostgreSQL (single instance), Spanner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 분할 시 거래 차단이 안전&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;sns--feed-ap&quot;&gt;SNS / Feed: AP&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;타임라인이 일시적으로 다르게 보여도 됨&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ Cassandra, DynamoDB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 분할이라도 게시 / 조회 가능&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;검색-인덱스-ap-eventual&quot;&gt;검색 인덱스: AP (eventual)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;검색 결과가 몇 초 지연돼도 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ Elasticsearch (near real-time)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;메시지-큐-두-모드&quot;&gt;메시지 큐: 두 모드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Kafka: AP (high throughput)&lt;/li&gt;
&lt;li&gt;RabbitMQ: 모드별 선택 (mirror queue 는 CP)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cap-의-흔한-오해&quot;&gt;CAP 의 흔한 오해&lt;/h2&gt;
&lt;h3 id=&quot;오해-1-ca-시스템도-가능&quot;&gt;오해 1: CA 시스템도 가능?&lt;/h3&gt;
&lt;p&gt;NO. 분산 환경에서 P 는 선택지 아님 (네트워크는 항상 깨질 수 있음). “CA” 는 single-node 시스템 (MySQL standalone).&lt;/p&gt;
&lt;h3 id=&quot;오해-2-ap--일관성-없음&quot;&gt;오해 2: AP = 일관성 없음?&lt;/h3&gt;
&lt;p&gt;NO. AP 는 eventual consistency 또는 weaker. 결국엔 수렴.&lt;/p&gt;
&lt;h3 id=&quot;오해-3-cap-가-영구-선택&quot;&gt;오해 3: CAP 가 영구 선택?&lt;/h3&gt;
&lt;p&gt;NO. 분할 발생 시점에만 trade-off. 정상 시간엔 PACELC 의 E (latency vs consistency).&lt;/p&gt;
&lt;h3 id=&quot;오해-4-p-는-datacenter-분할만&quot;&gt;오해 4: P 는 datacenter 분할만?&lt;/h3&gt;
&lt;p&gt;분할 = network 가 잠시라도 깨짐. 짧은 packet loss / latency spike 도 포함. 빈번히 발생.&lt;/p&gt;
&lt;h2 id=&quot;consensus-와의-관계&quot;&gt;consensus 와의 관계&lt;/h2&gt;
&lt;p&gt;CAP 의 “C” 를 보장하려면 합의 (consensus) 필요. Raft, Paxos 같은 알고리즘이 분할 시 majority 만 진행:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5-node cluster&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── 3-node majority (가능, 진행)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── 2-node minority (정지)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;majority 가 사라지면 (3+ 노드 분할) cluster 전체 정지. [[distributed-systems-consensus]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;함정과-베스트-프랙티스&quot;&gt;함정과 베스트 프랙티스&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CAP 는 정상 시 trade-off 안 다룸&lt;/strong&gt;: PACELC 가 더 현실적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P 는 항상&lt;/strong&gt;: CA 시스템은 single-node 에서만&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eventual consistency 도 정확한 정의&lt;/strong&gt;: 결국 수렴, 시간 보장 X&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tunable consistency 활용&lt;/strong&gt;: Cassandra 의 QUORUM 패턴&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;금융은 CP, SNS 는 AP&lt;/strong&gt;: 도메인 별 명확&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단순화하지 말 것&lt;/strong&gt;: 모든 read 가 strong 필요 X, 모든 write 가 eventual OK X&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;monitoring&lt;/strong&gt;: stale read 비율, partition 빈도 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[distributed-systems-consensus]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-distributed-transaction]]&lt;/li&gt;
&lt;li&gt;[[concurrency/stateless]]&lt;/li&gt;
&lt;li&gt;[[concurrency/stateful]]&lt;/li&gt;
&lt;li&gt;[[spring-cloud]]&lt;/li&gt;
&lt;li&gt;[[zero-downtime-deployment]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>distributed-systems</category><category>cap</category><category>consistency</category><category>availability</category><category>partition</category><author>koa (김신건)</author></item><item><title>[Distributed Systems] Consensus: Raft, Paxos</title><link>https://shinkeonkim.com/wiki/distributed-systems/distributed-systems-consensus/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/distributed-systems-consensus/</guid><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Consensus&lt;/strong&gt; (합의): &lt;strong&gt;여러 노드가 같은 값에 동의&lt;/strong&gt; 하는 분산 알고리즘. CAP 의 C 보장의 기반.&lt;/p&gt;
&lt;p&gt;용도:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Leader election&lt;/strong&gt;: 1개 leader 선출 (ZooKeeper, etcd, Consul)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replicated log&lt;/strong&gt;: 모든 노드가 같은 명령 순서로 적용 (DB replication, K8s etcd)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed lock&lt;/strong&gt;: 글로벌 락&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration management&lt;/strong&gt;: cluster 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대표 알고리즘:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Paxos&lt;/strong&gt; (1989, Lamport): 이론적 정석, 이해 어려움&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raft&lt;/strong&gt; (2014, Ongaro &amp;#x26; Ousterhout): Paxos 의 이해 가능한 대안. 현재 대세&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PBFT, Byzantine fault tolerance&lt;/strong&gt;: 악의적 노드 가정 (blockchain)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 페이지는 Raft 중심 (가장 보편적).&lt;/p&gt;
&lt;h2 id=&quot;raft-의-3가지-역할&quot;&gt;Raft 의 3가지 역할&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Leader]   ─── 1명만 존재&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── 모든 write 받음, log replication 책임&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Follower] ─── 나머지 모두&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── leader 의 명령 받아 적용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Candidate] ─── 전이 상태 (선거 중)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── leader 가 사라지면 follower 가 candidate 로 전환, 투표 요청&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;raft-의-핵심-term&quot;&gt;Raft 의 핵심: term&lt;/h2&gt;
&lt;p&gt;시간을 term (선거 회차) 으로 나눔:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;term 1: Leader = A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;term 2: Leader = B  (A 죽음, B 선출)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;term 3: Leader = A  (B 죽음, A 복귀)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 노드는 자신의 currentTerm 보유. &lt;strong&gt;term 이 낮은 노드의 요청은 거부&lt;/strong&gt; (오래된 leader 격리).&lt;/p&gt;
&lt;h2 id=&quot;leader-election&quot;&gt;Leader Election&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;follower 가 randomized timeout (150~300ms) 동안 leader heartbeat 못 받으면:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  1. 자신을 candidate 로 전환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2. currentTerm++&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  3. 자기 자신에 투표&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  4. 다른 노드에 RequestVote RPC 전송&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;응답 시나리오:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 과반수 (N/2 + 1) 표 받으면 → Leader 됨&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 다른 candidate 의 더 높은 term 보면 → follower 로 복귀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 표 안 모이면 → 새 election (다른 timeout 후)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;randomized timeout&lt;/code&gt; 이 split vote 회피의 핵심.&lt;/p&gt;
&lt;h2 id=&quot;log-replication&quot;&gt;Log Replication&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Leader] receives &quot;x = 5&quot; from client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Leader appends to its log:  [..., (term=3, x=5)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Leader sends AppendEntries RPC to all followers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Each follower appends to its log + replies OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Once majority replied → Leader commits (apply to state machine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. Leader notifies followers in next AppendEntries&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6. Followers apply committed entries to their state machine&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심: &lt;strong&gt;commit = majority 가 log 에 기록&lt;/strong&gt;. apply 는 그 후.&lt;/p&gt;
&lt;h3 id=&quot;log-matching-property&quot;&gt;Log Matching Property&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;같은 index + 같은 term 의 entry = 같은 명령 (강력한 invariant)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 leader 의 log 가 truth. follower 가 mismatch 면 leader 의 것으로 덮어쓰기.&lt;/p&gt;
&lt;h2 id=&quot;safety-어떤-leader-가-선출되나&quot;&gt;Safety: 어떤 leader 가 선출되나?&lt;/h2&gt;
&lt;p&gt;새 leader 는 &lt;strong&gt;모든 committed entry 를 가진 노드&lt;/strong&gt; 만 가능.&lt;/p&gt;
&lt;p&gt;RequestVote 시 candidate 가 자신의 last log term, index 보냄. follower 는 &lt;strong&gt;자기보다 stale 한 candidate 에게 투표 안 함&lt;/strong&gt; (rejects vote if candidate’s log is less up-to-date).&lt;/p&gt;
&lt;p&gt;majority vote 보장 + commit 조건 = “어느 commit 된 entry 는 영구히 보존”.&lt;/p&gt;
&lt;h2 id=&quot;membership-changes&quot;&gt;Membership Changes&lt;/h2&gt;
&lt;p&gt;cluster 멤버 추가 / 제거는 위험 (두 majority 존재 시 split brain):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;old: {A, B, C} → majority = 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;new: {A, B, C, D, E} → majority = 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;전환 중에 둘 다 자기를 majority 라고 주장 가능&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Raft 의 해결:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Joint consensus&lt;/strong&gt; (논문): old 와 new config 의 majority 모두 필요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single server changes&lt;/strong&gt; (실용): 한 번에 1명만 추가 / 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;snapshot--log-compaction&quot;&gt;Snapshot + Log Compaction&lt;/h2&gt;
&lt;p&gt;log 가 무한히 커지면 안 됨. 주기적으로 snapshot:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. State machine 의 현재 상태를 disk 에 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 그 시점 이전 log entry 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Snapshot 에 last included index, term 기록&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;복구 시 snapshot 적용 + 그 이후 log replay.&lt;/p&gt;
&lt;h2 id=&quot;raft-구현체&quot;&gt;Raft 구현체&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;시스템&lt;/th&gt;&lt;th&gt;사용처&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;etcd&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Kubernetes 의 control plane&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Consul&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;service discovery&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;distributed SQL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TiKV&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;TiDB 의 storage&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MongoDB (3.2+)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;replication&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hazelcast (4.0+)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;distributed cache&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;대부분 시스템이 직접 구현하거나 hashicorp/raft 라이브러리 사용.&lt;/p&gt;
&lt;h2 id=&quot;paxos-와의-비교&quot;&gt;Paxos 와의 비교&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Paxos&lt;/th&gt;&lt;th&gt;Raft&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;발표&lt;/td&gt;&lt;td&gt;1989&lt;/td&gt;&lt;td&gt;2014&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;이해도&lt;/td&gt;&lt;td&gt;매우 어려움&lt;/td&gt;&lt;td&gt;비교적 쉬움&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Leader 개념&lt;/td&gt;&lt;td&gt;명시적 X (Multi-Paxos 추가)&lt;/td&gt;&lt;td&gt;명시적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Log replication&lt;/td&gt;&lt;td&gt;명시적 X&lt;/td&gt;&lt;td&gt;명시적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;산업 채택&lt;/td&gt;&lt;td&gt;Spanner, Chubby&lt;/td&gt;&lt;td&gt;etcd, Consul, …&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Raft 가 “이해 가능한 alternative” 를 목표로 설계. 안전성은 동등.&lt;/p&gt;
&lt;h3 id=&quot;paxos-의-단계&quot;&gt;Paxos 의 단계&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prepare&lt;/strong&gt; (round): proposer 가 proposal number 보내 majority 의 promise 받음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accept&lt;/strong&gt;: 가장 높은 proposal number 의 value 를 majority 에 보냄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn&lt;/strong&gt;: 결정된 값 전파&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Multi-Paxos: leader 가 prepare 한 번 + 여러 accept (Raft 의 leader 와 유사).&lt;/p&gt;
&lt;h2 id=&quot;zookeeper-와-zab&quot;&gt;ZooKeeper 와 Zab&lt;/h2&gt;
&lt;p&gt;ZooKeeper 의 합의 알고리즘 &lt;strong&gt;Zab&lt;/strong&gt; (ZooKeeper Atomic Broadcast):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paxos / Raft 와 유사하지만 &lt;strong&gt;total order&lt;/strong&gt; 강제&lt;/li&gt;
&lt;li&gt;Leader 가 모든 update 를 ordered broadcast&lt;/li&gt;
&lt;li&gt;ZooKeeper 의 &lt;code&gt;zxid&lt;/code&gt; (zookeeper transaction id) 가 순서 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ZooKeeper 는 분산 락 / leader election 의 standard. ephemeral node + watch 가 강력.&lt;/p&gt;
&lt;h2 id=&quot;cap-와-합의의-관계&quot;&gt;CAP 와 합의의 관계&lt;/h2&gt;
&lt;p&gt;합의 알고리즘 = CP (강한 일관성).&lt;/p&gt;
&lt;p&gt;분할 시 minority partition 의 노드들은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;leader 가 없음 → write 불가&lt;/li&gt;
&lt;li&gt;또는 old leader 였다면 commit 못 함 → 정지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;majority partition 만 진행. &lt;strong&gt;가용성 손실 + 일관성 보장&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;자주-보는-함정&quot;&gt;자주 보는 함정&lt;/h2&gt;
&lt;h3 id=&quot;함정-1-split-brain&quot;&gt;함정 1: split brain&lt;/h3&gt;
&lt;p&gt;분할 후 양쪽이 각자 leader 선출 → 둘 다 update → 복구 후 충돌.&lt;/p&gt;
&lt;p&gt;Raft 의 term + majority 가 방어. minority 의 “leader” 는 다음 term 의 leader 로 인해 거부됨.&lt;/p&gt;
&lt;h3 id=&quot;함정-2-clock-skew&quot;&gt;함정 2: clock skew&lt;/h3&gt;
&lt;p&gt;Lamport timestamp / Vector clock 같은 logical clock 사용. wall clock 의존 시 NTP drift 로 깨짐.&lt;/p&gt;
&lt;p&gt;Raft 는 wall clock 의존 안 함 (term 이 logical).&lt;/p&gt;
&lt;h3 id=&quot;함정-3-2-노드-cluster-의-위험&quot;&gt;함정 3: 2 노드 cluster 의 위험&lt;/h3&gt;
&lt;p&gt;majority = 2/2. 1 노드 죽으면 cluster 정지. 3 노드가 최소.&lt;/p&gt;
&lt;h3 id=&quot;함정-4-짝수-노드-cluster&quot;&gt;함정 4: 짝수 노드 cluster&lt;/h3&gt;
&lt;p&gt;4 노드 cluster 의 majority = 3. 즉 3 노드 cluster 와 같은 fault tolerance (1 노드 fail). 그러나 메시지 비용 ↑. 홀수 권장.&lt;/p&gt;
&lt;h3 id=&quot;함정-5-cross-dc-합의의-latency&quot;&gt;함정 5: cross-DC 합의의 latency&lt;/h3&gt;
&lt;p&gt;majority 가 다른 DC 면 매 write 가 cross-DC latency. 보통 같은 region 에 3 노드. cross-region 은 별도 consensus (geo-replication).&lt;/p&gt;
&lt;h2 id=&quot;성능-trade-off&quot;&gt;성능 trade-off&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3 node cluster&lt;/strong&gt;: 1 fail tolerance, 빠른 합의&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5 node cluster&lt;/strong&gt;: 2 fail tolerance, 약간 느림&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;7+&lt;/strong&gt;: 거의 안 함 (메시지 비용 폭증)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;write throughput = 1 / RTT (대략). leader 가 majority 응답 기다림.&lt;/p&gt;
&lt;h2 id=&quot;함정과-베스트-프랙티스&quot;&gt;함정과 베스트 프랙티스&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;홀수 노드 (3, 5, 7)&lt;/strong&gt;: 짝수는 메시지 비용만 ↑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;majority 가 같은 region&lt;/strong&gt;: cross-DC 면 latency 폭주&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clock skew 무관&lt;/strong&gt;: 합의 자체엔 영향 X (logical clock)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;snapshot 주기적 + tunable&lt;/strong&gt;: log 무한 성장 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;client retry + idempotent&lt;/strong&gt;: leader 전환 중에도 안전&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;monitoring&lt;/strong&gt;: leader election 빈도, log size, applied vs committed gap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[cap-theorem]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-distributed-transaction]]&lt;/li&gt;
&lt;li&gt;[[zero-downtime-deployment]]&lt;/li&gt;
&lt;li&gt;[[concurrency/stateful]]&lt;/li&gt;
&lt;li&gt;[[spring-cloud]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>distributed-systems</category><category>consensus</category><category>raft</category><category>paxos</category><category>leader-election</category><author>koa (김신건)</author></item><item><title>[Distributed Systems] 분산 트랜잭션: 2PC, Saga, Outbox</title><link>https://shinkeonkim.com/wiki/distributed-systems/distributed-systems-distributed-transaction/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/distributed-systems/distributed-systems-distributed-transaction/</guid><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;분산 트랜잭션&lt;/strong&gt; 은 여러 service / DB / 메시지 큐에 걸친 작업을 ACID 처럼 묶는 문제. 마이크로서비스 / 이벤트 기반 아키텍처의 핵심 도전.&lt;/p&gt;
&lt;p&gt;3가지 접근:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;2PC (Two-Phase Commit)&lt;/strong&gt;: 동기 ACID, 전통적, 거의 안 씀&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saga&lt;/strong&gt;: 비동기 compensating action, 마이크로서비스 표준&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbox&lt;/strong&gt;: DB 트랜잭션 + 메시지 큐 일관성 보장 (Saga 의 보조)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;선택 기준: 시스템 복잡도, 일관성 요구, 가용성 trade-off.&lt;/p&gt;
&lt;h2 id=&quot;2pc-two-phase-commit&quot;&gt;2PC (Two-Phase Commit)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1단계 (Prepare)&lt;/strong&gt;: coordinator → 모든 participant 에게 “준비 됐어?”
&lt;strong&gt;2단계 (Commit)&lt;/strong&gt;: 모두 OK 면 → “commit”. 한 명이라도 NO 면 → “rollback”&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       [Coordinator]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ┌────────┼────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ↓        ↓        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; [DB A]   [DB B]   [DB C]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Phase 1 (Prepare):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   coordinator → &quot;준비?&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   each DB → write log, lock rows, &quot;yes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Phase 2 (Commit):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   coordinator → &quot;commit&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   each DB → commit, release lock, &quot;done&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;강한 ACID 보장&lt;/li&gt;
&lt;li&gt;표준 (XA Transaction)&lt;/li&gt;
&lt;li&gt;동기적 - 결과 즉시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blocking&lt;/strong&gt;: coordinator 죽으면 participant 가 락 잡은 채 영원히 대기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: 매 트랜잭션 = 2 라운드 + N participant&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single point of failure&lt;/strong&gt;: coordinator&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CP 시스템&lt;/strong&gt;: 가용성 손실&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JTA (Java Transaction API) + XA driver 가 구현. 대부분 modern 시스템은 회피.&lt;/p&gt;
&lt;h2 id=&quot;saga-pattern&quot;&gt;Saga Pattern&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;전체 트랜잭션을 여러 local 트랜잭션의 chain&lt;/strong&gt; 으로:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;T1 (서비스 A) → T2 (서비스 B) → T3 (서비스 C)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                ↓ 실패&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              C2 (B 보상) ← C3 (실패 처리)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 step 이 commit 후 다음 발동. 실패 시 &lt;strong&gt;compensating transaction&lt;/strong&gt; 으로 되돌리기.&lt;/p&gt;
&lt;h3 id=&quot;예-주문-처리&quot;&gt;예: 주문 처리&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Step 1: Order Service → 주문 생성 (status=pending)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Step 2: Payment Service → 결제 시도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   성공 → Step 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   실패 → Compensating: Order Service → 주문 취소&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Step 3: Inventory Service → 재고 차감&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   성공 → Step 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   실패 → Compensating: Payment Service → 환불 → Order Service → 취소&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Step 4: Shipping Service → 배송 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   성공 → 완료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   실패 → Compensating: 재고 복구, 환불, 주문 취소&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 service 가 자기 local 트랜잭션만 책임. cross-service 락 없음.&lt;/p&gt;
&lt;h3 id=&quot;두-가지-구현-방식&quot;&gt;두 가지 구현 방식&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Choreography&lt;/strong&gt;: 각 service 가 이벤트 발행 + 구독&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Order Service → &quot;OrderCreated&quot; 이벤트 → Kafka&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Payment Service ← &quot;OrderCreated&quot; 구독 → 결제 시도 → &quot;PaymentCompleted&quot; 발행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Inventory Service ← &quot;PaymentCompleted&quot; 구독 → ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;장점: 분산, 결합도 낮음.
단점: 흐름 추적 어려움, 디버깅 복잡.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Orchestration&lt;/strong&gt;: 중앙 orchestrator 가 흐름 제어&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Saga Orchestrator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── Order Service: 생성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── Payment Service: 결제 (실패 시 → 이전 단계 보상)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── Inventory Service: 차감&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └── Shipping Service: 시작&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;장점: 흐름 명시적, 디버깅 쉬움.
단점: orchestrator 가 단일 장애점, 복잡한 로직.&lt;/p&gt;
&lt;p&gt;도구: AWS Step Functions, Camunda, Temporal, Conductor.&lt;/p&gt;
&lt;h3 id=&quot;saga-의-제약&quot;&gt;Saga 의 제약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolation 없음&lt;/strong&gt;: 진행 중인 saga 의 중간 상태가 다른 트랜잭션에 보임 → “dirty read” 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eventually consistent&lt;/strong&gt;: 보상 완료까지 일시 비일관&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compensating transaction 설계 어려움&lt;/strong&gt;: idempotent + 영구 부수효과 없어야 (이메일 발송 후 보상 어려움)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대응 패턴:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Semantic lock&lt;/strong&gt;: 진행 중 자원에 flag 표시 (e.g., &lt;code&gt;order.status=processing&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commutative updates&lt;/strong&gt;: 순서 무관하게 결과 동일하도록&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pessimistic view&lt;/strong&gt;: 보수적 처리 (e.g., 미리 예약 → 확정)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;outbox-pattern&quot;&gt;Outbox Pattern&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;DB 트랜잭션 + 메시지 큐 발행&lt;/strong&gt; 의 atomicity 보장:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[전통 문제]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;INSERT order INTO orders ─ DB transaction ─ OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kafka.publish(OrderPlaced) ─ network call ─ 실패&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ DB 에 order 있지만 알림 X (inconsistent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;또는 반대로:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kafka.publish(OrderPlaced) ─ 성공&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;INSERT order ─ rollback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 알림 갔지만 DB 에 order 없음 (worse!)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Outbox 패턴&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[같은 DB 트랜잭션 안]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;BEGIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  INSERT INTO orders (...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  INSERT INTO outbox (event_type, payload, status=&apos;pending&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;COMMIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[별도 worker]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SELECT * FROM outbox WHERE status=&apos;pending&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → Kafka publish&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → UPDATE outbox SET status=&apos;sent&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 트랜잭션 = 단일 commit (atomic)&lt;/li&gt;
&lt;li&gt;Worker 가 죽어도 row 가 outbox 에 살아 있어 재시도&lt;/li&gt;
&lt;li&gt;at-least-once delivery (idempotent consumer 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;도구:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Debezium&lt;/strong&gt;: outbox 테이블의 CDC (Change Data Capture)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Modulith Events&lt;/strong&gt;: Spring 통합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;직접 구현&lt;/strong&gt;: 간단한 polling worker&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;spring-boot-의-outbox-예&quot;&gt;Spring Boot 의 outbox 예&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OrderService&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; final&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OrderRepository orderRepo;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; final&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OutboxRepository outboxRepo;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; final&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ObjectMapper objectMapper;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Order &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;place&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Cart &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;cart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Order order &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orderRepo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Order&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cart));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        OutboxEvent event &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OutboxEvent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        event.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setAggregateType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Order&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        event.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setAggregateId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(order.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        event.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setEventType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;OrderPlaced&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        event.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setPayload&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(objectMapper.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;writeValueAsString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEvent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(order)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        event.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setStatus&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;pending&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        outboxRepo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(event);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Component&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OutboxRelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; final&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OutboxRepository outboxRepo;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; final&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; KafkaTemplate&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; kafka;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    @&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Scheduled&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;fixedDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; relay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        List&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;OutboxEvent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; outboxRepo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;findTop100ByStatusOrderById&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;pending&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (OutboxEvent e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            kafka.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;orders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAggregateId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), e.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getPayload&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            e.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setStatus&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sent&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            outboxRepo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@TransactionalEventListener(AFTER_COMMIT)&lt;/code&gt; 도 비슷한 효과 ([[spring-events]]) 지만 worker 인스턴스 죽으면 event 손실 → outbox 가 더 안전.&lt;/p&gt;
&lt;h2 id=&quot;outbox-vs-2pc-vs-saga-정리&quot;&gt;Outbox vs 2PC vs Saga 정리&lt;/h2&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;2PC&lt;/th&gt;&lt;th&gt;Saga&lt;/th&gt;&lt;th&gt;Outbox&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;Strong&lt;/td&gt;&lt;td&gt;Eventual&lt;/td&gt;&lt;td&gt;Eventual&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Blocking&lt;/td&gt;&lt;td&gt;Yes (락)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Compensating 필요&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;(개별 step 에 따름)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 복잡도&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;가용성&lt;/td&gt;&lt;td&gt;낮음 (CP)&lt;/td&gt;&lt;td&gt;높음 (AP)&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용처&lt;/td&gt;&lt;td&gt;legacy (XA)&lt;/td&gt;&lt;td&gt;마이크로서비스 표준&lt;/td&gt;&lt;td&gt;DB + MQ 양립&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;대부분 modern: &lt;strong&gt;Saga + Outbox 조합&lt;/strong&gt;. 2PC 회피.&lt;/p&gt;
&lt;h2 id=&quot;idempotency&quot;&gt;Idempotency&lt;/h2&gt;
&lt;p&gt;분산 시스템의 모든 작업은 &lt;strong&gt;idempotent&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;재시도해도 결과 같음&lt;/li&gt;
&lt;li&gt;중복 메시지 처리해도 OK&lt;/li&gt;
&lt;li&gt;network 재시도 안전&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;구현 패턴:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idempotency key&lt;/strong&gt;: 클라이언트가 unique key 동봉 → 서버가 처음 보면 처리, 두 번째 보면 캐시 결과 반환&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditional update&lt;/strong&gt;: &lt;code&gt;WHERE status=&apos;pending&apos;&lt;/code&gt; → 이미 처리된 row 는 변경 안 됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed lock + check&lt;/strong&gt;: race 회피&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;자주-보는-함정&quot;&gt;자주 보는 함정&lt;/h2&gt;
&lt;h3 id=&quot;함정-1-보상-트랜잭션의-부수효과&quot;&gt;함정 1: 보상 트랜잭션의 부수효과&lt;/h3&gt;
&lt;p&gt;“이메일 발송” 같은 영구 부수효과는 보상 불가. 패턴:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부 호출은 saga 의 마지막 단계로&lt;/li&gt;
&lt;li&gt;또는 confirmation 단계 분리 (&lt;code&gt;PaymentPending&lt;/code&gt; → &lt;code&gt;PaymentConfirmed&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;함정-2-at-least-once-delivery-의-중복&quot;&gt;함정 2: at-least-once delivery 의 중복&lt;/h3&gt;
&lt;p&gt;Kafka / SQS 는 at-least-once. consumer 는 idempotent 처리 필수. Outbox 자체도 publish 후 status update 사이 crash 시 중복 가능.&lt;/p&gt;
&lt;h3 id=&quot;함정-3-outbox-의-throughput-한계&quot;&gt;함정 3: outbox 의 throughput 한계&lt;/h3&gt;
&lt;p&gt;polling worker 가 단일 인스턴스면 bottleneck. 해결:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;partition 별 worker&lt;/li&gt;
&lt;li&gt;Debezium 같은 CDC tool 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;함정-4-saga-의-부분-실패-디버깅&quot;&gt;함정 4: Saga 의 부분 실패 디버깅&lt;/h3&gt;
&lt;p&gt;choreography 면 흐름 추적 어려움. distributed tracing (OpenTelemetry) + correlation ID 필수.&lt;/p&gt;
&lt;h3 id=&quot;함정-5-two-generals-문제&quot;&gt;함정 5: Two-Generals 문제&lt;/h3&gt;
&lt;p&gt;DB commit + 메시지 큐 publish 의 atomicity 는 본질적으로 불가 (network 위에선). Outbox 가 best-effort.&lt;/p&gt;
&lt;h2 id=&quot;함정과-베스트-프랙티스&quot;&gt;함정과 베스트 프랙티스&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2PC 회피&lt;/strong&gt;: 모든 modern 시스템&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saga + Orchestration&lt;/strong&gt;: 흐름 추적 / 디버깅 우선이면&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbox 가 DB+MQ 일관성의 표준&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모든 작업 idempotent&lt;/strong&gt;: idempotency key + conditional update&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;distributed tracing 필수&lt;/strong&gt;: traceId + spanId&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;compensating action 설계가 핵심&lt;/strong&gt;: 영구 부수효과는 마지막에&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eventual consistency 명시적 안내&lt;/strong&gt;: UX 에 반영&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbox 의 worker scaling&lt;/strong&gt;: 단일 worker 가 bottleneck&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[cap-theorem]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-consensus]]&lt;/li&gt;
&lt;li&gt;[[spring-events]]&lt;/li&gt;
&lt;li&gt;[[spring-kafka]]&lt;/li&gt;
&lt;li&gt;[[spring-amqp-rabbitmq]]&lt;/li&gt;
&lt;li&gt;[[spring-transaction]]&lt;/li&gt;
&lt;li&gt;[[zero-downtime-deployment]]&lt;/li&gt;
&lt;li&gt;[[concurrency/pubsub-bus]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>distributed-systems</category><category>transaction</category><category>2pc</category><category>saga</category><category>outbox</category><category>eventual-consistency</category><author>koa (김신건)</author></item></channel></rss>