<?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>[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;관련-위키&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;[[Kafka Consumer Group]] (lag 기반 scaling)&lt;/li&gt;
&lt;li&gt;[[HTTP/2]] (flow control)&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] 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>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;Circuit Breaker&lt;/strong&gt; = 전기 회로의 차단기처럼, &lt;em&gt;백엔드 다운 / 느림 시 호출 자체를 차단&lt;/em&gt; → &lt;em&gt;빠른 실패 + 백엔드 회복 시간&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: 실패율 임계 초과&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Open --&gt; HalfOpen: 대기 시간 경과&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HalfOpen --&gt; Closed: 성공 N회&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HalfOpen --&gt; Open: 실패 1회&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 Circuit Breaker&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: ✓&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: 실패 누적 (3/10)&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: 실패율 50% &gt; 임계 → OPEN&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: ❌ fail-fast (호출 안함)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: 30초 대기 (Open)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over CB: Half-Open 으로 전환&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: 성공 → Closed&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;Failure rate&lt;/td&gt;&lt;td&gt;&lt;em&gt;실패율 &gt; X%&lt;/em&gt; (sliding window)&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;/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;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;라이브러리&quot;&gt;라이브러리&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;라이브러리&lt;/th&gt;&lt;th&gt;언어&lt;/th&gt;&lt;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;/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;기본&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&lt;/em&gt; (8 MB)&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;/tbody&gt;&lt;/table&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;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[하나 다운 = 자기 Pool 만 영향]&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;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;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;  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;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;임계가 &lt;em&gt;너무 민감&lt;/em&gt;&lt;/strong&gt; = 일시 jitter 로 false open. &lt;em&gt;짧은 window&lt;/em&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;/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;/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;[[Service Mesh]] (Envoy/Istio)&lt;/li&gt;
&lt;li&gt;[[api-gateway]]&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>[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>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;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;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;흔한-함정&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;/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] 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;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]] (sliding window)&lt;/li&gt;
&lt;li&gt;[[Redis Strings]] (token bucket)&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>[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>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;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;!-- 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;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;/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;/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>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;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;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>Synchronous (동기)</title><link>https://shinkeonkim.com/wiki/concurrency/synchronous/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/concurrency/synchronous/</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;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;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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[caller]                    [callee]&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;   │                         │ 작업 수행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   │ (waiting)              │&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;   │◄─────────── 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;   │&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;장점&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;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+ 의 &lt;strong&gt;Virtual Threads (JEP 444)&lt;/strong&gt; 가 이 한계를 깬다. 동기 코드 그대로 두면서도 수백만 동시 연결을 가능하게 만든다.&lt;/p&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;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;/ul&gt;</content:encoded><category>concurrency</category><category>programming-model</category><category>sync-async</category><author>koa (김신건)</author></item><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>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;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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Server 1 이 A 의 메시지 수신&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Server 1 → Redis publish &quot;channel-B&quot; with payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Server 2 가 &quot;channel-B&quot; 를 구독 중&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Redis → Server 2 에 메시지 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. Server 2 → B 에게 send&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;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;부하-특성&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;대안과-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;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;/ul&gt;</content:encoded><category>architecture</category><category>messaging</category><category>scaling</category><category>network</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>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;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;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;실무-예시&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;/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>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;/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;어떻게-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;실무-예시&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;함정과-한계&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;/ul&gt;</content:encoded><category>architecture</category><category>network</category><category>scaling</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><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>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;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;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;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;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;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;배포·롤링 업데이트 시 활성 세션을 안전하게 다른 서버로 옮기는 절차 필요.&lt;/p&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;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;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 완벽한 stateless
&lt;strong&gt;단점&lt;/strong&gt;: 토큰 만료/취소가 어려움, 토큰이 커짐&lt;/p&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;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;/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;</content:encoded><category>network</category><category>load-balancing</category><category>architecture</category><author>koa (김신건)</author></item></channel></rss>