<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그 - 위키 / concurrency</title><description>&apos;concurrency&apos; 카테고리 위키 페이지 최신 업데이트</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>Pub/Sub Bus</title><link>https://shinkeonkim.com/wiki/concurrency/pubsub-bus/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/pubsub-bus/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pub/Sub Bus&lt;/strong&gt; (Publish-Subscribe bus)는 메시지 송신자(publisher)와 수신자(subscriber)가 서로를 모르고도 통신할 수 있게 해주는 중간 메시지 라우팅 계층이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Publisher&lt;/strong&gt;: 특정 채널/토픽에 메시지를 발행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscriber&lt;/strong&gt;: 관심 있는 채널을 구독, 발행된 메시지 수신&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bus&lt;/strong&gt;: 둘 사이의 라우팅 담당 (Redis, Kafka, NATS, RabbitMQ 등)&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;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;h2 id=&quot;왜-필요한가-분산-stateful-서버의-확장-문제&quot;&gt;왜 필요한가, 분산 [[Stateful]] 서버의 확장 문제&lt;/h2&gt;
&lt;p&gt;WebSocket 같은 지속 연결 서버를 여러 인스턴스로 확장하면 새로운 문제가 생긴다.&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;Client A → Server 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Client B → Server 2 (다른 서버)&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 에게 메시지를 보내려면?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 1 은 B 와 연결되어 있지 않으니 직접 send 할 수 없다.&lt;/p&gt;
&lt;h3 id=&quot;해결책-pubsub-bus&quot;&gt;해결책: Pub/Sub Bus&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;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant CA as &quot;Client A&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S1 as &quot;Server 1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant BUS as &quot;Redis / Kafka&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S2 as &quot;Server 2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant CB as &quot;Client B&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:#E1E4E8&quot;&gt;    CA-&gt;&gt;S1: &quot;B 에게 메시지&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;BUS: publish(&quot;channel-B&quot;, payload)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BUS-&gt;&gt;S2: push (구독 중)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2-&gt;&gt;CB: send(payload)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 자기 클라이언트만 관리하면 됨. 서버 간 라우팅은 bus 가 담당.&lt;/p&gt;
&lt;h2 id=&quot;동작-패턴&quot;&gt;동작 패턴&lt;/h2&gt;
&lt;h3 id=&quot;channel-기반&quot;&gt;Channel 기반&lt;/h3&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Publisher&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redis.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;publish&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;chat:room:123&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ from: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, text: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;hello&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;// Subscriber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redis.&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;chat:room:123&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;message&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&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; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  forwardToClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data);&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;h3 id=&quot;pattern-기반-와일드카드&quot;&gt;Pattern 기반 (와일드카드)&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redis.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;psubscribe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;chat:room:*&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;channel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;message&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // chat:room:123, chat:room:456 모두 매칭&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;h3 id=&quot;topic-기반-계층화&quot;&gt;Topic 기반 (계층화)&lt;/h3&gt;
&lt;p&gt;Kafka 등에서 토픽을 partition 단위로 분배.&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;topic: user-events&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  partition 0: user_id % 3 == 0 인 이벤트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  partition 1: user_id % 3 == 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  partition 2: ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;socketio--redis-adapter-패턴&quot;&gt;Socket.IO + Redis Adapter 패턴&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; io&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; Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(httpServer);&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;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; pubClient&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; createClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ url: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;redis://localhost:6379&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; subClient&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pubClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicate&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:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([pubClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), subClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connect&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;io.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;adapter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createAdapter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(pubClient, subClient));&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:#E1E4E8&quot;&gt;io.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;room-123&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;msg&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, payload);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//   → Redis publish → 모든 서버 인스턴스 → 각자 자기 클라이언트에게 전달&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 코드는 단일 인스턴스 때와 동일. Bus 가 multi-server 라우팅을 투명하게 처리한다.&lt;/p&gt;
&lt;h2 id=&quot;redis-pubsub-vs-redis-streams&quot;&gt;Redis Pub/Sub vs Redis Streams&lt;/h2&gt;
&lt;p&gt;Redis 에는 두 가지 메시지 메커니즘이 있다.&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;    subgraph PS[&quot;Redis Pub/Sub&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P1[&quot;Publisher&quot;] --&gt;|&quot;즉시 전달&quot;| CH[&quot;Channel&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        CH --&gt;|&quot;구독 중인 경우만&quot;| S1[&quot;Subscriber A&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        CH --&gt;|&quot;구독 중인 경우만&quot;| S2[&quot;Subscriber B&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 ST[&quot;Redis Streams&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P2[&quot;Producer&quot;] --&gt;|&quot;영구 저장&quot;| STR[&quot;Stream (log)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        STR --&gt;|&quot;Consumer Group 1&quot;| CG1[&quot;Consumer&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        STR --&gt;|&quot;Consumer Group 2&quot;| CG2[&quot;Consumer&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        STR --&gt;|&quot;ID 기반 replay&quot;| RPL[&quot;새 Subscriber&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;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Redis Pub/Sub&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Redis Streams&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;메시지 보관&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;없음 (fire-and-forget)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;영구 로그&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;오프라인 수신자&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;메시지 유실&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;나중에 읽기 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Consumer Group&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;없음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;있음 (at-most-once / at-least-once)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;순서 보장&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;채널 내 순서 보장&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stream ID 순서 보장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;사용처&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;실시간 브로드캐스트&lt;/td&gt;&lt;td align=&quot;left&quot;&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Redis Streams (Kafka 대안)&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.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;xAdd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user-events&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;*&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, { userId: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;42&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, action: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;login&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;// Consumer Group 으로 읽기 (at-least-once)&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:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;stream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;messages&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:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;xReadGroup&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;  &apos;myGroup&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;consumer-1&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user-events&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, { COUNT: &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&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;h2 id=&quot;pubsub-vs-message-queue-차이점&quot;&gt;Pub/Sub vs Message Queue (차이점)&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Pub/Sub&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Message Queue&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;메시지 보관&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;즉시 전달 (fire-and-forget)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;큐에 보관, 컨슈머가 pull&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;다중 수신자&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모든 subscriber 가 받음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;보통 1 consumer 가 받음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;메시지 유실&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;subscriber 가 offline 이면 잃을 수 있음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;persistent 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;예시&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Redis Pub/Sub, NATS&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;RabbitMQ, SQS, Kafka (consumer group 모드)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Redis Pub/Sub&lt;/strong&gt; 은 단순 Pub/Sub. &lt;strong&gt;Kafka&lt;/strong&gt; 는 두 모드 모두 가능 (broadcast + consumer group).&lt;/p&gt;
&lt;h2 id=&quot;kafka-pubsub-과-consumer-group&quot;&gt;Kafka: Pub/Sub 과 Consumer Group&lt;/h2&gt;
&lt;p&gt;Kafka 는 두 패턴을 동시에 지원한다.&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 TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TOPIC[&quot;Topic: order-created\n(partition 0, 1, 2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TOPIC --&gt; GRP1[&quot;Consumer Group A\n(결제 서비스)\n각 partition 을 1 consumer 가 처리&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TOPIC --&gt; GRP2[&quot;Consumer Group B\n(알림 서비스)\n동일 메시지 복제 수신&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GRP1 --&gt; C1[&quot;Consumer A-0 (partition 0)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GRP1 --&gt; C2[&quot;Consumer A-1 (partition 1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GRP1 --&gt; C3[&quot;Consumer A-2 (partition 2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GRP2 --&gt; N1[&quot;Consumer B-0&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;같은 Group 내&lt;/strong&gt;: partition 마다 1 consumer 배정 (큐 방식, 메시지 1회 처리)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다른 Group&lt;/strong&gt;: 독립 오프셋, 같은 메시지를 각 Group 이 개별 수신 (Pub/Sub 방식)&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Redis Pub/Sub: 채널 내 단일 subscriber 에 대한 순서는 보장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              하지만 여러 서버에 broadcast 시 도달 순서는 보장 안 함&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:        partition 내 순서만 보장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              여러 partition 에 걸치면 순서 보장 안 함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              → 같은 key 를 같은 partition 으로 라우팅해서 순서 보장&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Kafka: key 로 partition 고정 → 순서 보장&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; producer.&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  topic: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user-events&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:#E1E4E8&quot;&gt;  messages: [{ key: userId.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), value: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&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 style=&quot;color:#6A737D&quot;&gt;  //          ↑ 같은 userId 는 항상 같은 partition&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;h2 id=&quot;부하-특성&quot;&gt;부하 특성&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;구성 요소&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;부하&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Publisher 서버&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;메시지마다 publish 호출 (~0.1 ms)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bus&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모든 publish 가 통과, 가장 부담 큰 단일 지점&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Subscriber 서버&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수신 + 자기 클라이언트에게 forward&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;Bus 가 SPOF (단일 장애점)&lt;/strong&gt; 가 될 수 있다. 고가용성 클러스터 (Redis Sentinel/Cluster, Kafka multi-broker) 가 필수.
또 다른 함정: 모든 메시지가 bus 를 통과하므로 &lt;strong&gt;bus 처리량이 시스템 전체의 상한&lt;/strong&gt;이 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;사용-시나리오&quot;&gt;사용 시나리오&lt;/h2&gt;
&lt;h3 id=&quot;1-websocket-클러스터&quot;&gt;1. WebSocket 클러스터&lt;/h3&gt;
&lt;p&gt;위에서 설명한 Socket.IO + Redis Adapter 패턴.&lt;/p&gt;
&lt;h3 id=&quot;2-마이크로서비스-간-이벤트-전파&quot;&gt;2. 마이크로서비스 간 이벤트 전파&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 &quot;order.created&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 결제 서비스 (subscribe)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 재고 서비스 (subscribe)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 알림 서비스 (subscribe)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 서비스가 독립적으로 진화 가능.&lt;/p&gt;
&lt;h3 id=&quot;3-실시간-알림-시스템&quot;&gt;3. 실시간 알림 시스템&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 &quot;notify:user-{id}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 사용자가 접속한 서버가 subscribe → 전달&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-cache-invalidation&quot;&gt;4. Cache invalidation&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;DB 업데이트 → publish &quot;cache-invalidate:user-{id}&quot;&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;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;subscriber 가 오프라인이면 Redis Pub/Sub 메시지는 유실&lt;/strong&gt;된다. 장애 복구 후 재구독해도 그 사이 메시지는 없다. 내구성이 필요하면 Redis Streams 또는 Kafka 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;Hot channel 문제&lt;/strong&gt;: 특정 채널에 메시지가 폭발적으로 몰리면 bus 에 부하가 집중된다. 채널을 partition 단위로 분리하거나 [[backpressure]] 적용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;Kafka consumer lag&lt;/strong&gt;: Consumer 가 처리 속도를 따라가지 못하면 lag 가 쌓인다. &lt;code&gt;kafka-consumer-groups.sh --describe&lt;/code&gt; 로 모니터링 필수.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;대안과-trade-off&quot;&gt;대안과 trade-off&lt;/h2&gt;
&lt;h3 id=&quot;grpc-streaming--서비스-디스커버리&quot;&gt;gRPC streaming + 서비스 디스커버리&lt;/h3&gt;
&lt;p&gt;서비스 간 직접 stream. Bus 없음. 더 빠르지만 N×M 연결 관리.&lt;/p&gt;
&lt;h3 id=&quot;service-mesh-istio-등&quot;&gt;Service mesh (Istio 등)&lt;/h3&gt;
&lt;p&gt;L7 라우팅으로 메시지 전달. 더 강력하지만 복잡.&lt;/p&gt;
&lt;h3 id=&quot;database-notification&quot;&gt;Database notification&lt;/h3&gt;
&lt;p&gt;PostgreSQL &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; 같은 DB 내장 Pub/Sub. 외부 인프라 불필요.&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:#6A737D&quot;&gt;-- Publisher (PostgreSQL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;NOTIFY user_events, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;userId&quot;: 42, &quot;action&quot;: &quot;login&quot;}&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;-- Subscriber (psycopg2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;set_isolation_level&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ISOLATION_LEVEL_AUTOCOMMIT)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cur&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;LISTEN user_events;&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;select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([conn], [], [])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;poll&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; notify &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; conn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;notifies&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;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;notify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;payload&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;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Stateful]] - Pub/Sub bus 가 필요한 이유: 상태를 가진 서버 확장&lt;/li&gt;
&lt;li&gt;[[Sticky Session]] - 대안적 접근: 특정 서버에 클라이언트 고정&lt;/li&gt;
&lt;li&gt;[[backpressure]] - 메시지 폭발 시 publisher 속도 제어&lt;/li&gt;
&lt;li&gt;[[circuit-breaker]] - Bus 장애 시 graceful degradation&lt;/li&gt;
&lt;li&gt;[[rate-limiting]] - publisher 속도 제어&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>architecture</category><category>messaging</category><category>scaling</category><category>network</category><author>koa (김신건)</author></item><item><title>[Concurrency] Connection Pool: DB, HTTP, gRPC 의 공통 패턴</title><link>https://shinkeonkim.com/wiki/concurrency/connection-pool/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/connection-pool/</guid><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Connection Pool&lt;/strong&gt; = &lt;em&gt;연결을 미리 만들어 두고 재사용&lt;/em&gt;. 매 요청마다 &lt;em&gt;TCP + TLS + auth&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;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Pool 의 동작 = &lt;em&gt;producer (request) / consumer (worker thread)&lt;/em&gt; 의 &lt;em&gt;blocking queue&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;Connection Pool&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;TCP handshake (~ms)&lt;/td&gt;&lt;td&gt;&lt;em&gt;0&lt;/em&gt; (재사용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TLS handshake (~수십 ms)&lt;/td&gt;&lt;td&gt;&lt;em&gt;0&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB auth + session 설정&lt;/td&gt;&lt;td&gt;&lt;em&gt;0&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OS file descriptor 한도 부담&lt;/td&gt;&lt;td&gt;제한적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TIME_WAIT socket 폭증&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;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;    Created[&quot;생성 (TCP + TLS + auth)&quot;] --&gt; Idle[Idle]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idle --&gt;|acquire| Active[Active]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Active --&gt;|release| Idle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Active --&gt;|에러| Expired[폐기]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idle --&gt;|idle timeout| Expired&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idle --&gt;|max lifetime| Expired&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Expired --&gt;|&quot;min_size 유지&quot;| Created&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;연결이 &lt;code&gt;max_lifetime&lt;/code&gt; 에 달하기 전에 교체되어야 LB / firewall 의 idle disconnect 로 인한 stale 에러를 피할 수 있다.&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;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 Pool&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;    App-&gt;&gt;Pool: acquire()&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;        Pool--&gt;&gt;App: connection&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;        Pool-&gt;&gt;Pool: 새 연결 생성 (max 까지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        alt max 미도달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Pool-&gt;&gt;DB: 새 TCP/TLS/auth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Pool--&gt;&gt;App: 새 connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        else max 도달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Pool--&gt;&gt;App: wait queue&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;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;DB: SELECT ...&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: rows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Pool: release()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pool--&gt;&gt;Pool: 연결 풀에 반환&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;code&gt;min_size&lt;/code&gt; / &lt;code&gt;idle&lt;/code&gt;&lt;/td&gt;&lt;td&gt;최소 idle 연결&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max_size&lt;/code&gt;&lt;/td&gt;&lt;td&gt;최대 연결&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;acquire_timeout&lt;/code&gt;&lt;/td&gt;&lt;td&gt;acquire 대기 한도&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;idle_timeout&lt;/code&gt;&lt;/td&gt;&lt;td&gt;idle 연결 만료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max_lifetime&lt;/code&gt;&lt;/td&gt;&lt;td&gt;연결의 최대 수명&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;validation_query&lt;/code&gt;&lt;/td&gt;&lt;td&gt;health check (&lt;code&gt;SELECT 1&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;leak_detection&lt;/code&gt;&lt;/td&gt;&lt;td&gt;반환 안 된 연결 추적&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;pool-크기-계산&quot;&gt;Pool 크기 계산&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;Pool size ≈ (cores × 2) + effective_spindle_count&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPU 가 &lt;em&gt;N 코어&lt;/em&gt; 면 &lt;em&gt;동시 처리 가능한 작업 ~ 2N&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;너무 큼&lt;/em&gt; = context switch / lock 경쟁 / DB 부담.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;너무 작음&lt;/em&gt; = wait queue 길어짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;littles-law&quot;&gt;Little’s Law&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;L = λ × W&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;연결 수 = 처리량 (req/s) × 응답 시간 (s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예: 1000 req/s, 100ms 응답 → &lt;em&gt;100 connections&lt;/em&gt; 가 &lt;em&gt;충분&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;대부분의 운영 사고는 pool size 가 너무 크기&lt;/em&gt; 때문. PG 의 &lt;code&gt;max_connections=200&lt;/code&gt; 에 &lt;em&gt;50 app instance × pool=10&lt;/em&gt; = 500 시도 → DB 거절.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;db-connection-pool-구조&quot;&gt;DB Connection Pool 구조&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[App instances]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A1[App 1&amp;#x3C;br/&gt;pool 10]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A2[App 2&amp;#x3C;br/&gt;pool 10]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A3[App N&amp;#x3C;br/&gt;pool 10]&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;    App --&gt; PgB[PgBouncer&amp;#x3C;br/&gt;session/transaction pool]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PgB --&gt; PG[(PostgreSQL&amp;#x3C;br/&gt;max 100)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;PgBouncer&lt;/em&gt; 같은 &lt;em&gt;external pooler&lt;/em&gt; 가 &lt;em&gt;N × app pool&lt;/em&gt; 을 &lt;em&gt;PG 의 작은 pool&lt;/em&gt; 로 &lt;em&gt;압축&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mode&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&lt;/code&gt;&lt;/td&gt;&lt;td&gt;클라이언트가 disconnect 할 때까지 연결 점유&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;transaction&lt;/code&gt;&lt;/td&gt;&lt;td&gt;트랜잭션 동안만 (가장 효율)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;statement&lt;/code&gt;&lt;/td&gt;&lt;td&gt;매 statement (위험, prepared 안 됨)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;http-connection-pool&quot;&gt;HTTP Connection Pool&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[&quot;HTTP Client&amp;#x3C;br/&gt;(axios, OkHttp, ...)&quot;] --&gt;|pool| Conn1[connection 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|pool| Conn2[connection 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|pool| Conn3[connection 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Conn1 &amp;#x26; Conn2 &amp;#x26; Conn3 --&gt; Server[Server]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[&quot;HTTP/1.1: per-host pool&amp;#x3C;br/&gt;HTTP/2: 1 connection 의 multiplexing&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;hikaricp-설정-java&quot;&gt;HikariCP 설정 (Java)&lt;/h2&gt;
&lt;p&gt;Java 생태계에서 가장 빠른 JDBC pool. Spring Boot 의 기본값이다.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HikariConfig config &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; HikariConfig&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;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setJdbcUrl&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;jdbc:postgresql://host:5432/mydb&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;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setUsername&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;app_user&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;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setPassword&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;password&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:#6A737D&quot;&gt;// 핵심 파라미터&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setMinimumIdle&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 style=&quot;color:#6A737D&quot;&gt;// 최소 idle 연결&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setMaximumPoolSize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);        &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:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setConnectionTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// acquire 대기 최대 3s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setIdleTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;600_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);       &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// idle 연결 10분 후 만료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setMaxLifetime&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1_800_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 연결 최대 수명 30분&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setLeakDetectionThreshold&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2s 반환 안 되면 경고&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;HikariDataSource dataSource &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; HikariDataSource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;code&gt;maxLifetime&lt;/code&gt; 은 DB 또는 LB 의 idle timeout 보다 짧게 설정. 그렇지 않으면 LB 가 서버 측 연결을 끊었을 때 stale connection 에러가 난다.&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# application.yml (Spring Boot)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spring&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:#85E89D&quot;&gt;  datasource&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:#85E89D&quot;&gt;    hikari&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:#85E89D&quot;&gt;      minimum-idle&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      maximum-pool-size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      connection-timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      idle-timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;600000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      max-lifetime&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1800000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;pgbouncer-설정&quot;&gt;PgBouncer 설정&lt;/h2&gt;
&lt;p&gt;PostgreSQL 전용 외부 connection pooler. 여러 app 인스턴스의 연결을 PG 의 작은 pool 로 압축한다.&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;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[databases]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;mydb&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=postgres-host &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=5432 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;dbname&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=mydb&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:#B392F0&quot;&gt;[pgbouncer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;listen_port&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     = 6432&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;listen_addr&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;auth_type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       = md5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auth_file&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       = /etc/pgbouncer/userlist.txt&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;pool_mode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;           = transaction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;max_client_conn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     = 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;default_pool_size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   = 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;min_pool_size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       = 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;reserve_pool_size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   = 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;reserve_pool_timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = 3&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;server_idle_timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  = 600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;server_lifetime&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      = 3600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;transaction mode&lt;/strong&gt; 가 효율이 가장 높다. 단, prepared statement 와 일부 세션 변수 (&lt;code&gt;SET LOCAL&lt;/code&gt;, &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;) 는 transaction 경계를 넘으면 동작하지 않는다.&lt;/p&gt;
&lt;h2 id=&quot;python-sqlalchemy-풀-설정&quot;&gt;Python SQLAlchemy 풀 설정&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;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sqlalchemy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_engine&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;engine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_engine(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;postgresql+psycopg2://user:pass@host:5432/db&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;    pool_size&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:#6A737D&quot;&gt;# 기본 pool 크기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    max_overflow&lt;/span&gt;&lt;span style=&quot;color:#F97583&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 style=&quot;color:#6A737D&quot;&gt;# pool_size 초과 허용 (임시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    pool_timeout&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# acquire 대기 최대 3s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    pool_recycle&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1800&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 30분마다 연결 재생성 (stale 방지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    pool_pre_ping&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:#6A737D&quot;&gt;# acquire 전 SELECT 1 validation&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;pool_pre_ping=True&lt;/code&gt; 가 stale connection 에러를 대부분 제거한다. 비용은 acquire 시 &lt;code&gt;SELECT 1&lt;/code&gt; 1회.&lt;/p&gt;
&lt;h2 id=&quot;모니터링-지표&quot;&gt;모니터링 지표&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;메트릭&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;주의 신호&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;active_connections&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;현재 사용 중인 연결 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;max_size&lt;/code&gt; 의 90% 이상 지속&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;idle_connections&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;대기 중인 연결 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;min_size&lt;/code&gt; 아래로 떨어지면 부족&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;wait_queue_length&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;acquire 대기 요청 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0 을 초과하면 pool 부족&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;connection_create_rate&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;초당 새 연결 생성 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;높으면 pool 크기 부족 또는 leak&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;checkout_timeout_rate&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;acquire 실패율&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;어느 값이든 즉각 조치&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;connection_error_rate&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;연결 에러율&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;DB 이슈 또는 stale 연결&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;HikariCP: Prometheus endpoint 또는 JMX 로 위 지표 모두 노출 가능.&lt;/p&gt;
&lt;h2 id=&quot;grpc-연결-풀&quot;&gt;gRPC 연결 풀&lt;/h2&gt;
&lt;p&gt;gRPC 는 HTTP/2 multiplexing 으로 단일 채널에서 수천 동시 스트림 처리가 가능하다. DB 처럼 대규모 pool 이 필요 없다.&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;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Go: grpc.ClientConn 재사용 (pool 없이도 됨)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;conn, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; grpc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Dial&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;service:50051&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, grpc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithTransportCredentials&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(insecure.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewCredentials&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:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&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;    log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err)&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;defer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conn.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Close&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;// 여러 goroutine 에서 같은 conn 동시 사용 가능&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pb.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewUserServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(conn)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pool 이 필요한 경우는 백엔드 인스턴스가 여럿이라 여러 address 로 분산할 때다.&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;Pool size &lt;em&gt;너무 큼&lt;/em&gt;&lt;/strong&gt; = DB 거절 + thrashing. Little’s Law 로 계산.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;max_lifetime&lt;/code&gt; 없음&lt;/strong&gt; = LB / firewall 의 &lt;em&gt;idle disconnect&lt;/em&gt; 후 stale connection 시도 → 에러.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leak (release 안 함)&lt;/strong&gt; = pool 고갈. &lt;em&gt;try-with-resources / context manager&lt;/em&gt; 강제.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation query 부재&lt;/strong&gt; = stale 연결로 &lt;em&gt;간헐적 에러&lt;/em&gt;. &lt;code&gt;SELECT 1&lt;/code&gt; 또는 &lt;em&gt;pool 자체의 keep-alive&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;hikaricp--표준-라이브러리&quot;&gt;HikariCP / 표준 라이브러리&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;HikariCP&lt;/td&gt;&lt;td&gt;Java (가장 빠름)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pgx / pgxpool&lt;/td&gt;&lt;td&gt;Go&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;psycopg pool, SQLAlchemy&lt;/td&gt;&lt;td&gt;Python&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;node-postgres&lt;/td&gt;&lt;td&gt;Node&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ActiveRecord&lt;/td&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[postgresql]], [[mysql-innodb]]&lt;/li&gt;
&lt;li&gt;[[Load Balancer]]&lt;/li&gt;
&lt;li&gt;[[backpressure]]&lt;/li&gt;
&lt;li&gt;[[redis]], 외부 세션 store 및 connection pool의 대안&lt;/li&gt;
&lt;li&gt;[[stateless]], 외부 store 에 의존하는 이유&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>connection-pool</category><category>database</category><category>concurrency</category><category>performance</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Concurrency] Retry + Exponential Backoff + Jitter</title><link>https://shinkeonkim.com/wiki/concurrency/retry-with-backoff/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/retry-with-backoff/</guid><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Retry with Exponential Backoff + Jitter&lt;/strong&gt; = &lt;em&gt;실패 시 점점 긴 간격으로 재시도, 랜덤 분산&lt;/em&gt;. 분산 시스템의 &lt;em&gt;thundering herd / retry storm&lt;/em&gt; 방지의 &lt;em&gt;표준&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;4가지-전략&quot;&gt;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 LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Linear[&quot;Linear&amp;#x3C;br/&gt;1s, 2s, 3s, 4s, ...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Exp[&quot;Exponential&amp;#x3C;br/&gt;1s, 2s, 4s, 8s, 16s, ...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ExpJ[&quot;Exp + Jitter&amp;#x3C;br/&gt;1s + random, 4s + random, ...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DecorJ[&quot;Decorrelated Jitter&amp;#x3C;br/&gt;AWS 권장&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;exponential&quot;&gt;Exponential&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sleep &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; base &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 style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# attempt 0: 1s, 1: 2s, 2: 4s, 3: 8s, ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문제: &lt;em&gt;모든 클라이언트가 같은 시각에 재시도&lt;/em&gt; → thundering herd.&lt;/p&gt;
&lt;h3 id=&quot;exponential--full-jitter&quot;&gt;Exponential + Full Jitter&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sleep &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; random.uniform(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, base &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 style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;완전 분산&lt;/em&gt;. AWS / Stripe 표준.&lt;/p&gt;
&lt;h3 id=&quot;decorrelated-jitter&quot;&gt;Decorrelated Jitter&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sleep &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; random.uniform(base, prev_sleep &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&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;과거 sleep 의 함수&lt;/em&gt;. &lt;em&gt;완전 무작위 + 점진 증가&lt;/em&gt;. 평균적으로 가장 짧은 retry 시간.&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;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 C as Client&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;    C-&gt;&gt;S: 1차 요청&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: 503 Unavailable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: wait 1s + jitter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: 2차 재시도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: 503 Unavailable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: wait 2s + jitter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: 3차 재시도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: 200 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 성공, 응답 반환&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 retry 사이에 &lt;code&gt;sleep&lt;/code&gt; 을 두고, sleep 은 attempt 가 늘어날수록 exponentially 커진다. jitter 로 동일 시각 재시도를 분산한다.&lt;/p&gt;
&lt;h2 id=&quot;비교-시뮬레이션&quot;&gt;비교 시뮬레이션&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;retry-가능한-에러만-retry&quot;&gt;Retry 가능한 에러만 retry&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;    E[에러 종류]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|5xx, timeout, conn refused| R[Retry OK]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|&quot;4xx (400, 401, 403, 404)&quot;| NoR[Retry 금지]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|429 Too Many Requests| Special[Retry-After 헤더 따름]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|&quot;409 Conflict (idempotent 아니면)&quot;| NoR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|503 + Retry-After| Special&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;4xx 는 클라이언트 오류&lt;/em&gt;. retry 해도 같은 결과. 5xx / network 만 retry 가치.&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;    Retry --&gt; Need[Idempotent operation 필수]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Need --&gt; Idem[Idempotency-Key 또는&amp;#x3C;br/&gt;at-most-once 보장]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비-멱등 (POST 결제) 은 &lt;em&gt;retry 가 중복 처리 위험&lt;/em&gt;. [[idempotency-keys]] 가 &lt;em&gt;짝&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;Max attempts&lt;/td&gt;&lt;td&gt;3-5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Max total time&lt;/td&gt;&lt;td&gt;30-60s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Per-attempt timeout&lt;/td&gt;&lt;td&gt;5-10s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Max sleep&lt;/td&gt;&lt;td&gt;16-32s&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;무한 retry = 사용자 &lt;em&gt;영원히 대기&lt;/em&gt;. 빠른 &lt;em&gt;deadline&lt;/em&gt; + fallback.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;deadline-전파&quot;&gt;Deadline 전파&lt;/h2&gt;
&lt;p&gt;분산 시스템에서는 클라이언트가 설정한 deadline 을 &lt;strong&gt;모든 하위 서비스로 전파&lt;/strong&gt;해야 한다. 그렇지 않으면 클라이언트는 이미 timeout 되었는데 하위 서비스는 계속 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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; grpc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; datetime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; datetime, timedelta&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;# gRPC: deadline 을 context 로 전파&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; grpc.insecure_channel(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;service:50051&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; channel:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    stub &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ServiceStub(channel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    deadline &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; datetime.utcnow() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timedelta(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;seconds&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # timeout 이 남은 시간으로 자동 설정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    resp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stub.CallMethod(request, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10.0&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;HTTP 에서는 &lt;code&gt;X-Request-Deadline&lt;/code&gt; 같은 커스텀 헤더 또는 &lt;code&gt;X-B3-Deadline&lt;/code&gt; 로 전파한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
deadline 전파 없이 retry 하면 &lt;em&gt;zombie retry&lt;/em&gt; 가 발생한다. 클라이언트는 504 를 받고 떠났는데 서버 내부에서는 수 분간 재시도가 계속된다. 자원 낭비 + 부하 증폭.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;구현-예시&quot;&gt;구현 예시&lt;/h2&gt;
&lt;h3 id=&quot;python-tenacity&quot;&gt;Python: tenacity&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tenacity &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&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;    retry,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    stop_after_attempt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    wait_exponential,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    wait_random,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    retry_if_exception_type,&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;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests&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:#B392F0&quot;&gt;@retry&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;    stop&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stop_after_attempt(&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:#FFAB70&quot;&gt;    wait&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wait_exponential(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;multiplier&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;16&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; wait_random(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&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;    retry&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;retry_if_exception_type((requests.ConnectionError, requests.Timeout)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    reraise&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&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; call_api&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;dict&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;    resp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.get(url, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#F97583&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:#E1E4E8&quot;&gt;    resp.raise_for_status()&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; resp.json()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;java-resilience4j&quot;&gt;Java: Resilience4j&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;RetryConfig config &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; RetryConfig.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;custom&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:#B392F0&quot;&gt;maxAttempts&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:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;intervalFunction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(IntervalFunction.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofExponentialRandomBackoff&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2.0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.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:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;retryOnException&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;instanceof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HttpServerErrorException)&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:#B392F0&quot;&gt;build&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;Retry retry &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Retry.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;api-call&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Supplier&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;&gt; decorated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Retry.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;decorateSupplier&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(retry, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; callDownstream&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;Try.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofSupplier&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(decorated).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;recover&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ex &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fallback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()).&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;go-backoff-라이브러리&quot;&gt;Go: backoff 라이브러리&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;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/cenkalti/backoff/v4&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&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:#E1E4E8&quot;&gt;b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; backoff.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewExponentialBackOff&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;b.InitialInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;b.MaxInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 16&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;b.MaxElapsedTime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Second&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;err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; backoff.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RetryNotify&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;    func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&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:#B392F0&quot;&gt; callAPI&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx)&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;    backoff.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b, ctx),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Duration&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;        log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Printf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;retry after &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, d, err)&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;h2 id=&quot;hedged-requests&quot;&gt;Hedged Requests&lt;/h2&gt;
&lt;p&gt;Retry 와 다른 전략: &lt;em&gt;첫 요청이 일정 시간 내에 응답 안 오면 두 번째 요청을 병렬로 보내고, 먼저 오는 응답을 사용&lt;/em&gt;. 실패 없이 tail latency 를 낮추는 패턴.&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;t=0:   → request 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;t=50ms → (응답 없음) → request 2 병렬 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;t=80ms ← response 2 도착 → response 1 취소&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;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; asyncio&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; hedged_call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, hedge_delay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.05&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;dict&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;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; aiohttp.ClientSession() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        task1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; asyncio.create_task(session.get(url))&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; asyncio.sleep(hedge_delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        task2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; asyncio.create_task(session.get(url))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        done, pending &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; asyncio.wait(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            [task1, task2], &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;return_when&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;asyncio.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;FIRST_COMPLETED&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; t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pending:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            t.cancel()&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:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; done.pop()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Hedging 은 &lt;em&gt;non-idempotent 요청 (POST 결제 등) 에는 금지&lt;/em&gt;. 멱등 읽기 (GET, SELECT) 에만 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;retry-after-헤더&quot;&gt;Retry-After 헤더&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;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&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 style=&quot;color:#79B8FF&quot;&gt; 429&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Too Many Requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Retry-After&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 30&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Retry-After&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Wed, 25 Jun 2026 12:30:00 GMT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;서버가 client 에게 &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;sdk--library&quot;&gt;SDK / Library&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;AWS SDK&lt;/td&gt;&lt;td&gt;모든 언어 (자동 retry)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Polly&lt;/td&gt;&lt;td&gt;.NET&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Resilience4j Retry&lt;/td&gt;&lt;td&gt;Java&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;tenacity&lt;/td&gt;&lt;td&gt;Python&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;node-fetch + retry&lt;/td&gt;&lt;td&gt;Node&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;backoff (Go)&lt;/td&gt;&lt;td&gt;Go&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;retry-budget&quot;&gt;Retry Budget&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;    Calls[전체 호출 수] --&gt; Budget[Retry 예산: 20%]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Budget --&gt; Limit[1000 호출 → 최대 200 retry]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Limit --&gt; Reject[초과 시 fast fail]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;한 service 의 retry 가 &lt;em&gt;전체 호출의 큰 비중&lt;/em&gt; 이면 &lt;em&gt;retry storm&lt;/em&gt;. &lt;em&gt;예산 한도&lt;/em&gt; 로 보호. gRPC 의 retry budget, Envoy retry policy.&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;Jitter 없음&lt;/strong&gt; = thundering herd. &lt;em&gt;전체 fleet 동시 retry&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모든 에러 retry&lt;/strong&gt; = 4xx 도 retry → 의미 없는 부하.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idempotent 아닌데 retry&lt;/strong&gt; = 결제 중복. Idempotency-Key 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Max attempt 무한&lt;/strong&gt; = 사용자 영원히 대기. timeout + fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nested retry&lt;/strong&gt; = 계층마다 retry (client → SDK → SDK 내부) → &lt;em&gt;retry × retry&lt;/em&gt; 폭증. 한 곳에서만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deadline 미전파&lt;/strong&gt; = 클라이언트 timeout 후에도 하위 서비스가 계속 재시도.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[circuit-breaker]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]]&lt;/li&gt;
&lt;li&gt;[[backpressure]]&lt;/li&gt;
&lt;li&gt;[[rate-limiting]]&lt;/li&gt;
&lt;li&gt;[[api-gateway]], gateway 레벨 retry 정책 설정&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>retry</category><category>backoff</category><category>jitter</category><category>resilience</category><category>concurrency</category><author>koa (김신건)</author></item><item><title>Stateful</title><link>https://shinkeonkim.com/wiki/concurrency/stateful/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/stateful/</guid><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Stateful (상태유지)&lt;/strong&gt; 서버는 클라이언트와의 세션·연결 컨텍스트를 메모리에 유지한다. 후속 요청·이벤트는 이 컨텍스트를 활용해 처리된다.&lt;/p&gt;
&lt;p&gt;같은 클라이언트의 요청은 같은 서버로 가야 의미가 있다. 그래서 수평 확장이 복잡해진다.&lt;/p&gt;
&lt;p&gt;전체 비교는 [[Stateless vs Stateful]] 페이지를 참조.&lt;/p&gt;
&lt;h2 id=&quot;핵심-특성&quot;&gt;핵심 특성&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;세션 상태&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 메모리에 보관&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수평 확장&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Sticky Session]] 또는 외부 store 필수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;장애 복원&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 다운 시 세션 손실 (외부 store 시 완화)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;인증 정보&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세션 ID 발급, 서버가 검증&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;캐싱&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;사용자별 응답 → 캐시 효율 낮음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;수평-확장-시각화&quot;&gt;수평 확장 시각화&lt;/h2&gt;
&lt;h3 id=&quot;sticky-session-구성&quot;&gt;Sticky Session 구성&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;    LB[Load Balancer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1[Client 1] --&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2[Client 2] --&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C3[Client 3] --&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt;|sticky A| SA[Server A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt;|sticky B| SB[Server B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA --&gt; MemA[Session A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SB --&gt; MemB[Session B]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LB 가 쿠키 또는 IP hash 로 클라이언트를 특정 서버에 고정한다. Server A 가 다운되면 그 세션들이 모두 손실된다.&lt;/p&gt;
&lt;h3 id=&quot;websocket-클러스터-pubsub-버스&quot;&gt;WebSocket 클러스터 (Pub/Sub 버스)&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;    C1[Client 1] --&gt; SA[Server A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2[Client 2] --&gt; SB[Server B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA --&gt;|publish| Bus[&quot;Redis Pub/Sub&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SB --&gt;|subscribe| Bus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bus --&gt;|&quot;broadcast&quot;| SA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bus --&gt;|&quot;broadcast&quot;| SB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 A 가 클라이언트 B 에게 메시지를 전달해야 할 때, Redis Pub/Sub 버스를 통해 서버 B 가 대신 전달한다. Socket.IO + Redis Adapter 패턴이 전형.&lt;/p&gt;
&lt;h2 id=&quot;어디에-상태가-사는가&quot;&gt;어디에 상태가 사는가&lt;/h2&gt;
&lt;h3 id=&quot;1-인메모리-가장-흔함&quot;&gt;1. 인메모리 (가장 흔함)&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;javascript&quot;&gt;&lt;code&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; sessions&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; Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// sessionId → user_data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;빠르지만 휘발성. 서버 재시작 = 세션 손실.&lt;/p&gt;
&lt;h3 id=&quot;2-활성-연결-자체&quot;&gt;2. 활성 연결 자체&lt;/h3&gt;
&lt;p&gt;WebSocket, SSE 의 경우 &lt;strong&gt;TCP 연결 자체가 상태 컨텍스트&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;javascript&quot;&gt;&lt;code&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; wss&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; WebSocketServer&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;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wss.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;connection&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ws&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.userId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decodeAuth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ws.upgradeReq);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.rooms &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; Set&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;  // 이 ws 객체가 사라질 때까지 컨텍스트 유지&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;/p&gt;
&lt;h3 id=&quot;3-외부-store-사실상-stateless-화&quot;&gt;3. 외부 store (사실상 Stateless 화)&lt;/h3&gt;
&lt;p&gt;세션 데이터를 Redis 같은 외부 store 에 두면 형식상 Stateless 서버가 된다.&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Express + connect-redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;session&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: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RedisStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ client: redis }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  secret: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xxx&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:#E1E4E8&quot;&gt;}));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;stateful-서버의-수평-확장&quot;&gt;Stateful 서버의 수평 확장&lt;/h2&gt;
&lt;p&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;Client X 가 Server A 에 로그인 → A의 메모리에 세션 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Client X 의 다음 요청 → LB가 Server B 로 라우팅 → B는 세션 모름 → 인증 실패&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;해결책-1-sticky-session&quot;&gt;해결책 1: [[Sticky Session]]&lt;/h3&gt;
&lt;p&gt;LB 가 같은 클라이언트를 같은 서버로. 가장 흔하고 가장 단순.&lt;/p&gt;
&lt;p&gt;단점: 서버 다운 시 세션 손실, 부하 불균형 가능.&lt;/p&gt;
&lt;h3 id=&quot;해결책-2-외부-세션-store&quot;&gt;해결책 2: 외부 세션 store&lt;/h3&gt;
&lt;p&gt;세션 데이터를 Redis 등에 두면 어느 서버나 조회 가능. 사실상 Stateless 화.&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;Client X → 아무 서버 → Redis 에서 세션 조회 → 처리&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매 요청마다 store I/O 추가.&lt;/p&gt;
&lt;h3 id=&quot;해결책-3-pubsub-bus-지속-연결-전용&quot;&gt;해결책 3: [[Pub/Sub bus]] (지속 연결 전용)&lt;/h3&gt;
&lt;p&gt;서버 간 메시지 라우팅용. WebSocket 같이 연결 자체가 본질적인 경우.&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;Server A 가 Client X 보유 / Server B 가 Client Y 보유&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A 에 &apos;Y 에게 메시지&apos; 도착 → Redis publish → B subscribe → Y 에게 전달&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Socket.IO + Redis Adapter 패턴이 전형.&lt;/p&gt;
&lt;h2 id=&quot;kubernetes-statefulset&quot;&gt;Kubernetes StatefulSet&lt;/h2&gt;
&lt;p&gt;Stateful 워크로드는 Kubernetes &lt;code&gt;StatefulSet&lt;/code&gt; 으로 운영한다. Deployment 와 달리 각 pod 에 &lt;strong&gt;안정적인 이름과 순서&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apps/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;StatefulSet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&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:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;chat-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&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:#85E89D&quot;&gt;  serviceName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;chat&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  replicas&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  selector&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:#85E89D&quot;&gt;    matchLabels&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:#85E89D&quot;&gt;      app&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;chat-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  template&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:#85E89D&quot;&gt;    spec&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:#85E89D&quot;&gt;      containers&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:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;chat-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;chat:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          volumeMounts&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:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;              mountPath&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  volumeClaimTemplates&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:#85E89D&quot;&gt;metadata&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:#85E89D&quot;&gt;        name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      spec&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:#85E89D&quot;&gt;        accessModes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ReadWriteOnce&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:#85E89D&quot;&gt;        resources&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:#85E89D&quot;&gt;          requests&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:#85E89D&quot;&gt;            storage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;10Gi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;StatefulSet 이 생성하는 pod 이름: &lt;code&gt;chat-server-0&lt;/code&gt;, &lt;code&gt;chat-server-1&lt;/code&gt;, &lt;code&gt;chat-server-2&lt;/code&gt;.
DNS 이름: &lt;code&gt;chat-server-0.chat.default.svc.cluster.local&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
StatefulSet 은 &lt;em&gt;삭제 순서도 역순으로 보장&lt;/em&gt;한다. 데이터베이스처럼 replica 순서가 중요한 시스템에 필수.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;[[k8s-statefulset]] 에서 더 자세한 설명을 볼 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;세션-어피니티-sticky-session-상세&quot;&gt;세션 어피니티 (Sticky Session) 상세&lt;/h2&gt;
&lt;h3 id=&quot;nginx-sticky-cookie&quot;&gt;NGINX sticky cookie&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;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;upstream&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; backend &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;    ip_hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# IP 기반 고정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server1:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server2:8080;&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:#6A737D&quot;&gt;# 또는 쿠키 기반&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;upstream&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; backend &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;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server1:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server2:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    sticky &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cookie srv_id expires=1h;&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;h3 id=&quot;aws-alb-sticky-session&quot;&gt;AWS ALB sticky session&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;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;TargetGroupAttributes&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:#79B8FF&quot;&gt;&quot;Key&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stickiness.enabled&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;Value&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&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:#79B8FF&quot;&gt;&quot;Key&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stickiness.type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;Value&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;lb_cookie&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:#79B8FF&quot;&gt;&quot;Key&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;stickiness.lb_cookie.duration_seconds&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;Value&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;86400&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 style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;고정이 너무 강하면 특정 서버에 트래픽이 쏠린다. &lt;code&gt;ip_hash&lt;/code&gt; 보다 &lt;code&gt;least_conn&lt;/code&gt; + sticky cookie 조합이 부하 분산에 유리하다.&lt;/p&gt;
&lt;h2 id=&quot;그레이스풀-드레인&quot;&gt;그레이스풀 드레인&lt;/h2&gt;
&lt;p&gt;Stateful 서버를 재배포할 때 활성 세션을 잃지 않으려면 graceful drain 이 필수다.&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. LB 가 해당 인스턴스를 풀에서 제거 (신규 트래픽 차단)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 기존 연결이 자연스럽게 끝나길 대기 (drain timeout: 보통 30-60s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. drain timeout 초과 → 강제 종료 (SIGKILL)&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Kubernetes: terminationGracePeriodSeconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&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:#85E89D&quot;&gt;  terminationGracePeriodSeconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  containers&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:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;chat-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      lifecycle&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:#85E89D&quot;&gt;        preStop&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:#85E89D&quot;&gt;          exec&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:#85E89D&quot;&gt;            command&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/bin/sh&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;-c&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sleep 5&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;WebSocket 의 경우 drain 신호를 받으면 클라이언트에게 &lt;code&gt;close frame&lt;/code&gt; 을 보내 재연결을 유도한다.&lt;/p&gt;
&lt;h2 id=&quot;실무-예시&quot;&gt;실무 예시&lt;/h2&gt;
&lt;h3 id=&quot;websocket-채팅-서버&quot;&gt;WebSocket 채팅 서버&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;javascript&quot;&gt;&lt;code&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;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; rooms&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; Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// room → Set&amp;#x3C;ws&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;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; wss&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; WebSocketServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ port: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&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;wss.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;connection&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ws&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;message&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&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&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:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;room&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;payload&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:#79B8FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data);&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; (type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;join&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;      if&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;rooms.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(room)) rooms.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(room, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Set&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;      rooms.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(room).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ws);&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;else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;message&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:#E1E4E8&quot;&gt;      rooms.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(room)?.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;peer&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&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; (peer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ws) peer.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(payload);&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;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;rooms&lt;/code&gt; Map 이 메모리 상태. 서버 재시작 = 모든 룸 정보 손실.&lt;/p&gt;
&lt;h3 id=&quot;실시간-게임-서버&quot;&gt;실시간 게임 서버&lt;/h3&gt;
&lt;p&gt;플레이어 위치·인벤토리·전투 상태를 서버 메모리에 유지. tick 단위로 업데이트.&lt;/p&gt;
&lt;p&gt;수평 확장은 보통 &lt;strong&gt;샤딩&lt;/strong&gt;, “월드 1 은 Server A, 월드 2 는 Server B” 식으로 분할.&lt;/p&gt;
&lt;h3 id=&quot;협업-도구-figma-notion&quot;&gt;협업 도구 (Figma, Notion)&lt;/h3&gt;
&lt;p&gt;문서 단위로 stateful 서버 1대가 모든 활성 사용자의 변경사항(OT/CRDT)을 메모리에 유지.&lt;/p&gt;
&lt;h2 id=&quot;함정과-한계&quot;&gt;함정과 한계&lt;/h2&gt;
&lt;h3 id=&quot;1-메모리-폭발&quot;&gt;1. 메모리 폭발&lt;/h3&gt;
&lt;p&gt;활성 연결마다 메모리. WebSocket + Socket.IO 5K 연결 = ~400-500 MB.&lt;/p&gt;
&lt;h3 id=&quot;2-서버-다운--사용자-인지-가능한-손실&quot;&gt;2. 서버 다운 = 사용자 인지 가능한 손실&lt;/h3&gt;
&lt;p&gt;다운 직후 활성 세션 / 연결이 모두 끊김. 재연결 + 재인증 + 잃어버린 메시지 재요청.&lt;/p&gt;
&lt;h3 id=&quot;3-운영-복잡도&quot;&gt;3. 운영 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[[Sticky Session]] 설정&lt;/li&gt;
&lt;li&gt;배포·롤링 업데이트 시 세션 마이그레이션 또는 graceful drain&lt;/li&gt;
&lt;li&gt;모니터링 (활성 연결 수, 메모리, GC 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-비용&quot;&gt;4. 비용&lt;/h3&gt;
&lt;p&gt;같은 동접 처리 시 Stateless 보다 더 많은 서버 인스턴스 필요 (메모리 빠르게 소진).&lt;/p&gt;
&lt;h2 id=&quot;통신-프로토콜과의-관계&quot;&gt;통신 프로토콜과의 관계&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;프로토콜&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateful 여부&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP 의미상&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Server-Sent Events&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stateful (지속 연결)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stateful (지속 양방향)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stateful (P2P 세션)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Long Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;의미상 Stateless, 운영상 비슷&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebTransport&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stateful&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;지속 연결 기반 프로토콜은 본질적으로 Stateful.&lt;/p&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Stateless]]&lt;/li&gt;
&lt;li&gt;[[Stateless vs Stateful]], 전체 비교&lt;/li&gt;
&lt;li&gt;[[Sticky Session]], 수평 확장의 핵심 회피책&lt;/li&gt;
&lt;li&gt;[[Pub/Sub bus]], WebSocket 클러스터 확장 패턴&lt;/li&gt;
&lt;li&gt;[[k8s-statefulset]], Kubernetes StatefulSet 상세&lt;/li&gt;
&lt;li&gt;[[connection-pool]], 외부 store 연결 관리&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>architecture</category><category>network</category><category>scaling</category><author>koa (김신건)</author></item><item><title>Stateless</title><link>https://shinkeonkim.com/wiki/concurrency/stateless/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/stateless/</guid><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Stateless (무상태)&lt;/strong&gt; 서버는 클라이언트의 요청 간 상태를 기억하지 않는다. 각 요청은 완전히 독립적이며, 처리에 필요한 모든 정보는 요청 자체에 담겨 있어야 한다.&lt;/p&gt;
&lt;p&gt;같은 요청이라면 어느 서버 인스턴스가 처리하든 결과가 같다는 게 핵심 속성. 이게 수평 확장을 단순하게 만든다.&lt;/p&gt;
&lt;p&gt;전체 비교는 [[Stateless vs Stateful]] 페이지를 참조.&lt;/p&gt;
&lt;h2 id=&quot;핵심-특성&quot;&gt;핵심 특성&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;세션 상태&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 메모리에 없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수평 확장&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;무제한, round-robin LB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;장애 복원&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 교체 시 사용자 무영향&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;인증 정보&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매 요청마다 토큰/키 첨부&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;캐싱&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;URL 기반 캐시 매우 효율적&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 LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1[Client 1] --&gt; LB[Load Balancer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2[Client 2] --&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C3[Client N] --&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt; S1[Server 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt; S2[Server 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt; S3[Server N]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1 &amp;#x26; S2 &amp;#x26; S3 --&gt; Redis[(Redis)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1 &amp;#x26; S2 &amp;#x26; S3 --&gt; DB[(Database)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 서버가 동일한 Redis 와 DB 를 바라본다. LB 가 어느 서버로 요청을 보내도 같은 결과가 나온다. 서버 추가는 인스턴스 수만 늘리면 끝이다.&lt;/p&gt;
&lt;p&gt;Health check 로 고장 난 서버를 풀에서 제거해도 나머지 서버가 즉시 트래픽을 흡수한다. 사용자에게는 무영향이다.&lt;/p&gt;
&lt;h2 id=&quot;어떻게-stateless-를-유지하는가&quot;&gt;어떻게 stateless 를 유지하는가&lt;/h2&gt;
&lt;p&gt;상태가 정말 필요 없는 경우는 드물다. 보통은 “&lt;strong&gt;상태를 서버 메모리에 두지 않는다&lt;/strong&gt;” 가 핵심이다. 어디에 두는가가 선택이다.&lt;/p&gt;
&lt;h3 id=&quot;1-외부-store-위임&quot;&gt;1. 외부 store 위임&lt;/h3&gt;
&lt;p&gt;세션·캐시·잠금 등을 Redis, DynamoDB, DB 에 둔다. 모든 서버 인스턴스가 같은 store 에 접근.&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;요청 → 어느 서버든 OK → Redis 에서 세션 조회 → 응답&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비용: 매 요청마다 store I/O (보통 1-3 ms). 운영 단순성을 위한 합리적 trade-off.&lt;/p&gt;
&lt;h3 id=&quot;2-클라이언트-측-상태-jwt-cookie&quot;&gt;2. 클라이언트 측 상태 (JWT, Cookie)&lt;/h3&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;로그인 → JWT 발급 → 클라이언트가 보관&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;이후 요청 → Authorization: Bearer &amp;#x3C;JWT&gt; → 서명 검증으로 사용자 식별&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-urlquery-에-상태-인코딩&quot;&gt;3. URL/Query 에 상태 인코딩&lt;/h3&gt;
&lt;p&gt;페이지네이션 커서, 필터 조건 등을 URL 에 담는다.&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;GET /products?cursor=eyJpZCI6MTIzfQ&amp;#x26;limit=20&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 cursor 만 읽고 응답.&lt;/p&gt;
&lt;h2 id=&quot;jwt-인증-플로우&quot;&gt;JWT 인증 플로우&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 C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S as Server&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;    C-&gt;&gt;S: POST /login, credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S-&gt;&gt;DB: SELECT user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB--&gt;&gt;S: user found&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: 200 OK, JWT signed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 클라이언트 JWT 보관&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: GET /api/profile, Authorization Bearer JWT&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: JWT 서명 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: 200 OK, profile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 요청 사이에 아무것도 기억하지 않는다. JWT 서명 키만 있으면 어느 인스턴스든 동일한 검증이 가능하다.&lt;/p&gt;
&lt;h2 id=&quot;실무-예시&quot;&gt;실무 예시&lt;/h2&gt;
&lt;h3 id=&quot;rest-api&quot;&gt;REST API&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;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /api/users/42&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 eyJhbGc...&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;→ 서버: JWT 검증, DB 에서 user 42 조회, 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   메모리에 세션 X&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;cdn--정적-자원&quot;&gt;CDN / 정적 자원&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;GET /static/main.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 어느 edge 서버든 같은 응답&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;serverless-함수-aws-lambda-등&quot;&gt;Serverless 함수 (AWS Lambda 등)&lt;/h3&gt;
&lt;p&gt;함수 실행마다 컨테이너가 새로 만들어질 수 있음 → stateless 가 강제됨.
세션은 DynamoDB / Redis 등에 위임 필수.&lt;/p&gt;
&lt;h2 id=&quot;redis-세션-스토어-구현&quot;&gt;Redis 세션 스토어 구현&lt;/h2&gt;
&lt;p&gt;JWT 대신 서버 측 세션 ID 를 유지해야 할 때도, 외부 store 에 세션을 두면 서버 자체는 stateless 로 운영할 수 있다.&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:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; uuid, json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis&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;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.Redis(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;redis-host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6379&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;TTL&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:#6A737D&quot;&gt;  # 1 hour&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; create_session&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &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;    sid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(uuid.uuid4())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    r.setex(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;session:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sid&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:#79B8FF&quot;&gt;TTL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, json.dumps({&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: user_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:#E1E4E8&quot;&gt; sid&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_session&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sid: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;dict&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&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;    raw &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.get(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;session:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sid&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;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.loads(raw) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; raw &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; delete_session&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sid: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;None&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;    r.delete(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;session:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sid&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redis Sentinel 또는 Redis Cluster 로 단일 장애점을 제거해야 한다. Redis 다운 = 전체 서비스 인증 불능이 되기 때문이다.&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Redis Sentinel docker-compose 간략 예시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&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:#85E89D&quot;&gt;  redis-master&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:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis:7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  redis-replica&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:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis:7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    command&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis-server --replicaof redis-master 6379&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  redis-sentinel&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:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis:7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    command&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis-sentinel /etc/sentinel.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;kubernetes에서의-stateless&quot;&gt;Kubernetes에서의 Stateless&lt;/h2&gt;
&lt;p&gt;Stateless 서버는 Kubernetes &lt;code&gt;Deployment&lt;/code&gt; 로 운영한다. [[k8s-statefulset]] 이 필요 없다.&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apps/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Deployment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&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:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;api-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&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:#85E89D&quot;&gt;  replicas&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  selector&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:#85E89D&quot;&gt;    matchLabels&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:#85E89D&quot;&gt;      app&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;api-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  template&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:#85E89D&quot;&gt;    spec&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:#85E89D&quot;&gt;      containers&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:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;api-server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;myapp:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          env&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:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;REDIS_URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;              valueFrom&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:#85E89D&quot;&gt;                secretKeyRef&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:#85E89D&quot;&gt;                  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis-secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;                  key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;url&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HPA 로 CPU / RPS 기반 자동 스케일도 가능하다.&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:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autoscale&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; deployment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; api-server&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --cpu-percent=70&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --min=2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --max=20&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pod 에 순서나 안정적인 네트워크 ID 가 없어도 상관없다. stateless 서버에는 이게 오히려 장점이다.&lt;/p&gt;
&lt;h2 id=&quot;언제-stateless를-선택하는가&quot;&gt;언제 Stateless를 선택하는가&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;상황&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;권장&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;REST API, 배치 처리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;퍼블릭 마이크로서비스&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Serverless / Lambda&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;실시간 채팅, 게임 서버&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateful]] 고려&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;비디오 스트리밍 세션&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateful]] 고려&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;P2P 협업 도구&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateful]] 고려&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;수평 확장·배포 단순성이 목표면 stateless 가 기본 선택이다. 저지연 양방향 통신이 핵심이면 [[Stateful]] 을 고려한다.&lt;/p&gt;
&lt;h2 id=&quot;함정과-한계&quot;&gt;함정과 한계&lt;/h2&gt;
&lt;h3 id=&quot;1-stateless-인데-외부-store-가-단일-장애점&quot;&gt;1. “Stateless 인데 외부 store 가 단일 장애점”&lt;/h3&gt;
&lt;p&gt;Redis 다운 → 모든 서버가 세션 조회 실패. Stateless 한 서버를 만들어놓고 외부 store 에 의존하면 그게 SPOF 가 된다. &lt;strong&gt;고가용성 Redis 클러스터, fallback 전략 필요&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;2-jwt-즉시-취소-어려움&quot;&gt;2. JWT 즉시 취소 어려움&lt;/h3&gt;
&lt;p&gt;JWT 는 만료 전까지 유효. 사용자 권한 변경/로그아웃을 즉시 반영하려면 &lt;strong&gt;블랙리스트&lt;/strong&gt; 같은 별도 store 가 필요 → 일부 stateful 화.&lt;/p&gt;
&lt;h3 id=&quot;3-per-request-토큰-검증-비용&quot;&gt;3. Per-request 토큰 검증 비용&lt;/h3&gt;
&lt;p&gt;매 요청마다 JWT 서명 검증 (CPU) + 외부 store 조회 (I/O). 트래픽이 많으면 누적된다.&lt;/p&gt;
&lt;h2 id=&quot;통신-프로토콜과의-관계&quot;&gt;통신 프로토콜과의 관계&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;프로토콜&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateless 친화도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP/1.1, 2, 3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ 완벽 (메시지 의미상)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Short Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Long Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ (HTTP 의미상, 운영상은 연결 점유)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Server-Sent Events&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗ Stateful (지속 연결)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗ [[Stateful]]&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗ Stateful&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;대부분의 REST API · CDN · 정적 사이트는 자연스럽게 stateless.&lt;/p&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Stateful]]&lt;/li&gt;
&lt;li&gt;[[Sticky Session]], Stateful 의 회피책&lt;/li&gt;
&lt;li&gt;[[Stateless vs Stateful]], 전체 비교&lt;/li&gt;
&lt;li&gt;[[connection-pool]], 외부 store 연결 관리&lt;/li&gt;
&lt;li&gt;[[rate-limiting]], Stateless API 보호&lt;/li&gt;
&lt;li&gt;[[jwt]], 클라이언트 측 상태 토큰&lt;/li&gt;
&lt;li&gt;[[redis]], 외부 세션 스토어&lt;/li&gt;
&lt;li&gt;[[k8s-statefulset]], Stateful 워크로드용 K8s 리소스&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>architecture</category><category>network</category><category>scaling</category><author>koa (김신건)</author></item><item><title>[Concurrency] Circuit Breaker: cascade failure 방어</title><link>https://shinkeonkim.com/wiki/concurrency/circuit-breaker/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/circuit-breaker/</guid><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Circuit Breaker&lt;/strong&gt; = 전기 회로의 차단기처럼, &lt;em&gt;백엔드 다운 / 느림 시 호출 자체를 차단&lt;/em&gt; → &lt;em&gt;빠른 실패 + 백엔드 회복 시간&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;[[backpressure]]와 함께 &lt;em&gt;cascade failure&lt;/em&gt; 방어의 핵심 패턴. 호출 실패가 연쇄적으로 전파되는 것을 막는다.&lt;/p&gt;
&lt;h2 id=&quot;3가지-상태&quot;&gt;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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stateDiagram-v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    [*] --&gt; Closed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Closed --&gt; Open : &quot;실패율 임계 초과&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Open --&gt; HalfOpen : &quot;대기 시간 경과&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HalfOpen --&gt; Closed : &quot;성공 N회&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HalfOpen --&gt; Open : &quot;실패 1회&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;th&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Closed&lt;/strong&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;strong&gt;Open&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;차단&lt;/td&gt;&lt;td&gt;&lt;em&gt;호출 안 함&lt;/em&gt;, 즉시 fail (또는 fallback)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Half-Open&lt;/strong&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;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 CB as &quot;Circuit Breaker&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Backend&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;    Note over CB: Closed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;CB: call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB-&gt;&gt;Backend: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Backend--&gt;&gt;CB: 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;CB: call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB-&gt;&gt;Backend: Fail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: &quot;실패 누적 (3/10)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;CB: call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB-&gt;&gt;Backend: Fail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: &quot;실패율 50% &gt; 임계 → OPEN&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;CB: call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB--&gt;&gt;Client: &quot;fail-fast (호출 안함)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: &quot;30초 대기 (Open)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: &quot;Half-Open으로 전환&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;CB: call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB-&gt;&gt;Backend: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: &quot;성공 → Closed&quot;&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;Failure rate&lt;/td&gt;&lt;td&gt;&lt;em&gt;실패율 &gt; X%&lt;/em&gt; (sliding window)&lt;/td&gt;&lt;td&gt;50%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Slow call rate&lt;/td&gt;&lt;td&gt;&lt;em&gt;N초 이상 응답 비율 &gt; X%&lt;/em&gt;&lt;/td&gt;&lt;td&gt;100ms 초과 50%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consecutive failures&lt;/td&gt;&lt;td&gt;&lt;em&gt;연속 N회 실패&lt;/em&gt;&lt;/td&gt;&lt;td&gt;5회&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Minimum calls&lt;/td&gt;&lt;td&gt;통계 유효 최소 호출 수&lt;/td&gt;&lt;td&gt;10회&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 LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Window[&quot;Sliding Window (count 기반, 10개)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R1[req] --&gt; R2[req] --&gt; R3[req] --&gt; R4[FAIL] --&gt; R5[FAIL]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R5 --&gt; R6[req] --&gt; R7[FAIL] --&gt; R8[req] --&gt; R9[FAIL] --&gt; R10[FAIL]&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;    Window --&gt;|&quot;실패율 50% &gt; 임계&quot;| Open[OPEN]&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;th&gt;기본값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;slidingWindowSize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;통계 집계 요청 수 (count) 또는 시간 (time)&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;failureRateThreshold&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Open 전환 실패율 (%)&lt;/td&gt;&lt;td&gt;50&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;slowCallDurationThreshold&lt;/code&gt;&lt;/td&gt;&lt;td&gt;slow call 기준 시간&lt;/td&gt;&lt;td&gt;60s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;slowCallRateThreshold&lt;/code&gt;&lt;/td&gt;&lt;td&gt;slow call 비율 임계 (%)&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;waitDurationInOpenState&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Open 유지 시간&lt;/td&gt;&lt;td&gt;60s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;permittedNumberOfCallsInHalfOpenState&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Half-Open 시 허용 호출 수&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;minimumNumberOfCalls&lt;/code&gt;&lt;/td&gt;&lt;td&gt;통계 유효 최소 호출 수&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;resilience4j-설정-예시&quot;&gt;Resilience4j 설정 예시&lt;/h2&gt;
&lt;h3 id=&quot;applicationyml&quot;&gt;application.yml&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;resilience4j&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:#85E89D&quot;&gt;  circuitbreaker&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:#85E89D&quot;&gt;    instances&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:#85E89D&quot;&gt;      payment-service&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:#85E89D&quot;&gt;        slidingWindowType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;COUNT_BASED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        slidingWindowSize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        minimumNumberOfCalls&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        failureRateThreshold&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        slowCallDurationThreshold&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;2000ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        slowCallRateThreshold&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        waitDurationInOpenState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;30s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        permittedNumberOfCallsInHalfOpenState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        automaticTransitionFromOpenToHalfOpenEnabled&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        recordExceptions&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;java.io.IOException&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;java.util.concurrent.TimeoutException&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        ignoreExceptions&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;com.example.BusinessException&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;java-코드&quot;&gt;Java 코드&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:#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; PaymentService&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:#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; CircuitBreaker cb;&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; ExternalPaymentClient client;&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:#B392F0&quot;&gt; PaymentService&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(CircuitBreakerRegistry &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;registry&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ExternalPaymentClient &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;client&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;        this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.cb &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; registry.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;circuitBreaker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;payment-service&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;        this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client;&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;    public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; PaymentResult &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;charge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ChargeRequest &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&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; cb.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;executeSupplier&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; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;charge&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:#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;    // Fallback 포함&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:#E1E4E8&quot;&gt; PaymentResult &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chargeWithFallback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ChargeRequest &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&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; Try.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofSupplier&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;            CircuitBreaker.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;decorateSupplier&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cb, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;charge&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:#E1E4E8&quot;&gt;        ).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;recover&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ex &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; PaymentResult.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;queued&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(req.&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;get&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:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cb.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getEventPublisher&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:#B392F0&quot;&gt;onStateTransition&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;warn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;CB [{}] {} -&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:#E1E4E8&quot;&gt;            e.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getCircuitBreakerName&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;getStateTransition&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getFromState&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;getStateTransition&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getToState&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:#B392F0&quot;&gt;onCallNotPermitted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        metrics.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cb.rejected&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getCircuitBreakerName&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;라이브러리&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;Resilience4j&lt;/td&gt;&lt;td&gt;Java&lt;/td&gt;&lt;td&gt;Hystrix 후계, 현재 표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Polly&lt;/td&gt;&lt;td&gt;.NET&lt;/td&gt;&lt;td&gt;표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sentinel&lt;/td&gt;&lt;td&gt;Java/Go&lt;/td&gt;&lt;td&gt;Alibaba, 트래픽 제어 통합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pybreaker&lt;/td&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;단순&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;opossum&lt;/td&gt;&lt;td&gt;Node&lt;/td&gt;&lt;td&gt;EventEmitter 친화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Envoy outlier detection&lt;/td&gt;&lt;td&gt;proxy&lt;/td&gt;&lt;td&gt;sidecar 자동&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Istio&lt;/td&gt;&lt;td&gt;service mesh&lt;/td&gt;&lt;td&gt;Envoy 기반 자동 CB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;hystrix-vs-resilience4j&quot;&gt;Hystrix vs Resilience4j&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Hystrix (2018 EOL)&lt;/th&gt;&lt;th&gt;Resilience4j&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Thread pool 격리&lt;/td&gt;&lt;td&gt;&lt;em&gt;기본&lt;/em&gt;&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Semaphore&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Reactive&lt;/td&gt;&lt;td&gt;일부&lt;/td&gt;&lt;td&gt;&lt;em&gt;기본 (Reactor/RxJava)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Lightweight&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;&lt;em&gt;O (8 MB)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;활성 개발&lt;/td&gt;&lt;td&gt;&lt;em&gt;X&lt;/em&gt;&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Spring Boot 통합&lt;/td&gt;&lt;td&gt;Spring Cloud Netflix&lt;/td&gt;&lt;td&gt;Spring Cloud CircuitBreaker&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;설정 방식&lt;/td&gt;&lt;td&gt;&lt;code&gt;@HystrixCommand&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;@CircuitBreaker&lt;/code&gt; + yml&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Hystrix는 2018년 EOL. Spring Cloud Netflix Hystrix도 deprecated. 신규 프로젝트는 &lt;strong&gt;Resilience4j&lt;/strong&gt; 사용. 기존 Hystrix 코드는 &lt;code&gt;@HystrixCommand&lt;/code&gt; → &lt;code&gt;@CircuitBreaker&lt;/code&gt; + Resilience4j 설정으로 마이그레이션.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;fallback&quot;&gt;Fallback&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:#B392F0&quot;&gt;@circuit_breaker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;payment&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; call_payment&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; external.charge()&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:#B392F0&quot;&gt;@call_payment.fallback&lt;/span&gt;&lt;/span&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; cached_response&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; { &lt;/span&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;queued&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# degraded but usable&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Resilience4j + Spring&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;CircuitBreaker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;inventory&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;fallbackMethod&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;inventoryFallback&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;public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; InventoryStatus &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;checkInventory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(String productId) {&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; inventoryClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;check&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(productId);&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;public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; InventoryStatus &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;inventoryFallback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(String productId, Exception ex) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;warn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Inventory CB open, returning cached: {}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, productId);&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; cache.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getOrDefault&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(productId, InventoryStatus.UNKNOWN);&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;blockquote&gt;
&lt;p&gt;단순 fail-fast보다 &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;h2 id=&quot;bulkhead와의-조합&quot;&gt;Bulkhead와의 조합&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&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        TPA[&quot;Thread Pool A (CB-A)&amp;#x3C;br/&gt;Payment Service&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        TPB[&quot;Thread Pool B (CB-B)&amp;#x3C;br/&gt;Inventory Service&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        TPC[&quot;Thread Pool C (CB-C)&amp;#x3C;br/&gt;Shipping Service&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;    Note[&quot;하나 다운 = 자기 Pool만 영향&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;자세한 건 [[backpressure]]의 Bulkhead.&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;resilience4j&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:#85E89D&quot;&gt;  bulkhead&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:#85E89D&quot;&gt;    instances&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:#85E89D&quot;&gt;      payment-service&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:#85E89D&quot;&gt;        maxConcurrentCalls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        maxWaitDuration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;100ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  thread-pool-bulkhead&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:#85E89D&quot;&gt;    instances&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:#85E89D&quot;&gt;      inventory-service&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:#85E89D&quot;&gt;        maxThreadPoolSize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        coreThreadPoolSize&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        queueCapacity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;envoy--istio-outlier-detection&quot;&gt;Envoy / Istio outlier detection&lt;/h2&gt;
&lt;p&gt;서비스 메시 레벨에서 &lt;em&gt;자동 CB&lt;/em&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Istio DestinationRule&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;networking.istio.io/v1alpha3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DestinationRule&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&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:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;payment-service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&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:#85E89D&quot;&gt;  host&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;payment-service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  trafficPolicy&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:#85E89D&quot;&gt;    outlierDetection&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:#85E89D&quot;&gt;      consecutive5xxErrors&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      interval&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;30s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      baseEjectionTime&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;30s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      maxEjectionPercent&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      minHealthPercent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;consecutive5xxErrors&lt;/code&gt;: 연속 5xx 횟수 → 해당 pod 제외&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baseEjectionTime&lt;/code&gt;: 제외 기간 (ejection 횟수에 비례해 증가)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxEjectionPercent&lt;/code&gt;: 최대 제외 비율 (전체 다운 방지)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;모니터링&quot;&gt;모니터링&lt;/h2&gt;
&lt;p&gt;CB 상태를 모르면 운영 불가. 필수 메트릭:&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;resilience4j_circuitbreaker_state{name=&quot;payment-service&quot;} 0  # 0=CLOSED, 1=OPEN, 2=HALF_OPEN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resilience4j_circuitbreaker_failure_rate{name=&quot;payment-service&quot;} 45.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resilience4j_circuitbreaker_calls_total{name=&quot;payment-service&quot;, kind=&quot;successful&quot;} 1234&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resilience4j_circuitbreaker_calls_total{name=&quot;payment-service&quot;, kind=&quot;failed&quot;} 56&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resilience4j_circuitbreaker_not_permitted_calls_total{name=&quot;payment-service&quot;} 89&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grafana 대시보드 권장 패널:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CB 상태 타임라인 (CLOSED/OPEN/HALF-OPEN)&lt;/li&gt;
&lt;li&gt;실패율 추이&lt;/li&gt;
&lt;li&gt;차단된 호출 수 (not_permitted)&lt;/li&gt;
&lt;li&gt;응답 시간 p99&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;em&gt;너무 민감&lt;/em&gt;&lt;/strong&gt; = 일시 jitter로 false open. &lt;em&gt;짧은 window&lt;/em&gt;에서 조심. &lt;code&gt;minimumNumberOfCalls&lt;/code&gt; 충분히 설정.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Half-Open 호출 다수 동시&lt;/strong&gt; = 한 번에 &lt;em&gt;모든 호출 통과&lt;/em&gt; → 다시 다운. &lt;em&gt;동시 호출 1개 또는 적은 수&lt;/em&gt; (&lt;code&gt;permittedNumberOfCallsInHalfOpenState: 3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fallback이 &lt;em&gt;다른 backend 호출&lt;/em&gt;&lt;/strong&gt; = fallback도 다운되면 &lt;em&gt;재귀 fail&lt;/em&gt;. fallback은 &lt;em&gt;로컬 / 캐시 only&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CB 메트릭 누락&lt;/strong&gt; = open 상태인지 운영자가 모름. 알림 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ignoreExceptions&lt;/code&gt; 미설정&lt;/strong&gt; = 비즈니스 예외 (400 Bad Request)도 실패로 카운트 → false open.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[backpressure]]&lt;/li&gt;
&lt;li&gt;[[retry-with-backoff]]&lt;/li&gt;
&lt;li&gt;[[rate-limiting]]&lt;/li&gt;
&lt;li&gt;[[Service Mesh]] (Envoy/Istio)&lt;/li&gt;
&lt;li&gt;[[api-gateway]]&lt;/li&gt;
&lt;li&gt;[[idempotency-keys]] (retry 시 중복 방지)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>circuit-breaker</category><category>resilience</category><category>fault-tolerance</category><category>microservices</category><category>backend</category><author>koa (김신건)</author></item><item><title>Sticky Session</title><link>https://shinkeonkim.com/wiki/concurrency/sticky-session/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/sticky-session/</guid><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Sticky Session&lt;/strong&gt; (= Session Affinity)은 로드밸런서가 &lt;strong&gt;같은 클라이언트의 요청을 항상 같은 백엔드 서버로 라우팅&lt;/strong&gt;하도록 하는 동작이다.&lt;/p&gt;
&lt;p&gt;[[Stateful]] 서버, 즉 세션 상태를 메모리에 들고 있는 WebSocket 서버, 인메모리 세션 store를 쓰는 HTTP 서버 등이 수평 확장될 때 필수적이다.&lt;/p&gt;
&lt;h2 id=&quot;왜-필요한가&quot;&gt;왜 필요한가&lt;/h2&gt;
&lt;p&gt;[[Stateless]] 서버는 어느 서버가 처리해도 결과가 같으므로 round-robin으로 분산하면 끝. 하지만 [[Stateful]] 서버는 다르다.&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;Client X가 Server A에 로그인 → A의 메모리에 세션 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Client X의 다음 요청 → Server B로 라우팅 → B는 세션 모름 → 인증 실패&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 막으려면 X의 모든 요청을 A로 보내야 한다 = sticky session.&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;    C1[&quot;Client A&amp;#x3C;br/&gt;(cookie: srv=A)&quot;] --&gt;|&quot;sticky → Server A&quot;| LB[Load Balancer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2[&quot;Client B&amp;#x3C;br/&gt;(cookie: srv=B)&quot;] --&gt;|&quot;sticky → Server B&quot;| LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C3[&quot;Client C&amp;#x3C;br/&gt;(새 요청, 쿠키 없음)&quot;] --&gt;|&quot;round-robin&quot;| LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt;|&quot;Session A 있음&quot;| SA[&quot;Server A&amp;#x3C;br/&gt;session: A&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt;|&quot;Session B 있음&quot;| SB[&quot;Server B&amp;#x3C;br/&gt;session: B&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB --&gt;|&quot;신규 배정&quot;| SC[&quot;Server C&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현-방법&quot;&gt;구현 방법&lt;/h2&gt;
&lt;h3 id=&quot;1-cookie-기반&quot;&gt;1. Cookie 기반&lt;/h3&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;Server A가 응답 → LB가 Set-Cookie: LB_ROUTE=server-a 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Client 다음 요청 → Cookie: LB_ROUTE=server-a → LB가 A로 라우팅&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AWS ALB의 stickiness 옵션, Nginx의 &lt;code&gt;sticky cookie&lt;/code&gt; 등이 이 방식.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: IP 변경에도 세션 유지 (모바일 유리)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 클라이언트가 쿠키를 지우면 세션 끊김, HTTPS가 아니면 탈취 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-ip-hash-기반&quot;&gt;2. IP Hash 기반&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;hash(client_ip) % N&lt;/code&gt;으로 서버 선택. 같은 IP는 같은 서버로.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 쿠키 불필요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 모바일 환경에서 IP가 자주 바뀜, 회사 NAT 뒤 사용자 다수가 한 서버로 몰림&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-urlheader-기반&quot;&gt;3. URL/Header 기반&lt;/h3&gt;
&lt;p&gt;JWT의 일부, 사용자 ID, 세션 ID 등을 해시해 라우팅.&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;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;upstream&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; backend &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;    hash &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$http_x_user_id consistent;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 10.0.0.1:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 10.0.0.2:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 10.0.0.3:8080;&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;h2 id=&quot;로드밸런서별-설정&quot;&gt;로드밸런서별 설정&lt;/h2&gt;
&lt;h3 id=&quot;aws-alb&quot;&gt;AWS ALB&lt;/h3&gt;
&lt;p&gt;ALB는 두 종류의 stickiness를 지원한다:&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. Load balancer generated cookie (AWSALB)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   - ALB가 자동으로 쿠키 생성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   - 기간: 1초 ~ 7일 설정 가능&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;2. Application-based cookie (AWSALBAPP)&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;/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;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;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;forward&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;TargetGroupArn&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;ForwardConfig&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;TargetGroupStickinessConfig&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;Enabled&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;DurationSeconds&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;86400&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;h3 id=&quot;nginx-upstream-sticky&quot;&gt;Nginx (upstream sticky)&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;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;upstream&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; app &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;    sticky &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cookie srv_id expires=1h path=/;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 10.0.0.1:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 10.0.0.2:8080;&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;h3 id=&quot;haproxy&quot;&gt;HAProxy&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;backend app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    balance roundrobin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cookie SERVERID insert indirect nocache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    server s1 10.0.0.1:8080 check cookie s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    server s2 10.0.0.2:8080 check cookie s2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;kubernetes-service-sessionaffinity&quot;&gt;Kubernetes Service sessionAffinity&lt;/h3&gt;
&lt;p&gt;K8s Service 오브젝트에서 클라이언트 IP 기반 affinity를 설정:&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;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&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:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;my-app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&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:#85E89D&quot;&gt;  selector&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:#85E89D&quot;&gt;    app&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;my-app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  sessionAffinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ClientIP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  sessionAffinityConfig&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:#85E89D&quot;&gt;    clientIP&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:#85E89D&quot;&gt;      timeoutSeconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10800&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # 3시간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  ports&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:#85E89D&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      targetPort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
K8s의 &lt;code&gt;sessionAffinity: ClientIP&lt;/code&gt;는 kube-proxy (iptables/ipvs) 레벨에서 작동한다. Ingress를 통하면 ingress controller가 별도로 cookie 기반 sticky를 처리해야 한다. Nginx Ingress Controller에서는 &lt;code&gt;nginx.ingress.kubernetes.io/affinity: &quot;cookie&quot;&lt;/code&gt; annotation 사용.&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 C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant LB as LoadBalancer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant SA as ServerA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant SB as ServerB&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;    C-&gt;&gt;LB: GET /login, no cookie&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB-&gt;&gt;SA: 신규 배정, round-robin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA-&gt;&gt;SA: 세션 생성, sess_id=abc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA--&gt;&gt;LB: 200 OK, Set-Cookie sess_id=abc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB--&gt;&gt;C: 200 OK, Set-Cookie AWSALB=hash_A + sess_id=abc&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;    C-&gt;&gt;LB: POST /order, Cookie AWSALB=hash_A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB-&gt;&gt;SA: AWSALB 쿠키로 A로 라우팅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA-&gt;&gt;SA: 세션 조회 OK, 주문 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SA--&gt;&gt;LB: 200 OK&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;    Note over SA: Server A 장애 발생&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;LB: GET /cart, Cookie AWSALB=hash_A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB-&gt;&gt;SB: A 다운, B로 재배정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SB-&gt;&gt;SB: sess_id=abc 없음, 세션 소실&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SB--&gt;&gt;C: 302 /login, 재로그인 필요&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sticky-session의-함정&quot;&gt;Sticky Session의 함정&lt;/h2&gt;
&lt;h3 id=&quot;1-부하-불균형&quot;&gt;1. 부하 불균형&lt;/h3&gt;
&lt;p&gt;특정 사용자/세션이 매우 무거우면 그 서버만 과부하. Round-robin의 균형 분산 효과가 사라진다.&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;    Heavy[&quot;Heavy User&amp;#x3C;br/&gt;(동영상 업로드 중)&quot;] --&gt;|sticky| SA[&quot;Server A&amp;#x3C;br/&gt;CPU 95%&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Light1[&quot;Light User&quot;] --&gt;|sticky| SB[&quot;Server B&amp;#x3C;br/&gt;CPU 10%&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Light2[&quot;Light User&quot;] --&gt;|sticky| SC[&quot;Server C&amp;#x3C;br/&gt;CPU 10%&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-서버-장애-시-세션-손실&quot;&gt;2. 서버 장애 시 세션 손실&lt;/h3&gt;
&lt;p&gt;A가 죽으면 A에 붙어있던 모든 클라이언트의 세션이 사라진다. 재로그인 / 재연결 필요.&lt;/p&gt;
&lt;h3 id=&quot;3-확장-시-재분배-필요&quot;&gt;3. 확장 시 재분배 필요&lt;/h3&gt;
&lt;p&gt;서버를 1개 추가하면 일부 사용자의 라우팅 키가 바뀌어야 함. consistent hashing 등으로 완화 가능.&lt;/p&gt;
&lt;h3 id=&quot;4-운영-복잡도-증가&quot;&gt;4. 운영 복잡도 증가&lt;/h3&gt;
&lt;p&gt;배포/롤링 업데이트 시 활성 세션을 안전하게 다른 서버로 옮기는 절차 필요. Blue-green 배포 시 세션 드레이닝이 필수.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
ALB의 &lt;code&gt;deregistration_delay&lt;/code&gt; (기본 300초) 동안 기존 연결이 서서히 다른 서버로 이전된다. 이 시간 내에 서버가 강제 종료되면 세션이 그냥 끊긴다. 충분한 draining 시간을 설정하거나 외부 store로 전환하는 것이 안전하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;대안-sticky를-피하는-방법&quot;&gt;대안: Sticky를 피하는 방법&lt;/h2&gt;
&lt;h3 id=&quot;1-외부-세션-store&quot;&gt;1. 외부 세션 store&lt;/h3&gt;
&lt;p&gt;세션 데이터를 Redis, Memcached, DynamoDB 등에 보관. 모든 서버가 같은 데이터에 접근 가능 → [[Stateless]] 서버처럼 동작.&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;Client → 아무 서버 → Redis에서 세션 조회 → 처리&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:#6A737D&quot;&gt;# Flask + Redis session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; flask_session &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis&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.config[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SESSION_TYPE&quot;&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:#9ECBFF&quot;&gt; &quot;redis&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.config[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SESSION_REDIS&quot;&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; redis.from_url(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;redis://redis:6379&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;Session(app)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;비용&lt;/strong&gt;: 매 요청마다 store I/O (보통 1-3 ms 추가)&lt;/p&gt;
&lt;h3 id=&quot;2-jwt-클라이언트-측-세션&quot;&gt;2. JWT (클라이언트 측 세션)&lt;/h3&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;Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 완벽한 stateless, 서버 수평 확장 자유&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 토큰 만료/취소가 어려움 (revocation list 필요), 토큰이 커짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-pubsub-bus-websocket-등-지속-연결&quot;&gt;3. Pub/Sub Bus (WebSocket 등 지속 연결)&lt;/h3&gt;
&lt;p&gt;각 서버가 자기 클라이언트만 관리하되, 서버 간 메시지는 Redis Pub/Sub으로 라우팅.&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;Socket.IO + Redis adapter 패턴:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Client A → Server 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Client B → Server 2 (다른 서버)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Server 1이 &quot;A → B에게 메시지&quot; 처리 시:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    1. Redis publish &quot;msg-to-B&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    2. Server 2가 subscribe → B에게 전달&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 align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;stateless&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;장애 내성&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;복잡도&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;비용&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Sticky session&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;외부 세션 store&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;높음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;Redis 비용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;JWT&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;높음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Pub/Sub bus&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O (WebSocket)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;높음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;높음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;Redis 비용&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 align=&quot;left&quot;&gt;시나리오&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;권장&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Stateless REST API&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Sticky 불필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;인메모리 세션 + 단일 서버&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;처음엔 sticky, 곧 외부 store로 마이그레이션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket / SSE&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외부 store + Pub/Sub bus 패턴 권장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;단기 캠페인 / 트래픽 작음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Sticky session으로 빠르게 시작 OK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;K8s 롤링 배포 빈번&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외부 store 필수 (Pod 재시작마다 세션 소실)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;게임 서버&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지속 연결 특성상 sticky 불가피, 장애 시 재접속 설계&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Sticky session은 “&lt;strong&gt;필요악&lt;/strong&gt;”이다. 가능하면 외부 store나 JWT로 [[Stateless]]화 하는 게 장기적으로 더 단순하다. 다만 WebSocket처럼 지속 연결이 본질적인 경우엔 피할 수 없으므로 Pub/Sub 같은 다른 추상화로 대처한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Stateful]]&lt;/li&gt;
&lt;li&gt;[[Stateless]]&lt;/li&gt;
&lt;li&gt;[[backpressure]] (WebSocket 연결 수 관리)&lt;/li&gt;
&lt;li&gt;[[circuit-breaker]] (서버 장애 감지)&lt;/li&gt;
&lt;li&gt;[[rate-limiting]] (연결당 제한)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>network</category><category>load-balancing</category><category>architecture</category><category>session</category><category>stateful</category><author>koa (김신건)</author></item><item><title>Synchronous (동기)</title><link>https://shinkeonkim.com/wiki/concurrency/synchronous/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/synchronous/</guid><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Synchronous (동기)&lt;/strong&gt; 는 호출자가 &lt;strong&gt;결과가 나올 때까지 기다리는&lt;/strong&gt; 실행 모델. 함수를 호출한 코드는 그 함수가 반환할 때까지 &lt;strong&gt;다음 줄로 진행하지 않는다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;이 정의는 Java의 &lt;code&gt;synchronized&lt;/code&gt; 키워드와는 &lt;strong&gt;별개의 개념&lt;/strong&gt;이다. &lt;code&gt;synchronized&lt;/code&gt;는 동시성 제어 (mutual exclusion), Synchronous는 호출 모델 (control flow).&lt;/p&gt;
&lt;p&gt;[[Asynchronous]]와의 대비, 그리고 [[Blocking]]과의 관계가 핵심.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Synchronous&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&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;&quot;/api/users&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &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:#B392F0&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);                          &lt;/span&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;/p&gt;
&lt;h2 id=&quot;4가지-조합&quot;&gt;4가지 조합&lt;/h2&gt;
&lt;p&gt;동기/비동기와 블로킹/논블로킹은 &lt;strong&gt;독립적인 두 축&lt;/strong&gt;이다. 조합하면 4가지가 나온다.&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 TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Sync[&quot;Synchronous (호출자가 결과를 기다림)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SB[&quot;Sync + Blocking\n가장 흔함\nthread sleep&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SNB[&quot;Sync + Non-Blocking\n드묾\nbusy-wait / spin&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 Async[&quot;Asynchronous (호출자가 결과를 기다리지 않음)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        AB[&quot;Async + Blocking\n안티패턴\ncallback 안에서 sleep&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ANB[&quot;Async + Non-Blocking\n고성능 IO\nNIO, epoll, io_uring&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;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;조합&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;호출자 대기&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;스레드 상태&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sync + Blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;sleep&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;socket.read()&lt;/code&gt;, &lt;code&gt;Thread.sleep()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sync + Non-Blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;active (spin)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Thread.onSpinWait()&lt;/code&gt;, busy-loop&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Async + Blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;sleep (안티패턴)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;callback 안에서 &lt;code&gt;Thread.sleep()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Async + Non-Blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;active (event loop)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Node.js, Netty, epoll&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;sync의-두-형태-blocking-vs-non-blocking&quot;&gt;Sync의 두 형태: blocking vs non-blocking&lt;/h2&gt;
&lt;p&gt;Synchronous는 호출자가 &lt;strong&gt;결과를 기다린다&lt;/strong&gt;는 의미이지, 그 동안 &lt;strong&gt;CPU를 점유&lt;/strong&gt;하는지는 별개. 따라서 두 가지가 가능.&lt;/p&gt;
&lt;h3 id=&quot;synchronous--blocking-가장-흔함&quot;&gt;Synchronous + Blocking (가장 흔함)&lt;/h3&gt;
&lt;p&gt;호출자가 결과를 기다리는 동안 &lt;strong&gt;스레드가 멈춘다 (block)&lt;/strong&gt;. OS가 스레드를 sleep 상태로 만들고 CPU를 다른 스레드에게 양보.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 전형적인 sync + blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Socket s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; serverSocket.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();         &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 연결이 올 때까지 block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String line &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reader.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 데이터가 올 때까지 block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Thread.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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 style=&quot;color:#6A737D&quot;&gt;// 1초 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;synchronous--non-blocking-드물지만-가능&quot;&gt;Synchronous + Non-Blocking (드물지만 가능)&lt;/h3&gt;
&lt;p&gt;호출자가 결과를 기다리지만 &lt;strong&gt;스레드는 활성 상태&lt;/strong&gt;로 계속 돈다. busy-waiting 또는 polling.&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;java&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:#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;result.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isReady&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;    Thread.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onSpinWait&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// hint, 스레드 양보 안 함&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;String value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result.&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CPU를 낭비하므로 거의 안 쓴다. 매우 짧은 대기에만 쓰이는 마이크로 최적화.&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;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Caller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Callee&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;    Caller-&gt;&gt;Callee: call()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Caller: waiting (blocked)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Callee-&gt;&gt;Callee: 작업 수행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Callee--&gt;&gt;Caller: return result&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Caller: 결과 처리 시작&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;호출자와 호출자의 스택 프레임이 &lt;strong&gt;일직선&lt;/strong&gt;으로 이어진다. 디버깅 시 stack trace가 그대로 보인다.&lt;/p&gt;
&lt;h2 id=&quot;동기-vs-비동기-대비&quot;&gt;동기 vs 비동기 대비&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;    participant C1 as &quot;Caller (Sync)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S as &quot;Service&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C2 as &quot;Caller (Async)&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:#E1E4E8&quot;&gt;    C1-&gt;&gt;S: call()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: blocked (대기)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C1: result&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: 처리 재개&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;    C2-&gt;&gt;S: call(callback)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C2: 즉시 다음 작업 진행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C2: callback(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C2: 콜백에서 처리&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;strong&gt;읽기 쉽다&lt;/strong&gt;: 위에서 아래로 흐름이 명확&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 처리가 자연스럽다&lt;/strong&gt;: &lt;code&gt;try / catch&lt;/code&gt;가 호출자 스택에 닿는다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디버깅이 쉽다&lt;/strong&gt;: breakpoint가 작동, stack trace가 일관&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;ul&gt;
&lt;li&gt;&lt;strong&gt;느린 작업은 스레드를 묶는다&lt;/strong&gt;: 동시에 1000명 응답하려면 1000개 스레드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU 활용도가 낮을 수 있다&lt;/strong&gt;: I/O 대기 중에는 CPU idle&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;ul&gt;
&lt;li&gt;&lt;strong&gt;응답이 빠르고 일관적&lt;/strong&gt;: 5ms 이내의 DB 쿼리 같은 작업&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;순차 흐름이 본질&lt;/strong&gt;: 단계 A의 결과로 단계 B를 결정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저부하 시스템&lt;/strong&gt;: 동시 접속 &amp;#x3C; 수백 수준&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트, 스크립트, 마이그레이션 도구&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;언제-asynchronous가-더-나은가&quot;&gt;언제 [[Asynchronous]]가 더 나은가&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;외부 API 호출 (수 초 걸릴 수 있음)&lt;/li&gt;
&lt;li&gt;수만 명 동시 접속을 받는 서버&lt;/li&gt;
&lt;li&gt;UI가 멈추면 안 되는 클라이언트&lt;/li&gt;
&lt;li&gt;여러 작업을 병렬로 진행해야 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;java의-동기-io-예&quot;&gt;Java의 동기 IO 예&lt;/h2&gt;
&lt;h3 id=&quot;http-클라이언트-sync&quot;&gt;HTTP 클라이언트 (sync)&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:#6A737D&quot;&gt;// HttpClient 동기 모드&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HttpClient client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HttpClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newHttpClient&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;HttpResponse&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;&gt; response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HttpRequest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newBuilder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(URI.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://api.example.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;build&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;    BodyHandlers.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofString&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:#6A737D&quot;&gt;// 응답이 도착할 때까지 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;file-io-sync&quot;&gt;File I/O (sync)&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;String content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Files.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;data.txt&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:#6A737D&quot;&gt;// 파일을 다 읽을 때까지 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;socket-sync&quot;&gt;Socket (sync)&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;ServerSocket server &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; ServerSocket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&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;Socket client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 연결을 받을 때까지 block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String line &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; BufferedReader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(...).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 데이터를 받을 때까지 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;언어별-동기-io-패턴&quot;&gt;언어별 동기 IO 패턴&lt;/h2&gt;
&lt;h3 id=&quot;python-requests&quot;&gt;Python (requests)&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests&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:#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; requests.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://api.example.com/users&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;data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.json()   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 응답 올 때까지 block&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;# 비동기 대안: httpx + asyncio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; httpx, asyncio&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; fetch&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;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; httpx.AsyncClient() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client:&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:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://api.example.com/users&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; response.json()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;nodejs&quot;&gt;Node.js&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 동기 (fs.readFileSync) - 이벤트 루프 블로킹&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; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fs.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;data.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &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&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;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; data&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; fs.promises.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readFile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;data.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;utf8&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;blockquote&gt;
&lt;p&gt;[!WARNING]
Node.js에서 &lt;code&gt;*Sync&lt;/code&gt; 함수 (예: &lt;code&gt;readFileSync&lt;/code&gt;, &lt;code&gt;execSync&lt;/code&gt;)는 이벤트 루프 전체를 블로킹한다. 서버 코드에서는 절대 사용 금지. 스크립트나 초기화 코드에서만 허용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;sync-모델의-동시성-스레드-풀&quot;&gt;Sync 모델의 동시성: 스레드 풀&lt;/h2&gt;
&lt;p&gt;대량 동시성을 위해 sync 코드는 &lt;strong&gt;스레드 풀&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ExecutorService pool &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Executors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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:#6A737D&quot;&gt;// 매 요청마다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pool.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// sync call, but in separate thread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r);&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;스레드 1개당 메모리 ~1MB&lt;/li&gt;
&lt;li&gt;100 스레드 = 100MB&lt;/li&gt;
&lt;li&gt;10,000 동시 연결 → ~10GB, 비현실적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Java 21+의 **Virtual Threads (JEP 444)**가 이 한계를 깬다. 동기 코드 그대로 두면서도 수백만 동시 연결을 가능하게 만든다.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Virtual Threads: 동기 코드 그대로, 스레드 수 제한 없음&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 style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; executor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Executors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newVirtualThreadPerTaskExecutor&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;    executor.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 여전히 sync, 하지만 virtual thread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r);&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;h2 id=&quot;sync와-blocking의-관계&quot;&gt;sync와 [[Blocking]]의 관계&lt;/h2&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;표현&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;synchronous&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호출자가 결과를 기다린다 (control flow)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;그 동안 스레드가 멈춘다 (thread state)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;같이 쓰이지만 다른 차원. 대부분의 sync 코드는 blocking이지만, &lt;strong&gt;non-blocking sync&lt;/strong&gt;도 가능 (&lt;code&gt;Thread.onSpinWait()&lt;/code&gt;, busy-loop)하다.&lt;/p&gt;
&lt;p&gt;반대로 &lt;strong&gt;async + blocking&lt;/strong&gt;도 가능 (콜백 안에서 다시 sleep하는 경우, 자주 보는 안티패턴).&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;/strong&gt;: 동기 자체가 느린 게 아니라, &lt;em&gt;느린 I/O를 동기로 기다리는 것&lt;/em&gt;이 문제. 빠른 I/O는 동기가 더 단순하고 충분히 빠르다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스레드 풀 크기 과소 설정&lt;/strong&gt;: 동시 요청 수 &gt; 스레드 수 → 큐 대기 → 응답 지연. 모니터링 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual Thread에서 synchronized 블록&lt;/strong&gt;: Java 21 Virtual Thread는 &lt;code&gt;synchronized&lt;/code&gt; 블록 안에서 blocking 시 carrier thread를 점유 (pinning). &lt;code&gt;ReentrantLock&lt;/code&gt; 사용 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js에서 Sync 함수 사용&lt;/strong&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;[[Asynchronous]]&lt;/li&gt;
&lt;li&gt;[[Blocking]]&lt;/li&gt;
&lt;li&gt;[[Non-Blocking]]&lt;/li&gt;
&lt;li&gt;[[비동기와 타이밍, 콜백부터 async/await까지의 발전사]]&lt;/li&gt;
&lt;li&gt;[[backpressure]] (동기 호출 과부하 시 흐름 제어)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>concurrency</category><category>programming-model</category><category>sync-async</category><category>blocking</category><category>io</category><author>koa (김신건)</author></item><item><title>[Concurrency] Backpressure: 흐름 제어로 시스템 보호</title><link>https://shinkeonkim.com/wiki/concurrency/backpressure/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/backpressure/</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;Backpressure&lt;/strong&gt; = &lt;em&gt;생산자가 소비자 속도에 맞춰 자기 속도 조절&lt;/em&gt;. 분산 시스템의 &lt;em&gt;cascade failure&lt;/em&gt; 방지.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Backpressure 의 핵심: &lt;em&gt;“받는 쪽이 처리 못 함” 을 &lt;em&gt;주는 쪽이 알아야&lt;/em&gt; 한다&lt;/em&gt;. 모르면 &lt;em&gt;버퍼 폭발 + OOM + 시스템 다운&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;    Fast[Fast Producer&amp;#x3C;br/&gt;10000 msg/s] --&gt;|push| Buf[Unbounded Buffer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Buf --&gt;|consume| Slow[Slow Consumer&amp;#x3C;br/&gt;100 msg/s]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Buf -.OOM.-&gt; Crash[💥]&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;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;blockquote&gt;
&lt;p&gt;Producer-Consumer 에서 &lt;em&gt;Producer 가 항상 빠르면&lt;/em&gt; buffer 가 &lt;em&gt;무한&lt;/em&gt; 으로. &lt;em&gt;backpressure 의 본질적 시나리오&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;4가지-전략&quot;&gt;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 TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[Producer &gt; Consumer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Buf[1. Bounded buffer + Block]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Drop[&quot;2. Drop (lossy)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Sample[3. Sampling]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Pull[&quot;4. Pull-based (demand)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-bounded-buffer--block&quot;&gt;1. Bounded Buffer + Block&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;queue &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BoundedQueue(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;maxsize&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Producer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;queue.put(msg)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 가득 차면 block 또는 timeout&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;단순.&lt;/li&gt;
&lt;li&gt;Producer 가 &lt;em&gt;blocking&lt;/em&gt; → 상위 backpressure 자동 전파.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-drop-lossy&quot;&gt;2. Drop (Lossy)&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;python&quot;&gt;&lt;code&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;    queue.put_nowait(msg)&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:#E1E4E8&quot;&gt; QueueFull:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 드롭&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;metrics / log&lt;/em&gt; 같은 &lt;em&gt;손실 OK&lt;/em&gt; 데이터.&lt;/li&gt;
&lt;li&gt;Drop policy: oldest, newest, sample.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-sampling&quot;&gt;3. Sampling&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;python&quot;&gt;&lt;code&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; random.random() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&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 style=&quot;color:#6A737D&quot;&gt;# 10% 만 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    queue.put(msg)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;대규모 telemetry.&lt;/li&gt;
&lt;li&gt;통계적으로 유의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-pull-based-reactive-streams&quot;&gt;4. Pull-based (Reactive Streams)&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;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 P as Producer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Consumer&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;    C-&gt;&gt;P: request(10) (demand)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;C: next(msg) × 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;P: request(5) (more demand)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;C: next(msg) × 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Consumer 가 요청한 만큼만 전송&lt;/em&gt;. &lt;em&gt;Producer 가 자기 속도 자동 조절&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;라이브러리: Project Reactor (Java), RxJava, ReactiveX, Akka Streams.&lt;/p&gt;
&lt;h2 id=&quot;시스템-레벨-backpressure&quot;&gt;시스템 레벨 Backpressure&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;    LB --&gt; Sv1[Service 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sv1 --&gt; Sv2[Service 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sv2 --&gt; DB[(DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB -.포화.-&gt; Sv2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sv2 -.503.-&gt; Sv1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sv1 -.429.-&gt; LB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LB -.queue.-&gt; Client&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;LB&lt;/td&gt;&lt;td&gt;429 Too Many Requests&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Service&lt;/td&gt;&lt;td&gt;503 Service Unavailable&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB&lt;/td&gt;&lt;td&gt;connection pool 고갈&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Network&lt;/td&gt;&lt;td&gt;TCP window 축소&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;http2-quic-의-flow-control&quot;&gt;HTTP/2, QUIC 의 flow control&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;    Sender --&gt;|DATA| Receiver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Receiver --&gt;|WINDOW_UPDATE +N| Sender&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[Sender 는 window 안에서만]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 건 [[HTTP/2]] 의 flow control 절.&lt;/p&gt;
&lt;h2 id=&quot;tcp-의-flow-control&quot;&gt;TCP 의 flow control&lt;/h2&gt;
&lt;p&gt;TCP 의 &lt;em&gt;sliding window&lt;/em&gt; 가 &lt;em&gt;전송 레이어 backpressure&lt;/em&gt;. 자세한 건 [[tcp]].&lt;/p&gt;
&lt;h2 id=&quot;bulkhead&quot;&gt;Bulkhead&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;    Pool1[&quot;Pool A (10 threads)&amp;#x3C;br/&gt;→ service A&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pool2[&quot;Pool B (10 threads)&amp;#x3C;br/&gt;→ service B&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pool3[&quot;Pool C (10 threads)&amp;#x3C;br/&gt;→ service C&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Fail[&quot;service A 다운&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Fail --&gt; Pool1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pool1 -.포화.-&gt; Pool1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[Pool A 만 영향. B/C 는 정상]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Thread pool 을 service 별로 분리&lt;/em&gt; → 한 service 다운이 &lt;em&gt;전체 다운&lt;/em&gt; 안 되게.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;shedding-load-shedding&quot;&gt;Shedding (Load Shedding)&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[현재 부하 &gt; 한도?]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|예| Shed[&quot;일부 요청 거절 (503)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|아니오| Process[정상 처리]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;모든 요청을 &lt;em&gt;느리게 처리&lt;/em&gt; 하는 것보다 &lt;em&gt;일부 거절하고 나머지는 빠르게&lt;/em&gt;. Google SRE 의 &lt;em&gt;graceful degradation&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;adaptive-concurrency&quot;&gt;Adaptive Concurrency&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;    Lat[현재 latency 측정] --&gt; Calc[Little&apos;s Law 로&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;    Calc --&gt; Cap[concurrency 한도 조정]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cap --&gt; Limit[새 요청 제한]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Netflix Concurrency Limits.&lt;/li&gt;
&lt;li&gt;Envoy adaptive concurrency.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;latency 가 늘면 동시성 줄임&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;Unbounded queue&lt;/strong&gt; = OOM 보장. &lt;em&gt;모든 buffer 는 bounded&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drop 정책 &lt;em&gt;명확하지 않음&lt;/em&gt;&lt;/strong&gt; = 운영자가 &lt;em&gt;어떤 데이터 사라지는지&lt;/em&gt; 모름. 메트릭 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backpressure &lt;em&gt;전파 안 됨&lt;/em&gt;&lt;/strong&gt; = 한 계층만 잡고 &lt;em&gt;위&lt;/em&gt;에서 압박 지속. cascade fail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pull-based 의 &lt;em&gt;너무 큰 request(N)&lt;/em&gt;&lt;/strong&gt; = 사실상 push 와 같음. &lt;em&gt;적절한 batch size&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;java-reactive-streams-api-구현&quot;&gt;Java: Reactive Streams API 구현&lt;/h2&gt;
&lt;p&gt;Project Reactor (Spring WebFlux 기반) 를 사용한 pull-based backpressure 예시:&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Flux&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;&gt; upstream &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Flux.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;generate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sink &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sink.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetchNextItem&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;upstream&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:#B392F0&quot;&gt;onBackpressureBuffer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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 style=&quot;color:#6A737D&quot;&gt;// bounded buffer&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:#B392F0&quot;&gt;publishOn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Schedulers.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;boundedElastic&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:#B392F0&quot;&gt;subscribe&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:#E1E4E8&quot;&gt; BaseSubscriber&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;&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:#F97583&quot;&gt;Override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        protected&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hookOnSubscribe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Subscription &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;subscription&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:#B392F0&quot;&gt;            request&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:#6A737D&quot;&gt;// 최초 50개 요청&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;Override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        protected&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hookOnNext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(String &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;value&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:#B392F0&quot;&gt;            process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(value);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;            request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 처리 완료 후 1개씩 추가 요청&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;onBackpressureBuffer&lt;/code&gt; / &lt;code&gt;onBackpressureDrop&lt;/code&gt; / &lt;code&gt;onBackpressureLatest&lt;/code&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;th&gt;사용 시나리오&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;onBackpressureBuffer(n)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Bounded buffer&lt;/td&gt;&lt;td&gt;순간 burst 허용, OOM 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;onBackpressureDrop()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Drop newest&lt;/td&gt;&lt;td&gt;실시간 센서 데이터, 손실 OK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;onBackpressureLatest()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Keep latest only&lt;/td&gt;&lt;td&gt;UI 화면 갱신, 최신값만 유의미&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;onBackpressureError()&lt;/code&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;p&gt;자세히는 [[spring-webflux]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;kafka-producer-설정&quot;&gt;Kafka Producer 설정&lt;/h2&gt;
&lt;p&gt;Kafka 에서 producer 의 backpressure 는 &lt;code&gt;max.block.ms&lt;/code&gt; 와 &lt;code&gt;buffer.memory&lt;/code&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;properties&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Producer 설정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;buffer.memory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=33554432        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 총 버퍼 크기 (32 MB)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;max.block.ms&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=60000            &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 버퍼가 가득 찼을 때 block 최대 시간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;batch.size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=16384              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# batch 크기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;linger.ms&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=5                   &lt;/span&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;code&gt;KafkaProducer.send()&lt;/code&gt; 가 &lt;code&gt;max.block.ms&lt;/code&gt; 동안 blocking. 그래도 공간이 안 나면 &lt;code&gt;TimeoutException&lt;/code&gt;. 이것이 Kafka producer 레벨의 backpressure. Consumer lag 기반 스케일링은 [[kafka-consumer-group]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;resilience4j-와의-연계&quot;&gt;Resilience4j 와의 연계&lt;/h2&gt;
&lt;p&gt;Backpressure 는 단독으로 사용하기보다 circuit breaker, rate limiter 와 조합한다.&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;    Client[&quot;클라이언트 요청&quot;] --&gt; RL[&quot;Rate Limiter\n초당 N건 제한&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RL --&gt; CB[&quot;Circuit Breaker\n실패율 임계 초과 시 차단&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB --&gt; BP[&quot;Bulkhead\n동시 실행 수 제한&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BP --&gt; Svc[&quot;Service&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Svc -.실패.-&gt; CB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CB -.open.-&gt; FBK[&quot;Fallback 응답&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Resilience4j&lt;/code&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Supplier&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;&gt; decorated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Decorators.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofSupplier&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; remoteService.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;call&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:#B392F0&quot;&gt;withRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(rateLimiter)&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:#B392F0&quot;&gt;withCircuitBreaker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(circuitBreaker)&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:#B392F0&quot;&gt;withBulkhead&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(bulkhead)&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:#B392F0&quot;&gt;decorate&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;[[circuit-breaker]] 와 [[rate-limiting]] 함께 설계해야 실질적인 cascade failure 방지가 된다.&lt;/p&gt;
&lt;h2 id=&quot;관찰-가능성-observability&quot;&gt;관찰 가능성 (Observability)&lt;/h2&gt;
&lt;p&gt;Backpressure 가 발동하고 있는지 모르면 운영 중에 발견하지 못한다. 필수 메트릭:&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;th&gt;경보 기준&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;queue.size&lt;/code&gt; / &lt;code&gt;buffer.used&lt;/code&gt;&lt;/td&gt;&lt;td&gt;현재 버퍼 점유율&lt;/td&gt;&lt;td&gt;&gt; 80%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;drop.rate&lt;/code&gt;&lt;/td&gt;&lt;td&gt;초당 드롭된 메시지 수&lt;/td&gt;&lt;td&gt;&gt; 0 (손실 불허 시)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;block.time&lt;/code&gt;&lt;/td&gt;&lt;td&gt;producer block 시간&lt;/td&gt;&lt;td&gt;&gt; SLO 임계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;consumer.lag&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Kafka consumer lag&lt;/td&gt;&lt;td&gt;지속 증가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;http.server.requests&lt;/code&gt; + 429/503 비율&lt;/td&gt;&lt;td&gt;HTTP 레이어 shed 비율&lt;/td&gt;&lt;td&gt;급증 시 알람&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Micrometer + Prometheus + Grafana 스택으로 위 메트릭을 시각화하면 backpressure 발동 시점과 원인을 즉시 파악할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[connection-pool]]&lt;/li&gt;
&lt;li&gt;[[circuit-breaker]]&lt;/li&gt;
&lt;li&gt;[[rate-limiting]]&lt;/li&gt;
&lt;li&gt;[[retry-with-backoff]]&lt;/li&gt;
&lt;li&gt;[[spring-webflux]]&lt;/li&gt;
&lt;li&gt;[[kafka-consumer-group]] (lag 기반 scaling)&lt;/li&gt;
&lt;li&gt;[[http-2]] (flow control)&lt;/li&gt;
&lt;li&gt;[[tcp]] (TCP sliding window)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>backpressure</category><category>flow-control</category><category>reactive</category><category>concurrency</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Concurrency] Rate Limiting: token bucket, sliding window</title><link>https://shinkeonkim.com/wiki/concurrency/rate-limiting/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/rate-limiting/</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;Rate Limiting&lt;/strong&gt; = &lt;em&gt;시간당 요청 수 제한&lt;/em&gt;. API 보호, fair use, 비용 제어, DDoS 완화.&lt;/p&gt;
&lt;h2 id=&quot;5가지-알고리즘&quot;&gt;5가지 알고리즘&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[Fixed Window]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[Sliding Log]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[Sliding Window Counter]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[Token Bucket]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E[Leaky Bucket]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F[GCRA]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-fixed-window&quot;&gt;1. Fixed Window&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;[00:00 ~ 00:01] count = 100 (한도)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[00:01 ~ 00:02] count 리셋 → 100&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:#E1E4E8&quot;&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rl:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;minute_bucket&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.incr(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redis.expire(key, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&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:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: deny()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;단순.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;경계 burst&lt;/em&gt;: 00:00:59 에 100 + 00:01:00 에 100 = &lt;em&gt;2초 안에 200&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-sliding-log&quot;&gt;2. Sliding Log&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;모든 요청의 timestamp&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rl:log:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;user&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;now &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&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:#E1E4E8&quot;&gt;redis.zadd(key, { req_id: now })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redis.zremrangebyscore(key, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, now &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&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;count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.zcard(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; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: deny()&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; (요청마다 entry).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-sliding-window-counter&quot;&gt;3. Sliding Window Counter&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Fixed Window + 이전 window 비율&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;current_count + previous_count * (1 - elapsed_in_current/window)&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;적은 메모리 + Fixed 의 burst 완화&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-token-bucket&quot;&gt;4. Token Bucket&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;    Refill[1초마다 토큰 N개] --&gt; Bucket[(Bucket: max 100)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bucket --&gt;|토큰 1개 소비| Request[요청 통과]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bucket --&gt;|토큰 없음| Deny[429]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Burst 허용&lt;/em&gt; (bucket 차있을 때).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;평균 rate&lt;/em&gt; 보장.&lt;/li&gt;
&lt;li&gt;AWS / GitHub / Discord API 표준.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-leaky-bucket&quot;&gt;5. Leaky Bucket&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 TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Req[요청] --&gt;|채움| B[Bucket]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|일정 rate 로 소비| Output[처리]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B -.넘침.-&gt; Drop[Drop]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;완전 평탄화&lt;/em&gt;. burst 흡수 + 일정 출력.&lt;/li&gt;
&lt;li&gt;queue 기반.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-gcra-generic-cell-rate-algorithm&quot;&gt;6. GCRA (Generic Cell Rate Algorithm)&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;TAT (Theoretical Arrival Time) 계산&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;요청 시각이 TAT 보다 충분히 앞이면 허용&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Token Bucket 수학적 변형&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;단일 변수&lt;/em&gt; 로 표현. &lt;em&gt;Redis 1 명령&lt;/em&gt; 으로 가능.&lt;/li&gt;
&lt;li&gt;Cloudflare, Stripe 가 사용.&lt;/li&gt;
&lt;/ul&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;th&gt;Burst&lt;/th&gt;&lt;th&gt;구현&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Fixed Window&lt;/td&gt;&lt;td&gt;&lt;em&gt;적음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;낮음 (경계 burst)&lt;/td&gt;&lt;td&gt;큼&lt;/td&gt;&lt;td&gt;가장 단순&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sliding Log&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;Sorted Set&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sliding Window Counter&lt;/td&gt;&lt;td&gt;적음&lt;/td&gt;&lt;td&gt;보통&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;td&gt;2 counter&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Token Bucket&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;Leaky Bucket&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;좋음&lt;/td&gt;&lt;td&gt;평탄&lt;/td&gt;&lt;td&gt;Queue&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GCRA&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;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;분산-환경-구현-redis&quot;&gt;분산 환경 구현 (Redis)&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;# Token Bucket (Lua atomic)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TOKEN_BUCKET_LUA&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local key = KEYS[1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local now = tonumber(ARGV[1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local rate = tonumber(ARGV[2])         -- tokens per sec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local capacity = tonumber(ARGV[3])&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:#9ECBFF&quot;&gt;local last = redis.call(&apos;HMGET&apos;, key, &apos;tokens&apos;, &apos;last&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local tokens = tonumber(last[1]) or capacity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local last_ts = tonumber(last[2]) or now&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:#9ECBFF&quot;&gt;local delta = math.max(0, now - last_ts) * rate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;tokens = math.min(capacity, tokens + delta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;local allowed = tokens &gt;= 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;if allowed then tokens = tokens - 1 end&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:#9ECBFF&quot;&gt;redis.call(&apos;HMSET&apos;, key, &apos;tokens&apos;, tokens, &apos;last&apos;, now)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;redis.call(&apos;EXPIRE&apos;, key, math.ceil(capacity / rate * 2))&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:#9ECBFF&quot;&gt;return allowed and 1 or 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
분산 rate limit 는 &lt;em&gt;Redis 한 곳&lt;/em&gt; 으로 &lt;em&gt;원자적 카운터&lt;/em&gt;. &lt;em&gt;분산 락 없이&lt;/em&gt; Lua 스크립트.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;http-응답-표준&quot;&gt;HTTP 응답 표준&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;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&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 style=&quot;color:#79B8FF&quot;&gt; 429&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Too Many Requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Retry-After&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-RateLimit-Limit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-RateLimit-Remaining&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-RateLimit-Reset&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 1719318060&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;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rate_limit_exceeded&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;retry_after&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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;/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;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt; Edge[&quot;CDN / WAF&amp;#x3C;br/&gt;(IP 단위)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Edge --&gt; GW[&quot;API Gateway&amp;#x3C;br/&gt;(API key 단위)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GW --&gt; App[&quot;App&amp;#x3C;br/&gt;(user 단위)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App --&gt; DB[&quot;DB&amp;#x3C;br/&gt;(쿼리 단위)&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;CDN&lt;/td&gt;&lt;td&gt;IP, geographic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WAF&lt;/td&gt;&lt;td&gt;의심 패턴&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API Gateway&lt;/td&gt;&lt;td&gt;API key, OAuth token&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;App&lt;/td&gt;&lt;td&gt;user, tenant&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB&lt;/td&gt;&lt;td&gt;query, connection&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;tier-based-rate-limit&quot;&gt;Tier-based Rate Limit&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:#E1E4E8&quot;&gt;limits &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;free&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:    {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;per_min&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;burst&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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:#9ECBFF&quot;&gt;    &quot;pro&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:     {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;per_min&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;burst&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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:#9ECBFF&quot;&gt;    &quot;enterprise&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;per_min&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;burst&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50000&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;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Stripe, OpenAI 같은 &lt;em&gt;유료 API&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;단일 노드 in-memory counter&lt;/strong&gt; = N 노드면 N배 한도. &lt;em&gt;분산 store 필수&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP 기반만&lt;/strong&gt; = NAT 뒤 &lt;em&gt;수천 사용자가 한 IP&lt;/em&gt;. user + IP 조합.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry-After 무시 (클라이언트)&lt;/strong&gt; = 무한 retry → DDoS 자기 자신.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Burst 너무 큼&lt;/strong&gt; = bucket 가득 차면 &lt;em&gt;순간 throughput 폭증&lt;/em&gt;. 백엔드 보호 안 됨.&lt;/li&gt;
&lt;/ol&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;    Q1[&quot;메모리 제약 있음?&quot;] --&gt;|&quot;예&quot;| Q2[&quot;Burst 허용?&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&quot;아니오&quot;| SL[&quot;Sliding Log 적합&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;예&quot;| TB[&quot;Token Bucket&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;아니오&quot;| GCRA[&quot;GCRA or Leaky Bucket&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TB --&gt; Q3[&quot;분산 환경?&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GCRA --&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;|&quot;예&quot;| REDIS[&quot;Redis Lua 스크립트&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|&quot;아니오&quot;| MEM[&quot;In-memory&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;클라이언트-구현-패턴&quot;&gt;클라이언트 구현 패턴&lt;/h2&gt;
&lt;p&gt;클라이언트 측에서 429 를 올바르게 처리하지 않으면 오히려 서버에 더 큰 부하를 줄 수 있습니다.&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:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; random&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; api_call_with_backoff&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fn, max_retries&lt;/span&gt;&lt;span style=&quot;color:#F97583&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:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(max_retries):&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; fn()&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; response.status_code &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 429&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        retry_after &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(response.headers.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Retry-After&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&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:#E1E4E8&quot;&gt;        wait &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; retry_after &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 style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; random.uniform(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&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;        time.sleep(wait)&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;    raise&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 style=&quot;color:#9ECBFF&quot;&gt;&quot;Rate limit 초과: 재시도 실패&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;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;code&gt;Retry-After&lt;/code&gt; 헤더를 반드시 준수하세요. 무시하고 즉시 재시도하면 자기 DDoS 가 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;rate-limit-모니터링&quot;&gt;Rate Limit 모니터링&lt;/h2&gt;
&lt;p&gt;관찰해야 할 핵심 지표:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;지표&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;rate_limit_hits_total&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;총 차단 횟수, spike = 공격 또는 버그&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;tokens_remaining&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;버킷 잔여 토큰, 낮으면 한도 근접&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;p99 latency&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Redis 조회 추가 지연 모니터링&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Top-N blocked users&lt;/td&gt;&lt;td align=&quot;left&quot;&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Prometheus 메트릭 예시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; prometheus_client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Counter&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;rate_limit_hits &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Counter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &apos;rate_limit_hits_total&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:#9ECBFF&quot;&gt;    &apos;Rate limit 에 걸린 요청 수&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:#E1E4E8&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;endpoint&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_tier&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:#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:#B392F0&quot;&gt;@app.route&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/resource&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; resource&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; rate_limiter.allow(request.user):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        rate_limit_hits.labels(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            endpoint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/resource&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:#FFAB70&quot;&gt;            user_tier&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request.user.tier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ).inc()&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; jsonify(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;rate_limit_exceeded&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;429&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;strong&gt;Warm-up 정책&lt;/strong&gt;: 신규 사용자는 초반 더 관대하게 처리하여 정상 사용 패턴 파악.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate Limit 예외&lt;/strong&gt;: 내부 서비스, health check endpoint 는 화이트리스트 처리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 카운터&lt;/strong&gt;: 극한 성능 필요 시 로컬 카운터로 먼저 판단, Redis 는 비동기 동기화 (근사).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다중 버킷&lt;/strong&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;limits &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;    &apos;per_second&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &apos;per_minute&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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:#9ECBFF&quot;&gt;    &apos;per_hour&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2000&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:#6A737D&quot;&gt;# 셋 다 통과해야 허용 (AND 조건)&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;[[backpressure]]&lt;/li&gt;
&lt;li&gt;[[circuit-breaker]]&lt;/li&gt;
&lt;li&gt;[[retry-with-backoff]]&lt;/li&gt;
&lt;li&gt;[[api-gateway]]&lt;/li&gt;
&lt;li&gt;[[redis-sorted-sets|Redis Sorted Sets]]&lt;/li&gt;
&lt;li&gt;[[redis-strings|Redis Strings]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>rate-limit</category><category>throttling</category><category>concurrency</category><category>api</category><category>backend</category><author>koa (김신건)</author></item><item><title>Asynchronous (비동기)</title><link>https://shinkeonkim.com/wiki/concurrency/asynchronous/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/asynchronous/</guid><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous (비동기)&lt;/strong&gt; 는 호출자가 &lt;strong&gt;결과를 즉시 기다리지 않고&lt;/strong&gt; 다음 줄로 진행하는 실행 모델. 결과는 나중에 콜백, Future, Promise, 이벤트 등의 메커니즘으로 전달된다.&lt;/p&gt;
&lt;p&gt;[[Synchronous]] 의 정반대 개념. [[Non-Blocking]] 과 자주 같이 쓰이지만 별개의 차원이다.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Asynchronous, with Future&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CompletableFuture&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;&gt; future &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/users&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:#6A737D&quot;&gt;// 다음 줄로 즉시 진행, 결과를 안 기다림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;doOtherWork&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;// 나중에 결과가 필요할 때&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; future.&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:#6A737D&quot;&gt;// 그 때 결과를 기다림 (다시 sync 가 됨)&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/users&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (err, result) &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&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; (err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);&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:#6A737D&quot;&gt;// 즉시 다음 줄로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;doOtherWork&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;비동기-호출-흐름&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[caller]                    [callee or scheduler]&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;   │── call ───────────────►│ (작업 시작, 별도 컨텍스트)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   │◄─── 즉시 return ───────│&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;   │ doOtherWork()           │&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;   │◄─────── callback ──────│&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;호출자의 일직선 흐름이 &lt;strong&gt;두 갈래&lt;/strong&gt; 로 갈라진다. callback 이 진입하는 시점은 호출자의 스택과 다른 context.&lt;/p&gt;
&lt;h2 id=&quot;콜백-future-이벤트-세-가지-패턴&quot;&gt;콜백, Future, 이벤트, 세 가지 패턴&lt;/h2&gt;
&lt;h3 id=&quot;1-callback-콜백&quot;&gt;1. Callback (콜백)&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;service.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;callAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(arg, (err, result) &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&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; (err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; onResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);&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;/p&gt;
&lt;h3 id=&quot;2-future--promise--completablefuture&quot;&gt;2. Future / Promise / CompletableFuture&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;CompletableFuture&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;&gt; f &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; service.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;callAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(arg);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;f.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;thenApply&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;transform)&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:#B392F0&quot;&gt;thenAccept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;publish)&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:#B392F0&quot;&gt;exceptionally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;handle);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;체이닝이 가능해 콜백 지옥을 해결. Java 의 &lt;code&gt;CompletableFuture&lt;/code&gt;, JavaScript 의 &lt;code&gt;Promise&lt;/code&gt;, C# 의 &lt;code&gt;Task&lt;/code&gt; 가 같은 개념.&lt;/p&gt;
&lt;h3 id=&quot;3-event--reactive-stream&quot;&gt;3. Event / Reactive Stream&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;publisher.&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:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Subscriber&amp;#x3C;&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; onNext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(String &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;item&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; onError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Throwable &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;t&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; onComplete&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;여러 결과가 시간에 걸쳐 도착하는 경우. Reactor, RxJava, Akka Streams, Project Reactor (Spring WebFlux) 등이 이 방식.&lt;/p&gt;
&lt;h2 id=&quot;async-의-두-형태-blocking-vs-non-blocking&quot;&gt;Async 의 두 형태, blocking vs non-blocking&lt;/h2&gt;
&lt;p&gt;비동기 모델도 [[Blocking]] / [[Non-Blocking]] 두 가지로 갈라진다.&lt;/p&gt;
&lt;h3 id=&quot;async--non-blocking-가장-흔함-진짜-비동기&quot;&gt;Async + Non-Blocking (가장 흔함, 진짜 비동기)&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;client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;thenAccept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;process);&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;호출 스레드는 등록만 하고 즉시 반환. OS 또는 framework 가 I/O 완료 시 callback 을 실행. Node.js, Netty, Vert.x, Spring WebFlux 등이 이 모델.&lt;/p&gt;
&lt;h3 id=&quot;async--blocking-안티패턴-자주-발견됨&quot;&gt;Async + Blocking (안티패턴, 자주 발견됨)&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:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Thread&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// blocking I/O&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r);&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:#B392F0&quot;&gt;start&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;호출자 입장에서는 비동기지만, 실제로는 다른 스레드에서 blocking. 그래도 호출자 스레드가 즉시 풀려난다는 점은 비동기.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
이 패턴은 &lt;strong&gt;스레드를 많이 만든다&lt;/strong&gt;. 동시 1000 요청이면 1000 스레드. 대부분의 sync API 를 async 화하는 ExecutorService 패턴이 이렇게 동작. Virtual Threads (JEP 444) 가 이 문제를 줄여준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;java-의-비동기-api&quot;&gt;Java 의 비동기 API&lt;/h2&gt;
&lt;h3 id=&quot;completablefuture&quot;&gt;CompletableFuture&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;CompletableFuture.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;supplyAsync&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:#B392F0&quot;&gt; fetchData&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:#B392F0&quot;&gt;thenApply&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parse)&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:#B392F0&quot;&gt;thenCombine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(other, (a, b) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; merge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, b))&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:#B392F0&quot;&gt;thenAccept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(System.out&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;println)&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:#B392F0&quot;&gt;exceptionally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(t &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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;failed&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, t);&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; 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;    });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비동기 + 체이닝 + 예외 처리 모두 가능.&lt;/p&gt;
&lt;h3 id=&quot;reactive-spring-webflux-project-reactor&quot;&gt;Reactive (Spring WebFlux, Project Reactor)&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;Mono&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; userRepo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;findById&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;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;switchIfEmpty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Mono.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;error&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; NotFoundException&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:#B392F0&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;enrich)&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:#B392F0&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Duration.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofSeconds&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;backpressure (생산자 / 소비자 속도 조절) 까지 자연스럽게 다룸.&lt;/p&gt;
&lt;h3 id=&quot;virtual-threads-java-21&quot;&gt;Virtual Threads (Java 21+)&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:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; executor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Executors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newVirtualThreadPerTaskExecutor&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;    Future&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;&gt; f &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; executor.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&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&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; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// sync 같은 코드, virtual thread 에서&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;strong&gt;코드는 sync 처럼 보이지만 underlying 은 비동기적으로 스케줄&lt;/strong&gt; 된다. Java 의 새로운 동시성 모델.&lt;/p&gt;
&lt;h2 id=&quot;장점&quot;&gt;장점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;확장성&lt;/strong&gt;, 같은 스레드 풀로 수만 동시 요청 처리 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자원 효율&lt;/strong&gt;, I/O 대기 중 스레드를 풀어둠&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI 응답성&lt;/strong&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;ul&gt;
&lt;li&gt;&lt;strong&gt;읽기 어렵다&lt;/strong&gt;, 콜백 지옥, 비순차적 흐름&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디버깅이 힘들다&lt;/strong&gt;, stack trace 가 단편적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 처리가 복잡&lt;/strong&gt;, 어디서 catch 할지 분명하지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트가 어렵다&lt;/strong&gt;, race condition, timing 의존&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sync-코드를-async-로-만드는-흔한-방법&quot;&gt;sync 코드를 async 로 만드는 흔한 방법&lt;/h2&gt;
&lt;h3 id=&quot;java-의-completablefuturesupplyasync&quot;&gt;Java 의 &lt;code&gt;CompletableFuture.supplyAsync&lt;/code&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CompletableFuture&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;&gt; future &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CompletableFuture.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;supplyAsync&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:#F97583&quot;&gt;-&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:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blockingCall&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 원래 sync 였던 코드&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;    threadPool&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;호출자에게는 비동기지만 내부적으로는 별도 스레드에서 sync 호출. 진짜 non-blocking 으로 만들려면 라이브러리 자체가 NIO 기반으로 다시 작성돼야 한다.&lt;/p&gt;
&lt;h3 id=&quot;spring-의-async&quot;&gt;Spring 의 &lt;code&gt;@Async&lt;/code&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;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:#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; EmailService&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:#F97583&quot;&gt;Async&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:#E1E4E8&quot;&gt; CompletableFuture&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sendAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(String &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&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;        // sync 코드&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        smtpClient.&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&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; CompletableFuture.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;completedFuture&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;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;    }&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;annotation 으로 손쉽게 비동기화. 내부는 thread pool 으로 위임.&lt;/p&gt;
&lt;h2 id=&quot;비동기와-동시성-concurrency-의-관계&quot;&gt;비동기와 동시성 (Concurrency) 의 관계&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;비동기 ≠ 병렬 (Parallel) ≠ 동시 (Concurrent)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Concurrent&lt;/strong&gt;: 동시에 여러 일이 진행 중 (실제 동시 실행 여부 무관)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parallel&lt;/strong&gt;: 실제로 동시에 실행 (멀티 코어)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asynchronous&lt;/strong&gt;: 호출자가 결과를 즉시 기다리지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비동기 모델은 &lt;strong&gt;single thread 에서도 concurrent&lt;/strong&gt; 일 수 있다 (Node.js, JS 의 event loop). 비동기는 동시성을 &lt;em&gt;가능하게&lt;/em&gt; 하는 도구일 뿐.&lt;/p&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Synchronous]]&lt;/li&gt;
&lt;li&gt;[[Blocking]]&lt;/li&gt;
&lt;li&gt;[[Non-Blocking]]&lt;/li&gt;
&lt;li&gt;[[비동기와 타이밍, 콜백부터 async/await까지의 발전사]]&lt;/li&gt;
&lt;li&gt;[[콜백 지옥]]&lt;/li&gt;
&lt;li&gt;[[Promise]]&lt;/li&gt;
&lt;li&gt;[[async/await]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>concurrency</category><category>programming-model</category><category>sync-async</category><author>koa (김신건)</author></item><item><title>Blocking (블로킹)</title><link>https://shinkeonkim.com/wiki/concurrency/blocking/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/blocking/</guid><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Blocking&lt;/strong&gt; 은 호출이 &lt;strong&gt;결과가 준비될 때까지 호출 스레드를 멈춰두는&lt;/strong&gt; 실행 방식. 멈춰 있는 동안 그 스레드는 다른 일을 할 수 없다. OS 가 스레드를 sleep 상태로 만들고 CPU 를 다른 스레드에게 넘겨준다 (대부분의 경우).&lt;/p&gt;
&lt;p&gt;[[Non-Blocking]] 의 반대 개념. [[Synchronous]] 와 자주 같이 쓰이지만 &lt;strong&gt;별도의 차원&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;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;p&gt;Thread T 가 blocking I/O 를 호출하면 BLOCKED 상태로 들어가 CPU 를 양보한다. OS scheduler 는 그 사이 Thread U 에게 CPU 를 돌리고, I/O 가 끝나면 T 를 깨워 다시 CPU 를 할당한다. 호출자의 코드 흐름에서는 “한 줄짜리 sync 호출” 로 보이지만, 스레드 입장에서는 깊은 잠에 든 시간이 존재한다.&lt;/p&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Thread T:  [────── blocking call ──────][다음 작업]&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;           │ ← CPU 다른 스레드에 양보     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           │   (block 동안 idle)         │&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;T 스레드는 결과가 도착할 때까지 어떤 코드도 실행하지 않는다. CPU 는 다른 스레드를 처리한다.&lt;/p&gt;
&lt;h2 id=&quot;스레드-상태-전이&quot;&gt;스레드 상태 전이&lt;/h2&gt;
&lt;p&gt;Java 스레드 상태 중 blocking 이 발생하는 지점을 요약하면 다음과 같다.&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;    NEW --&gt; RUNNABLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RUNNABLE --&gt;|&quot;blocking call&quot;| BLOCKED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BLOCKED --&gt;|&quot;I/O 완료 / 락 해제&quot;| RUNNABLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RUNNABLE --&gt;|&quot;Object.wait&quot;| WAITING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WAITING --&gt;|&quot;notify / 인터럽트&quot;| RUNNABLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RUNNABLE --&gt;|&quot;완료&quot;| TERMINATED&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BLOCKED&lt;/code&gt;: I/O, lock 획득, sleep 시 진입. &lt;code&gt;WAITING&lt;/code&gt;: 명시적 &lt;code&gt;wait()&lt;/code&gt; / &lt;code&gt;join()&lt;/code&gt; 시 진입. 두 상태 모두 CPU 를 점유하지 않는다. OS scheduler 가 다른 스레드에게 CPU 를 배분한다.&lt;/p&gt;
&lt;h2 id=&quot;가장-흔한-예&quot;&gt;가장 흔한 예&lt;/h2&gt;
&lt;h3 id=&quot;파일-io-blocking&quot;&gt;파일 I/O (blocking)&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;String content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Files.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;data.txt&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:#6A737D&quot;&gt;// 파일을 다 읽을 때까지 스레드 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;네트워크-io-blocking&quot;&gt;네트워크 I/O (blocking)&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;ServerSocket ss &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; ServerSocket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&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;Socket client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ss.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 연결이 올 때까지 block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String line &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reader.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 데이터 한 줄 올 때까지 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;락-대기-blocking&quot;&gt;락 대기 (blocking)&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:#F97583&quot;&gt;synchronized&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (obj) {                      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 다른 스레드가 락을 갖고 있으면 block&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;lock.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();                              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ReentrantLock, 락을 얻을 때까지 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;잠-자기-blocking&quot;&gt;잠 자기 (blocking)&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;Thread.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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 style=&quot;color:#6A737D&quot;&gt;// 1초 block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;TimeUnit.SECONDS.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);                &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 같음&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;blockingqueue-blocking&quot;&gt;BlockingQueue (blocking)&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;BlockingQueue&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; q &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:#E1E4E8&quot;&gt; LinkedBlockingQueue&amp;#x3C;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Task t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;take&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();                        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 원소가 들어올 때까지 block&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 style=&quot;color:#B392F0&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(task);                              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 큐가 가득 차면 block&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이름에 “Blocking” 이 명시적으로 들어있다.&lt;/p&gt;
&lt;h2 id=&quot;blocking-의-코드-특징&quot;&gt;blocking 의 코드 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;호출 후 즉시 결과 처리 가능&lt;/strong&gt;, return 값을 바로 쓸 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stack trace 가 일직선&lt;/strong&gt;, 디버깅이 쉽다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러는 그 자리에서 throw&lt;/strong&gt;, &lt;code&gt;try/catch&lt;/code&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;java&quot;&gt;&lt;code&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;    String result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blockingCall&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// sync + blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);                  &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:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (IOException &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&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:#B392F0&quot;&gt;    handle&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e);                        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 같은 메서드에서 catch&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;h2 id=&quot;blocking-의-비용&quot;&gt;blocking 의 비용&lt;/h2&gt;
&lt;p&gt;스레드가 block 됐다고 CPU 가 노는 건 아니다. OS 가 다른 스레드를 스케줄링한다. 하지만:&lt;/p&gt;
&lt;h3 id=&quot;1-스레드-자체가-비싸다&quot;&gt;1. 스레드 자체가 비싸다&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JVM 의 platform thread 는 OS 스레드와 1:1&lt;/li&gt;
&lt;li&gt;스레드 1개당 메모리 ~1MB (스택)&lt;/li&gt;
&lt;li&gt;100,000 스레드 → ~100GB RAM, 비현실적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 &lt;strong&gt;blocking 코드 + 많은 동시 요청&lt;/strong&gt; 은 스레드 풀로 제한해야 한다. 풀이 꽉 차면 요청들이 큐에서 기다린다.&lt;/p&gt;
&lt;h3 id=&quot;2-context-switch-가-잦다&quot;&gt;2. context switch 가 잦다&lt;/h3&gt;
&lt;p&gt;스레드가 block 되면 OS scheduler 가 다른 스레드로 전환. 이 비용은 마이크로초 수준이지만 누적되면 무시 못 한다.&lt;/p&gt;
&lt;h3 id=&quot;3-응답성-손실&quot;&gt;3. 응답성 손실&lt;/h3&gt;
&lt;p&gt;UI 스레드가 block 되면 화면이 멈춘다. Android 의 &lt;code&gt;NetworkOnMainThreadException&lt;/code&gt;, Swing 의 EDT (Event Dispatch Thread) 보호가 모두 이 문제를 막기 위한 것.&lt;/p&gt;
&lt;h2 id=&quot;blocking--synchronous-가-가장-흔하다&quot;&gt;blocking + [[Synchronous]] 가 가장 흔하다&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// sync + blocking&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;sync&lt;/strong&gt;: 호출자가 결과를 기다린다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;blocking&lt;/strong&gt;: 스레드가 멈춘다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대부분의 전통적 Java 코드 (HttpURLConnection, JDBC, JPA, file I/O) 가 이 모델.&lt;/p&gt;
&lt;h2 id=&quot;blocking--asynchronous-도-가능&quot;&gt;blocking + [[Asynchronous]] 도 가능&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Thread&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);         &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 별도 스레드에서 blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r);&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:#B392F0&quot;&gt;start&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;호출자는 즉시 풀려나지만, 새 스레드가 block. 자주 보는 안티패턴.&lt;/p&gt;
&lt;h2 id=&quot;blocking-의-대안-nio-와-비동기-io&quot;&gt;blocking 의 대안, NIO 와 비동기 I/O&lt;/h2&gt;
&lt;p&gt;전통적 Java I/O (java.io.*) 는 모두 blocking. Java NIO (&lt;code&gt;java.nio.\*&lt;/code&gt;) 는 &lt;strong&gt;non-blocking I/O&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// NIO non-blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ServerSocketChannel channel &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ServerSocketChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&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;channel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;configureBlocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&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;SocketChannel client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; channel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// null 즉시 반환 (연결 없으면)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 모델로 &lt;strong&gt;Netty, Vert.x, Spring WebFlux&lt;/strong&gt; 같은 high-throughput 서버가 가능.&lt;/p&gt;
&lt;p&gt;자세히는 [[Non-Blocking]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;virtual-threads-blocking-의-의미가-바뀌다&quot;&gt;Virtual Threads, blocking 의 의미가 바뀌다&lt;/h2&gt;
&lt;p&gt;Java 21+ 의 Virtual Thread (JEP 444) 는 blocking 코드를 &lt;strong&gt;저비용으로&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;java&quot;&gt;&lt;code&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 style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; executor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Executors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newVirtualThreadPerTaskExecutor&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;    executor.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// blocking call, 그러나 virtual thread 라 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r);&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;Virtual thread 는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스택을 힙에 저장 (KB 수준)&lt;/li&gt;
&lt;li&gt;block 되면 carrier thread 에서 unmount → carrier 는 다른 virtual thread 처리&lt;/li&gt;
&lt;li&gt;결과 도착 시 carrier 에 다시 mount&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;수백만 동시 연결을 blocking sync 코드로 처리 가능. 사실상 “blocking 의 비용” 을 거의 0 으로 만든다.&lt;/p&gt;
&lt;h2 id=&quot;blocking-의-장단점-요약&quot;&gt;blocking 의 장단점 요약&lt;/h2&gt;
&lt;h3 id=&quot;장점&quot;&gt;장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;코드가 직관적, 위→아래&lt;/li&gt;
&lt;li&gt;디버깅, stack trace 일관&lt;/li&gt;
&lt;li&gt;에러 처리 자연스러움&lt;/li&gt;
&lt;li&gt;기존 코드 재사용 쉬움 (대부분의 JDK / 라이브러리가 blocking)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;단점&quot;&gt;단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스레드 자원 소모 (platform thread 기준)&lt;/li&gt;
&lt;li&gt;동시 요청 수 한계&lt;/li&gt;
&lt;li&gt;UI 스레드를 block 하면 응답성 손실&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;언제-blocking-이-적절한가&quot;&gt;언제 blocking 이 적절한가&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;순차 흐름이 본질&lt;/strong&gt; (DB 트랜잭션 안 여러 쿼리 순차)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU 바운드 작업&lt;/strong&gt; (계산 위주, 어차피 스레드가 일함)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동시 요청 수가 적당&lt;/strong&gt; (수백 ~ 수천)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual Thread 사용 환경&lt;/strong&gt; (Java 21+)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단순함이 우선&lt;/strong&gt; (스크립트, 마이그레이션, 배치)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;언제-non-blocking-이-더-나은가&quot;&gt;언제 [[Non-Blocking]] 이 더 나은가&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;수만 ~ 수십만 동시 연결&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O 가 매우 느리고 가변적&lt;/strong&gt; (외부 API 등)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;백프레셔 제어 필요&lt;/strong&gt; (스트림 처리)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;함수형 / reactive 패러다임&lt;/strong&gt; 채택&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-blocking--threadpoolexecutor-풀-고갈&quot;&gt;1. blocking + ThreadPoolExecutor 풀 고갈&lt;/h3&gt;
&lt;p&gt;스레드 풀 안에서 blocking I/O 를 하면 모든 스레드가 block 되는 순간 새 요청은 큐에 쌓인다. 풀 크기를 I/O latency 기준으로 계산하지 않으면 타임아웃과 OOM 로 이어진다.&lt;/p&gt;
&lt;p&gt;Little’s Law: &lt;code&gt;N = λ × W&lt;/code&gt; (평균 동시 스레드 = 초당 요청 × 평균 응답 시간). I/O 가 느리면 N 이 커진다.&lt;/p&gt;
&lt;h3 id=&quot;2-synchronized-블록-내부에서-blocking-io&quot;&gt;2. synchronized 블록 내부에서 blocking I/O&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:#F97583&quot;&gt;synchronized&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (lock) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    String data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;readLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// block 동안 lock 을 계속 보유&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;lock 을 보유한 채로 block 되면, 다른 스레드가 같은 lock 을 기다리며 연쇄 block 발생. 가능하면 I/O 완료 후 sync 블록에 진입.&lt;/p&gt;
&lt;h3 id=&quot;3-virtual-thread-와-synchronized-pinning&quot;&gt;3. Virtual Thread 와 synchronized pinning&lt;/h3&gt;
&lt;p&gt;Java 21 Virtual Thread 는 blocking call 시 carrier thread 에서 unmount 하지만, &lt;strong&gt;native synchronized 블록 안에서는 unmount 하지 못한다&lt;/strong&gt; (JDK 23 이전 기본값). 이를 “pinning” 이라 부르며, [[ReentrantLock]] 으로 교체하면 해소된다.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ✗ Virtual Thread 에서 pinning 발생&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;synchronized&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&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:#B392F0&quot;&gt;    blockingCall&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:#6A737D&quot;&gt;// ✓ Virtual Thread 친화&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lock.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lock&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 style=&quot;color:#B392F0&quot;&gt;blockingCall&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;finally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { lock.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unlock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-interruptedexception-무시&quot;&gt;4. InterruptedException 무시&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:#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;    q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;take&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:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (InterruptedException &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&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:#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;// ✓ 복원 필수&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;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (InterruptedException &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&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;    Thread.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;currentThread&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;interrupt&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;;&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;blocking 메서드는 대부분 &lt;code&gt;InterruptedException&lt;/code&gt; 을 던진다. 무시하면 스레드 종료 신호가 사라져 graceful shutdown 이 불가능해진다.&lt;/p&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Non-Blocking]]&lt;/li&gt;
&lt;li&gt;[[Synchronous]]&lt;/li&gt;
&lt;li&gt;[[Asynchronous]]&lt;/li&gt;
&lt;li&gt;[[ReentrantLock]]&lt;/li&gt;
&lt;li&gt;[[volatile]]&lt;/li&gt;
&lt;li&gt;[[비동기와 타이밍, 콜백부터 async/await까지의 발전사]]&lt;/li&gt;
&lt;li&gt;Brian Goetz, &lt;em&gt;Java Concurrency in Practice&lt;/em&gt;, §5 Building Blocks&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>concurrency</category><category>programming-model</category><category>blocking-nonblocking</category><category>thread</category><author>koa (김신건)</author></item><item><title>Non-Blocking (논블로킹)</title><link>https://shinkeonkim.com/wiki/concurrency/non-blocking/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/non-blocking/</guid><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Non-Blocking&lt;/strong&gt; 은 호출이 &lt;strong&gt;결과를 기다리지 않고 즉시 반환&lt;/strong&gt; 하는 실행 방식. 결과가 준비되지 않았다면 “아직 안 됐다” 라는 신호 (&lt;code&gt;null&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;-1&lt;/code&gt;, &lt;code&gt;Optional.empty()&lt;/code&gt; 등) 를 반환하고, 호출 스레드는 다른 일을 계속 한다.&lt;/p&gt;
&lt;p&gt;[[Blocking]] 의 반대 개념. [[Asynchronous]] 와 자주 같이 쓰이지만 별개 차원.&lt;/p&gt;
&lt;h2 id=&quot;두-가지-의미-io-와-동시성&quot;&gt;두 가지 의미, I/O 와 동시성&lt;/h2&gt;
&lt;p&gt;“Non-Blocking” 은 두 분야에서 미묘하게 다른 뜻으로 쓰인다.&lt;/p&gt;
&lt;h3 id=&quot;1-non-blocking-io&quot;&gt;1. Non-Blocking I/O&lt;/h3&gt;
&lt;p&gt;OS 의 I/O 호출 시, 데이터가 준비되지 않았으면 즉시 “준비 안 됨” 으로 반환.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Java NIO non-blocking mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;SocketChannel ch &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SocketChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&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;ch.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;configureBlocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);            &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ← non-blocking 모드&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;ByteBuffer buf &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ByteBuffer.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;allocate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1024&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;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ch.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf);                   &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;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&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:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&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;    // n 바이트 읽음&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;Selector&lt;/code&gt; 또는 epoll / kqueue 등의 OS 메커니즘과 결합해 &lt;strong&gt;한 스레드가 수만 소켓을 동시 처리&lt;/strong&gt; 가능.&lt;/p&gt;
&lt;h3 id=&quot;2-non-blocking-동시성-lock-free&quot;&gt;2. Non-Blocking 동시성 (lock-free)&lt;/h3&gt;
&lt;p&gt;스레드가 락을 기다리지 않고 진행하는 알고리즘. &lt;strong&gt;CAS (Compare-And-Swap)&lt;/strong&gt; 같은 atomic 연산으로 구현.&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AtomicInteger counter &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; AtomicInteger&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;counter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// CAS 루프, lock-free&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt;, &lt;code&gt;ConcurrentLinkedQueue&lt;/code&gt;, &lt;code&gt;AtomicReference&lt;/code&gt; 등이 이 카테고리.&lt;/p&gt;
&lt;p&gt;이 글에서는 두 의미를 다 다룬다.&lt;/p&gt;
&lt;h2 id=&quot;non-blocking-io-의-흐름&quot;&gt;Non-Blocking I/O 의 흐름&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;p&gt;Thread T 한 개가 &lt;code&gt;selector.select()&lt;/code&gt; 로 준비된 소켓 집합을 받아 순차적으로 read/process/write 한다. 각 소켓 작업은 즉시 반환되므로 한 소켓의 지연이 다른 소켓의 처리를 막지 않는다. 작업이 끝나면 다시 &lt;code&gt;select()&lt;/code&gt; 로 돌아가 다음 ready 이벤트를 기다린다.&lt;/p&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Thread T (1 개):&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;  │ for each ready socket:                       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   socket.read(buf)   // 즉시 반환             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   process(buf)       // CPU 작업              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   socket.write(...)  // 즉시 반환             │&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;        │ Selector.select() 가 준비된 소켓을 알려줌&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심: &lt;strong&gt;OS 가 어떤 소켓이 read/write 가능한지 알려주고&lt;/strong&gt;, 우리는 그 소켓에만 작업한다. block 되지 않는다.&lt;/p&gt;
&lt;h2 id=&quot;가장-단순한-non-blocking-io-코드&quot;&gt;가장 단순한 Non-Blocking I/O 코드&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Selector selector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Selector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&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;ServerSocketChannel server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ServerSocketChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&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;server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bind&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; InetSocketAddress&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&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;server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;configureBlocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&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;server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(selector, SelectionKey.OP_ACCEPT);&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;while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    selector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 이벤트 있을 때까지 대기 (이건 block 임)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Set&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SelectionKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; keys &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;selectedKeys&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; (SelectionKey key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; keys) {&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; (key.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isAcceptable&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;            SocketChannel client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// non-blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;configureBlocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&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;            client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(selector, SelectionKey.OP_READ);&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;else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (key.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isReadable&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;            SocketChannel client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (SocketChannel) key.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;channel&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;            client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// non-blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            // process buf&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;        keys.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key);&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;strong&gt;Netty, Vert.x, Node.js&lt;/strong&gt; 의 사상 기반.&lt;/p&gt;
&lt;h2 id=&quot;non-blocking-동시성-lock-free-예&quot;&gt;Non-Blocking 동시성 (lock-free) 예&lt;/h2&gt;
&lt;h3 id=&quot;cas-기반-atomic-연산&quot;&gt;CAS 기반 atomic 연산&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;AtomicInteger counter &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; AtomicInteger&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;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; incrementAndGet&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;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; prev, next;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    do&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;        prev &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; prev &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&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:#F97583&quot;&gt;while&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:#B392F0&quot;&gt;compareAndSet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(prev, next));   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// CAS 실패 시 재시도&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; next;&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;락을 잡지 않고 atomic 하게 증가. 실패하면 그냥 다시 시도.&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 스레드가 같은 값을 읽고 동시에 &lt;code&gt;CAS(prev, next)&lt;/code&gt; 를 시도하면 한 쪽만 성공한다. 실패한 쪽은 어떤 락도 걸지 않은 채 다시 &lt;code&gt;get()&lt;/code&gt; 부터 시작해 재시도한다. 이 “비관적 lock 없음 + 낙관적 retry” 가 non-blocking 동시성의 핵심.&lt;/p&gt;
&lt;h3 id=&quot;michael--scott-알고리즘-concurrentlinkedqueue&quot;&gt;Michael &amp;#x26; Scott 알고리즘 (ConcurrentLinkedQueue)&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ConcurrentLinkedQueue.offer(x):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  newNode = Node(x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  loop:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tail = currentTail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next = tail.next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (next == null) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // tail 의 next 를 newNode 로 CAS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if (CAS(tail.next, null, newNode)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        CAS(currentTail, tail, newNode)   // tail 도 이동 (best effort)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&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;    } else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      CAS(currentTail, tail, next)         // 다른 스레드가 추가 중, tail 따라잡기&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;p&gt;여러 스레드가 동시에 offer 해도 락 없이 일관성 유지. 다만 같은 자리에서 CAS 가 실패하면 재시도하므로 &lt;strong&gt;고경합 시 성능 저하&lt;/strong&gt; (livelock 까지 갈 수 있음).&lt;/p&gt;
&lt;h2 id=&quot;non-blocking-의-장점&quot;&gt;Non-Blocking 의 장점&lt;/h2&gt;
&lt;h3 id=&quot;자원-효율&quot;&gt;자원 효율&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스레드 1개로 수만 동시 연결&lt;/li&gt;
&lt;li&gt;메모리, context switch 비용 절감&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;응답성&quot;&gt;응답성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;빠른 요청이 느린 요청에 막히지 않음&lt;/li&gt;
&lt;li&gt;“fail-fast” 가능 (즉시 사용 불가 응답)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;백프레셔-자연스러움&quot;&gt;백프레셔 자연스러움&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;큐가 가득 차면 즉시 “꽉 참” 반환&lt;/li&gt;
&lt;li&gt;Reactive Streams 의 핵심 메커니즘&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;non-blocking-의-단점&quot;&gt;Non-Blocking 의 단점&lt;/h2&gt;
&lt;h3 id=&quot;코드-복잡도&quot;&gt;코드 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;상태 머신 같은 흐름, 직선형이 아님&lt;/li&gt;
&lt;li&gt;callback hell 의 원조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;디버깅-어려움&quot;&gt;디버깅 어려움&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;stack trace 가 callback 마다 끊김&lt;/li&gt;
&lt;li&gt;“어떤 요청이 이 callback 을 호출했나” 추적 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cpu-바인드-작업과-충돌&quot;&gt;CPU 바인드 작업과 충돌&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;non-blocking 스레드가 무거운 계산을 하면 이벤트 루프가 멈춤&lt;/li&gt;
&lt;li&gt;“이벤트 루프를 절대 블록하지 말라” 가 원칙&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;java-의-non-blocking-api&quot;&gt;Java 의 Non-Blocking API&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;영역&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;API&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;I/O&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;java.nio.channels.*&lt;/code&gt; (Channel + Selector + ByteBuffer)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;java.net.http.HttpClient&lt;/code&gt; (&lt;code&gt;sendAsync()&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Netty, Vert.x, Reactor Netty&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Atomic&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;java.util.concurrent.atomic.*&lt;/code&gt; (AtomicInteger 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Concurrent collections&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt;, &lt;code&gt;ConcurrentLinkedQueue&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Reactive&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Project Reactor, RxJava&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebFlux&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Spring 5+ Reactive web stack&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;non-blocking--asynchronous--진짜-비동기&quot;&gt;non-blocking + [[Asynchronous]] = 진짜 비동기&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HttpClient client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HttpClient.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newHttpClient&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;CompletableFuture&amp;#x3C;HttpResponse&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;&gt;&gt; f &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sendAsync&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,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BodyHandlers.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ofString&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:#E1E4E8&quot;&gt;f.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;thenApply&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(HttpResponse&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body)&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:#B392F0&quot;&gt;thenAccept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(System.out&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;println);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 호출자는 즉시 풀려나고, response 가 오면 callback 실행&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 모델로 단일 스레드가 수만 요청을 동시 처리 가능.&lt;/p&gt;
&lt;h2 id=&quot;non-blocking--synchronous-드뭄&quot;&gt;non-blocking + [[Synchronous]] (드뭄)&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;java&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:#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;queue.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isEmpty&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:#79B8FF&quot;&gt; false&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;    Thread.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onSpinWait&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &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:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Task t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; queue.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;poll&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;busy-waiting 또는 spin-wait. CPU 를 점유하므로 매우 짧은 대기에만 적절. &lt;code&gt;Atomic*&lt;/code&gt; 의 CAS 루프가 이 패턴.&lt;/p&gt;
&lt;h2 id=&quot;virtual-threads-와의-관계&quot;&gt;Virtual Threads 와의 관계&lt;/h2&gt;
&lt;p&gt;Java 21+ Virtual Thread (JEP 444) 는 흥미로운 입장. &lt;strong&gt;코드는 blocking 처럼 보이지만 underlying 은 non-blocking&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;java&quot;&gt;&lt;code&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 style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; executor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Executors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;newVirtualThreadPerTaskExecutor&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;    executor.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        String r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 코드는 blocking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // 실제로는 virtual thread 가 unmount 됨&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;이제 개발자는 non-blocking 의 복잡함 없이 그 효율을 누린다. 다만 java.io.* 의 일부는 여전히 native blocking (file I/O 일부) 이라 carrier thread 가 잡힐 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;자주-헷갈리는-표현-정리&quot;&gt;자주 헷갈리는 표현 정리&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;표현&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호출 스레드가 결과 올 때까지 멈춤&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;non-blocking&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;즉시 반환, 결과는 다른 방법으로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;synchronous&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호출자가 결과를 기다린다 (control flow)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;asynchronous&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호출자가 결과를 안 기다린다&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;lock-free&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;락 없이 동시성 안전 (CAS 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;wait-free&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모든 스레드가 finite step 안에 완료 (lock-free 의 강한 버전)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;특히 lock-free 와 wait-free 는 학술적 구분. 실무에서는 보통 “lock-free” 가 사용됨.&lt;/p&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Blocking]]&lt;/li&gt;
&lt;li&gt;[[Synchronous]]&lt;/li&gt;
&lt;li&gt;[[Asynchronous]]&lt;/li&gt;
&lt;li&gt;[[volatile]]&lt;/li&gt;
&lt;li&gt;[[CopyOnWriteArrayList]]&lt;/li&gt;
&lt;li&gt;[[비동기와 타이밍, 콜백부터 async/await까지의 발전사]]&lt;/li&gt;
&lt;li&gt;Maurice Herlihy &amp;#x26; Nir Shavit, &lt;em&gt;The Art of Multiprocessor Programming&lt;/em&gt;, §10-12&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>concurrency</category><category>programming-model</category><category>blocking-nonblocking</category><category>nio</category><category>lock-free</category><author>koa (김신건)</author></item><item><title>Stateless vs Stateful</title><link>https://shinkeonkim.com/wiki/concurrency/stateless-stateful/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/stateless-stateful/</guid><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Stateless (무상태)&lt;/strong&gt;: 서버가 클라이언트 요청 간 상태를 기억하지 않음. 각 요청은 완전히 독립적이며 필요한 모든 정보는 요청에 담겨 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stateful (상태유지)&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;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 애니메이션이 같은 인프라(Client + LB + 3 서버)에서 두 모드가 어떻게 다르게 동작하는지, 그리고 장애 시 어떤 차이가 나는지를 한눈에 보여준다.&lt;/p&gt;
&lt;h2 id=&quot;기본-비교&quot;&gt;기본 비교&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateless&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateful&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;서버 메모리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;요청 처리에만 필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;활성 세션마다 메모리&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;수평 확장&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매우 쉬움, 어느 서버든 OK&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;어려움, [[Sticky Session]] 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;장애 복원&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 교체 무영향&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세션 손실 (외부 store 시 완화)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;로드밸런서&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단순 round-robin&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;client → server 일관 라우팅 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;캐싱&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;URL 기반 캐시 효율적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캐시 효율 낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;인증 정보&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매 요청마다 보냄 (JWT, API key)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세션 ID 1회 → 서버가 검증&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;네트워크-계층의-stateless--stateful&quot;&gt;네트워크 계층의 stateless / stateful&lt;/h2&gt;
&lt;p&gt;각 프로토콜이 명세상 어떤 상태성을 가지는지.&lt;/p&gt;

































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;프로토콜&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;상태성&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;IP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;패킷 단위 라우팅, 연결 개념 없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[UDP]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;비연결&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[TCP]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;연결, sequence number, window 등 모두 상태&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[TLS]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateful&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세션 키, sequence number, certificate 검증 결과&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP/1.0&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매 요청마다 새 TCP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP/1.1 (keep-alive)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless (HTTP 의미)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TCP 자체는 stateful&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP/2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless (HTTP 의미)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TCP + HPACK 동적 테이블은 stateful&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP/3 ([[QUIC]])&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless (HTTP 의미)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QUIC connection 자체는 stateful&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지속 연결 + 컨텍스트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Server-Sent Events&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지속 연결, 단방향&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;P2P 세션, DTLS 키&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;“HTTP 는 stateless 다”&lt;/strong&gt; 라는 익숙한 말은 &lt;em&gt;HTTP 메시지 의미&lt;/em&gt; 차원의 진실이다. 하부의 TCP·TLS·HPACK 동적 테이블은 모두 stateful 이지만, &lt;strong&gt;응용 계층의 HTTP 가 세션 상태를 기억하지 않는다&lt;/strong&gt;는 게 핵심 의미.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;서버에서-상태를-어떻게-관리하나&quot;&gt;서버에서 상태를 어떻게 관리하나&lt;/h2&gt;
&lt;h3 id=&quot;stateless-서버의-구조&quot;&gt;Stateless 서버의 구조&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;  ↓&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;필요시 외부 store 조회 (DB, Redis)&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;모든 메모리 즉시 해제&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 프로세스가 가진 상태:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP 연결 풀 (네트워크 계층, 응용은 무관)&lt;/li&gt;
&lt;li&gt;캐시 (선택)&lt;/li&gt;
&lt;li&gt;설정 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;세션·사용자 데이터 같은 &lt;strong&gt;응용 상태는 0&lt;/strong&gt;. 어떤 인스턴스도 동일하게 동작한다.&lt;/p&gt;
&lt;h3 id=&quot;stateful-서버의-구조&quot;&gt;Stateful 서버의 구조&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;연결 수립 (WebSocket / SSE / 로그인)&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;  - user_id, permissions, last_active&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - WebSocket 연결 핸들&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;이후 요청·이벤트마다 세션 컨텍스트 조회 (메모리 lookup)&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 메모리에 활성 세션 수만큼의 객체.&lt;/p&gt;
&lt;h3 id=&quot;활성-세션-객체의-일반적-크기&quot;&gt;활성 세션 객체의 일반적 크기&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;종류&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;일반적 크기&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP 인메모리 세션&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1~5 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;user_id, role, 일부 캐시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket 세션 (raw &lt;code&gt;ws&lt;/code&gt;)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~3.5 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;frame buffer + state&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket 세션 (Socket.IO)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;del&gt;60&lt;/del&gt;90 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;룸, 어댑터, ACK 추적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SSE 세션&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~1.2 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP response stream&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC 세션&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;큼, 미디어 코덱 상태 포함&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 백 KB ~ MB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;(출처: Ark Protocol 2026, Wolf-tech 2026 측정값)&lt;/p&gt;
&lt;h3 id=&quot;stateful-서버의-수평-확장-전략&quot;&gt;Stateful 서버의 수평 확장 전략&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;[[Sticky Session]]&lt;/strong&gt;: LB 가 같은 클라이언트를 같은 서버로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;외부 세션 store&lt;/strong&gt;: Redis 등에 위임 → 사실상 Stateless 화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pub/Sub bus&lt;/strong&gt;: 서버 간 메시지 라우팅 (Socket.IO + Redis Adapter)&lt;/li&gt;
&lt;/ol&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;WebSocket 클러스터 + Redis Adapter:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Server A 가 Client X 보유&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Server B 가 Client Y 보유&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → Client X 에게 &apos;B 로 가야할 메시지&apos; 가 오면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Server A → Redis publish &quot;for-Y&quot; → Server B subscribe → Client Y 에게 전달&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;브라우저클라이언트에서-상태를-어떻게-관리하나&quot;&gt;브라우저(클라이언트)에서 상태를 어떻게 관리하나&lt;/h2&gt;
&lt;p&gt;서버가 stateless 면 누군가 상태를 보관해야 한다. 보통 클라이언트.&lt;/p&gt;
&lt;h3 id=&quot;1-cookies&quot;&gt;1. Cookies&lt;/h3&gt;
&lt;p&gt;서버가 &lt;code&gt;Set-Cookie&lt;/code&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:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 자동 첨부, HttpOnly 로 JS 접근 차단 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 매 요청에 헤더 추가 (페이로드 오버헤드), CSRF 위험&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-localstorage--sessionstorage&quot;&gt;2. LocalStorage / SessionStorage&lt;/h3&gt;
&lt;p&gt;JavaScript 가 명시적으로 읽고 쓰는 클라이언트 측 키-값 저장소.&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;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;localStorage.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setItem&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;token&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;abc123&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);  &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:#E1E4E8&quot;&gt;sessionStorage.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setItem&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;temp&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xyz&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 탭 닫으면 사라짐&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 5~10 MB 까지 저장, JS 자유 접근&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: XSS 에 취약, 자동 첨부 안 됨 (직접 헤더 추가 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-indexeddb&quot;&gt;3. IndexedDB&lt;/h3&gt;
&lt;p&gt;본격적인 클라이언트 DB. 큰 데이터, 인덱싱, 트랜잭션 지원.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;오프라인 PWA, 캐싱 라이브러리 (Apollo cache 등)에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-jwt-json-web-token&quot;&gt;4. JWT (JSON Web Token)&lt;/h3&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;header.payload.signature&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;eyJhbGciOiJI...  .eyJzdWIiOiI...  .Tjw7XYZ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 완벽한 stateless 서버, 어느 인스턴스도 같은 검증 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 토큰 즉시 취소 어려움 (블랙리스트 필요), 토큰 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-websocket-open-connection-자체&quot;&gt;5. WebSocket Open Connection 자체&lt;/h3&gt;
&lt;p&gt;WebSocket / SSE 가 열려 있는 동안은 &lt;strong&gt;TCP 연결 자체가 상태 컨텍스트&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연결 ID 가 곧 사용자 식별자&lt;/li&gt;
&lt;li&gt;끊기면 재인증 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;클라이언트-상태-보관-비교&quot;&gt;클라이언트 상태 보관 비교&lt;/h3&gt;






















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;자동 첨부&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;XSS 안전&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;만료 제어&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;크기&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Cookie (HttpOnly)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;서버 제어&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;~4KB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;LocalStorage&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;영구&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5-10MB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SessionStorage&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;탭 단위&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5-10MB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;IndexedDB&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗ (XSS 영향)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;영구&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;GB 단위&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;JWT in Cookie&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;exp 클레임&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;~1-2KB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;JWT in localStorage&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✗&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;exp 클레임&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;~1-2KB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&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;strong&gt;Stateless (JWT)&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;1. POST /login { user, pass }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Server: 검증 후 JWT 발급&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Response: { token: &quot;eyJ...&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Client: LocalStorage 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. 이후 요청마다: Authorization: Bearer eyJ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6. Server: 토큰 서명 검증만으로 사용자 식별, 메모리에 세션 X&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Stateful (Server Session)&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;1. POST /login { user, pass }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Server: 검증 후 메모리에 세션 생성, { sid: &quot;abc123&quot; → user_data }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Response: Set-Cookie: session=abc123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Client: 쿠키 자동 보관&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. 이후 요청마다: Cookie: session=abc123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6. Server: 메모리에서 sid 로 세션 조회, 서버에 데이터 있음&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;예시-2-채팅-두-가지-설계&quot;&gt;예시 2: 채팅, 두 가지 설계&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Stateful 만으로 (Socket.IO)&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;- Server 가 모든 활성 연결 보유&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- &quot;user-1 이 user-2 에게&quot; 메시지: 서버가 메모리에서 user-2 의 socket 찾아 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- 수평 확장 시: Redis Adapter 로 서버 간 라우팅&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Stateless + Stateful 혼합 (메시지 큐)&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;- Stateless 서버가 메시지 수신 → DB 저장 + 큐에 발행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- 별도 Push 서버 (Stateful) 가 활성 연결 보유 → 큐 구독 → 사용자에게 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- 메시지 영속화는 Stateless 쪽이 책임, 전달은 Stateful 쪽이 책임&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;예시-3-장바구니&quot;&gt;예시 3: 장바구니&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Stateless (LocalStorage)&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;const cart = JSON.parse(localStorage.getItem(&apos;cart&apos;) || &apos;[]&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cart.push({ productId: 42, qty: 1 });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;localStorage.setItem(&apos;cart&apos;, JSON.stringify(cart));&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;p&gt;&lt;strong&gt;Stateful (Server Session)&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;POST /cart/add { productId: 42, qty: 1 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 서버: 메모리의 사용자 세션에 cart 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 다른 디바이스에서 로그인해도 같은 cart 표시됨 (장점)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 비로그인 시 작동 안 함, sticky session 필요 (단점)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;stateless-vs-stateful-의-결정-트리&quot;&gt;“Stateless vs Stateful” 의 결정 트리&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;질문 1: 서버가 클라이언트 간 사이에 기억해야 할 데이터가 있나?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├─ NO  → Stateless (REST API)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └─ YES → 질문 2&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;질문 2: 그 데이터를 서버 메모리에 두는 게 본질적인가?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├─ NO  → Stateless + 외부 store (Redis, DB)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └─ YES → 질문 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;질문 3: 지속 연결(WebSocket 등) 이 본질적인가?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├─ NO  → 재고 (외부 store 로 더 단순해질 수 있음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └─ YES → Stateful 수용 + Sticky Session 또는 Pub/Sub bus&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
“Stateful 이 필요하다” 라는 판단은 신중해야 한다. &lt;strong&gt;외부 store + Stateless 의 조합이 거의 항상 가능&lt;/strong&gt;하고 운영이 단순하다. WebSocket 같은 지속 연결이 정말 본질적인 경우만 진짜 Stateful 을 받아들이는 게 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련&quot;&gt;관련&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm&quot;&gt;Roy Fielding, REST 박사 학위 논문&lt;/a&gt; (Stateless 가 REST 의 핵심 제약)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>architecture</category><category>network</category><category>scaling</category><author>koa (김신건)</author></item></channel></rss>