<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그 - 위키 / database-internals</title><description>&apos;database-internals&apos; 카테고리 위키 페이지 최신 업데이트</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>[DB] Transaction Isolation Levels: 완벽 가이드</title><link>https://shinkeonkim.com/wiki/database-internals/transaction-isolation-levels/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/transaction-isolation-levels/</guid><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Transaction Isolation Level (트랜잭션 격리 수준)&lt;/strong&gt; = &lt;em&gt;동시에 실행되는 여러 트랜잭션이 서로의 변경을 어디까지 볼 수 있는지&lt;/em&gt; 규정하는 수준. ACID 의 &lt;em&gt;I&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;격리를 완전히 없애면 최대 처리량, 완전히 직렬화하면 최상의 정합성. DB 는 이 사이 &lt;em&gt;4단계 (ANSI SQL 표준)&lt;/em&gt; 를 제공한다. &lt;strong&gt;더 강한 격리 = 더 많은 이상 현상 방지 = 더 낮은 동시성.&lt;/strong&gt; 실무는 이 트레이드오프를 이해하고 상황에 맞게 고르는 문제다.&lt;/p&gt;
&lt;h2 id=&quot;이-문서의-흐름&quot;&gt;이 문서의 흐름&lt;/h2&gt;
&lt;p&gt;각 격리 수준을 낮은 것부터 하나씩 짚는다. 각 수준에서:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;이 수준은 무엇을 보장하는가&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하지만 여기서도 어떤 문제가 발생하는가&lt;/strong&gt; (애니메이션으로 시각화)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;그 문제의 해결법과 대안&lt;/strong&gt; (다음 수준으로 올릴지, 다른 기법으로 우회할지)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;격리-수준-매트릭스-전체-지도&quot;&gt;격리 수준 매트릭스 (전체 지도)&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;이상 현상&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;READ UNCOMMITTED&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;READ COMMITTED&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;REPEATABLE READ&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;SERIALIZABLE&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Dirty Read&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;✓ 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Non-Repeatable Read&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;✓ 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Phantom Read&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;✓ 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Lost Update&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;△ SI는 감지&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✓ 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Write Skew&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;✓ 방지&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;엔진별 차이&lt;/strong&gt;가 매우 크다. 특히 REPEATABLE READ 는 &lt;em&gt;같은 이름이라도 완전히 다른 것&lt;/em&gt;. 아래에서 자세히.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;level-1-read-uncommitted&quot;&gt;Level 1: READ UNCOMMITTED&lt;/h2&gt;
&lt;h3 id=&quot;이-수준의-정의&quot;&gt;이 수준의 정의&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;커밋되지 않은 값까지 다른 트랜잭션에 노출된다.&lt;/strong&gt; 어떤 격리도 없다시피 하는 상태.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ANSI 4단계 중 가장 낮음&lt;/li&gt;
&lt;li&gt;락을 거의 사용 안 함 → 최대 동시성&lt;/li&gt;
&lt;li&gt;그러나 정합성은 최하&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;발생하는-문제-dirty-read&quot;&gt;발생하는 문제: Dirty Read&lt;/h3&gt;
&lt;p&gt;트랜잭션 A 가 UPDATE 만 하고 아직 COMMIT 안 한 시점에 트랜잭션 B 가 그 값을 읽어 간다. A 가 ROLLBACK 하면 B 는 &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;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;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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 초기: balance = 500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UNCOMMITTED&lt;/span&gt;&lt;span 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;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &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:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#6A737D&quot;&gt;-- 아직 COMMIT 안 함&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;-- T2 (별도 세션)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#6A737D&quot;&gt;-- 결과: 1000  ← 미커밋 값 (dirty)!&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;-- T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ROLLBACK&lt;/span&gt;&lt;span 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;-- balance 는 다시 500 이 됨. T2 가 봤던 1000 은 유령.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 문제인가&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;T2 는 1000 을 보고 다음 판단 (송금 승인, 통계 계산 등) 을 내림&lt;/li&gt;
&lt;li&gt;실제 커밋된 적 없는 값에 기반한 결정 → 데이터 일관성 파괴&lt;/li&gt;
&lt;li&gt;ROLLBACK 이 잦다면 유령 데이터가 시스템 곳곳에 남음&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&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;격리 올림&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;READ COMMITTED 이상 사용&lt;/td&gt;&lt;td&gt;대부분의 실무 (권장)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MVCC 엔진 채택&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;PostgreSQL, MySQL, Oracle 등&lt;/td&gt;&lt;td&gt;&lt;em&gt;구조적으로&lt;/em&gt; dirty read 가 불가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;read-only 명시&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;SET TRANSACTION READ ONLY;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;대략적 통계, 리포트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;replica 분리&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Analytics 는 replica DB 로&lt;/td&gt;&lt;td&gt;최신성 덜 중요한 워크로드&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;PostgreSQL&lt;/strong&gt; 은 &lt;code&gt;READ UNCOMMITTED&lt;/code&gt; 를 지정해도 &lt;em&gt;내부적으로 READ COMMITTED 로 승격&lt;/em&gt;. MVCC 특성상 dirty read 가 원천 불가능. 즉 PostgreSQL 에서 이 수준을 “쓰는” 것은 명목상만 의미 있음.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;READ UNCOMMITTED 를 실제 쓰는 경우가 있나?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL Server 의 &lt;code&gt;WITH (NOLOCK)&lt;/code&gt; hint 로 유사 효과. 대시보드 스냅샷 등에 종종 쓰이지만 &lt;em&gt;공식적으로 지양&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Analytics/OLAP 워크로드는 대개 replica 로 분리하는 편이 낫다.&lt;/li&gt;
&lt;li&gt;결국 &lt;em&gt;거의 안 씀&lt;/em&gt;. 실무에서는 이 수준을 이해하되 실제 사용은 피한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;level-2-read-committed&quot;&gt;Level 2: READ COMMITTED&lt;/h2&gt;
&lt;h3 id=&quot;이-수준의-정의-1&quot;&gt;이 수준의 정의&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;커밋된 값만 읽는다.&lt;/strong&gt; Dirty Read 는 완전히 방지.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL, Oracle, SQL Server 의 &lt;em&gt;기본값&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;MVCC 엔진에서는 &lt;em&gt;문장 (statement) 마다&lt;/em&gt; 새 스냅샷을 잡음&lt;/li&gt;
&lt;li&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL 기본&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 이 시점의 committed 값만 봄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;발생하는-문제-non-repeatable-read&quot;&gt;발생하는 문제: Non-Repeatable Read&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;같은 트랜잭션 안에서 같은 SELECT 를 두 번 실행했는데 결과가 다르다.&lt;/strong&gt; 두 SELECT 사이에 다른 tx 가 UPDATE + COMMIT 하면 발생.&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;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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 초기: balance = 500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; COMMITTED&lt;/span&gt;&lt;span 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;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 500&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;-- T2 (별도 세션): UPDATE + COMMIT 완료&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;-- T1 (계속)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 1000  ← 같은 tx인데 다름!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 문제인가&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 안에서 계산에 쓴 값이 중간에 바뀜&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;총액 = 첫 SELECT&lt;/code&gt;, &lt;code&gt;수수료 = 두번째 SELECT * 0.1&lt;/code&gt; 조합에서 총액과 수수료 비율이 불일치&lt;/li&gt;
&lt;li&gt;리포트 생성 중 값이 흔들리면 통계가 왜곡됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;해결법--대안-1&quot;&gt;해결법 / 대안&lt;/h3&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;격리 올림&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;REPEATABLE READ 이상&lt;/td&gt;&lt;td&gt;같은 값 보장, 성능 약간 손해&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;명시적 락&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; (row lock)&lt;/td&gt;&lt;td&gt;정확, 대기 시간 발생&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;원자 연산&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;UPDATE ... SET x = x - 100&lt;/code&gt;&lt;/td&gt;&lt;td&gt;애초에 read-modify-write 안 함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;애플리케이션 캐싱&lt;/strong&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;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 옵션 1: REPEATABLE READ 로 올림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPEATABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&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;-- 옵션 2: FOR UPDATE 로 row 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span 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;-- 이후 tx 끝까지 다른 tx의 UPDATE는 대기&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;-- 옵션 3: 원자 UPDATE 로 변경&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#6A737D&quot;&gt;-- read-modify-write 자체를 피함&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;strong&gt;일반 CRUD 웹 서비스는 READ COMMITTED 로 충분.&lt;/strong&gt; Non-Repeatable Read 가 실제 문제가 되는 상황은 리포트, 잔액 계산 등 &lt;em&gt;같은 값을 여러 번 참조하는 로직&lt;/em&gt;. 그런 부분만 골라 REPEATABLE READ 나 FOR UPDATE 를 붙이는 편이 낫다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;level-3-repeatable-read&quot;&gt;Level 3: REPEATABLE READ&lt;/h2&gt;
&lt;h3 id=&quot;이-수준의-정의-2&quot;&gt;이 수준의 정의&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;같은 트랜잭션 안에서 같은 row 는 항상 같은 값.&lt;/strong&gt; ANSI 정의로는 Non-Repeatable Read 만 방지하고 Phantom Read 는 허용. 하지만 &lt;em&gt;실제 엔진마다 다르다&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL InnoDB 의 &lt;em&gt;기본값&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;PostgreSQL 은 명시적으로 지정 시 사용&lt;/li&gt;
&lt;li&gt;MVCC 엔진에서는 &lt;em&gt;트랜잭션 시작 시점의 스냅샷&lt;/em&gt;을 tx 끝까지 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;엔진별 실제 구현&lt;/strong&gt;:&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;엔진&lt;/th&gt;&lt;th&gt;구현 방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Non-Rep&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Phantom&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Lost Update&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Write Skew&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MySQL InnoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;2PL + next-key lock (범위 잠금)&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;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Snapshot Isolation (MVCC)&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;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Oracle&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;미지원 (READ COMMITTED / SERIALIZABLE 만)&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;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;2PL&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;✗&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;같은 REPEATABLE READ 라도 엔진마다 완전히 다르다.&lt;/strong&gt; MySQL RR 은 사실상 SERIALIZABLE 에 가까운 락 사용, PostgreSQL RR 은 Snapshot Isolation (락 없이 스냅샷). 이름 하나로 여러 개념을 뭉뚱그린 대표적 사례.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;발생하는-문제-phantom-read-ansi-정의상&quot;&gt;발생하는 문제: Phantom Read (ANSI 정의상)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;같은 WHERE 조건을 두 번 실행했는데 row 개수가 다르다.&lt;/strong&gt; UPDATE 가 아니라 &lt;em&gt;INSERT/DELETE&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;p&gt;&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 초기: orders 에 amount &gt; 100 인 row 3개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPEATABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span 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;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; amount &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;;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T2 (별도 세션)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders (id, amount) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;999&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1 (계속)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; amount &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;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ANSI 정의상: 4 (phantom!)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- MySQL InnoDB: 3 (next-key lock으로 방지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL: 3 (snapshot으로 방지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 문제인가&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WHERE 범위에 해당하는 row 개수를 두 번 재는 로직에서 결과가 달라짐&lt;/li&gt;
&lt;li&gt;예: 재고 검색 → 예약 처리 → 재확인 시 새 항목이 튀어나옴&lt;/li&gt;
&lt;li&gt;리포트 통계, 배치 처리에서 count/sum 이 흔들림&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;해결법--대안-2&quot;&gt;해결법 / 대안&lt;/h3&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;MySQL InnoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;REPEATABLE READ (기본)&lt;/td&gt;&lt;td&gt;&lt;em&gt;이미 방지됨&lt;/em&gt; (next-key lock). 별도 조치 불필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;REPEATABLE READ (SI)&lt;/td&gt;&lt;td&gt;&lt;em&gt;이미 방지됨&lt;/em&gt; (snapshot). 스냅샷 시점 이후 insert 는 안 보임&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Oracle / SQL Server (2PL)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;REPEATABLE READ&lt;/td&gt;&lt;td&gt;SERIALIZABLE 로 올리거나 &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; 로 범위 잠금&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;모든 엔진&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;안전 확실 필요&lt;/td&gt;&lt;td&gt;SERIALIZABLE 또는 predicate lock&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;MySQL 의 next-key lock 이란?&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;[gap lock][row lock][gap lock][row lock][gap lock]...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WHERE 조건에 매칭되는 인덱스 범위 전체에 lock. 새 row 가 그 범위에 INSERT 되지 못하게 막아 phantom 방지. 강력하지만 &lt;em&gt;예상치 못한 deadlock&lt;/em&gt; 이 잦음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL 의 SI 는?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션 시작 시점의 스냅샷만 계속 봄. T2 가 나중에 INSERT + COMMIT 해도 T1 의 스냅샷에는 없으므로 count 가 그대로.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;PostgreSQL REPEATABLE READ 는 Snapshot Isolation 이지 진짜 Serializable 이 아니다.&lt;/strong&gt; 아래 Write Skew 절에서 다룰 함정이 있다. 정합성이 극단적으로 중요하면 SERIALIZABLE 로 올려야 함.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;level-4-serializable&quot;&gt;Level 4: SERIALIZABLE&lt;/h2&gt;
&lt;h3 id=&quot;이-수준의-정의-3&quot;&gt;이 수준의 정의&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;개념적으로 트랜잭션이 하나씩 직렬 실행된 것과 같은 결과.&lt;/strong&gt; 모든 이상 현상을 방지.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가장 강한 격리 수준&lt;/li&gt;
&lt;li&gt;성능 대가 큼 (엔진마다 다름)&lt;/li&gt;
&lt;li&gt;진짜 필요한 경우에만 (금융, 재고, 티켓팅 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;엔진별 구현 방식&lt;/strong&gt;:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;엔진&lt;/th&gt;&lt;th&gt;구현&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;PostgreSQL 9.1+&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;SSI (Serializable Snapshot Isolation)&lt;/td&gt;&lt;td&gt;&lt;em&gt;성능 손해 적음&lt;/em&gt;, 충돌 시 abort → retry 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MySQL InnoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Strict 2PL (모든 SELECT 이 shared lock 획득)&lt;/td&gt;&lt;td&gt;대기 많음, deadlock 위험&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;2PL (SERIALIZABLE 힌트)&lt;/td&gt;&lt;td&gt;MySQL 유사&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;SSI 변형&lt;/td&gt;&lt;td&gt;기본이 SERIALIZABLE&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;serializable-을-쓸-때-알아야-할-것&quot;&gt;SERIALIZABLE 을 쓸 때 알아야 할 것&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;abort → retry 패턴 필수&lt;/strong&gt; (특히 PostgreSQL SSI):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; psycopg2&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;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    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:#F97583&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conn.cursor() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            cur.execute(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BEGIN ISOLATION LEVEL SERIALIZABLE&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:#E1E4E8&quot;&gt;            cur.execute(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;COMMIT&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;        break&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; psycopg2.errors.SerializationFailure:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        conn.rollback()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.01&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; attempt))  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 지수 backoff&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; attempt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            raise&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 abort 가 필요한가&lt;/strong&gt;: SSI 는 각 tx 가 자유롭게 진행하다가 커밋 직전에 &lt;em&gt;충돌 감지&lt;/em&gt;. 감지되면 한쪽을 강제 abort (SQLSTATE 40001). 애플리케이션은 이를 잡아 재시도해야 함.&lt;/p&gt;
&lt;h3 id=&quot;성능-트레이드오프&quot;&gt;성능 트레이드오프&lt;/h3&gt;
&lt;p&gt;일반 워크로드에서 READ COMMITTED 대비:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL SSI&lt;/strong&gt;: 약 10-20% 느림&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2PL 기반 (MySQL, SQL Server)&lt;/strong&gt;: 최대 수 배 느림 (경합 심한 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
“무조건 SERIALIZABLE” 은 나쁜 선택. 대부분 상황은 &lt;em&gt;READ COMMITTED + 필요한 로직에 SELECT FOR UPDATE 또는 원자 UPDATE&lt;/em&gt; 조합이 최적. SERIALIZABLE 은 &lt;em&gt;진짜로 write skew 위험이 있는 로직&lt;/em&gt; 에만.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;ansi-표준을-벗어난-이상-현상들&quot;&gt;ANSI 표준을 벗어난 이상 현상들&lt;/h2&gt;
&lt;p&gt;ANSI SQL-92 표준의 4단계 매트릭스는 &lt;em&gt;3가지 이상 현상만&lt;/em&gt; 정의했다. Berenson et al. (1995) 논문이 이 결함을 지적하며 2가지를 추가했다.&lt;/p&gt;
&lt;h3 id=&quot;lost-update-읽고--계산--쓰기-경쟁&quot;&gt;Lost Update (읽고 → 계산 → 쓰기 경쟁)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;같은 데이터를 두 tx 가 read-modify-write 하는 사이, 한쪽의 UPDATE 가 사라진다.&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;&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 초기: count = 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 로컬 계산: 10 + 1 = 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&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:#79B8FF&quot;&gt; 11&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T2 (T1과 병렬)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 10 (T1 커밋 전에 SELECT)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 로컬 계산: 10 + 1 = 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&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:#79B8FF&quot;&gt; 11&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1이 쓴 값 위에 덮어씀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 결과: count = 11 (기대치는 12)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;어느 수준에서 발생하는가&lt;/strong&gt;: READ COMMITTED, REPEATABLE READ 모두 가능. PostgreSQL 의 SI (REPEATABLE READ) 는 first-writer-wins 로 감지해서 두번째 UPDATE 에서 에러를 냄. SERIALIZABLE 은 항상 방지.&lt;/p&gt;
&lt;p&gt;&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ① 원자 UPDATE (가장 단순, 항상 우선)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&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; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#6A737D&quot;&gt;-- 단일 UPDATE 문은 atomic. race condition 없음.&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;-- ② 낙관적 lock (version 컬럼)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&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; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; version&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&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;-- 0 row affected 면 race 발생 → 애플리케이션에서 재시도&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;-- ③ 비관적 lock (SELECT FOR UPDATE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- row 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; counters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&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; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ④ SERIALIZABLE 로 격리 올림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SERIALIZABLE&lt;/span&gt;&lt;span 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;-- 충돌 시 abort 재시도&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;-- ⑤ 별도 원자 저장소 (예: Redis INCR)&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;원자 UPDATE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;단순 counter/누계, 사용 가능하면 항상&lt;/td&gt;&lt;td&gt;복잡 로직 못 표현&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;낙관적 lock&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;비관적 lock&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;충돌 잦음&lt;/td&gt;&lt;td&gt;대기 시간 ↑, deadlock 위험&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SERIALIZABLE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;정확성 우선&lt;/td&gt;&lt;td&gt;abort 재시도 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Redis 등 별도 저장소&lt;/strong&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;h3 id=&quot;write-skew-snapshot-isolation-의-치명적-함정&quot;&gt;Write Skew (Snapshot Isolation 의 치명적 함정)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;각자의 스냅샷 안에서는 조건이 참이지만, 두 tx 의 결과를 합치면 불변량이 깨진다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lost Update 와 다른 점: Lost Update 는 &lt;em&gt;같은 row&lt;/em&gt; 를 두 tx 가 UPDATE 해서 하나가 사라짐. Write Skew 는 &lt;em&gt;다른 row&lt;/em&gt; 를 각자 UPDATE 해서 first-writer-wins 감지가 안 됨.&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;strong&gt;시나리오: 병원 온콜 스케줄&lt;/strong&gt; (위 애니메이션)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;규칙: 최소 1명은 항상 on-call&lt;/li&gt;
&lt;li&gt;현재 Alice, Bob 두 명이 on-call&lt;/li&gt;
&lt;li&gt;둘 다 아파서 동시에 off-call 요청&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 초기: on-call 2명 (alice, bob)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T1 (Alice의 세션)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPEATABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 2, 조건 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 성공&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- T2 (Bob의 세션, T1과 동시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPEATABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 2 (자기 스냅샷), 조건 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;bob&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 성공 (T1이 alice 를 건드렸을 뿐 bob 은 안 건드렸으므로)&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 0 !! 규칙 위반&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 SI 가 못 막나&lt;/strong&gt;: 두 tx 가 &lt;em&gt;다른 row&lt;/em&gt; 를 UPDATE 했으므로 write-write 충돌이 없음. SI 의 first-writer-wins 는 같은 row 에 대해서만 감지. 각자의 스냅샷 안에서는 count(on_call) &gt;= 1 조건이 참이었지만, 실제로는 결합되어 위반됨.&lt;/p&gt;
&lt;p&gt;&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ① SERIALIZABLE (PostgreSQL SSI는 이를 자동 감지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SERIALIZABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 동시 T2 시도 시 ERROR: could not serialize access due to read/write dependencies&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ② SELECT ... FOR UPDATE 로 predicate 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span 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;-- 이제 다른 tx는 on_call=true 인 row 를 UPDATE 할 수 없음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ③ materializing the conflict (강제 write-write 충돌 만들기)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; on_call_lock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (locked &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BOOLEAN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call_lock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (true);&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;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call_lock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; locked &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 모든 tx가 이 row 를 UPDATE → 충돌 감지 가능&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&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 style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; true;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; doctors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; false &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Write Skew 는 &lt;em&gt;ANSI 표준에 없다&lt;/em&gt;. Berenson et al. 이 SI 의 함정을 지적하며 도입한 개념. PostgreSQL SSI (9.1+) 가 이를 자동 감지하는 유일한 격리 수준. MySQL SERIALIZABLE 은 strict 2PL 로 결과적으로 방지하지만 방법이 다름.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;postgresql-ssi-serializable-snapshot-isolation&quot;&gt;PostgreSQL SSI (Serializable Snapshot Isolation)&lt;/h2&gt;
&lt;p&gt;PostgreSQL 9.1 부터 도입된 SSI 는 &lt;em&gt;Snapshot Isolation 위에 predicate lock 추적을 얹어&lt;/em&gt; 진정한 serializability 를 달성한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T1[T1 시작 스냅샷] --&gt; Read1[T1 SELECT / UPDATE]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T2[T2 시작 스냅샷] --&gt; Read2[T2 SELECT / UPDATE]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read1 --&gt; Track[predicate + row 접근 기록]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read2 --&gt; Track&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Track --&gt; Commit{둘 commit 시도}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Commit --&gt; Cycle{의존성 순환 감지?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cycle --&gt;|예| Abort[한쪽 abort&amp;#x3C;br/&gt;SQLSTATE 40001]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cycle --&gt;|아니오| OK[둘 다 commit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Abort --&gt; Retry[애플리케이션 재시도]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;핵심 아이디어&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;각 tx 는 자기 스냅샷 위에서 자유롭게 실행 (락 최소)&lt;/li&gt;
&lt;li&gt;백그라운드에서 &lt;em&gt;predicate lock&lt;/em&gt; 추적 (T1 이 이 범위를 읽었다, T2 는 이 row 를 썼다)&lt;/li&gt;
&lt;li&gt;커밋 시점에 &lt;em&gt;rw-dependency&lt;/em&gt; 순환 있으면 한쪽 abort&lt;/li&gt;
&lt;li&gt;애플리케이션은 40001 잡아서 재시도&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: throughput 손해 적음 (2PL 대비 큰 차이), 정확성 완벽.
&lt;strong&gt;단점&lt;/strong&gt;: 재시도 코드 필수. false positive 가능하지만 정확성에는 영향 없음.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvcc-기반-스냅샷-격리의-원리&quot;&gt;MVCC 기반 스냅샷 격리의 원리&lt;/h2&gt;
&lt;p&gt;각 트랜잭션은 시작 시점의 &lt;em&gt;스냅샷&lt;/em&gt; 을 잡고, 그 시점 이후의 변경은 자기에게 보이지 않는다.&lt;/p&gt;
&lt;p&gt;PostgreSQL 은 각 row (tuple) 에 hidden column &lt;code&gt;xmin&lt;/code&gt;, &lt;code&gt;xmax&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;row 1: (id=1, name=&apos;Alice&apos;, xmin=100, xmax=0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        → T200 이 UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;row 1: (id=1, name=&apos;Alice&apos;, xmin=100, xmax=200)  ← 옛 버전&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;row 1: (id=1, name=&apos;Bob&apos;,   xmin=200, xmax=0)    ← 새 버전&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;트랜잭션 T150 은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;xmin ≤ 150 이고 (xmax = 0 or xmax &gt; 150) 인 tuple 만 봄&lt;/li&gt;
&lt;li&gt;즉, “내 스냅샷 시점에 살아 있던” 버전만 봄&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자세한 구현은 [[mvcc]] 참조.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실무-가이드&quot;&gt;실무 가이드&lt;/h2&gt;
&lt;h3 id=&quot;언제-어느-수준을&quot;&gt;언제 어느 수준을?&lt;/h3&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;일반 CRUD, 짧은 tx&lt;/td&gt;&lt;td&gt;&lt;strong&gt;READ COMMITTED&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;성능 좋음, dirty read 만 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;배치 리포트 (일관 스냅샷)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;같은 값 여러 번 사용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;잔액 계산, 재고 확인&lt;/td&gt;&lt;td&gt;&lt;strong&gt;REPEATABLE READ + FOR UPDATE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;phantom 안전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;금융 거래, 티켓 발권&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SERIALIZABLE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;write skew 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Analytics (최신성 덜 중요)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;READ COMMITTED READ ONLY&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;최대 동시성&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 세션 전체&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SESSION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SERIALIZABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 트랜잭션 하나만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPEATABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; READ&lt;/span&gt;&lt;span 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;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL: 시작과 동시에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SERIALIZABLE&lt;/span&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;-- 프로그램 레벨 (JDBC 예시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;connection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;setTransactionIsolation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Connection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TRANSACTION_SERIALIZABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;프레임워크-예시&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Django&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; transaction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; connection&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;@transaction.atomic&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; transfer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(from_id, to_id, amount):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; connection.cursor() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cursor:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        cursor.execute(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SET TRANSACTION ISOLATION LEVEL SERIALIZABLE&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;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Spring&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Transactional&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isolation&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Isolation.SERIALIZABLE)&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; transfer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Long from, Long to, BigDecimal amount) { ... }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 프레임워크별 사용은 [[django-transactions]], [[spring-transaction]] 참조.&lt;/p&gt;
&lt;h2 id=&quot;실제-db-엔진-매핑&quot;&gt;실제 DB 엔진 매핑&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;SERIALIZABLE 구현&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;PostgreSQL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;READ COMMITTED&lt;/td&gt;&lt;td&gt;SSI (Serializable Snapshot Isolation)&lt;/td&gt;&lt;td&gt;RR = Snapshot Isolation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MySQL InnoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;REPEATABLE READ&lt;/td&gt;&lt;td&gt;Strict 2PL&lt;/td&gt;&lt;td&gt;RR 에서 next-key lock 으로 phantom 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Oracle&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;READ COMMITTED&lt;/td&gt;&lt;td&gt;SI 유사 (완전 SS 아님)&lt;/td&gt;&lt;td&gt;RR 미지원&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;READ COMMITTED&lt;/td&gt;&lt;td&gt;2PL&lt;/td&gt;&lt;td&gt;RCSI 옵션 (MVCC), Snapshot 옵션 별개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;SERIALIZABLE&lt;/td&gt;&lt;td&gt;SSI 변형&lt;/td&gt;&lt;td&gt;기본이 SS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;(트랜잭션은 SS)&lt;/td&gt;&lt;td&gt;SI&lt;/td&gt;&lt;td&gt;단일 문서는 원자적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;(item 원자성)&lt;/td&gt;&lt;td&gt;TransactWriteItems (SSI 유사)&lt;/td&gt;&lt;td&gt;트랜잭션 API 사용 시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cassandra&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;(트랜잭션 미지원)&lt;/td&gt;&lt;td&gt;LWT (Lightweight Transactions)&lt;/td&gt;&lt;td&gt;Paxos 기반 우회&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“REPEATABLE READ 라니 안전하겠지”&lt;/strong&gt; = MySQL 과 PostgreSQL 의 RR 은 완전히 다른 것. Write Skew 는 어느 쪽에서도 발생.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SERIALIZABLE 만 박고 abort 처리 없음&lt;/strong&gt; = 부하 시 40001 폭증하여 요청 실패. &lt;em&gt;재시도 코드 필수&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;READ COMMITTED 로 잔액 계산&lt;/strong&gt; = tx 안에서도 잔액이 바뀔 수 있음. 은행 앱에서 문제.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL RR + gap lock deadlock&lt;/strong&gt; = 의도치 않은 범위 잠금으로 데드락 폭증. &lt;code&gt;SHOW ENGINE INNODB STATUS\G&lt;/code&gt; 로 진단.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;낙관적 lock 없이 read-modify-write&lt;/strong&gt; = 트래픽 증가 시 lost update 폭증.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SELECT FOR UPDATE&lt;/code&gt; 를 잔뜩 걸음&lt;/strong&gt; = 대기 시간 폭증 + deadlock. 필요한 최소 범위만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;긴 트랜잭션 (long-running tx)&lt;/strong&gt; = VACUUM 방해, snapshot 유지 비용, lock 대기 폭증. 5분 넘으면 검토.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;격리 수준을 애플리케이션 코드에서 매번 바꿈&lt;/strong&gt; = 세션 풀에서 오염 (다음 요청이 이전 설정 상속). 명시적 리셋 필요.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;anti-pattern-lock-으로-모든-것-해결-시도&quot;&gt;Anti-Pattern: Lock 으로 모든 것 해결 시도&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ✗ 나쁜 예: 모든 SELECT 에 FOR UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; products &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;span 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;-- → deadlock 폭증, 대기 시간 ↑&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;개선&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;필요한 곳만 SELECT … FOR UPDATE (충돌 실제 발생하는 row 만)&lt;/li&gt;
&lt;li&gt;순서 일관성 (항상 users → orders → products 순으로 lock)&lt;/li&gt;
&lt;li&gt;SSI 로 전환 (retry 로 처리)&lt;/li&gt;
&lt;li&gt;원자 UPDATE 가능하면 그것으로&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[mvcc]], MVCC 구현 상세, xmin/xmax, snapshot&lt;/li&gt;
&lt;li&gt;[[postgresql]], PostgreSQL 프로세스 모델 + MVCC&lt;/li&gt;
&lt;li&gt;[[mysql-innodb]], InnoDB 락 시스템 (record, gap, next-key)&lt;/li&gt;
&lt;li&gt;[[sql-dcl-tcl]], TCL 명령 (BEGIN, COMMIT, SAVEPOINT)&lt;/li&gt;
&lt;li&gt;[[wal-write-ahead-log]], 크래시 복구와 durability&lt;/li&gt;
&lt;li&gt;[[spring-transaction]], Spring &lt;code&gt;@Transactional&lt;/code&gt; 사용법&lt;/li&gt;
&lt;li&gt;[[django-transactions]], Django 트랜잭션 관리&lt;/li&gt;
&lt;li&gt;[[distributed-systems-distributed-transaction]], 분산 트랜잭션 (2PC, Saga)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>transaction</category><category>isolation</category><category>database</category><category>acid</category><category>mvcc</category><category>postgresql</category><category>mysql</category><author>koa (김신건)</author></item><item><title>N+1 쿼리 문제</title><link>https://shinkeonkim.com/wiki/database-internals/n-plus-one-problem/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/n-plus-one-problem/</guid><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;N+1 쿼리 문제&lt;/strong&gt; = ORM 이 부모 entity N 개를 조회한 뒤, &lt;strong&gt;각 부모의 연관 entity 를 하나씩 로드&lt;/strong&gt;하면서 추가 SQL 이 N 번 발생하는 성능 안티패턴.&lt;/p&gt;
&lt;p&gt;이름의 의미:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1&lt;/strong&gt;: 부모 목록 SELECT (1번)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N&lt;/strong&gt;: 각 부모마다 연관 SELECT (N번)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;총 N+1 쿼리&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQL 한 번에 끝낼 작업을 부모 개수만큼 반복 발생시키므로, 데이터가 커질수록 응답시간이 선형이 아니라 &lt;strong&gt;쿼리 왕복 (round-trip) 만큼 폭발&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;ORM (Hibernate/JPA, Active Record, Django ORM, Sequelize, TypeORM 등) 공통 문제. 원인은 대부분 &lt;strong&gt;lazy loading + 반복문&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;lazy loading + 반복문이 만드는 N+1 → JOIN 으로 1쿼리에 해결.&lt;/p&gt;
&lt;h2 id=&quot;왜-발생하나&quot;&gt;왜 발생하나&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:#6A737D&quot;&gt;// 1. Post 목록 조회 (SQL 1번)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;List&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; postRepository.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;findAll&lt;/span&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;// 2. 반복문에서 author 접근 (SQL N번)&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; (Post p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    System.out.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAuthor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 매번 lazy proxy 초기화 → SELECT&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;발생 SQL:&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;posts 100 개면 SQL &lt;strong&gt;101 번&lt;/strong&gt;. &lt;code&gt;p.getAuthor()&lt;/code&gt; 를 호출하는 순간 ORM 이 lazy proxy 를 초기화하려고 개별 SELECT 를 날리기 때문.&lt;/p&gt;
&lt;h3 id=&quot;눈에-안-보이는-위치에서도-발생&quot;&gt;눈에 안 보이는 위치에서도 발생&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;템플릿 렌더링: Thymeleaf, JSP 에서 &lt;code&gt;${post.author.name}&lt;/code&gt; 순회&lt;/li&gt;
&lt;li&gt;Jackson 직렬화: &lt;code&gt;@JsonManagedReference&lt;/code&gt; 순회 시 lazy 컬렉션 접근&lt;/li&gt;
&lt;li&gt;toString() / equals() / hashCode(): 연관 필드 사용하면 함정&lt;/li&gt;
&lt;li&gt;로깅: &lt;code&gt;log.info(&quot;posts: {}&quot;, posts)&lt;/code&gt; 도 컬렉션 전체 순회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;성능-영향-실제-수치-감각&quot;&gt;성능 영향 (실제 수치 감각)&lt;/h2&gt;
&lt;p&gt;DB 왕복 지연 (round-trip time, RTT):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;로컬 DB&lt;/strong&gt;: 0.1 ~ 1 ms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;같은 리전 클라우드&lt;/strong&gt;: 1 ~ 5 ms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다른 리전&lt;/strong&gt;: 20 ~ 100 ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Post 100 건 조회 + 각 author lazy load 라면:&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;환경&lt;/th&gt;&lt;th&gt;JOIN 1쿼리&lt;/th&gt;&lt;th&gt;N+1 (101쿼리)&lt;/th&gt;&lt;th&gt;차이&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;로컬 DB (1ms RTT)&lt;/td&gt;&lt;td&gt;~10 ms&lt;/td&gt;&lt;td&gt;~110 ms&lt;/td&gt;&lt;td&gt;11 배&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;같은 리전 (3ms RTT)&lt;/td&gt;&lt;td&gt;~15 ms&lt;/td&gt;&lt;td&gt;~350 ms&lt;/td&gt;&lt;td&gt;23 배&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;다른 리전 (50ms RTT)&lt;/td&gt;&lt;td&gt;~80 ms&lt;/td&gt;&lt;td&gt;~5,100 ms&lt;/td&gt;&lt;td&gt;63 배&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;트래픽이 낮은 개발 환경에서는 문제가 안 보이지만 운영에서 QPS 가 오르면 즉시 스레드 풀 고갈 / DB connection 고갈로 연결된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;응답시간뿐 아니라 DB 부하도 문제&lt;/strong&gt;. 100 배 늘어난 쿼리 수는 커넥션 풀 대기, 락 경합, statement cache 압박을 동반한다.&lt;/p&gt;
&lt;h2 id=&quot;감지-방법&quot;&gt;감지 방법&lt;/h2&gt;
&lt;h3 id=&quot;1-sql-로깅&quot;&gt;1. SQL 로깅&lt;/h3&gt;
&lt;p&gt;가장 기본. 개발 / 스테이징에서 SQL 로그 켜고 순회 로직 실행:&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;# Spring / JPA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spring&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  jpa&lt;/span&gt;&lt;span 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;    show-sql&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    properties&lt;/span&gt;&lt;span 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;      hibernate&lt;/span&gt;&lt;span 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;        format_sql&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;logging.level.org.hibernate.SQL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DEBUG&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;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Rails&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Logger&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;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;STDOUT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Django&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;LOGGING&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;loggers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;django.db.backends&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;DEBUG&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;handlers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;console&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 쿼리 패턴이 다른 파라미터로 반복되면 N+1 신호.&lt;/p&gt;
&lt;h3 id=&quot;2-자동-감지-도구&quot;&gt;2. 자동 감지 도구&lt;/h3&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;JPA / Hibernate&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/vladmihalcea/hypersistence-utils&quot;&gt;Hypersistence Utils&lt;/a&gt; &lt;code&gt;assertSelectCount()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rails&lt;/td&gt;&lt;td&gt;&lt;code&gt;bullet&lt;/code&gt; gem: 개발 환경 자동 알림 + CI 실패&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rails 6.1+&lt;/td&gt;&lt;td&gt;&lt;code&gt;strict_loading: true&lt;/code&gt; 옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Django&lt;/td&gt;&lt;td&gt;&lt;code&gt;django-debug-toolbar&lt;/code&gt; SQL 패널 + &lt;code&gt;django-silk&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Prisma&lt;/td&gt;&lt;td&gt;&lt;code&gt;prisma-query-count&lt;/code&gt; 유틸&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TypeORM&lt;/td&gt;&lt;td&gt;&lt;code&gt;logger: &apos;advanced-console&apos;&lt;/code&gt; + 수동 추적&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;3-통합-테스트에서-쿼리-수-assert&quot;&gt;3. 통합 테스트에서 쿼리 수 assert&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;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Spring 예시 (Hypersistence Utils)&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;Test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; findAllWithAuthor_uses_single_query&lt;/span&gt;&lt;span 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;    var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; assertion &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SQLStatementCountValidator.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;reset&lt;/span&gt;&lt;span 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;    postService.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAllPostsWithAuthor&lt;/span&gt;&lt;span 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;    SQLStatementCountValidator.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;assertSelectCount&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&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;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Rails / RSpec (with rspec-sqlimit)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;no N+1&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  expect { get &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/posts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; make_database_queries&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;count:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Django&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; test_list_no_n_plus_one&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    with&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.assertNumQueries(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.client.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/posts/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;리팩터링 시 N+1 재발을 막는 안전망.&lt;/p&gt;
&lt;h2 id=&quot;해결-전략-3가지&quot;&gt;해결 전략 3가지&lt;/h2&gt;
&lt;h3 id=&quot;전략-1-join-한-쿼리에-다-가져오기&quot;&gt;전략 1: JOIN (한 쿼리에 다 가져오기)&lt;/h3&gt;
&lt;p&gt;부모와 자식을 &lt;strong&gt;한 번의 SQL JOIN&lt;/strong&gt; 으로 조회. 왕복이 1번.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p.&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, a.&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post p&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INNER JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;author_id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPA&lt;/strong&gt;: [[spring-jpa-fetch-join|JOIN FETCH]] 또는 &lt;code&gt;@EntityGraph&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rails&lt;/strong&gt;: &lt;code&gt;eager_load(:author)&lt;/code&gt; 또는 &lt;code&gt;includes(:author).references(:author)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Django&lt;/strong&gt;: &lt;code&gt;select_related(&quot;author&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 1쿼리
&lt;strong&gt;단점&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컬렉션 (ToMany) 이면 카르테시안 곱 → row 곱해짐&lt;/li&gt;
&lt;li&gt;큰 결과에서 JOIN 자체가 무거움&lt;/li&gt;
&lt;li&gt;Pageable 과 컬렉션 조합 시 문제 (JPA 는 in-memory paging 경고)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;전략-2-in-절-배치-조회-부모--자식-2쿼리&quot;&gt;전략 2: IN 절 배치 조회 (부모 + 자식 2쿼리)&lt;/h3&gt;
&lt;p&gt;부모 먼저 조회, 자식은 &lt;code&gt;WHERE parent_id IN (...)&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; comment &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;IN&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:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ..., &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPA&lt;/strong&gt;: &lt;code&gt;@BatchSize&lt;/code&gt;, &lt;code&gt;default_batch_fetch_size&lt;/code&gt;, &lt;code&gt;@EntityGraph&lt;/code&gt; (fetch join 아닌 subselect)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rails&lt;/strong&gt;: &lt;code&gt;preload(:comments)&lt;/code&gt; (항상 IN 방식) 또는 &lt;code&gt;includes(:comments)&lt;/code&gt; (조건 없으면 자동 IN)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Django&lt;/strong&gt;: &lt;code&gt;prefetch_related(&quot;comments&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;카르테시안 곱 없음&lt;/li&gt;
&lt;li&gt;페이징과 잘 어울림&lt;/li&gt;
&lt;li&gt;컬렉션 다수 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;: 2쿼리 (그래도 N+1 대비 극적 개선)&lt;/p&gt;
&lt;h3 id=&quot;전략-3-캐시--counter-컬럼&quot;&gt;전략 3: 캐시 / counter 컬럼&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자주 접근하는 count&lt;/strong&gt; → 부모에 컬럼으로 저장 (&lt;code&gt;Rails counter_cache&lt;/code&gt;, Hibernate &lt;code&gt;@Formula&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자주 접근하는 값&lt;/strong&gt; → 캐시 계층 (Redis, application cache)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정말 자주 반복 조회&lt;/strong&gt; → CQRS / read model 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;관계 로딩 자체를 제거하는 접근.&lt;/p&gt;
&lt;h2 id=&quot;프레임워크별-해결&quot;&gt;프레임워크별 해결&lt;/h2&gt;
&lt;h3 id=&quot;jpa--hibernate&quot;&gt;JPA / Hibernate&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;// 1. fetch join&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;Query&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SELECT p FROM Post p JOIN FETCH p.author&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; findAllWithAuthor&lt;/span&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;// 2. @EntityGraph&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;EntityGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;attributePaths&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:#9ECBFF&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; findAll&lt;/span&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;// 3. @BatchSize (Hibernate 확장)&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;Entity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Post&lt;/span&gt;&lt;span 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;OneToMany&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;mappedBy&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;post&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; LAZY)&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;BatchSize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    List&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Comment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; comments;&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;LAZY 로 매핑 + 쿼리마다 필요한 것만 명시&lt;/strong&gt;. 자세히는 [[spring-jpa-fetch-type]] 과 [[spring-jpa-fetch-join]] 참고.&lt;/p&gt;
&lt;h3 id=&quot;rails--active-record&quot;&gt;Rails / Active Record&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;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 1. includes (자동 선택: preload 또는 eager_load)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:author&lt;/span&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;# 2. preload (항상 IN 절)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;preload&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:author&lt;/span&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;# 3. eager_load (항상 LEFT OUTER JOIN)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;eager_load&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:author&lt;/span&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;# 4. strict_loading (Rails 6.1+): 의도 안 한 lazy 차단&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  has_many &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:comments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;strict_loading:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세히는 [[rails-query-optimization]] 참고. 개발 환경에는 &lt;code&gt;bullet&lt;/code&gt; gem 필수.&lt;/p&gt;
&lt;h3 id=&quot;django&quot;&gt;Django&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:#6A737D&quot;&gt;# 1. select_related (FK, OneToOne → JOIN)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post.objects.select_related(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 2. prefetch_related (ToMany → IN 절)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post.objects.prefetch_related(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;comments&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 3. Prefetch: 세밀 제어&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db.models &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Prefetch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post.objects.prefetch_related(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Prefetch(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;comments&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        queryset&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Comment.objects.filter(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;approved&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).select_related(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세히는 [[django-orm-advanced]] 참고.&lt;/p&gt;
&lt;h3 id=&quot;nodejs-orm-참고&quot;&gt;Node.js ORM (참고)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Sequelize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;findAll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ include: [{ model: User, as: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;author&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;// TypeORM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;postRepository.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ relations: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;author&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;// Prisma&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;prisma.post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;findMany&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ include: { author: &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 라이브러리 모두 include 지정이 필수. Prisma 는 아예 lazy 개념이 없어 명시 없으면 관계가 로드되지 않는다 (다른 종류의 안전 장치).&lt;/p&gt;
&lt;h2 id=&quot;프레임워크-공통-함정&quot;&gt;프레임워크 공통 함정&lt;/h2&gt;
&lt;h3 id=&quot;함정-1-has_many-through--컬렉션-n1&quot;&gt;함정 1: &lt;code&gt;has_many :through&lt;/code&gt; / 컬렉션 N+1&lt;/h3&gt;
&lt;p&gt;부모 → 중간 테이블 → 손자 조회 시 두 레벨 N+1 이 동시에 발생.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Rails: post → tags 를 통해 → categories&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;posts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { |p| &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { |t| t.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;category&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;해결: 깊은 nested &lt;code&gt;includes(tags: :category)&lt;/code&gt; 또는 단계별 preload.&lt;/p&gt;
&lt;h3 id=&quot;함정-2-직렬화에서-노출&quot;&gt;함정 2: 직렬화에서 노출&lt;/h3&gt;
&lt;p&gt;컨트롤러에서는 fetch 명시했지만 serializer / view 에서 다른 관계를 추가로 접근하는 경우.&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;// PostDto&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; String &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAuthorProfileImage&lt;/span&gt;&lt;span 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; post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getAuthor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getProfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getImageUrl&lt;/span&gt;&lt;span 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;    //                       ^^^^^^^^^ 여기서 lazy proxy 초기화!&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;해결&lt;/strong&gt;: DTO 로 미리 projection 하거나 fetch 시점에 모든 필요한 필드 포함.&lt;/p&gt;
&lt;h3 id=&quot;함정-3-자체-관계-self-referencing&quot;&gt;함정 3: 자체 관계 (self-referencing)&lt;/h3&gt;
&lt;p&gt;트리 구조에서 재귀적으로 부모/자식 접근하면 depth 만큼 N+1 배가.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_ancestry&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(comment):&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; [comment] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; get_ancestry(comment.parent) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; comment.parent &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [comment]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 각 재귀마다 SELECT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: recursive CTE (Postgres &lt;code&gt;WITH RECURSIVE&lt;/code&gt;), materialized path, closure table 같은 트리 저장 패턴.&lt;/p&gt;
&lt;h3 id=&quot;함정-4-조건부-접근&quot;&gt;함정 4: 조건부 접근&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;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getFeatured&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getComments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&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:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//                        ^^^^^^^^^^^^^^^^^^^^^^^^^ featured 아닌 것도 있으면 안 걸리지만 걸리면 lazy 초기화&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: 조건 검사에 필요한 값도 미리 fetch 하거나 SIZE() 를 SQL 수준에서 계산.&lt;/p&gt;
&lt;h3 id=&quot;함정-5-kotlin-data-class--java-record-의-자동-참조&quot;&gt;함정 5: Kotlin data class / Java record 의 자동 참조&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@JsonAutoDetect&lt;/code&gt;, Lombok &lt;code&gt;@ToString(includeAllFields=true)&lt;/code&gt; 등이 lazy 필드를 자동으로 건드릴 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: &lt;code&gt;@ToString(exclude = {&quot;comments&quot;})&lt;/code&gt;, &lt;code&gt;@JsonIgnore&lt;/code&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;JPA 개발 환경&lt;/td&gt;&lt;td&gt;SQL 로깅 + &lt;code&gt;hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JPA 테스트&lt;/td&gt;&lt;td&gt;Hypersistence Utils &lt;code&gt;assertSelectCount()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JPA 운영&lt;/td&gt;&lt;td&gt;p6spy, datasource-proxy, APM (Datadog / New Relic)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rails 개발&lt;/td&gt;&lt;td&gt;&lt;code&gt;bullet&lt;/code&gt; gem, &lt;code&gt;strict_loading&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rails 테스트&lt;/td&gt;&lt;td&gt;&lt;code&gt;rspec-sqlimit&lt;/code&gt;, &lt;code&gt;make_database_queries&lt;/code&gt; matcher&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Django 개발&lt;/td&gt;&lt;td&gt;&lt;code&gt;django-debug-toolbar&lt;/code&gt;, &lt;code&gt;django-silk&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Django 테스트&lt;/td&gt;&lt;td&gt;&lt;code&gt;assertNumQueries&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;프레임워크 무관&lt;/td&gt;&lt;td&gt;pg_stat_statements (Postgres), slow query log (MySQL), APM 트레이스&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;왜-orm-이-이런-함정을-만드나&quot;&gt;왜 ORM 이 이런 함정을 만드나&lt;/h2&gt;
&lt;p&gt;ORM 의 핵심 가치는 &lt;strong&gt;객체 그래프처럼 데이터를 다루는 편의성&lt;/strong&gt;. &lt;code&gt;post.author.name&lt;/code&gt; 이 자연스럽게 SQL 로 바뀌면 좋겠지만, 그러려면 미리 어떤 관계가 필요할지 알아야 한다.&lt;/p&gt;
&lt;p&gt;두 극단:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;모든 관계를 eager 로 미리 로드&lt;/strong&gt;: 매 쿼리마다 불필요한 대량 데이터 (JPA 의 &lt;code&gt;FetchType.EAGER&lt;/code&gt; 함정)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lazy 로만 두고 접근 시점에 초기화&lt;/strong&gt;: N+1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;해답은 옵트인 eager&lt;/strong&gt;. 기본 lazy, 필요한 곳에서 명시적으로 fetch 선언. 모든 성숙한 ORM 이 이 방향으로 수렴한다 (JPA 는 &lt;code&gt;@ManyToOne&lt;/code&gt; 기본이 EAGER 라 예외, 잘못된 기본값).&lt;/p&gt;
&lt;h2 id=&quot;함정과-베스트-프랙티스&quot;&gt;함정과 베스트 프랙티스&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;연관 매핑은 기본 LAZY&lt;/strong&gt; (JPA 는 &lt;code&gt;@ManyToOne&lt;/code&gt; 도 명시적으로 LAZY 지정)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쿼리 시점에 필요한 관계만 명시적으로 fetch&lt;/strong&gt; (JOIN FETCH, includes, select_related)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JOIN vs IN 배치 상황별 선택&lt;/strong&gt;: ToOne 은 JOIN, ToMany 는 IN 배치 우선&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컬렉션 여러 개 fetch 는 위험&lt;/strong&gt;: 하나만 JOIN + 나머지는 IN 배치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;직렬화 / view 에서 관계 접근하지 마세요&lt;/strong&gt;: DTO 로 미리 projection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 감지 도구 항상 켜기&lt;/strong&gt;: 개발에 bullet / debug-toolbar, 테스트에 assertNumQueries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자주 접근하는 count 는 counter 컬럼&lt;/strong&gt;: 관계 로딩 자체를 회피&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션 밖에서 관계 접근 금지&lt;/strong&gt;: &lt;code&gt;LazyInitializationException&lt;/code&gt; 함정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;APM 트레이스로 운영 관찰&lt;/strong&gt;: SQL 로그로는 안 보이는 hot path 발견&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QueryDSL / Criteria API 로 fetch 위치 명시&lt;/strong&gt;: JPQL 이 익숙하지 않은 팀에서&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[spring-jpa-fetch-join]] - JPA 의 JOIN FETCH 상세&lt;/li&gt;
&lt;li&gt;[[spring-jpa-fetch-type]] - FetchType.LAZY/EAGER&lt;/li&gt;
&lt;li&gt;[[spring-jpa-persistence-context]] - 영속성 컨텍스트와 lazy proxy&lt;/li&gt;
&lt;li&gt;[[rails-query-optimization]] - Rails preload/eager_load/includes&lt;/li&gt;
&lt;li&gt;[[django-orm-advanced]] - Django select_related/prefetch_related&lt;/li&gt;
&lt;li&gt;[[jpa-rails-eager-comparison]] - JPA EAGER vs Rails eager_load&lt;/li&gt;
&lt;li&gt;[[query-explain-plan]] - EXPLAIN 으로 실제 실행 계획 확인&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>orm</category><category>performance</category><category>query</category><category>n-plus-one</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB Internals] B-Tree 내부: split, merge, search 알고리즘</title><link>https://shinkeonkim.com/wiki/database-internals/b-tree-internals/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/b-tree-internals/</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;B-Tree&lt;/strong&gt; (Bayer &amp;#x26; McCreight, 1972) = &lt;em&gt;디스크 I/O 최적화된 balanced multi-way search tree&lt;/em&gt;. &lt;em&gt;모든 leaf 깊이 동일&lt;/em&gt; + &lt;em&gt;노드당 큰 fan-out&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;기존 개요는 [[btree-indexing]] 참고. 본 페이지는 &lt;em&gt;내부 알고리즘&lt;/em&gt; 에 집중.&lt;/p&gt;
&lt;h2 id=&quot;왜-b-tree&quot;&gt;왜 B-Tree?&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{&quot;인덱스 자료구조&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;BST&quot;| BST[&quot;Binary Search Tree&amp;#x3C;br/&gt;깊이 log N, disk I/O 큼&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;Red-Black&quot;| RB[&quot;균형이지만 여전히 log₂ N&amp;#x3C;br/&gt;메모리 자료구조&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;B-Tree&quot;| BT[&quot;log_m N 깊이 (m=fan-out)&amp;#x3C;br/&gt;disk I/O 최소&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BST --&gt; BAD[&quot;N=10^9 → 30 depth = 30 disk read&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BT --&gt; GOOD[&quot;N=10^9 + m=100 → 5 depth = 5 disk read&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;핵심&lt;/strong&gt;: &lt;em&gt;한 노드 = 한 disk page (8-16KB)&lt;/em&gt;. Fan-out 이 크면 &lt;em&gt;깊이가 얕음&lt;/em&gt; → I/O 최소.&lt;/p&gt;
&lt;h2 id=&quot;정의-knuth&quot;&gt;정의 (Knuth)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Order m&lt;/em&gt; 인 B-Tree 는:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 leaf 는 같은 깊이.&lt;/li&gt;
&lt;li&gt;각 노드 (root 제외) 는 &lt;em&gt;⌈m/2⌉-1 ~ m-1 keys&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Root 는 &lt;em&gt;1 ~ m-1 keys&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;각 internal node 는 &lt;em&gt;k+1 children&lt;/em&gt; (k = keys).&lt;/li&gt;
&lt;li&gt;노드 내 keys 정렬됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;구조-order-4-예&quot;&gt;구조 (Order 4 예)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;[30 | 60]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; A[&quot;[10 | 20]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; B[&quot;[40 | 50]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; C[&quot;[70 | 80 | 90]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; LA1[&quot;[5]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; LA2[&quot;[15]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; LA3[&quot;[25]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; LB1[&quot;[35]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; LB2[&quot;[45]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; LB3[&quot;[55]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; LC1[&quot;[65]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; LC2[&quot;[75]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; LC3[&quot;[85]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; LC4[&quot;[95]&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;search-olog_m-n&quot;&gt;Search: O(log_m N)&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;search(root, key):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  node = root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  while node is not null:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    i = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while i &amp;#x3C; node.n and key &gt; node.keys[i]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      i++&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if i &amp;#x3C; node.n and key == node.keys[i]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      return node.values[i]   # HIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node.is_leaf:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      return null              # MISS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node = node.children[i]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 노드에서 &lt;em&gt;binary search&lt;/em&gt; (또는 linear if 노드 작음).&lt;/p&gt;
&lt;h2 id=&quot;insertion--split&quot;&gt;Insertion + Split&lt;/h2&gt;
&lt;p&gt;새 key 삽입 → &lt;em&gt;leaf full 이면 split&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Before[&quot;[10 | 20 | 30 | 40] (full, m=4)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Before --&gt; Insert[&quot;Insert 25&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Insert --&gt; Split&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Split[&quot;Split&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Mid[&quot;25 up to parent&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Left[&quot;[10 | 20]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Right[&quot;[30 | 40]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&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;insert(node, key):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if node.is_leaf:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    insert_in_order(node.keys, key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node.n &gt; m-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      split(node)&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;    child = find_child(node, key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    insert(child, key)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;split-leaf-full&quot;&gt;Split (leaf full)&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;    Note over Leaf: [10, 20, 30, 40, 50] (5 keys, m=4)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leaf-&gt;&gt;Leaf: median = 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leaf-&gt;&gt;NewLeft: [10, 20]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leaf-&gt;&gt;NewRight: [40, 50]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leaf-&gt;&gt;Parent: push 30 up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Parent-&gt;&gt;Parent: 30 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt parent full&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Parent-&gt;&gt;Parent: recursive split&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;중간값을 parent 로 push&lt;/em&gt;. Root split 시 &lt;em&gt;tree 깊이 +1&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;deletion--merge&quot;&gt;Deletion + Merge&lt;/h2&gt;
&lt;p&gt;Key 삭제 → &lt;em&gt;leaf 가 minimum (⌈m/2⌉-1) 미만&lt;/em&gt; → &lt;em&gt;sibling 에서 빌리거나 merge&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{&quot;Leaf underflow&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;sibling 이 여유 있음&quot;| Borrow[&quot;Rotate: sibling 에서 key 이동&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;sibling 도 minimum&quot;| Merge[&quot;Merge with sibling + parent key 내림&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Merge --&gt;|&quot;parent underflow&quot;| Recurse[&quot;Recursive underflow up&quot;]&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;delete(node, key):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if node.is_leaf:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node.keys.remove(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node.n &amp;#x3C; ⌈m/2⌉ - 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      rebalance(node)&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;    # Internal: 후속자 (in-order successor) 로 교체 후 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    successor = min_of_right_subtree(node, key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node.replace(key, successor)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    delete(right_child, successor)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;rotation-borrow-from-sibling&quot;&gt;Rotation (borrow from sibling)&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;    Before[&quot;parent[30]&amp;#x3C;br/&gt;left[10,20] right[40,50,60]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Before --&gt;|&quot;underflow left&quot;| Rot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Rot[&quot;parent[40]&amp;#x3C;br/&gt;left[10,20,30] right[50,60]&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;merge&quot;&gt;Merge&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;    Before[&quot;parent[30]&amp;#x3C;br/&gt;left[10] right[40] (둘 다 min)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Before --&gt;|&quot;merge&quot;| After[&quot;parent[]&amp;#x3C;br/&gt;merged[10,30,40]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    After --&gt;|&quot;parent underflow&quot;| Cascade[&quot;Cascade up&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;height-분석&quot;&gt;Height 분석&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;N keys, fan-out m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Height h:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  minimum: m^h - 1 keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → h = log_m(N)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;N = 10^9, m = 100 → h ≈ 4.5 = 최대 5 disk read&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;cache-friendly-구현&quot;&gt;Cache-friendly 구현&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;    Node[&quot;Node (8KB page)&quot;] --&gt; Header[&quot;Header (n, is_leaf)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node --&gt; Keys[&quot;Keys array (정렬)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node --&gt; Children[&quot;Children pointers 또는 values&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[&quot;Binary search on keys array&amp;#x3C;br/&gt;모두 sequential memory access&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;페이지 내부 = 배열 + binary search&lt;/em&gt;. Cache miss 최소.&lt;/p&gt;
&lt;h2 id=&quot;동시성-concurrency&quot;&gt;동시성 (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 TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[&quot;Multi-writer&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Lock1[&quot;Latch coupling / Crabbing&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Lock2[&quot;Optimistic latch coupling (OLC)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Lock3[&quot;Bw-Tree (lock-free)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Lock4[&quot;Blink-tree&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;기법&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Latch coupling&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Parent lock 잡고 child lock, parent 해제. Deadlock 없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Optimistic (OLC)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Version 기반, 낙관적. 실패 시 재시도&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bw-Tree&lt;/strong&gt; (Microsoft)&lt;/td&gt;&lt;td&gt;Lock-free, delta record + consolidation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Blink-tree&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Right-link pointer, split 중 lookup 가능&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;postgresql-특화&quot;&gt;PostgreSQL 특화&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;기법&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Nbtree&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;PG 의 B-Tree (nbtree.c)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;HOT&lt;/strong&gt; (Heap Only Tuple)&lt;/td&gt;&lt;td&gt;인덱스 update 안 하는 tuple 최적화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Duplicate deduplication&lt;/strong&gt; (PG 13+)&lt;/td&gt;&lt;td&gt;중복 key 압축 저장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bottom-up index deletion&lt;/strong&gt; (PG 14+)&lt;/td&gt;&lt;td&gt;HOT 실패 시 인덱스 정리&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Random insert 순서&lt;/strong&gt; = split 폭증. Sequential insert (auto-increment) 가 최적.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UUID v4 primary key + B-Tree&lt;/strong&gt; = &lt;em&gt;random&lt;/em&gt; → 페이지 분할 극심. ULID / UUID v7 (정렬 가능) 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bloat&lt;/strong&gt; = MVCC 로 dead tuple 누적. &lt;code&gt;REINDEX CONCURRENTLY&lt;/code&gt; 정기.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fan-out 계산&lt;/strong&gt; = column 폭 큰 index = 노드당 key 수 적음 = 트리 깊음. &lt;em&gt;포함할 컬럼 최소&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[btree-indexing]] (개요)&lt;/li&gt;
&lt;li&gt;[[b-plus-tree-internals]]&lt;/li&gt;
&lt;li&gt;[[r-tree]]&lt;/li&gt;
&lt;li&gt;[[gin-index-deep]]&lt;/li&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>btree</category><category>database</category><category>index</category><category>algorithm</category><category>tree</category><author>koa (김신건)</author></item><item><title>[DB Internals] B+ Tree 내부: leaf linked list, range scan</title><link>https://shinkeonkim.com/wiki/database-internals/b-plus-tree-internals/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/b-plus-tree-internals/</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;B+ Tree&lt;/strong&gt; = B-Tree 의 변형. &lt;em&gt;모든 데이터가 leaf 에만&lt;/em&gt;, &lt;em&gt;internal node 는 navigation key만&lt;/em&gt;. Leaf 들이 &lt;em&gt;linked list&lt;/em&gt; 로 연결.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;현대 RDB 의 표준 인덱스&lt;/em&gt;. PostgreSQL, MySQL InnoDB, Oracle, SQL Server 모두 B+ Tree 기반.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;b-tree-vs-b-tree&quot;&gt;B-Tree vs B+ Tree&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 BT[&quot;B-Tree (internal 도 데이터 저장)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BT_R[&quot;Root: 30(data) | 60(data)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BT_L1[&quot;Leaf: 10(d)|20(d)|25(d)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BT_L2[&quot;Leaf: 40(d)|50(d)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BT_R --&gt; BT_L1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BT_R --&gt; BT_L2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph BP[&quot;B+ Tree (internal 은 key 만)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_R[&quot;Root: 30 | 60 (navigation)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_L1[&quot;Leaf: 10,20,25 (data)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_L2[&quot;Leaf: 40,50 (data)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_L3[&quot;Leaf: 70,80,90 (data)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_R --&gt; BP_L1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_R --&gt; BP_L2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_R --&gt; BP_L3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_L1 -.next.-&gt; BP_L2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BP_L2 -.next.-&gt; BP_L3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;B-Tree&lt;/th&gt;&lt;th&gt;B+ Tree&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Internal 에 데이터&lt;/td&gt;&lt;td&gt;&lt;em&gt;예&lt;/em&gt;&lt;/td&gt;&lt;td&gt;아니오 (key 만)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Leaf 연결 (linked list)&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;Range scan&lt;/td&gt;&lt;td&gt;각 노드 순회&lt;/td&gt;&lt;td&gt;&lt;em&gt;linked list scan (빠름!)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fan-out&lt;/td&gt;&lt;td&gt;작음 (data 도 저장)&lt;/td&gt;&lt;td&gt;&lt;em&gt;큼&lt;/em&gt; (navigation key 만)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;트리 깊이&lt;/td&gt;&lt;td&gt;깊음&lt;/td&gt;&lt;td&gt;&lt;em&gt;얕음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Point lookup&lt;/td&gt;&lt;td&gt;&lt;em&gt;early exit 가능&lt;/em&gt;&lt;/td&gt;&lt;td&gt;항상 leaf 까지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;공간 효율&lt;/td&gt;&lt;td&gt;나쁨 (중복)&lt;/td&gt;&lt;td&gt;좋음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;range-scan-의-우월성&quot;&gt;Range Scan 의 우월성&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BETWEEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;2026-06-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;2026-06-30&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;    Q-&gt;&gt;Root: search &apos;2026-06-01&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root-&gt;&gt;Internal: navigate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Internal-&gt;&gt;Leaf1: found start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loop leaf linked list traversal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Leaf1--&gt;&gt;Q: return rows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Leaf1-&gt;&gt;Leaf2: next pointer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Leaf2--&gt;&gt;Q: return rows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Leaf2-&gt;&gt;Leaf3: next pointer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Leaf3--&gt;&gt;Q: return rows until &gt; &apos;2026-06-30&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Leaf 만 순회 → &lt;em&gt;disk sequential I/O&lt;/em&gt;. B-Tree 는 매번 root 부터 다시 시작해야 함.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;clustered-index-innodb&quot;&gt;Clustered Index (InnoDB)&lt;/h2&gt;
&lt;p&gt;MySQL InnoDB 는 &lt;em&gt;Primary Key 의 B+ Tree leaf 가 실제 row 데이터&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;Root: navigation keys&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; L1[&quot;Leaf 1: full row data (PK=1..100)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; L2[&quot;Leaf 2: full row data (PK=101..200)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; L3[&quot;Leaf 3: full row data (PK=201..300)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L1 -.next.-&gt; L2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L2 -.next.-&gt; L3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[&quot;Secondary index → PK 값 저장 → 다시 클러스터 인덱스 조회&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vs PostgreSQL 은 &lt;em&gt;heap 별도, 인덱스는 heap tuple ID 만&lt;/em&gt;. 자세한 건 [[mysql-innodb]], [[postgresql]].&lt;/p&gt;
&lt;h2 id=&quot;leaf-linked-list&quot;&gt;Leaf Linked List&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;Leaf 1: [10, 20, 25] → Leaf 2: [40, 50] → Leaf 3: [70, 80, 90] → null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 leaf 페이지 header:&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;- prev_leaf pointer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- next_leaf pointer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- n_keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- keys[] + values[] (or row data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;Reverse range scan&lt;/em&gt; 도 O(k). &lt;code&gt;prev_leaf&lt;/code&gt; 로 역방향 순회.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;fan-out-계산-실전&quot;&gt;Fan-out 계산 (실전)&lt;/h2&gt;
&lt;p&gt;InnoDB 페이지 = 16KB:&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;Internal node fan-out:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  key (bigint 8B) + pointer (6B) = 14B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  16KB / 14B ≈ 1170 children&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;Height 계산:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  N = 10^9, fan-out = 1170&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  h = ceil(log_1170(10^9)) ≈ 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 3 disk read 로 도달!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;split-알고리즘-b-tree&quot;&gt;Split 알고리즘 (B+ Tree)&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;    Before[&quot;Leaf: [10, 20, 30, 40, 50, 60] (full)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Before --&gt; Split&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Split[&quot;Split (median=30)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L[&quot;New left: [10, 20, 30]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R[&quot;New right: [40, 50, 60]&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;    Split --&gt; Parent[&quot;Parent 에 40 push (30이 아님!)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Parent --&gt;|&quot;prev/next 갱신&quot;| Linked[&quot;Linked list 유지&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;B+ Tree 는 중간 key 를 &lt;em&gt;복사&lt;/em&gt; 해서 parent 에 push&lt;/strong&gt; (B-Tree 는 &lt;em&gt;이동&lt;/em&gt;). Leaf 에도 유지되어야 range scan 가능.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;sequential-insert-의-함정&quot;&gt;Sequential Insert 의 함정&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;PK = auto-increment (1, 2, 3, ...) 만 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 항상 *마지막 leaf* 에만 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 마지막 leaf 만 자꾸 split&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ tree 오른쪽으로 편향 성장&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;InnoDB 는 &lt;em&gt;change buffer&lt;/em&gt; 로 완화. 다른 DB 는 &lt;em&gt;bulk load&lt;/em&gt; 시 자동 balance.&lt;/p&gt;
&lt;h2 id=&quot;random-insert-의-함정&quot;&gt;Random Insert 의 함정&lt;/h2&gt;
&lt;p&gt;UUID v4 primary key:&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;UUID_v4 = random&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 매 insert 마다 *다른 leaf 페이지*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ Cache miss + 페이지 분할 폭증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 쓰기 성능 5-10배 저하&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auto-increment BIGINT (관계형 정통)&lt;/li&gt;
&lt;li&gt;ULID / UUID v7 (시간 순 정렬 가능)&lt;/li&gt;
&lt;li&gt;InnoDB: &lt;code&gt;innodb_autoinc_lock_mode&lt;/code&gt; 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;reverse-scan--order-by-desc&quot;&gt;Reverse Scan + ORDER BY DESC&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;B+ Tree 의 &lt;em&gt;leaf prev pointer&lt;/em&gt; 로 마지막에서 역행.&lt;/li&gt;
&lt;li&gt;MySQL InnoDB 는 자동 지원.&lt;/li&gt;
&lt;li&gt;PostgreSQL 은 &lt;em&gt;reverse index&lt;/em&gt; 옵션 (&lt;code&gt;CREATE INDEX ... DESC&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;concurrency-latch-coupling&quot;&gt;Concurrency: Latch Coupling&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;    T-&gt;&gt;Root: 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root--&gt;&gt;T: children&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T-&gt;&gt;Child: 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T-&gt;&gt;Root: 잠금 해제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T-&gt;&gt;Grandchild: 잠금&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T-&gt;&gt;Child: 잠금 해제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over T,Grandchild: 항상 위→아래 순서 → deadlock free&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;InnoDB, PG 모두 &lt;em&gt;latch coupling&lt;/em&gt; 기본. 자세한 건 [[b-tree-internals]].&lt;/p&gt;
&lt;h2 id=&quot;bulk-loading&quot;&gt;Bulk Loading&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;...&apos;&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;B+ Tree 를 &lt;em&gt;bottom-up 으로 구축&lt;/em&gt; (sort + fill):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;데이터 정렬 (외부 정렬).&lt;/li&gt;
&lt;li&gt;Leaf 페이지 sequential fill (fillfactor 90%).&lt;/li&gt;
&lt;li&gt;Internal 페이지 bottom-up 생성.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;일반 insert 보다 &lt;em&gt;수배 빠름&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fillfactor&quot;&gt;Fillfactor&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_orders&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders(id) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (fillfactor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 90&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;90% = 페이지의 10% 여유. Insert 시 분할 지연.&lt;/li&gt;
&lt;li&gt;100% = 쓰기 성능 저하 + 잦은 분할.&lt;/li&gt;
&lt;li&gt;Read-only 인덱스는 100% (공간 절약).&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;UUID v4 PK&lt;/strong&gt; = 성능 킬러. UUID v7 / ULID / BIGINT.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fillfactor 100&lt;/strong&gt; = 쓰기 많은 테이블에서 분할 폭증.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Descending index 안 만듦&lt;/strong&gt; = 자주 &lt;code&gt;ORDER BY x DESC&lt;/code&gt; 하면 성능 손해.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-column index 순서 잘못&lt;/strong&gt; = &lt;em&gt;가장 선택도 높은&lt;/em&gt; 컬럼을 앞으로.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[btree-indexing]]&lt;/li&gt;
&lt;li&gt;[[b-tree-internals]]&lt;/li&gt;
&lt;li&gt;[[r-tree]]&lt;/li&gt;
&lt;li&gt;[[mysql-innodb]]&lt;/li&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>btree</category><category>b-plus-tree</category><category>database</category><category>index</category><category>range-query</category><author>koa (김신건)</author></item><item><title>[DB Internals] CHAR / VARCHAR / TEXT / JSONB: 문자열 타입 비교</title><link>https://shinkeonkim.com/wiki/database-internals/char-varchar-text-jsonb/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/char-varchar-text-jsonb/</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;문자열 저장 타입 4가지 비교. &lt;em&gt;성능 + 저장 + 유연성&lt;/em&gt; 트레이드오프.&lt;/p&gt;
&lt;h2 id=&quot;4가지-비교-postgresql&quot;&gt;4가지 비교 (PostgreSQL)&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;뒤 공백&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;CHAR(N)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;N (고정)&lt;/td&gt;&lt;td&gt;&lt;em&gt;padding&lt;/em&gt;&lt;/td&gt;&lt;td&gt;붙임&lt;/td&gt;&lt;td&gt;옛 표준, 거의 안 씀&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;VARCHAR(N)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;실제 크기&lt;/td&gt;&lt;td&gt;유지&lt;/td&gt;&lt;td&gt;옛 습관, 대부분 상황&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;TEXT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;무제한&lt;/em&gt;&lt;/td&gt;&lt;td&gt;실제 크기&lt;/td&gt;&lt;td&gt;유지&lt;/td&gt;&lt;td&gt;&lt;em&gt;PG 권장&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;JSONB&lt;/code&gt;&lt;/td&gt;&lt;td&gt;무제한 (내부 1GB)&lt;/td&gt;&lt;td&gt;binary + toast&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;postgresql-의-특별한-진실&quot;&gt;PostgreSQL 의 특별한 진실&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;PostgreSQL: CHAR, VARCHAR, TEXT 는 *성능 동일*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ 모두 내부적으로 *varlena* (variable-length array) 로 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ length 검증 오버헤드만 다름&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;PostgreSQL 에서는 거의 항상 &lt;code&gt;TEXT&lt;/code&gt; 사용&lt;/strong&gt;. &lt;code&gt;VARCHAR(N)&lt;/code&gt; 은 &lt;em&gt;길이 제약 필요&lt;/em&gt; 할 때만. &lt;code&gt;CHAR(N)&lt;/code&gt; 은 사실상 &lt;em&gt;legacy&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;hello&apos; (5 chars)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  CHAR(20)   → 20 bytes (padding + length header)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  VARCHAR(20) → 5 + 1 header = 6 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  VARCHAR    → 5 + 1 header = 6 bytes (제약 없음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  TEXT       → 5 + 1 header = 6 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  JSONB      → 파싱된 binary + headers ≈ 20+ bytes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;mysql-의-차이&quot;&gt;MySQL 의 차이&lt;/h2&gt;
&lt;p&gt;MySQL 은 &lt;em&gt;진짜 다르다&lt;/em&gt;:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;CHAR(N)&lt;/th&gt;&lt;th&gt;VARCHAR(N)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;저장&lt;/td&gt;&lt;td&gt;&lt;em&gt;N bytes 고정&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;실제 크기 + 1-2 byte header&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;뒤 공백&lt;/td&gt;&lt;td&gt;저장 X (&lt;code&gt;TRIM&lt;/code&gt; 자동)&lt;/td&gt;&lt;td&gt;저장 O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;성능&lt;/td&gt;&lt;td&gt;&lt;em&gt;약간 빠름&lt;/em&gt; (고정)&lt;/td&gt;&lt;td&gt;약간 느림&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용&lt;/td&gt;&lt;td&gt;고정 길이 (해시, 코드)&lt;/td&gt;&lt;td&gt;대부분&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- MySQL 만의 팁&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;country_code &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 항상 2자리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sha256_hex   &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 항상 64자&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  VARCHAR&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;-- 가변&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;big_text     &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;          -- BLOB 영역&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;innodb-의-text-특별함&quot;&gt;InnoDB 의 TEXT 특별함&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;    Row[&quot;InnoDB row (16KB page)&quot;] --&gt; Small[&quot;작은 VARCHAR/TEXT → row 안 저장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Row --&gt; Large[&quot;큰 TEXT/BLOB → 별도 page (overflow)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Row --&gt; Ptr[&quot;Row 에는 pointer 만&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;768 bytes 이내 → row 내 저장&lt;/li&gt;
&lt;li&gt;초과 → &lt;em&gt;별도 페이지 + pointer&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;결과: &lt;em&gt;TEXT 자주 안 쓰는 컬럼&lt;/em&gt; 은 성능 손실 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;postgresql-toast&quot;&gt;PostgreSQL TOAST&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;    Row[&quot;PG row (8KB page)&quot;] --&gt; Check{&quot;&gt; 2KB?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Check --&gt;|아니오| Inline[&quot;Inline 저장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Check --&gt;|예| Comp[&quot;1. 압축 (LZ4/pglz)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Comp --&gt;|여전히 큼| Toast[&quot;2. TOAST table 별도 저장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Toast --&gt; Ptr[&quot;Row 에는 pointer&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;The Oversized-Attribute Storage Technique&lt;/em&gt;. PostgreSQL 자동 처리.&lt;/p&gt;
&lt;h2 id=&quot;jsonb-의-특별함&quot;&gt;JSONB 의 특별함&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  event_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  payload JSONB&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;vs &lt;code&gt;TEXT&lt;/code&gt; 로 JSON 저장:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;TEXT (JSON)&lt;/th&gt;&lt;th&gt;JSONB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;저장&lt;/td&gt;&lt;td&gt;&lt;em&gt;원본 텍스트&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;binary parsed&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;인덱싱&lt;/td&gt;&lt;td&gt;어려움&lt;/td&gt;&lt;td&gt;&lt;em&gt;GIN 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Query&lt;/td&gt;&lt;td&gt;&lt;code&gt;-&gt;&lt;/code&gt; 매번 parse&lt;/td&gt;&lt;td&gt;빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Key 순서&lt;/td&gt;&lt;td&gt;보존&lt;/td&gt;&lt;td&gt;손실&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Insert 속도&lt;/td&gt;&lt;td&gt;&lt;em&gt;빠름&lt;/em&gt;&lt;/td&gt;&lt;td&gt;느림 (parse)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Read 속도&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;p&gt;자세한 JSONB 는 [[postgresql-jsonb]].&lt;/p&gt;
&lt;h2 id=&quot;성능-벤치마크-직관&quot;&gt;성능 벤치마크 (직관)&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;언제-무엇을&quot;&gt;언제 무엇을?&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{데이터 형태}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;고정 길이 코드 (ISO, hash)&quot;| MC[&quot;MySQL: CHAR(N)&amp;#x3C;br/&gt;PG: TEXT + CHECK 제약&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;제한된 가변 (이메일, name)&quot;| MV[&quot;MySQL: VARCHAR(N)&amp;#x3C;br/&gt;PG: TEXT + CHECK&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;긴 본문 (article, log)&quot;| T[&quot;TEXT (PG/MySQL)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;schemaless 문서&quot;| J[&quot;JSONB&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|&quot;binary (image, encrypted)&quot;| B[&quot;BYTEA (PG) / BLOB (MySQL)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;postgresql-권장-pattern&quot;&gt;PostgreSQL 권장 pattern&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ✓ 권장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; users&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id         &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  email      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UNIQUE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; CHECK&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(email) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 320&lt;/span&gt;&lt;span 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;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; CHECK&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BETWEEN&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  country    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; CHECK&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(country) &lt;/span&gt;&lt;span style=&quot;color:#F97583&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; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; country ~ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;^[A-Z]{2}$&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;  bio        &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  preferences JSONB &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ✗ 옛 습관&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; users&lt;/span&gt;&lt;span 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;  email      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;320&lt;/span&gt;&lt;span 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;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       VARCHAR&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:#E1E4E8&quot;&gt;  country    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  bio        &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  preferences JSONB&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;mysql-권장-pattern&quot;&gt;MySQL 권장 pattern&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; users&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id         &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGINT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AUTO_INCREMENT,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  email      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;320&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UNIQUE&lt;/span&gt;&lt;span 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;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       VARCHAR&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 style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  country    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  bio        &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  preferences &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  -- MySQL 8.0+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) ENGINE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;InnoDB &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CHARSET&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;utf8mb4 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COLLATE=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;utf8mb4_0900_ai_ci;&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL: 인덱스 key 최대 2704 bytes (BTree)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users(email);   &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;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users(bio);      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 큰 bio 에러&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;-- 부분 인덱스 (prefix)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users((&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(bio, &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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- MySQL: 인덱스 prefix length 명시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_bio&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users(bio(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 앞 255 chars 만&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;collation-정렬-규칙&quot;&gt;Collation (정렬 규칙)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&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;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; COLLATE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ko-KR-x-icu&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- MySQL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; VARCHAR&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 style=&quot;color:#F97583&quot;&gt;CHARACTER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; utf8mb4 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COLLATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; utf8mb4_ko_0900_as_cs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Collation&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;utf8mb4_bin&lt;/code&gt;&lt;/td&gt;&lt;td&gt;binary (정확 매칭)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt;&lt;/td&gt;&lt;td&gt;대소문자/악센트 무시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ko_KR.UTF-8&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;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PG 에서 VARCHAR(N) 성능 이유로 사용&lt;/strong&gt; = &lt;em&gt;같은 성능&lt;/em&gt;. TEXT + CHECK 로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL 에서 모든 곳에 TEXT&lt;/strong&gt; = row 밖 저장 → 성능 손실. VARCHAR 우선.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CHAR 의 뒤 공백 사고&lt;/strong&gt; = &lt;code&gt;WHERE name = &apos;koa&apos;&lt;/code&gt; vs &lt;code&gt;&apos;koa   &apos;&lt;/code&gt; → 결과 다름.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UTF-8 3-byte vs 4-byte&lt;/strong&gt; = MySQL 옛 &lt;code&gt;utf8&lt;/code&gt; 은 &lt;em&gt;3-byte&lt;/em&gt; (이모지 저장 안 됨). &lt;em&gt;&lt;code&gt;utf8mb4&lt;/code&gt;&lt;/em&gt; 필수.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;li&gt;[[mysql-innodb]]&lt;/li&gt;
&lt;li&gt;[[postgresql-jsonb]]&lt;/li&gt;
&lt;li&gt;[[b-plus-tree-internals]] (인덱스)&lt;/li&gt;
&lt;li&gt;[[wal-write-ahead-log]] (TOAST 저장)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>mysql</category><category>string</category><category>char</category><category>varchar</category><category>text</category><category>jsonb</category><category>toast</category><author>koa (김신건)</author></item><item><title>[DB Internals] GIN Index 깊이: 구조, jsonb_path_ops, tsvector</title><link>https://shinkeonkim.com/wiki/database-internals/gin-index-deep/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/gin-index-deep/</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;GIN (Generalized Inverted Index)&lt;/strong&gt; = &lt;em&gt;한 값이 여러 항목&lt;/em&gt; 을 가지는 데이터 (배열, JSON, tsvector) 의 &lt;em&gt;역색인&lt;/em&gt;. PostgreSQL 의 강력한 확장점.&lt;/p&gt;
&lt;p&gt;일반 인덱스 비교는 [[gin-gist-hash-indexes]] 참고. 본 페이지는 &lt;em&gt;내부 구조&lt;/em&gt; 에 집중.&lt;/p&gt;
&lt;h2 id=&quot;왜-gin&quot;&gt;왜 GIN?&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;    B[&quot;B-Tree: 한 row = 한 key&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; B1[&quot;ex: name = &apos;kim&apos;&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G[&quot;GIN: 한 row = 여러 key&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; G1[&quot;ex: tags = [&apos;ruby&apos;, &apos;rails&apos;, &apos;redis&apos;]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; G2[&quot;ex: payload = { &apos;user_id&apos;: 42, &apos;tags&apos;: [&apos;a&apos;, &apos;b&apos;] }&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; G3[&quot;ex: to_tsvector(&apos;rabbit hat magic&apos;)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구조&quot;&gt;구조&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;Entry B-Tree&amp;#x3C;br/&gt;(term/element)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; E1[&quot;&apos;ruby&apos; → posting list&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; E2[&quot;&apos;rails&apos; → posting list&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; E3[&quot;&apos;redis&apos; → posting list&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E1 --&gt; P1[&quot;Posting list: [doc1, doc3, doc7, ...]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P1 --&gt;|&quot;크면&quot;| PT[&quot;Posting Tree&amp;#x3C;br/&gt;(B-Tree of doc IDs)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;구성&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Entry Tree&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;모든 term 의 B-Tree&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Posting List&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Term → doc ID 목록 (작을 때 배열)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Posting Tree&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Term 의 doc 이 많을 때 B-Tree&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;예시-배열-컬럼&quot;&gt;예시 (배열 컬럼)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  tags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts (tags) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;ruby&quot;,&quot;rails&quot;,&quot;redis&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- id=1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;rails&quot;,&quot;postgres&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- id=2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;ruby&quot;,&quot;postgres&quot;,&quot;json&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- id=3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_tags&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(tags);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Entry B-Tree:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;json&apos;     → [3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;postgres&apos; → [2, 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;rails&apos;    → [1, 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;redis&apos;    → [1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;ruby&apos;     → [1, 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;Query: tags @&gt; ARRAY[&apos;ruby&apos;, &apos;postgres&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → &apos;ruby&apos; posting: [1, 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → &apos;postgres&apos; posting: [2, 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 교집합: [3]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;연산자&quot;&gt;연산자&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;연산자&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;th&gt;예&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@&gt;&lt;/code&gt; (contains)&lt;/td&gt;&lt;td&gt;왼쪽이 오른쪽 포함&lt;/td&gt;&lt;td&gt;&lt;code&gt;tags @&gt; ARRAY[&apos;ruby&apos;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;#x3C;@&lt;/code&gt; (contained by)&lt;/td&gt;&lt;td&gt;왼쪽이 오른쪽에 포함&lt;/td&gt;&lt;td&gt;&lt;code&gt;tags &amp;#x3C;@ ARRAY[&apos;ruby&apos;,&apos;rails&apos;,&apos;redis&apos;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; (overlap)&lt;/td&gt;&lt;td&gt;하나라도 공통&lt;/td&gt;&lt;td&gt;&lt;code&gt;tags &amp;#x26;&amp;#x26; ARRAY[&apos;ruby&apos;,&apos;python&apos;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&lt;/code&gt; (key exists)&lt;/td&gt;&lt;td&gt;JSON key 존재&lt;/td&gt;&lt;td&gt;&lt;code&gt;payload ? &apos;user_id&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;`?&lt;/td&gt;&lt;td&gt;` (any key)&lt;/td&gt;&lt;td&gt;여러 key 중 하나&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&amp;#x26;&lt;/code&gt; (all keys)&lt;/td&gt;&lt;td&gt;모든 key 존재&lt;/td&gt;&lt;td&gt;&lt;code&gt;payload ?&amp;#x26; array[&apos;a&apos;,&apos;b&apos;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;jsonb_ops-vs-jsonb_path_ops&quot;&gt;jsonb_ops vs jsonb_path_ops&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 1. jsonb_ops (기본): 모든 key 와 value 인덱싱&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_payload_full&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload);&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;-- 2. jsonb_path_ops: @&gt; 만 인덱싱 (더 작음, 더 빠름)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_payload_path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload jsonb_path_ops);&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;jsonb_ops&lt;/th&gt;&lt;th&gt;jsonb_path_ops&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;인덱스 크기&lt;/td&gt;&lt;td&gt;&lt;em&gt;큼&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;~1/3&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;지원 연산자&lt;/td&gt;&lt;td&gt;&lt;code&gt;@&gt;&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, `?&lt;/td&gt;&lt;td&gt;&lt;code&gt;, &lt;/code&gt;?&amp;#x26;&lt;code&gt;, &lt;/code&gt;@?&lt;code&gt;, &lt;/code&gt;@@`&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Insert 속도&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;Query 속도&lt;/td&gt;&lt;td&gt;보통&lt;/td&gt;&lt;td&gt;&lt;em&gt;빠름 (@&gt;)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;대부분 &lt;code&gt;@&gt;&lt;/code&gt; 만 쓴다&lt;/em&gt; → &lt;strong&gt;&lt;code&gt;jsonb_path_ops&lt;/code&gt; 권장&lt;/strong&gt;. 크기 1/3, 속도 3x.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;full-text-search-tsvector&quot;&gt;Full-Text Search (tsvector)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; articles&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  body &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  body_tsv TSVECTOR &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GENERATED&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ALWAYS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (to_tsvector(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;english&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, body)) STORED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_body_tsv&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; articles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(body_tsv);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 검색&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; articles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; body_tsv @@ to_tsquery(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;english&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;rabbit &amp;#x26; hat&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;-- 랭킹&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ts_rank(body_tsv, query) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; articles, to_tsquery(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;english&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;rabbit&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) query&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; body_tsv @@ query&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rank &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DESC&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;pg_trgm-like-최적화&quot;&gt;pg_trgm (LIKE 최적화)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION pg_trgm;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_name_trgm&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin_trgm_ops);&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;-- Prefix / suffix / substring 모두 인덱스 활용!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LIKE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;%kim%&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ILIKE &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;kim%&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; % &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;kim&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;blockquote&gt;
&lt;p&gt;&lt;code&gt;pg_trgm&lt;/code&gt; = &lt;em&gt;3-gram (trigram)&lt;/em&gt; 분해 → GIN. LIKE 성능 획기적 향상.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;fastupdate--pending-list&quot;&gt;Fastupdate + Pending List&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;    Insert[&quot;INSERT&quot;] --&gt; Pending[&quot;Pending list&amp;#x3C;br/&gt;(빠른 accumulate)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pending -.gin_pending_list_limit.-&gt; Merge[&quot;주기적 merge into GIN&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Merge --&gt; GIN[(GIN 본체)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;기본 활성&lt;/em&gt;. INSERT/UPDATE 폭증 시 GIN 재계산이 비쌈 → &lt;em&gt;일단 임시 저장&lt;/em&gt; + &lt;em&gt;배치 merge&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 비활성 (query 성능 우선)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ... &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(...) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (fastupdate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; off&lt;/span&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;gin_pending_list_limit &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 4MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Pending list 가 크면 &lt;em&gt;query 시 pending list + GIN 둘 다 스캔&lt;/em&gt; → 느림. &lt;code&gt;VACUUM&lt;/code&gt; 이 merge 트리거.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;gin-크기-문제&quot;&gt;GIN 크기 문제&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 인덱스 크기 확인&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pg_size_pretty(pg_relation_size(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;idx_payload_full&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;-- 재빌드 (bloat 정리)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;REINDEX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INDEX&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CONCURRENTLY idx_payload_full;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;큰 GIN 인덱스 (수십 GB) 는 흔함. &lt;em&gt;&lt;code&gt;jsonb_path_ops&lt;/code&gt;&lt;/em&gt; 로 크기 대폭 감소.&lt;/p&gt;
&lt;h2 id=&quot;gin--partial-index&quot;&gt;GIN + partial index&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 특정 조건만 인덱싱&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_recent_tags&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(tags)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; interval &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;30 days&apos;&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;strong&gt;Hot data&lt;/strong&gt; 만 인덱싱 → 크기 감소 + 빌드 시간 단축.&lt;/p&gt;
&lt;h2 id=&quot;insert-성능&quot;&gt;Insert 성능&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;GIN insert cost:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - term 하나마다 posting list update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - N terms in row → N updates&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;배열 컬럼에 30개 tag 삽입:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  30 개의 posting list 업데이트 = 매우 비쌈&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;  - fastupdate ON (기본): 임시 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Bulk load: COPY + CREATE INDEX (기존 인덱스 drop)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;gin-vs-gist-vs-bloom&quot;&gt;GIN vs GiST vs Bloom&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;GIN&lt;/th&gt;&lt;th&gt;GiST&lt;/th&gt;&lt;th&gt;Bloom&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;자료구조&lt;/td&gt;&lt;td&gt;Inverted&lt;/td&gt;&lt;td&gt;Balanced tree&lt;/td&gt;&lt;td&gt;Bit vector&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Insert 속도&lt;/td&gt;&lt;td&gt;&lt;em&gt;느림&lt;/em&gt;&lt;/td&gt;&lt;td&gt;빠름&lt;/td&gt;&lt;td&gt;매우 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Query 속도&lt;/td&gt;&lt;td&gt;&lt;em&gt;빠름&lt;/em&gt;&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;매우 빠름 (fp)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;다값 지원&lt;/td&gt;&lt;td&gt;&lt;em&gt;예&lt;/em&gt;&lt;/td&gt;&lt;td&gt;예&lt;/td&gt;&lt;td&gt;아니오&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용&lt;/td&gt;&lt;td&gt;jsonb, array, tsvector&lt;/td&gt;&lt;td&gt;spatial, range&lt;/td&gt;&lt;td&gt;multi-column =&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;jsonb_ops&lt;/code&gt; 항상 사용&lt;/strong&gt; = 크기 3배, 속도 저하. &lt;code&gt;@&gt;&lt;/code&gt; 만이면 &lt;code&gt;jsonb_path_ops&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pending list 무한 성장&lt;/strong&gt; = query 느림. VACUUM 정기.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;작은 배열에도 GIN&lt;/strong&gt; = 배열 항목 수 &amp;#x3C;10 면 별도 컬럼 + B-Tree 가 나을 수도.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HOT update 안 됨 in GIN&lt;/strong&gt; = GIN 은 &lt;em&gt;tuple ID 저장&lt;/em&gt; → row 위치 바뀌면 update.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[gin-gist-hash-indexes]]&lt;/li&gt;
&lt;li&gt;[[postgresql-jsonb]]&lt;/li&gt;
&lt;li&gt;[[b-tree-internals]]&lt;/li&gt;
&lt;li&gt;[[r-tree]]&lt;/li&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>gin</category><category>postgresql</category><category>index</category><category>inverted</category><category>full-text</category><category>jsonb</category><author>koa (김신건)</author></item><item><title>[DB Internals] R-Tree: 공간 인덱스, MBR, split</title><link>https://shinkeonkim.com/wiki/database-internals/r-tree/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/r-tree/</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;R-Tree&lt;/strong&gt; (Guttman, 1984) = &lt;em&gt;공간 데이터 (2D, 3D)&lt;/em&gt; 를 인덱싱하는 &lt;em&gt;balanced tree&lt;/em&gt;. 각 노드는 &lt;em&gt;Minimum Bounding Rectangle (MBR)&lt;/em&gt; 을 저장.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;지도, GIS, 위치 기반 서비스, 3D 게임, CAD&lt;/em&gt; 의 표준. PostGIS, MySQL Spatial, SQL Server Spatial 모두 R-Tree 계열.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;왜-b-tree-로-안-되나&quot;&gt;왜 B-Tree 로 안 되나?&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;공간 데이터: (x, y) 좌표&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Query: &quot;이 사각형 안의 점들&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;B-Tree 는 &lt;em&gt;1차원&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ✗ 각 축을 별도 인덱스 → 매우 비효율&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; points(x);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; points(y);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; points &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BETWEEN&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BETWEEN&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 40&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 두 인덱스 AND, 대부분 안 씀&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;R-Tree = &lt;em&gt;n차원 공간의 계층적 분할&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;구조-2d-예&quot;&gt;구조 (2D 예)&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;    Root[&quot;Root MBR: (0,0)-(100,100)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; A[&quot;A MBR: (0,0)-(50,60)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; B[&quot;B MBR: (40,50)-(100,100)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; A1[&quot;Leaf: 점들 in (0,0)-(30,40)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; A2[&quot;Leaf: 점들 in (20,30)-(50,60)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; B1[&quot;Leaf: 점들 in (40,50)-(70,80)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; B2[&quot;Leaf: 점들 in (60,70)-(100,100)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 노드가 &lt;em&gt;자기 자식들을 감싸는 최소 사각형 (MBR)&lt;/em&gt; 저장. B-Tree 처럼 &lt;em&gt;balanced&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;mbr-minimum-bounding-rectangle&quot;&gt;MBR (Minimum Bounding Rectangle)&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;점: (10, 20)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;선분: (5, 5) - (15, 25) → MBR = (5,5)-(15,25)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;원: 중심 (10, 10), 반지름 5 → MBR = (5,5)-(15,15)&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;R-Tree 는 &lt;em&gt;실제 도형이 아닌 MBR 로 계산&lt;/em&gt; → 빠름. Precision 은 별도 filter 로.&lt;/p&gt;
&lt;h2 id=&quot;search&quot;&gt;Search&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;    Q-&gt;&gt;Root: query rectangle (Q)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root-&gt;&gt;Root: 자식 MBR 중 Q 와 *overlap* 하는 것만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root-&gt;&gt;A: overlap? YES → 재귀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root-&gt;&gt;B: overlap? NO → skip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A-&gt;&gt;A1: overlap? YES&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A-&gt;&gt;A2: overlap? NO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A1--&gt;&gt;Q: candidate 점 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q-&gt;&gt;Q: 실제 도형 vs Q 확인 (refinement)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2 단계 필터링&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt;: MBR 겹침 확인 (빠름, 근사)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refinement&lt;/strong&gt;: 실제 도형 vs 쿼리 (정확, 느림)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;insertion--split&quot;&gt;Insertion + Split&lt;/h2&gt;
&lt;p&gt;새 도형 → &lt;em&gt;가장 적게 확장되는&lt;/em&gt; 자식 선택 → leaf 도달:&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;insert(root, entry):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if leaf:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    add entry to leaf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if leaf overflow:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      split(leaf)&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;    best_child = choose_subtree(node, entry)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    insert(best_child, entry)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    update MBR of node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;split-알고리즘-핵심&quot;&gt;Split 알고리즘 (핵심)&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;    Overflow[&quot;Leaf overflow (M+1 entries)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Overflow --&gt; Strategy{&quot;Split 전략&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Strategy --&gt; Linear[&quot;Linear split (Guttman)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Strategy --&gt; Quad[&quot;Quadratic split (더 좋은 quality)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Strategy --&gt; RStar[&quot;R* Tree (best quality)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;알고리즘&lt;/th&gt;&lt;th&gt;시간&lt;/th&gt;&lt;th&gt;품질&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Linear&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Quadratic&lt;/td&gt;&lt;td&gt;O(N²)&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R* Tree&lt;/td&gt;&lt;td&gt;O(N log N)&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;h3 id=&quot;r-tree-2026-표준&quot;&gt;R* Tree (2026 표준)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Beckmann et al. (1990)&lt;/strong&gt; 의 개선:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Overflow 시 재삽입&lt;/em&gt; (forced reinsertion).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Overlap 최소화&lt;/em&gt; + &lt;em&gt;coverage 최소화&lt;/em&gt; + &lt;em&gt;dead space 최소화&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;PostGIS 의 GiST 가 R* 기반.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;r-tree-vs-quad-tree-vs-kd-tree&quot;&gt;R-Tree vs Quad-tree vs KD-Tree&lt;/h2&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;R-Tree&lt;/th&gt;&lt;th&gt;Quad-tree&lt;/th&gt;&lt;th&gt;KD-Tree&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Balanced&lt;/td&gt;&lt;td&gt;&lt;em&gt;예&lt;/em&gt;&lt;/td&gt;&lt;td&gt;아니오&lt;/td&gt;&lt;td&gt;아니오&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Disk I/O&lt;/td&gt;&lt;td&gt;&lt;em&gt;최적&lt;/em&gt;&lt;/td&gt;&lt;td&gt;나쁨&lt;/td&gt;&lt;td&gt;나쁨&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dimension&lt;/td&gt;&lt;td&gt;낮음 (2-6)&lt;/td&gt;&lt;td&gt;2D&lt;/td&gt;&lt;td&gt;낮음 (2-10)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB 사용&lt;/td&gt;&lt;td&gt;&lt;em&gt;표준&lt;/em&gt;&lt;/td&gt;&lt;td&gt;드묾&lt;/td&gt;&lt;td&gt;드묾&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;삽입&lt;/td&gt;&lt;td&gt;복잡&lt;/td&gt;&lt;td&gt;간단&lt;/td&gt;&lt;td&gt;복잡&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;갱신&lt;/td&gt;&lt;td&gt;느림&lt;/td&gt;&lt;td&gt;빠름&lt;/td&gt;&lt;td&gt;매우 느림 (rebuild)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;postgis-사용&quot;&gt;PostGIS 사용&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION postgis;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; places&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  location&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; GEOMETRY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4326&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- WGS 84&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_places_geom&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 반경 검색&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ST_DWithin(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ST_MakePoint(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;126&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;97&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;55&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)::&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;geography&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&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; location&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&amp;#x26; ST_MakeEnvelope(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;126&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;127&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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 style=&quot;color:#79B8FF&quot;&gt;37&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4326&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;PostGIS 는 &lt;em&gt;GiST + R-Tree&lt;/em&gt;. &lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; (overlap), &lt;code&gt;&amp;#x3C;@&lt;/code&gt; (contains), &lt;code&gt;~=&lt;/code&gt; (equal).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;postgis-의-성능-팁&quot;&gt;PostGIS 의 성능 팁&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 1. Geometry 대신 geography (구면 거리 정확)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; GEOGRAPHY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4326&lt;/span&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;-- 2. SP-GiST 는 point 만 (더 빠름)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; spgist(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;location&lt;/span&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;-- 3. Clustered index 로 physical order 개선&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CLUSTER places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx_places_geom;&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;-- 4. Materialized bounding box column (자주 검색시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; COLUMN bbox BOX2D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  GENERATED&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ALWAYS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ST_Envelope(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) STORED;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist(bbox);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;gist-vs-sp-gist-vs-brin-공간&quot;&gt;GiST vs SP-GiST vs BRIN (공간)&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;인덱스&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;GiST&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;일반 (Point, Polygon, Range) - R-Tree 기반&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SP-GiST&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Point only, Quadtree - 더 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BRIN&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;시계열 좌표 (센서 데이터)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자세한 건 [[gin-index-deep]] / [[gin-gist-hash-indexes]].&lt;/p&gt;
&lt;h2 id=&quot;r-tree-알고리즘-3d&quot;&gt;R-Tree 알고리즘 (3D+)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[&quot;R-Tree 확장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; RD[&quot;R-Tree in R^d (d dimensions)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Priority[&quot;Priority R-Tree (I/O 최적)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; M[&quot;M-Tree (metric space, arbitrary distance)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; HR[&quot;Hilbert R-Tree (space-filling curve)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;M-Tree&lt;/strong&gt; = &lt;em&gt;임의 거리 함수&lt;/em&gt; (문자열 편집 거리 등) 가능. &lt;strong&gt;HNSW&lt;/strong&gt; (벡터 검색) 은 완전히 다른 접근.&lt;/p&gt;
&lt;p&gt;자세한 벡터 검색은 [[elasticsearch-vector-search]], [[Redis Vector Search]].&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; (overlap) 만 쓰고 refinement 안 함&lt;/strong&gt; = MBR 겹치는 &lt;em&gt;false positive&lt;/em&gt;. &lt;code&gt;ST_Intersects&lt;/code&gt; 로 정밀.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geography vs Geometry 혼용&lt;/strong&gt; = 거리 단위 다름. 명시.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SRID 불일치&lt;/strong&gt; = 검색 실패. &lt;code&gt;ST_Transform&lt;/code&gt; 으로 통일.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;큰 폴리곤의 R-Tree&lt;/strong&gt; = MBR 이 너무 커서 &lt;em&gt;false positive 폭증&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;[[b-tree-internals]]&lt;/li&gt;
&lt;li&gt;[[b-plus-tree-internals]]&lt;/li&gt;
&lt;li&gt;[[gin-index-deep]]&lt;/li&gt;
&lt;li&gt;[[gin-gist-hash-indexes]]&lt;/li&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;li&gt;[[elasticsearch-vector-search]] (다른 접근)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>r-tree</category><category>spatial</category><category>index</category><category>gis</category><category>postgis</category><author>koa (김신건)</author></item><item><title>[DB Internals] Redis 8 / Valkey 9: 라이센스 분기, 신 데이터 구조, 실전 운영</title><link>https://shinkeonkim.com/wiki/database-internals/redis/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis/</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;Redis&lt;/strong&gt; 는 &lt;em&gt;in-memory key-value 데이터 구조 서버&lt;/em&gt;. 2009년 &lt;a href=&quot;https://github.com/antirez&quot;&gt;Salvatore Sanfilippo (antirez)&lt;/a&gt; 가 &lt;a href=&quot;https://github.com/redis/redis/commit/ed9b544e10c731b32a0876d56b9215f0c89f7a73&quot;&gt;Lua Manuscripts&lt;/a&gt; 의 한 줄에서 시작해 &lt;em&gt;서비스 인프라의 표준 부품&lt;/em&gt; 이 되었다.&lt;/p&gt;
&lt;p&gt;2026-06 시점의 풍경은 &lt;em&gt;2년 전과 완전히 다르다&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;라이센스 분기&lt;/strong&gt;: 2024-03 의 BSD 종료 이후 &lt;a href=&quot;https://valkey.io&quot;&gt;Valkey&lt;/a&gt; 라는 &lt;em&gt;Linux Foundation 산하 BSD fork&lt;/em&gt; 가 본격 자리잡았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;antirez 복귀&lt;/strong&gt;: 2024-11 에 antirez 가 Redis 사로 돌아오고, 2025-05 의 &lt;em&gt;Redis 8&lt;/em&gt; 에서 &lt;em&gt;AGPLv3 옵션 추가&lt;/em&gt; + 새 데이터 구조 &lt;em&gt;vector set&lt;/em&gt; 를 직접 만들어 발표.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack 통합&lt;/strong&gt;: RediSearch, RedisJSON, RedisTimeSeries, Bloom, CMS, TopK 가 &lt;em&gt;Redis 8 코어&lt;/em&gt; 로 들어왔다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vector DB 로서의 부상&lt;/strong&gt;: LLM semantic cache, conversation memory, agent memory 의 &lt;em&gt;de facto&lt;/em&gt; 저장소.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 페이지는 &lt;em&gt;2026-06 시점의 운영자/도입자 시점&lt;/em&gt; 에서 Redis / Valkey 양쪽의 동향과 &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;2009&lt;/td&gt;&lt;td&gt;antirez 가 Redis 0.x 공개. BSD-3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2018-2019&lt;/td&gt;&lt;td&gt;Redis Labs (이후 Redis Inc.) 가 &lt;em&gt;모듈 (RediSearch, RedisJSON 등)&lt;/em&gt; 에 RSAL/Commons Clause 적용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2024-03-20&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Redis 7.4&lt;/strong&gt; 부터 &lt;em&gt;core&lt;/em&gt; 까지 &lt;a href=&quot;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&quot;&gt;RSALv2 + SSPLv1 dual&lt;/a&gt;. BSD 종료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2024-03-28&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Linux Foundation 이 &lt;a href=&quot;https://valkey.io&quot;&gt;Valkey&lt;/a&gt; fork 발표&lt;/strong&gt;. Redis 7.2.4 기반 BSD-3. AWS / Google / Oracle / Snap / Ericsson 등 참여&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2024-04-16&lt;/td&gt;&lt;td&gt;Valkey 7.2.5 GA (BSD-3, 첫 안정 fork 릴리즈)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2024-09-16&lt;/td&gt;&lt;td&gt;Valkey 8.0 GA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2024-11&lt;/td&gt;&lt;td&gt;&lt;strong&gt;&lt;a href=&quot;https://antirez.com/news/144&quot;&gt;antirez 가 Redis 로 복귀&lt;/a&gt;&lt;/strong&gt; (developer evangelist). 커뮤니티 신호 큼&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2025-04-02&lt;/td&gt;&lt;td&gt;Valkey 8.1 (new hash table, 50M ZSet 처리)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2025-05-01&lt;/td&gt;&lt;td&gt;&lt;strong&gt;&lt;a href=&quot;https://redis.io/blog/agplv3/&quot;&gt;Redis 8 + AGPLv3 옵션 추가&lt;/a&gt;&lt;/strong&gt;. Redis Stack 코어 통합. vector set 첫 공개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2025-10-21&lt;/td&gt;&lt;td&gt;Valkey 9.0 (atomic slot migration, hash field expiration, numbered databases)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2026-05-19&lt;/td&gt;&lt;td&gt;Valkey 9.1 (보안 / 성능 / 관측성)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;핵심-의미&quot;&gt;핵심 의미&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt; 는 &lt;em&gt;AGPLv3 (OSI 승인) / RSALv2 / SSPLv1&lt;/em&gt; 의 &lt;em&gt;triple license&lt;/em&gt;. 사용자는 &lt;em&gt;AGPLv3 옵션&lt;/em&gt; 으로 &lt;em&gt;진짜 OSS&lt;/em&gt; 로 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Valkey&lt;/strong&gt; 는 &lt;em&gt;BSD-3&lt;/em&gt;. 라이센스 제약이 가장 가볍다. AWS / Google / Oracle 의 &lt;em&gt;managed 서비스&lt;/em&gt; 가 대부분 Valkey 기반.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;API 호환&lt;/em&gt; 은 &lt;em&gt;대체로&lt;/em&gt; 유지된다. 2026-06 시점 두 줄기 모두 &lt;em&gt;RESP3 호환&lt;/em&gt;. 다만 &lt;em&gt;신 데이터 구조 (vector set, Array, RediSearch SVS-VAMANA)&lt;/em&gt; 는 &lt;em&gt;각자 자기 동네에서 먼저&lt;/em&gt; 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;도입 결정: &lt;em&gt;managed&lt;/em&gt; 면 클라우드 벤더가 미는 &lt;em&gt;Valkey&lt;/em&gt; 를 우선 검토. &lt;em&gt;Stack 기능 (search, JSON, time-series)&lt;/em&gt; 을 &lt;em&gt;통째로&lt;/em&gt; 쓰려면 &lt;em&gt;Redis 8&lt;/em&gt;. 직접 호스팅하면 &lt;em&gt;AGPL 의 obligation&lt;/em&gt; 을 평가.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;redis-8-시리즈-2025-05--&quot;&gt;Redis 8 시리즈 (2025-05 ~ )&lt;/h2&gt;
&lt;h3 id=&quot;redis-80-2025-05&quot;&gt;Redis 8.0 (2025-05)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vector set 데이터 구조 (preview)&lt;/strong&gt;: &lt;em&gt;antirez 가 직접 작성&lt;/em&gt;. HNSW 인덱싱 + 도메인 임베딩 친화 API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack 통합&lt;/strong&gt;: &lt;code&gt;RediSearch&lt;/code&gt;, &lt;code&gt;RedisJSON&lt;/code&gt;, &lt;code&gt;RedisTimeSeries&lt;/code&gt;, &lt;code&gt;RedisBloom&lt;/code&gt;, &lt;code&gt;Cuckoo&lt;/code&gt;, &lt;code&gt;Count-Min Sketch&lt;/code&gt;, &lt;code&gt;TopK&lt;/code&gt; 가 &lt;em&gt;core 안&lt;/em&gt; 으로. 더 이상 &lt;em&gt;modules 묶음&lt;/em&gt; 을 별도 배포할 필요가 없다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;새 hash 명령어&lt;/strong&gt;: &lt;code&gt;HGETEX&lt;/code&gt;, &lt;code&gt;HSETEX&lt;/code&gt;, &lt;code&gt;HGETDEL&lt;/code&gt; (세션 / 캐시 패턴에서 &lt;em&gt;atomic + ttl&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능&lt;/strong&gt;: 일부 명령어 &lt;em&gt;최대 87% latency 단축&lt;/em&gt;, replica 메모리 &lt;em&gt;35% 절감&lt;/em&gt;, &lt;em&gt;2x throughput&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장 ACL&lt;/strong&gt;: 새 데이터 구조에 맞춘 권한 모델.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;redis-82&quot;&gt;Redis 8.2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Streams&lt;/strong&gt;: &lt;code&gt;XDELEX&lt;/code&gt; / &lt;code&gt;XACKDEL&lt;/code&gt; (라이프사이클 명령). consumer group 관리가 &lt;em&gt;세분화&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BITOP&lt;/strong&gt; 새 연산자 (복잡한 bitmap 워크플로).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RediSearch&lt;/strong&gt;: &lt;code&gt;SVS-VAMANA&lt;/code&gt; &lt;em&gt;벡터 인덱스 타입&lt;/em&gt;, &lt;code&gt;SHARD_K_RATIO&lt;/code&gt; (KNN 샤드 후보 조절).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyspace notifications&lt;/strong&gt;: 새 event type 으로 &lt;em&gt;데이터 변경 추적&lt;/em&gt; 강화.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;redis-84&quot;&gt;Redis 8.4&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strings&lt;/strong&gt;: &lt;em&gt;atomic compare-and-set&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streams&lt;/strong&gt;: &lt;em&gt;idle entry claiming&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt;: &lt;em&gt;hybrid search&lt;/em&gt; (벡터 + 텍스트 + 태그를 한 쿼리에서).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SIMD 최적화&lt;/strong&gt;, &lt;strong&gt;JSON 처리 개선&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;redis-86--88&quot;&gt;Redis 8.6 → 8.8&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;새 데이터 구조&lt;/em&gt; &lt;strong&gt;&lt;code&gt;Array&lt;/code&gt;&lt;/strong&gt; (antirez 직접). &lt;em&gt;append-mostly + index access&lt;/em&gt; 워크로드 타겟.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;INCREX&lt;/code&gt;&lt;/strong&gt;: &lt;em&gt;INCR + 윈도우 카운터 + bounds + expiration&lt;/em&gt; 통합 &lt;em&gt;rate limiter&lt;/em&gt;. 한 줄로 토큰 버킷 비슷한 패턴 구현 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;XNACK&lt;/code&gt;&lt;/strong&gt;: streams 에서 &lt;em&gt;consumer 가 명시적으로 메시지를 release&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ZUNION&lt;/code&gt; / &lt;code&gt;ZINTER&lt;/code&gt; / &lt;code&gt;ZUNIONSTORE&lt;/code&gt; / &lt;code&gt;ZINTERSTORE&lt;/code&gt;&lt;/strong&gt;: 새 &lt;code&gt;COUNT&lt;/code&gt; aggregator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;JSON.SET&lt;/code&gt;&lt;/strong&gt;: 새 &lt;code&gt;FPHA&lt;/code&gt; argument (homogeneous FP array).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TS.RANGE&lt;/code&gt; / &lt;code&gt;TS.MRANGE&lt;/code&gt;&lt;/strong&gt;: 한 명령에서 &lt;em&gt;multiple aggregators&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;FT.HYBRID&lt;/code&gt;&lt;/strong&gt;: 새 &lt;code&gt;KNN&lt;/code&gt; 절 옵션 + &lt;code&gt;FT.PROFILE&lt;/code&gt; 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;valkey-시리즈-2024-04--&quot;&gt;Valkey 시리즈 (2024-04 ~ )&lt;/h2&gt;
&lt;h3 id=&quot;valkey-725-2024-04&quot;&gt;Valkey 7.2.5 (2024-04)&lt;/h3&gt;
&lt;p&gt;Redis 7.2.4 기반 첫 GA. BSD-3. &lt;em&gt;드롭인 호환&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;valkey-80-2024-09&quot;&gt;Valkey 8.0 (2024-09)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;메모리 효율 개선 (압축 인코딩 튜닝).&lt;/li&gt;
&lt;li&gt;1M RPS 벤치마크 달성 (싱글 노드 + 클라이언트 fleet).&lt;/li&gt;
&lt;li&gt;새 &lt;em&gt;async I/O&lt;/em&gt; 모델.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;valkey-81-2025-04&quot;&gt;Valkey 8.1 (2025-04)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;새 &lt;em&gt;hash table&lt;/em&gt; 디자인 (&lt;a href=&quot;https://valkey.io/blog/new-hash-table/&quot;&gt;blog&lt;/a&gt;). 50M 멤버 ZSet 시나리오에서 메모리 / 처리량 &lt;em&gt;동시 개선&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;성능 / 관측성 강화.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;valkey-90-2025-10-21&quot;&gt;Valkey 9.0 (2025-10-21)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atomic Slot Migration&lt;/strong&gt;: 리샤딩이 &lt;em&gt;원자적&lt;/em&gt;. 기존의 &lt;em&gt;복잡한 PRE/MIGRATE/MIGRATING/STABLE&lt;/em&gt; 트랜잭션을 &lt;em&gt;추상화&lt;/em&gt; 했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash Field Expiration&lt;/strong&gt;: 키 전체가 아니라 &lt;em&gt;해시 필드 하나마다&lt;/em&gt; TTL. 세션 / 토큰 관리 패턴이 &lt;em&gt;훨씬 간결&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Numbered Databases (재설계)&lt;/strong&gt;: cluster 모드에서도 namespacing 가능하도록 &lt;em&gt;부활&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vector Search 모듈 1.0&lt;/strong&gt; (별도 GA: 2025-07).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bloom Filter / JSON / Search&lt;/strong&gt; 가 &lt;em&gt;valkey-bundle&lt;/em&gt; 단일 컨테이너로 묶임.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;valkey-91-2026-05-19&quot;&gt;Valkey 9.1 (2026-05-19)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;보안 강화 (TLS 운영 옵션, 기본값 정비).&lt;/li&gt;
&lt;li&gt;관측성 (&lt;code&gt;COMMANDLOG&lt;/code&gt;, &lt;code&gt;SLOT-STATS&lt;/code&gt;, per-thread I/O metrics).&lt;/li&gt;
&lt;li&gt;효율성, 클라이언트 라이브러리 호환성.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;데이터-구조-카탈로그-2026&quot;&gt;데이터 구조 카탈로그 (2026)&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;String&lt;/td&gt;&lt;td&gt;&lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;INCR&lt;/code&gt;, &lt;code&gt;APPEND&lt;/code&gt;&lt;/td&gt;&lt;td&gt;캐시, 카운터, flag&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;List&lt;/td&gt;&lt;td&gt;&lt;code&gt;LPUSH&lt;/code&gt;, &lt;code&gt;RPOP&lt;/code&gt;, &lt;code&gt;LRANGE&lt;/code&gt;, &lt;code&gt;BLPOP&lt;/code&gt;&lt;/td&gt;&lt;td&gt;큐, 큐 (FIFO), 최근 이벤트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hash&lt;/td&gt;&lt;td&gt;&lt;code&gt;HSET&lt;/code&gt;, &lt;code&gt;HGET&lt;/code&gt;, &lt;code&gt;HGETEX&lt;/code&gt;, &lt;code&gt;HEXPIRE&lt;/code&gt; (필드 단위)&lt;/td&gt;&lt;td&gt;객체, 세션, 사용자 프로필&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Set&lt;/td&gt;&lt;td&gt;&lt;code&gt;SADD&lt;/code&gt;, &lt;code&gt;SISMEMBER&lt;/code&gt;, &lt;code&gt;SINTER&lt;/code&gt;&lt;/td&gt;&lt;td&gt;태그, 멤버십, 중복 제거&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sorted Set (ZSet)&lt;/td&gt;&lt;td&gt;&lt;code&gt;ZADD&lt;/code&gt;, &lt;code&gt;ZRANGEBYSCORE&lt;/code&gt;, &lt;code&gt;ZRANGEBYLEX&lt;/code&gt;, &lt;code&gt;ZUNION ... COUNT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;리더보드, 시간순 인덱스, 우선순위 큐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Stream&lt;/td&gt;&lt;td&gt;&lt;code&gt;XADD&lt;/code&gt;, &lt;code&gt;XREAD&lt;/code&gt;, &lt;code&gt;XREADGROUP&lt;/code&gt;, &lt;code&gt;XACK&lt;/code&gt;, &lt;code&gt;XACKDEL&lt;/code&gt;, &lt;code&gt;XNACK&lt;/code&gt;&lt;/td&gt;&lt;td&gt;append-only log, 메시지 큐, event sourcing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HyperLogLog&lt;/td&gt;&lt;td&gt;&lt;code&gt;PFADD&lt;/code&gt;, &lt;code&gt;PFCOUNT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;대규모 cardinality 추정 (UV 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Geo&lt;/td&gt;&lt;td&gt;&lt;code&gt;GEOADD&lt;/code&gt;, &lt;code&gt;GEOSEARCH&lt;/code&gt;&lt;/td&gt;&lt;td&gt;위치 인덱스, 반경 검색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bitmap&lt;/td&gt;&lt;td&gt;&lt;code&gt;SETBIT&lt;/code&gt;, &lt;code&gt;BITCOUNT&lt;/code&gt;, &lt;code&gt;BITOP&lt;/code&gt;&lt;/td&gt;&lt;td&gt;일자별 활성 사용자, 권한 비트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bitfield&lt;/td&gt;&lt;td&gt;&lt;code&gt;BITFIELD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;작은 정수 패킹 (memory micro-opt)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Vector Set&lt;/strong&gt; &lt;em&gt;(Redis 8)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;VADD&lt;/code&gt;, &lt;code&gt;VSIM&lt;/code&gt;, &lt;code&gt;VLINKS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;임베딩 검색, semantic cache, agent memory&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Array&lt;/strong&gt; &lt;em&gt;(Redis 8.8)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ARRPUSH&lt;/code&gt;, &lt;code&gt;ARRGET&lt;/code&gt; 등&lt;/td&gt;&lt;td&gt;append-mostly index 패턴&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bloom / Cuckoo / CMS / TopK&lt;/td&gt;&lt;td&gt;&lt;code&gt;BF.ADD&lt;/code&gt;, &lt;code&gt;BF.EXISTS&lt;/code&gt; 등&lt;/td&gt;&lt;td&gt;확률적 자료구조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JSON &lt;em&gt;(Redis 8 / Valkey JSON)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;JSON.SET&lt;/code&gt;, &lt;code&gt;JSON.GET&lt;/code&gt;, &lt;code&gt;JSON.ARRAPPEND&lt;/code&gt;&lt;/td&gt;&lt;td&gt;문서 저장, partial update&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Time Series&lt;/td&gt;&lt;td&gt;&lt;code&gt;TS.ADD&lt;/code&gt;, &lt;code&gt;TS.RANGE&lt;/code&gt;, &lt;code&gt;TS.MRANGE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;메트릭, IoT 텔레메트리&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;stream-vs-pubsub&quot;&gt;Stream vs Pub/Sub&lt;/h3&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Pub/Sub&lt;/th&gt;&lt;th&gt;Stream&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;영속성&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt; (구독자가 없으면 사라짐)&lt;/td&gt;&lt;td&gt;영속 (TTL/MAXLEN 으로 관리)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consumer group&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;&lt;em&gt;있음&lt;/em&gt; (Kafka 와 유사)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ACK&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;&lt;code&gt;XACK&lt;/code&gt;, &lt;code&gt;XACKDEL&lt;/code&gt;, &lt;code&gt;XNACK&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합&lt;/td&gt;&lt;td&gt;알림, 신호 (lossy OK)&lt;/td&gt;&lt;td&gt;메시지 큐, event log&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Pub/Sub 의 &lt;em&gt;fan-out 신호&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;p&gt;Stream / List 큐의 &lt;em&gt;FIFO&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;p&gt;자세한 비교는 [[Redis Pub Sub vs Streams]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;redisearch--valkey-search-벡터--텍스트&quot;&gt;RediSearch / Valkey Search: 벡터 + 텍스트&lt;/h2&gt;
&lt;p&gt;LLM 시대의 &lt;em&gt;Redis 의 두 번째 정체성&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FT.CREATE idx ON HASH PREFIX 1 doc:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  SCHEMA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    title TEXT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tag   TAG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    embedding VECTOR HNSW 6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      TYPE FLOAT32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      DIM 1536&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      DISTANCE_METRIC COSINE&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;FT.SEARCH idx &quot;@tag:{backend} =&gt;[KNN 10 @embedding $vec AS score]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  PARAMS 2 vec &amp;#x3C;bytes&gt; DIALECT 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;알고리즘&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;FLAT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;정확한 brute-force, 작은 데이터셋&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HNSW&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Approximate Nearest Neighbor 표준. 빠름, 메모리 큼&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SVS-VAMANA&lt;/code&gt; &lt;em&gt;(Redis 8.2+)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;대규모 + 메모리 효율. Intel/Microsoft 가 밀고 있는 알고리즘&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Hybrid search&lt;/em&gt; (&lt;code&gt;FT.HYBRID&lt;/code&gt;) 는 &lt;em&gt;키워드 + 벡터&lt;/em&gt; 점수를 합쳐 &lt;em&gt;RRF (Reciprocal Rank Fusion)&lt;/em&gt; 로 섞는다. RAG 시스템에서 &lt;em&gt;키워드 매치 + 의미 검색&lt;/em&gt; 을 한 호출로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;실전-운영-패턴&quot;&gt;실전 운영 패턴&lt;/h2&gt;
&lt;h3 id=&quot;1-캐시-가장-흔한-시작&quot;&gt;1. 캐시 (가장 흔한 시작)&lt;/h3&gt;
&lt;p&gt;캐시 HIT (~6ms) 와 MISS (~72ms) 의 직관:&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;pre class=&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;ul&gt;
&lt;li&gt;&lt;strong&gt;TTL 정책&lt;/strong&gt;: &lt;em&gt;짧고 자주&lt;/em&gt; 가 안전. 데이터 모양 / 인기 분포에 따라 &lt;em&gt;layered TTL&lt;/em&gt; (자주 쓰이는 키는 길게, 나머지 짧게).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache stampede&lt;/strong&gt;: TTL 만료 시 &lt;em&gt;동시에 많은 클라이언트&lt;/em&gt; 가 백엔드를 친다. &lt;em&gt;probabilistic early refresh&lt;/em&gt;, &lt;em&gt;single-flight lock&lt;/em&gt;, &lt;em&gt;stale-while-revalidate&lt;/em&gt; 패턴으로 완화.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache aside vs Read-through vs Write-behind&lt;/strong&gt;: Rails 의 &lt;code&gt;Rails.cache.fetch&lt;/code&gt; 는 &lt;em&gt;cache aside&lt;/em&gt;. write-through 는 트랜잭션 보장이 필요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자세한 패턴 카탈로그는 [[Redis Cache Patterns]] 참고.&lt;/p&gt;
&lt;h3 id=&quot;2-분산-락&quot;&gt;2. 분산 락&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SET key value NX PX &amp;#x3C;ms&gt;&lt;/code&gt; + Lua 로 &lt;em&gt;compare-and-delete&lt;/em&gt;. Redis 의 &lt;a href=&quot;https://redis.io/docs/latest/develop/use/patterns/distributed-locks/&quot;&gt;Redlock&lt;/a&gt; 알고리즘.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;Martin Kleppmann 의 비판&lt;/a&gt; 과 antirez 의 &lt;a href=&quot;http://antirez.com/news/101&quot;&gt;반박&lt;/a&gt; 이 유명. &lt;em&gt;정확한 mutual exclusion&lt;/em&gt; 을 보장해야 하는 영역 (예: 결제) 에는 &lt;em&gt;Redlock 만으로 충분하지 않다&lt;/em&gt;. &lt;em&gt;fencing token&lt;/em&gt; 이 추가로 필요.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;idempotent + retry-safe&lt;/em&gt; 한 작업에는 Redlock 단순 패턴이 &lt;em&gt;충분히 실용적&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-큐-sidekiq--bullmq--rq&quot;&gt;3. 큐 (Sidekiq / BullMQ / RQ)&lt;/h3&gt;
&lt;p&gt;producer / consumer 의 &lt;em&gt;blocking queue&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;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;a href=&quot;https://github.com/sidekiq/sidekiq&quot;&gt;Sidekiq&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;td&gt;List + Sorted Set (scheduled) + Hash (job hash)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.bullmq.io/&quot;&gt;BullMQ&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Node&lt;/td&gt;&lt;td&gt;Stream + Sorted Set, 강력한 priority&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://python-rq.org/&quot;&gt;RQ&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;List 기반 단순&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/resque/resque&quot;&gt;Resque&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;td&gt;List 기반 (Sidekiq 의 선조)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.celeryq.dev/&quot;&gt;Celery (Redis broker)&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;List + ZSet&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Sidekiq Pro/Enterprise&lt;/em&gt; 는 &lt;em&gt;reliable fetch&lt;/em&gt; (RPOPLPUSH 패턴) 으로 &lt;em&gt;crash 시 작업 손실&lt;/em&gt; 을 막는다. &lt;em&gt;Stream + consumer group&lt;/em&gt; 으로 다시 짤 수 있는 영역이지만 &lt;em&gt;호환성&lt;/em&gt; 으로 List 가 유지.&lt;/p&gt;
&lt;h3 id=&quot;4-vector-store--semantic-cache-llm-시대&quot;&gt;4. Vector store / Semantic cache (LLM 시대)&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;th&gt;구현&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Semantic cache&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;질문 embedding 으로 &lt;em&gt;KNN search&lt;/em&gt; → 임계값 이상이면 &lt;em&gt;캐시 히트&lt;/em&gt;. LLM 호출 비용 절감&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Conversation memory&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;XADD&lt;/code&gt; 로 turn 을 append + &lt;code&gt;VADD&lt;/code&gt; 로 embedding 저장. 검색 시 &lt;em&gt;최근 N turn + 의미 유사 M turn&lt;/em&gt; 혼합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Agent memory (Mem0 식)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;세션 단위 short-term&lt;/em&gt; + &lt;em&gt;장기 long-term&lt;/em&gt; 분리. Valkey + Mem0 사례 (&lt;a href=&quot;https://valkey.io/blog/ai-agent-memory-with-valkey-and-mem0/&quot;&gt;blog&lt;/a&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;RAG retrieval&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;FT.HYBRID&lt;/code&gt; 로 &lt;em&gt;키워드 + 벡터&lt;/em&gt; 결합&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;5-rate-limiting&quot;&gt;5. Rate Limiting&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Token bucket&lt;/strong&gt;: ZSet timestamp 또는 &lt;em&gt;Lua script&lt;/em&gt; 로 atomic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sliding window&lt;/strong&gt;: 마찬가지로 &lt;em&gt;Lua&lt;/em&gt; 또는 &lt;code&gt;INCREX&lt;/code&gt; &lt;em&gt;(Redis 8.8)&lt;/em&gt; 단일 명령.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fixed window&lt;/strong&gt;: &lt;code&gt;INCR&lt;/code&gt; + &lt;code&gt;EXPIRE NX&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;운영-결정점&quot;&gt;운영 결정점&lt;/h2&gt;
&lt;h3 id=&quot;persistence-rdb-vs-aof&quot;&gt;Persistence: RDB vs AOF&lt;/h3&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;RDB&lt;/td&gt;&lt;td&gt;주기적 snapshot&lt;/td&gt;&lt;td&gt;빠름, &lt;em&gt;분 단위 손실&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AOF&lt;/td&gt;&lt;td&gt;모든 write 를 &lt;em&gt;append-only file&lt;/em&gt;&lt;/td&gt;&lt;td&gt;손실 최소, &lt;em&gt;느림 + 큰 파일&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RDB + AOF&lt;/td&gt;&lt;td&gt;둘 다&lt;/td&gt;&lt;td&gt;&lt;em&gt;부팅&lt;/em&gt; 빠르고 &lt;em&gt;손실&lt;/em&gt; 적음. 권장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;appendfsync everysec&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1초 1회 fsync&lt;/td&gt;&lt;td&gt;손실 ≤ 1초, 거의 표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;appendfsync always&lt;/code&gt;&lt;/td&gt;&lt;td&gt;매 write fsync&lt;/td&gt;&lt;td&gt;손실 0, throughput 큰 영향&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;캐시 전용이면 &lt;em&gt;둘 다 끄고&lt;/em&gt; 도 OK. &lt;em&gt;primary data store&lt;/em&gt; 면 &lt;em&gt;AOF + RDB hybrid&lt;/em&gt; + replica.&lt;/p&gt;
&lt;h3 id=&quot;cluster-vs-sentinel-vs-standalone&quot;&gt;Cluster vs Sentinel vs Standalone&lt;/h3&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;Standalone&lt;/td&gt;&lt;td&gt;단일 노드 + replica 옵션. &lt;em&gt;작은 워크로드&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sentinel&lt;/td&gt;&lt;td&gt;자동 failover, &lt;em&gt;키 분할 없음&lt;/em&gt;. &lt;em&gt;데이터셋이 한 노드 메모리 안&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cluster&lt;/td&gt;&lt;td&gt;16384 슬롯 분할. 수평 확장. &lt;em&gt;멀티 키 명령 제약&lt;/em&gt; (&lt;code&gt;{hashtag}&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;Valkey 9.0 의 &lt;em&gt;atomic slot migration&lt;/em&gt; 으로 &lt;em&gt;Cluster 운영의 가장 무서운 부분&lt;/em&gt; 이 크게 완화. 2026 시점 &lt;em&gt;Cluster 진입 장벽&lt;/em&gt; 이 낮아졌다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;메모리--eviction&quot;&gt;메모리 / Eviction&lt;/h3&gt;
&lt;p&gt;LRU 의 동작 직관:&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;maxmemory &amp;#x3C;bytes&gt;&lt;/code&gt; + &lt;code&gt;maxmemory-policy&lt;/code&gt;:&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Policy&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;noeviction&lt;/code&gt; (기본)&lt;/td&gt;&lt;td&gt;메모리 초과 시 write 에러&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-lru&lt;/code&gt;&lt;/td&gt;&lt;td&gt;모든 키 중 LRU (캐시 전용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;volatile-lru&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TTL 있는 키 중 LRU&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-lfu&lt;/code&gt;&lt;/td&gt;&lt;td&gt;LFU (자주 쓰이는 키 우선 유지)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-random&lt;/code&gt;&lt;/td&gt;&lt;td&gt;랜덤&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;memory fragmentation&lt;/em&gt; 모니터링: &lt;code&gt;INFO memory&lt;/code&gt; 의 &lt;code&gt;mem_fragmentation_ratio&lt;/code&gt; (이상적 1.0 ~ 1.5). 1.5 초과면 jemalloc 의 fragmentation. &lt;code&gt;MEMORY PURGE&lt;/code&gt; 또는 &lt;em&gt;restart&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;pipeline-vs-lua-vs-function&quot;&gt;Pipeline vs Lua vs Function&lt;/h3&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;Pipeline&lt;/td&gt;&lt;td&gt;여러 명령을 &lt;em&gt;한 번에 보내고 응답을 모음&lt;/em&gt;. atomic 아님. RTT 절약&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MULTI / EXEC&lt;/code&gt;&lt;/td&gt;&lt;td&gt;atomic batch. WATCH 로 &lt;em&gt;optimistic&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EVAL&lt;/code&gt; (Lua)&lt;/td&gt;&lt;td&gt;atomic 스크립트. &lt;em&gt;SCRIPT LOAD&lt;/em&gt; 로 캐시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;FUNCTION&lt;/code&gt; &lt;em&gt;(Redis 7+)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Lua 함수를 &lt;em&gt;서버에 등록&lt;/em&gt;. 명명 + 버전 관리 가능. Lua 보다 권장&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;&lt;a href=&quot;https://valkey.io&quot;&gt;Valkey&lt;/a&gt;&lt;/td&gt;&lt;td&gt;BSD-3&lt;/td&gt;&lt;td&gt;Redis 7.2 fork. Linux Foundation. AWS / Google managed&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.dragonflydb.io/&quot;&gt;DragonflyDB&lt;/a&gt;&lt;/td&gt;&lt;td&gt;BSL 1.1&lt;/td&gt;&lt;td&gt;&lt;em&gt;멀티스레드&lt;/em&gt; 재구현. Redis API 호환. 25x throughput claim&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.keydb.dev/&quot;&gt;KeyDB&lt;/a&gt;&lt;/td&gt;&lt;td&gt;BSD-3 (Snap 인수)&lt;/td&gt;&lt;td&gt;Redis fork + 멀티스레드. &lt;em&gt;최근 활동 줄어듦&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://memcached.org/&quot;&gt;Memcached&lt;/a&gt;&lt;/td&gt;&lt;td&gt;BSD-3&lt;/td&gt;&lt;td&gt;&lt;em&gt;순수 캐시&lt;/em&gt;. 데이터 구조 / 영속성 없음. 멀티스레드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://microsoft.github.io/garnet/&quot;&gt;Garnet&lt;/a&gt;&lt;/td&gt;&lt;td&gt;MIT (Microsoft)&lt;/td&gt;&lt;td&gt;RESP 호환 &lt;em&gt;.NET&lt;/em&gt; 구현. cluster 미흡&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://etcd.io/&quot;&gt;Etcd&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Apache 2.0&lt;/td&gt;&lt;td&gt;강한 일관성 (Raft). &lt;em&gt;KV + watch&lt;/em&gt;. 캐시 아닌 &lt;em&gt;coordination&lt;/em&gt; 용&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;선택 기준:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;managed 가격 + 호환성&lt;/em&gt; → Valkey (대형 클라우드 추천)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;throughput&lt;/em&gt; + &lt;em&gt;멀티코어 풀활용&lt;/em&gt; → DragonflyDB&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Stack 기능 (vector / search / JSON)&lt;/em&gt; 전부 + AGPL 가능 → Redis 8&lt;/li&gt;
&lt;li&gt;&lt;em&gt;순수 캐시 + 단순함&lt;/em&gt; → Memcached&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;김신건의-현장-메모-hera-webapp-운영&quot;&gt;김신건의 현장 메모 (hera-webapp 운영)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Sidekiq + Redis&lt;/em&gt; 4년 운영. 가장 자주 본 알람은 &lt;em&gt;Sidekiq 큐 적체&lt;/em&gt; 와 &lt;em&gt;Redis 메모리 사용량&lt;/em&gt;. &lt;code&gt;INFO memory&lt;/code&gt; 와 &lt;code&gt;INFO clients&lt;/code&gt; 를 &lt;em&gt;대시보드 핵심 지표&lt;/em&gt; 로.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;enqueue 폭발&lt;/em&gt; 은 보통 &lt;em&gt;큐별 분리&lt;/em&gt; 와 &lt;em&gt;concurrency 분리&lt;/em&gt; 로 해결. &lt;em&gt;critical / default / mailers / low&lt;/em&gt; 등 우선순위 큐를 정의하고 워커 풀을 &lt;em&gt;큐별로 분리&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;대량 job + Redis 메모리 압박&lt;/em&gt; 시: &lt;em&gt;job payload 를 Redis 에 넣지 말고&lt;/em&gt; &lt;em&gt;DB id 만&lt;/em&gt; (또는 &lt;em&gt;S3 reference&lt;/em&gt;). Sidekiq 의 default 가 &lt;em&gt;전체 args 직렬화&lt;/em&gt; 라 의외로 함정.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sentinel&lt;/em&gt; 운영 시 &lt;em&gt;failover 동안 short-lived 클라이언트 timeout&lt;/em&gt; 이 가장 흔한 사고. &lt;em&gt;application level retry&lt;/em&gt; 와 &lt;em&gt;connection pool&lt;/em&gt; 의 &lt;em&gt;failover 동작 명세&lt;/em&gt; 가 같이 가야 한다.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;RediSearch / vector&lt;/em&gt; 는 hera-webapp 시기에는 별 필요 없었지만 &lt;em&gt;역량 리포트&lt;/em&gt; 같은 &lt;em&gt;세션/대화 기반&lt;/em&gt; 흐름에서는 &lt;em&gt;Redis 8 vector set + semantic cache&lt;/em&gt; 가 &lt;em&gt;프롬프트 비용&lt;/em&gt; 을 &lt;em&gt;주 단위 그래프&lt;/em&gt; 로 끌어내릴 수 있는 가장 빠른 손.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;AGPL&lt;/em&gt; 의무: &lt;em&gt;SaaS 노출&lt;/em&gt; 면 &lt;em&gt;AGPL 의 source 공개 의무&lt;/em&gt; 가 켜진다. 도입 전 &lt;em&gt;법무 / 컴플라이언스&lt;/em&gt; 와 확인. &lt;em&gt;Valkey 가 BSD&lt;/em&gt; 라는 점이 &lt;em&gt;managed 환경&lt;/em&gt; 에서 거의 결정 요인.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;p&gt;운영 / 아키텍처 개념:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis Persistence]] (RDB / AOF / Hybrid, fsync 정책)&lt;/li&gt;
&lt;li&gt;[[Redis Replication]] (PSYNC, Sentinel, failover)&lt;/li&gt;
&lt;li&gt;[[Redis Cluster]] (16384 슬롯, MOVED/ASK, atomic slot migration)&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]] (cache-aside, stampede, eviction)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (휘발 신호 vs 영속 로그, consumer group)&lt;/li&gt;
&lt;li&gt;[[Redis Distributed Lock]] (SET NX, Redlock, fencing token)&lt;/li&gt;
&lt;li&gt;[[Redis Vector Search]] (HNSW, SVS-VAMANA, semantic cache)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자료구조별:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis Strings]] (String + Bitmap + Bitfield, counter)&lt;/li&gt;
&lt;li&gt;[[Redis Lists]] (List, quicklist/listpack, Sidekiq 큐)&lt;/li&gt;
&lt;li&gt;[[Redis Hashes]] (Hash + Field TTL (7.4+))&lt;/li&gt;
&lt;li&gt;[[Redis Sets]] (Set, intset / hashtable, 멤버십)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (ZSet, skiplist + dict, 리더보드)&lt;/li&gt;
&lt;li&gt;[[Redis Streams]] (Stream, radix tree, consumer group 내부)&lt;/li&gt;
&lt;li&gt;[[Redis HyperLogLog Geo]] (HLL + Geo, 카디널리티 / 공간 인덱스)&lt;/li&gt;
&lt;li&gt;[[Redis Probabilistic]] (Bloom / Cuckoo / CMS / TopK)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;인접 위키:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[Sidekiq]] (Rails + Redis 큐 운영)&lt;/li&gt;
&lt;li&gt;[[MVCC]] (DB 와의 일관성 패턴 대비)&lt;/li&gt;
&lt;li&gt;[[Zero Downtime Deployment]] (Sentinel / Cluster failover 와 무중단)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;</content:encoded><category>database</category><category>redis</category><category>valkey</category><category>cache</category><category>queue</category><category>vector-search</category><category>key-value</category><category>in-memory</category><author>koa (김신건)</author></item><item><title>[DB] Cassandra: LSM-tree, quorum, eventually consistent</title><link>https://shinkeonkim.com/wiki/database-internals/cassandra/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/cassandra/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Apache Cassandra&lt;/strong&gt; = &lt;em&gt;마스터리스 (peer-to-peer) 분산 KV 스토어&lt;/em&gt;. &lt;em&gt;write 우선&lt;/em&gt;, &lt;em&gt;eventually consistent&lt;/em&gt;, &lt;em&gt;수평 무한 확장&lt;/em&gt;. 2008 Facebook 의 Dynamo + BigTable 결합.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ScyllaDB&lt;/strong&gt; = Cassandra 호환 &lt;em&gt;C++ 재구현&lt;/em&gt;. &lt;em&gt;10x throughput&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;Cassandra&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;토폴로지&lt;/td&gt;&lt;td&gt;&lt;em&gt;peer-to-peer&lt;/em&gt; (master 없음)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;&lt;em&gt;튜닝 가능&lt;/em&gt; (per query)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;스토리지&lt;/td&gt;&lt;td&gt;&lt;em&gt;LSM-tree&lt;/em&gt; (write-optimized)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;쿼리 언어&lt;/td&gt;&lt;td&gt;&lt;em&gt;CQL&lt;/em&gt; (SQL-like)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;인덱스&lt;/td&gt;&lt;td&gt;primary key + secondary (제한)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Join&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;트랜잭션&lt;/td&gt;&lt;td&gt;&lt;em&gt;제한적&lt;/em&gt; (LWT, lightweight transaction)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;lsm-tree-log-structured-merge-tree&quot;&gt;LSM-Tree (Log-Structured Merge-Tree)&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;    Write[Write] --&gt; CommitLog[&quot;Commit Log&amp;#x3C;br/&gt;(WAL)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Write --&gt; Memtable[&quot;Memtable&amp;#x3C;br/&gt;(메모리)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Memtable -.flush.-&gt; SST1[SSTable L0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SST1 -.compact.-&gt; SST2[SSTable L1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SST2 -.compact.-&gt; SST3[SSTable L2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SST3 -.compact.-&gt; SST4[SSTable L3]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;컴포넌트&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Commit Log&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;WAL (crash recovery)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Memtable&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;메모리 정렬 자료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SSTable&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;불변 정렬 파일 (디스크)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Compaction&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;SSTable 들을 &lt;em&gt;큰 SSTable 로 병합&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;외부 정렬 머지의 일반 직관. LSM-tree 의 &lt;em&gt;compaction&lt;/em&gt; 도 &lt;em&gt;level 들을 순차 머지&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;lsm-vs-b-tree&quot;&gt;LSM vs B-tree&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;B-tree&lt;/th&gt;&lt;th&gt;LSM&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Write&lt;/td&gt;&lt;td&gt;&lt;em&gt;in-place&lt;/em&gt; (random)&lt;/td&gt;&lt;td&gt;&lt;em&gt;append&lt;/em&gt; (sequential)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Read&lt;/td&gt;&lt;td&gt;1 lookup&lt;/td&gt;&lt;td&gt;&lt;em&gt;여러 SSTable 검색&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Write amplification&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;td&gt;큼 (compaction)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Read amplification&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;td&gt;큼&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합&lt;/td&gt;&lt;td&gt;OLTP read-heavy&lt;/td&gt;&lt;td&gt;write-heavy, log-like&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;클러스터-토폴로지&quot;&gt;클러스터 토폴로지&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Ring[Token Ring]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N1[Node A&amp;#x3C;br/&gt;token 0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N2[Node B&amp;#x3C;br/&gt;token 1/3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N3[Node C&amp;#x3C;br/&gt;token 2/3]&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;    N1 --- N2 --- N3 --- N1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Key1[hash key=42] --&gt;|시계방향| N2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;consistent hashing ring&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;N 개 노드&lt;/em&gt;, &lt;em&gt;Replication Factor (RF)&lt;/em&gt; 만큼 복제.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;전체가 peer&lt;/em&gt;. master 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;consistency-level-per-query&quot;&gt;Consistency Level (per query)&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Level&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;ANY&lt;/code&gt;&lt;/td&gt;&lt;td&gt;어떤 노드라도 응답 (write)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ONE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1 노드 응답&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;TWO&lt;/code&gt;, &lt;code&gt;THREE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;2 / 3 노드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;QUORUM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;과반 (RF/2 + 1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LOCAL_QUORUM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;같은 DC 안 과반&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EACH_QUORUM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;모든 DC 의 과반&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ALL&lt;/code&gt;&lt;/td&gt;&lt;td&gt;모든 replica&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;quorum-의-w--r--n-조건&quot;&gt;Quorum 의 W + R &gt; N 조건&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;    Write[W = QUORUM] --&gt; N[RF = 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read[R = QUORUM]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cond[&quot;W + R &gt; N → 항상 최신 read&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Write --&gt; Cond&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read --&gt; Cond&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;RF = 3, W = QUORUM (2), R = QUORUM (2). W + R = 4 &gt; 3 → &lt;em&gt;최소 1 노드는 양쪽 모두 봄&lt;/em&gt; → &lt;em&gt;strong consistency&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;read-path&quot;&gt;Read Path&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;    Client-&gt;&gt;Coord: SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;R1: read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;R2: read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par 응답 대기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R1--&gt;&gt;Coord: v1 (ts=100)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R2--&gt;&gt;Coord: v2 (ts=110)&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;    Coord-&gt;&gt;Coord: 최신 (ts=110) 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Coord-&gt;&gt;R1: read repair (백그라운드)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Last-write-wins by timestamp&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;cql-cassandra-query-language&quot;&gt;CQL (Cassandra Query Language)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; KEYSPACE shop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  WITH&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; replication &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:#9ECBFF&quot;&gt;&apos;class&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;NetworkTopologyStrategy&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;dc1&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; orders&lt;/span&gt;&lt;span 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;  user_id UUID,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  order_id TIMEUUID,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  total &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span 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;  status&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (user_id, order_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:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CLUSTERING &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (order_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DESC&lt;/span&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;-- partition key = user_id, clustering key = order_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order_id &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;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders (user_id, order_id, total, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (?, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), ?, ?);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;키&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;Partition Key&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;partition (노드) 결정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Clustering Key&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;partition 안의 정렬&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;lwt-lightweight-transaction&quot;&gt;LWT (Lightweight Transaction)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users (id, email) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (?, ?) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span 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;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; email &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:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;IF&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; email &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ?;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Paxos 기반&lt;/em&gt;. &lt;em&gt;4 round-trip&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;느림&lt;/em&gt;. 가끔만 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;적합--부적합&quot;&gt;적합 / 부적합&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{사용 패턴}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Good[적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Bad[부적합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G1[Write-heavy log]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G2[Time-series]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G3[IoT telemetry]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Good --&gt; G4[수십 TB 데이터]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B1[복잡한 ad-hoc 쿼리]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B2[강한 트랜잭션]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B3[Join 많음]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bad --&gt; B4[작은 데이터셋]&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;Secondary index 남용&lt;/strong&gt; = 거의 항상 &lt;em&gt;전체 노드 fan-out&lt;/em&gt;. 모든 access pattern 에 &lt;em&gt;전용 테이블&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tombstone (삭제 마커) 누적&lt;/strong&gt; = 압축 안 되면 read 폭증. &lt;code&gt;gc_grace_seconds&lt;/code&gt; + 정기 repair.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-partition query&lt;/strong&gt; = 비싸다. partition key 디자인이 &lt;em&gt;결정적&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strong consistency 기대&lt;/strong&gt; = QUORUM 으로도 &lt;em&gt;시계 어긋남 시 잘못된 결과&lt;/em&gt;. &lt;em&gt;NTP + 보수적 timestamp&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;[[dynamodb]] (비슷한 디자인)&lt;/li&gt;
&lt;li&gt;[[mongodb]]&lt;/li&gt;
&lt;li&gt;[[sharding-vs-partitioning]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-consensus]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>cassandra</category><category>nosql</category><category>distributed</category><category>lsm-tree</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB] DynamoDB: PK + SK, single-table design, GSI / LSI</title><link>https://shinkeonkim.com/wiki/database-internals/dynamodb/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/dynamodb/</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;DynamoDB&lt;/strong&gt; 는 AWS 의 &lt;em&gt;fully managed key-value + document NoSQL&lt;/em&gt;. &lt;em&gt;low-latency, infinite scale, schemaless&lt;/em&gt;. &lt;strong&gt;single-table design&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;Primary Key = Partition Key (PK)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           또는 Partition Key + Sort Key (PK + SK) ← Composite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;키&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;PK (Partition Key)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;hash → 물리 partition 결정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SK (Sort Key)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;같은 PK 안의 &lt;em&gt;정렬 키&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;DynamoDB 의 모든 query 는 PK 를 &lt;em&gt;반드시&lt;/em&gt; 지정&lt;/em&gt;. &lt;em&gt;SK 는 range query 가능&lt;/em&gt;. 이 제약이 &lt;em&gt;single-table design 을 강제&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;item-예시&quot;&gt;Item 예시&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;PK&lt;/th&gt;&lt;th&gt;SK&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Data&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;USER#42&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;PROFILE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;user&lt;/td&gt;&lt;td&gt;name, email&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;USER#42&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ORDER#2026-06-25#o_1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;order&lt;/td&gt;&lt;td&gt;total, items&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;USER#42&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ORDER#2026-06-24#o_2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;order&lt;/td&gt;&lt;td&gt;total, items&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;USER#42&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ADDR#home&lt;/code&gt;&lt;/td&gt;&lt;td&gt;address&lt;/td&gt;&lt;td&gt;city, street&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ORDER#o_1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;META&lt;/code&gt;&lt;/td&gt;&lt;td&gt;order&lt;/td&gt;&lt;td&gt;userId, status&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;한 테이블에 여러 entity type 동시 저장&lt;/em&gt;. 한 query 로 &lt;em&gt;user 의 모든 데이터&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;PK = USER#42  → 모든 row (PROFILE, ORDER, ADDR)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PK = USER#42 AND SK begins_with(ORDER#) → 주문만&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;capacity-mode&quot;&gt;Capacity Mode&lt;/h2&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;모드&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Provisioned&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RCU/WCU 미리 설정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;On-Demand&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;자동 스케일, 사용량 청구&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;단위&lt;/th&gt;&lt;th&gt;1 RCU&lt;/th&gt;&lt;th&gt;1 WCU&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Eventually consistent&lt;/td&gt;&lt;td&gt;2 read/sec (4KB)&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Strongly consistent&lt;/td&gt;&lt;td&gt;1 read/sec&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Write&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;1 write/sec (1KB)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;strongly-vs-eventually-consistent-read&quot;&gt;Strongly vs Eventually Consistent Read&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;    Write[Write] --&gt; Leader[Leader replica]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leader -.async.-&gt; R1[Replica 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Leader -.async.-&gt; R2[Replica 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read1[Strong read] --&gt; Leader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read2[Eventual read] --&gt; R1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Read2 --&gt; R2&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;Strong&lt;/th&gt;&lt;th&gt;Eventual&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;약간 높음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cost (RCU)&lt;/td&gt;&lt;td&gt;2배&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;&lt;em&gt;항상 최신&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;최근 쓰기 못 봄 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;gsi-vs-lsi&quot;&gt;GSI vs LSI&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;LSI (Local)&lt;/th&gt;&lt;th&gt;GSI (Global)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;PK&lt;/td&gt;&lt;td&gt;&lt;em&gt;같은 base table 의 PK&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;임의 키&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;동시 생성&lt;/td&gt;&lt;td&gt;&lt;em&gt;create 시만&lt;/em&gt;&lt;/td&gt;&lt;td&gt;언제든&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;strong / eventual&lt;/td&gt;&lt;td&gt;&lt;em&gt;eventual 만&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;크기 제한&lt;/td&gt;&lt;td&gt;10GB / PK&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;개수&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;    Base[&quot;Base table (PK, SK)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Base --&gt; LSI1[&quot;LSI (PK, new_SK)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Base --&gt; GSI1[&quot;GSI (different_PK, different_SK)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;single-table-design&quot;&gt;Single-Table Design&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q[모든 access pattern 정의]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; Choose[PK + SK 선택]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Choose --&gt; One[ONE table 에 모든 entity]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    One --&gt; Why[*Join 없이* 한 query 로 끝]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;RDB 사고 (테이블 = entity)&lt;/em&gt; 와 &lt;em&gt;완전 다른 패러다임&lt;/em&gt;. &lt;em&gt;access pattern 을 먼저 정의&lt;/em&gt;, 그에 맞춰 &lt;em&gt;PK/SK 디자인&lt;/em&gt;. 잘못 설계하면 &lt;em&gt;전체 마이그레이션&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;query-vs-scan&quot;&gt;Query vs Scan&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Query (효율적, PK 필수)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;QUERY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PK&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;USER#42&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; SK&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; begins_with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ORDER#&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Scan (전체 테이블 순회, 비효율)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SCAN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FilterExpression:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;status = paid&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;em&gt;Scan 은 거의 항상 안티패턴&lt;/em&gt;. 모든 partition 순회 → 비용 폭발. &lt;em&gt;Scan 이 자주 필요하면 &lt;em&gt;디자인 결함&lt;/em&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;dax-dynamodb-accelerator&quot;&gt;DAX (DynamoDB Accelerator)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App --&gt; DAX[&quot;DAX Cluster&amp;#x3C;br/&gt;(in-memory cache)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DAX --&gt;|miss| DDB[(DynamoDB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DAX -.-&gt;|hit| App&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;마이크로초 단위 latency&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;write-through&lt;/em&gt; 캐시.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;읽기 중심&lt;/em&gt; 워크로드에서 효과.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;streams--lambda&quot;&gt;Streams + Lambda&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;    DDB[(DynamoDB Table)] --&gt;|change stream| Streams[DDB Streams]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Streams --&gt; Lambda[Lambda]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lambda --&gt; ES[ElasticSearch]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lambda --&gt; SNS[SNS]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lambda --&gt; SQS[SQS]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Change Data Capture&lt;/em&gt;. &lt;em&gt;secondary index 가 부족할 때&lt;/em&gt; Streams 로 &lt;em&gt;외부 검색 인덱스&lt;/em&gt; 동기화.&lt;/p&gt;
&lt;h2 id=&quot;partiql&quot;&gt;PartiQL&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- SQL-like 쿼리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MyTable&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; PK &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;USER#42&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; begins_with(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SK&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ORDER#&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;익숙한 SQL 문법이지만 &lt;em&gt;내부는 여전히 PK/SK 제약 적용&lt;/em&gt;. &lt;em&gt;RDB 쿼리처럼 작성하면 Scan 폭발&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;Hot partition&lt;/strong&gt; = 특정 PK 에 트래픽 집중. &lt;em&gt;분산 키 디자인&lt;/em&gt; (해시 prefix).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;너무 큰 item&lt;/strong&gt; = 400KB 제한. 큰 데이터는 &lt;em&gt;S3&lt;/em&gt; + DDB 에 &lt;em&gt;참조&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scan 의존 쿼리&lt;/strong&gt; = 비용 폭발. GSI 추가.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eventual consistency 무시&lt;/strong&gt; = 방금 쓴 값이 &lt;em&gt;없을 수&lt;/em&gt; 있음. strong read 명시 또는 &lt;em&gt;재시도&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[mongodb]] (문서 DB 대안)&lt;/li&gt;
&lt;li&gt;[[Redis]] (KV 대안)&lt;/li&gt;
&lt;li&gt;[[aws-iam]] (DDB 권한)&lt;/li&gt;
&lt;li&gt;[[sharding-vs-partitioning]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>dynamodb</category><category>nosql</category><category>aws</category><category>key-value</category><category>document</category><author>koa (김신건)</author></item><item><title>[DB] EXPLAIN 읽기: scan, join, sort, cost</title><link>https://shinkeonkim.com/wiki/database-internals/query-explain-plan/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/query-explain-plan/</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;EXPLAIN&lt;/strong&gt; = &lt;em&gt;쿼리 실행 계획 표시&lt;/em&gt;. &lt;strong&gt;EXPLAIN ANALYZE&lt;/strong&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;EXPLAIN (ANALYZE, BUFFERS, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VERBOSE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, FORMAT &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;LEFT JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders o &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; o&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user_id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;created_at&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;2026-01-01&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GROUP BY&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;scan-종류&quot;&gt;Scan 종류&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;    Scan{Scan 종류}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scan --&gt; Seq[&quot;Seq Scan&amp;#x3C;br/&gt;전체 테이블 순회&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scan --&gt; Idx[&quot;Index Scan&amp;#x3C;br/&gt;인덱스 → 테이블&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scan --&gt; IdxOnly[&quot;Index Only Scan&amp;#x3C;br/&gt;인덱스에서만&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scan --&gt; Bitmap[&quot;Bitmap Scan&amp;#x3C;br/&gt;bitmap 으로 페이지 모음&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;Scan&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;Seq Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;전체 테이블&lt;/td&gt;&lt;td&gt;작은 테이블, 대부분 row 반환&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Index Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;인덱스 → row&lt;/td&gt;&lt;td&gt;선택적 (적은 row)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Index Only Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;인덱스 only (heap 안 봄)&lt;/td&gt;&lt;td&gt;covering index&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bitmap Index Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;여러 인덱스 결합 + bitmap&lt;/td&gt;&lt;td&gt;다 인덱스 조합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bitmap Heap Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;bitmap → 페이지 fetch&lt;/td&gt;&lt;td&gt;위와 짝&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Tid Scan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;직접 tid 접근&lt;/td&gt;&lt;td&gt;극히 드뭄&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;join-종류&quot;&gt;Join 종류&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Join&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;Nested Loop&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;for outer { for inner }&lt;/td&gt;&lt;td&gt;작은 inner + 인덱스&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hash Join&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;hash 빌드 → 룩업&lt;/td&gt;&lt;td&gt;큰 양쪽, 등가 조건&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Merge Join&lt;/strong&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;pre class=&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;위 애니메이션은 &lt;em&gt;Grace Hash Join&lt;/em&gt; (메모리 부족 시 분할 hash). 대용량 join 의 일반 직관.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;explain-analyze-출력-읽기&quot;&gt;EXPLAIN ANALYZE 출력 읽기&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;Aggregate  (cost=1024.50..1024.51 rows=1 width=8) (actual time=15.234..15.236 rows=1 loops=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&gt;  Hash Join  (cost=12.50..1020.50 rows=1600 width=4) (actual time=0.234..14.123 rows=1542 loops=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Hash Cond: (o.user_id = u.id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        -&gt;  Seq Scan on orders o  (cost=0.00..900.00 rows=50000 width=8) (actual time=0.012..5.234 rows=49832 loops=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        -&gt;  Hash  (cost=10.00..10.00 rows=200 width=8) (actual time=0.190..0.190 rows=215 loops=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              -&gt;  Index Scan using idx_users_created on users u  (cost=0.42..10.00 rows=200 width=8) (actual time=0.012..0.123 rows=215 loops=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    Index Cond: (created_at &gt; &apos;2026-01-01&apos;::date)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Planning Time: 0.345 ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Execution Time: 15.456 ms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;필드&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cost=A..B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;A = 시작 비용, B = 총 비용 (페이지 수 단위)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rows=N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;예상 row 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;width=N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;평균 row 바이트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;actual time=A..B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;실제 시작/종료 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rows=actual&lt;/code&gt;&lt;/td&gt;&lt;td&gt;실제 row 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;loops=N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;이 노드가 &lt;em&gt;N번 실행&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Planning Time&lt;/code&gt;&lt;/td&gt;&lt;td&gt;optimizer 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Execution Time&lt;/code&gt;&lt;/td&gt;&lt;td&gt;실행 시간&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;예상 vs 실제&lt;/em&gt; row 수 차이가 &lt;em&gt;수십배&lt;/em&gt; 면 &lt;em&gt;통계 부정확&lt;/em&gt; → &lt;code&gt;ANALYZE table_name&lt;/code&gt; 으로 갱신.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;자주-보는-안티-패턴&quot;&gt;자주 보는 안티 패턴&lt;/h2&gt;
&lt;h3 id=&quot;1-seq-scan-on-큰-테이블&quot;&gt;1. Seq Scan on 큰 테이블&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;Seq Scan on big_table  (rows=10000000)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Filter: (status = &apos;pending&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Rows Removed by Filter: 9990000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 인덱스 추가 또는 &lt;em&gt;partial index&lt;/em&gt; (&lt;code&gt;WHERE status = &apos;pending&apos;&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;2-nested-loop-on-큰-양쪽&quot;&gt;2. Nested Loop on 큰 양쪽&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;Nested Loop  (loops=100000)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&gt;  Index Scan on table_a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&gt;  Index Scan on table_b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 한쪽이 &lt;em&gt;수만 row&lt;/em&gt; 면 &lt;em&gt;Hash Join&lt;/em&gt; 으로 가야 함. &lt;code&gt;set enable_nestloop = off&lt;/code&gt; 로 테스트.&lt;/p&gt;
&lt;h3 id=&quot;3-sort--disk&quot;&gt;3. Sort + Disk&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;Sort  (Sort Method: external merge  Disk: 1234kB)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;code&gt;work_mem&lt;/code&gt; 부족. session 또는 query 단위 증가.&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;외부 정렬 (disk spill) 동작 직관. &lt;em&gt;임시 파일이 디스크에 쓰임&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;4-bitmap-heap-scan--작은-rows&quot;&gt;4. Bitmap Heap Scan + 작은 rows&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;Bitmap Heap Scan  (rows=10)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&gt;  Bitmap Index Scan&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 좋은 패턴. 인덱스가 &lt;em&gt;적절히&lt;/em&gt; 작동.&lt;/p&gt;
&lt;h2 id=&quot;자주-쓰는-옵션&quot;&gt;자주 쓰는 옵션&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;EXPLAIN (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ANALYZE,      &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;  BUFFERS,      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- shared/local/temp buffer hits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  VERBOSE&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;  COSTS &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;OFF&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- cost 숨김 (clean diff)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  FORMAT &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JSON&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;/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;a href=&quot;https://explain.depesz.com/&quot;&gt;explain.depesz.com&lt;/a&gt;&lt;/td&gt;&lt;td&gt;색상 코딩&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://explain.dalibo.com/&quot;&gt;pev2 (Dalibo)&lt;/a&gt;&lt;/td&gt;&lt;td&gt;tree 시각화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/pgstatstatements.html&quot;&gt;pg_stat_statements&lt;/a&gt;&lt;/td&gt;&lt;td&gt;누적 통계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/auto-explain.html&quot;&gt;auto_explain&lt;/a&gt;&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;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; (without ANALYZE) 만 신뢰&lt;/strong&gt; = optimizer 의 &lt;em&gt;추정&lt;/em&gt;. 실제 행동은 다를 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;actual rows&lt;/code&gt; 와 &lt;code&gt;rows=&lt;/code&gt; 차이 무시&lt;/strong&gt; = stale 통계. ANALYZE 필요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Hash Join&lt;/code&gt; 의 &lt;em&gt;큰 memory&lt;/em&gt;&lt;/strong&gt; = work_mem 부족 시 disk spill. 그래도 &lt;em&gt;Nested Loop 보다 빠를 수&lt;/em&gt; 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;planner hint 부재&lt;/strong&gt; = PostgreSQL 은 &lt;em&gt;공식 hint 없음&lt;/em&gt;. SET 변수로 &lt;em&gt;간접 제어&lt;/em&gt;. (MySQL 은 &lt;code&gt;STRAIGHT_JOIN&lt;/code&gt; 등 hint 직접.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[btree-indexing]], [[gin-gist-hash-indexes]]&lt;/li&gt;
&lt;li&gt;[[postgresql]], [[mysql-innodb]]&lt;/li&gt;
&lt;li&gt;[[mvcc]]&lt;/li&gt;
&lt;li&gt;[[transaction-isolation-levels]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>explain</category><category>query-plan</category><category>postgresql</category><category>performance</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB] GIN / GiST / Hash / BRIN: 비-B-tree 인덱스</title><link>https://shinkeonkim.com/wiki/database-internals/gin-gist-hash-indexes/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/gin-gist-hash-indexes/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;B-tree&lt;/em&gt; 외 PostgreSQL 의 &lt;em&gt;특수 인덱스&lt;/em&gt; 들. 자세한 B-tree 는 [[btree-indexing]] 참고.&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;인덱스&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;B-tree&lt;/td&gt;&lt;td&gt;&lt;em&gt;= &amp;#x3C; &gt; BETWEEN ORDER BY&lt;/em&gt; (default)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hash&lt;/td&gt;&lt;td&gt;&lt;em&gt;=&lt;/em&gt; 만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GIN&lt;/td&gt;&lt;td&gt;&lt;em&gt;다값 (배열, JSONB, full-text)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GiST&lt;/td&gt;&lt;td&gt;&lt;em&gt;공간, 범위, 유사도&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SP-GiST&lt;/td&gt;&lt;td&gt;&lt;em&gt;공간 partitioning&lt;/em&gt; (Quadtree, kd-tree)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BRIN&lt;/td&gt;&lt;td&gt;&lt;em&gt;큰 시계열, 잘 정렬된 데이터&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bloom&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;gin-generalized-inverted-index&quot;&gt;GIN (Generalized Inverted Index)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;하나의 컬럼이 여러 값&lt;/em&gt; 인 경우 (배열, JSONB, tsvector).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_tags&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(tags);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tags @&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ARRAY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&apos;ruby&apos;, &apos;rails&apos;];   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 빠름&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_jsonb&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload jsonb_path_ops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload @&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;type&quot;: &quot;click&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_fts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; articles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(to_tsvector(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;english&apos;&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:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; articles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; to_tsvector(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;english&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, body) @@ to_tsquery(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;postgres&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;    Doc1[&quot;doc 1: [a, b, c]&quot;] --&gt; Inv[&quot;a → [doc1, doc3]&amp;#x3C;br/&gt;b → [doc1, doc2]&amp;#x3C;br/&gt;c → [doc1]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Doc2[&quot;doc 2: [b, d]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Doc3[&quot;doc 3: [a, e]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inv --&gt; Q[&quot;query: contains &apos;a&apos; → [doc1, doc3]&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;JSONB 의 가장 흔한 인덱스&lt;/em&gt;. &lt;code&gt;@&gt;&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;?&amp;#x26;&lt;/code&gt;, &lt;code&gt;?|&lt;/code&gt; 연산자 가속.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;gist-generalized-search-tree&quot;&gt;GiST (Generalized Search Tree)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;tree 구조의 일반화&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostGIS 공간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_geom&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist(geom);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; places &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ST_DWithin(geom, ST_Point(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37&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&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;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_range&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bookings &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist(during);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bookings &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; during &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;[2026-06-25, 2026-06-26)&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::tsrange;&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;-- pg_trgm 유사도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION pg_trgm;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_name_trgm&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gist_trgm_ops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; % &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;koa&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;h2 id=&quot;brin-block-range-index&quot;&gt;BRIN (Block Range Index)&lt;/h2&gt;
&lt;p&gt;큰 테이블 + &lt;em&gt;물리 순서 ≈ 논리 순서&lt;/em&gt; 일 때. &lt;em&gt;block 의 min/max&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; brin(created_at);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;2026-06-25&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;B-tree&lt;/th&gt;&lt;th&gt;BRIN&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;크기&lt;/td&gt;&lt;td&gt;큼 (~ 10% of table)&lt;/td&gt;&lt;td&gt;&lt;em&gt;작음&lt;/em&gt; (~0.01%)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;정밀도&lt;/td&gt;&lt;td&gt;정확&lt;/td&gt;&lt;td&gt;대략 (block range)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합&lt;/td&gt;&lt;td&gt;일반&lt;/td&gt;&lt;td&gt;&lt;em&gt;시계열, append-only&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;수억 row 의 시계열&lt;/em&gt; 에서 &lt;em&gt;BRIN 이 B-tree 의 1/100 메모리&lt;/em&gt;. 단 &lt;em&gt;INSERT 순서와 정렬 컬럼이 일치&lt;/em&gt; 해야 효과.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;hash-index&quot;&gt;Hash Index&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_email_hash&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(email);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; email &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;koa@x.com&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:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; email &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;LIKE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;koa%&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;em&gt;= 검색만&lt;/em&gt;. range 불가.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;PostgreSQL 10+ 부터 WAL 지원&lt;/em&gt; (이전엔 replication 안 됨).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;B-tree 가 거의 항상 더 좋음&lt;/em&gt;. &lt;em&gt;극히 큰 컬럼&lt;/em&gt; 의 &lt;em&gt;= 만 검색&lt;/em&gt; 일 때 약간 우위.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sp-gist&quot;&gt;SP-GiST&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- IP 주소 등 partitioning 자연&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_ip&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; logs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; spgist(client_ip);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Quadtree, kd-tree, radix tree&lt;/em&gt; 를 &lt;em&gt;generic 으로 구현&lt;/em&gt;. &lt;em&gt;비균등 분포&lt;/em&gt; 데이터에 강함.&lt;/p&gt;
&lt;h2 id=&quot;bloom-index&quot;&gt;Bloom Index&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION bloom;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_multi&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bloom(user_id, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, source)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  WITH&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, col1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, col2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, col3 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#F97583&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;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;다컬럼 동등성&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;확률적&lt;/em&gt;: false positive 가능 (PG 가 &lt;em&gt;재검증&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;모든 컬럼 동시 조회&lt;/em&gt; 에서 &lt;em&gt;큰 B-tree 다수&lt;/em&gt; 대신.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;인덱스-선택-매트릭스&quot;&gt;인덱스 선택 매트릭스&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{쿼리 유형?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&quot;= &amp;#x3C; &gt; BETWEEN ORDER BY&quot;| Btree[B-tree]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&quot;=&quot;| Hash[B-tree 또는 Hash]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|배열 / JSONB / FTS| GIN[GIN]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|공간 / 범위 / 유사도| GiST[GiST]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|시계열 큼 + 정렬| BRIN[BRIN]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|다컬럼 = AND| Bloom[Bloom 또는 B-tree composite]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|IP / 비균등| SPGist[SP-GiST]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;인덱스-크기-비교-1억-row&quot;&gt;인덱스 크기 비교 (1억 row)&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&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;JSONB 에 &lt;em&gt;B-tree&lt;/em&gt; 인덱스&lt;/strong&gt; = 잘못. GIN 으로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모든 컬럼에 인덱스&lt;/strong&gt; = INSERT 폭증. &lt;em&gt;사용 안 되는 인덱스&lt;/em&gt; &lt;code&gt;pg_stat_user_indexes.idx_scan = 0&lt;/code&gt; 으로 식별.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BRIN + 무작위 insert&lt;/strong&gt; = 효과 &lt;em&gt;0&lt;/em&gt;. &lt;em&gt;clustered insert&lt;/em&gt; 가 전제.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hash index&lt;/code&gt; 의존&lt;/strong&gt; = 옛 버전은 &lt;em&gt;WAL 안됨&lt;/em&gt; → replica 에 없음. PG 10+ 만.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[btree-indexing]] (default)&lt;/li&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;li&gt;[[query-explain-plan]]&lt;/li&gt;
&lt;li&gt;[[Redis Vector Search]] (벡터 인덱스 대안)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>index</category><category>gin</category><category>gist</category><category>brin</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB] MongoDB: 문서 DB, WiredTiger, replica set, aggregation</title><link>https://shinkeonkim.com/wiki/database-internals/mongodb/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/mongodb/</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;MongoDB&lt;/strong&gt; 는 &lt;em&gt;BSON (binary JSON) 문서 기반 NoSQL DB&lt;/em&gt;. &lt;em&gt;schema-less&lt;/em&gt; 라기보다 &lt;em&gt;flexible schema&lt;/em&gt;. 2026 시점 &lt;em&gt;Atlas (managed)&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;RDB&lt;/th&gt;&lt;th&gt;MongoDB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Database&lt;/td&gt;&lt;td&gt;Database&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Table&lt;/td&gt;&lt;td&gt;Collection&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Row&lt;/td&gt;&lt;td&gt;Document (BSON)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Column&lt;/td&gt;&lt;td&gt;Field&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Index&lt;/td&gt;&lt;td&gt;Index (B-tree)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JOIN&lt;/td&gt;&lt;td&gt;&lt;code&gt;$lookup&lt;/code&gt; (aggregation)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Foreign key&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;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;ObjectId(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&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:#79B8FF&quot;&gt;  &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;koa&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;koa@x.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;home&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Seoul&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;work&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SF&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tags&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;createdAt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;ISODate(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T00:00:00Z&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&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;em&gt;nested + 배열&lt;/em&gt; 자유로움. &lt;em&gt;non-join read&lt;/em&gt; 가 빠름.&lt;/p&gt;
&lt;h2 id=&quot;wiredtiger-스토리지-엔진&quot;&gt;WiredTiger 스토리지 엔진&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;    Op[op] --&gt; WT[WiredTiger]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WT --&gt; Cache[&quot;메모리 cache (~50% RAM)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WT --&gt; Journal[&quot;Journal (WAL)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WT --&gt; Snappy[Snappy 압축]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WT --&gt; DataFiles[(BTree 데이터 파일)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;B-tree 인덱스.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Document-level locking&lt;/em&gt; (이전 글로벌 lock 에서 진화).&lt;/li&gt;
&lt;li&gt;Snappy / Zstd 압축.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;replica-set&quot;&gt;Replica Set&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[(Primary)] --&gt;|oplog| S1[(Secondary 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|oplog| S2[(Secondary 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|write| P&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|&quot;read (옵션)&quot;| S1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|&quot;read (옵션)&quot;| S2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;1 primary + N secondary.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;자동 failover&lt;/em&gt; (election).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;읽기 분산&lt;/em&gt; 옵션 (read preference).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sharding&quot;&gt;Sharding&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt; Mongos[&quot;mongos&amp;#x3C;br/&gt;(라우터)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Mongos --&gt; Config[(Config Servers)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Mongos --&gt; Shard1[(Shard 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Mongos --&gt; Shard2[(Shard 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Mongos --&gt; Shard3[(Shard 3)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;shard key&lt;/em&gt; 로 분산.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hashed&lt;/code&gt;, &lt;code&gt;ranged&lt;/code&gt;, &lt;code&gt;zone&lt;/code&gt; 정책.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;chunk&lt;/em&gt; 단위 자동 balancing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;aggregation-pipeline&quot;&gt;Aggregation Pipeline&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;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;db.orders.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;aggregate&lt;/span&gt;&lt;span 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;  { $match: { status: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;paid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { $lookup: { from: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, localField: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;userId&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, foreignField: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, as: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { $unwind: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;$user&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { $group: { _id: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;$user.country&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, total: { $sum: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;$amount&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { $sort: { total: &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;  { $limit: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Stage&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;$match&lt;/code&gt;&lt;/td&gt;&lt;td&gt;WHERE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$project&lt;/code&gt;&lt;/td&gt;&lt;td&gt;SELECT&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$group&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GROUP BY&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$sort&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ORDER BY&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$limit&lt;/code&gt; / &lt;code&gt;$skip&lt;/code&gt;&lt;/td&gt;&lt;td&gt;LIMIT / OFFSET&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$lookup&lt;/code&gt;&lt;/td&gt;&lt;td&gt;LEFT JOIN&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$unwind&lt;/code&gt;&lt;/td&gt;&lt;td&gt;배열 분해&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$facet&lt;/code&gt;&lt;/td&gt;&lt;td&gt;다중 sub-pipeline&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$bucket&lt;/code&gt;&lt;/td&gt;&lt;td&gt;범위 그룹화&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;aggregation 순서 최적화&lt;/em&gt;: &lt;code&gt;$match&lt;/code&gt; 와 &lt;code&gt;$project&lt;/code&gt; 를 &lt;em&gt;최대한 일찍&lt;/em&gt;. 인덱스 활용 + 처리량 감소.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;index&quot;&gt;Index&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;Single field&lt;/td&gt;&lt;td&gt;단일&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Compound&lt;/td&gt;&lt;td&gt;다중&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multikey&lt;/td&gt;&lt;td&gt;배열 필드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Text&lt;/td&gt;&lt;td&gt;full-text&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2dsphere&lt;/td&gt;&lt;td&gt;공간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hashed&lt;/td&gt;&lt;td&gt;sharding 친화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Wildcard&lt;/td&gt;&lt;td&gt;임의 필드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TTL&lt;/td&gt;&lt;td&gt;자동 만료&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;db.orders.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createIndex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ userId: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, createdAt: &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;db.events.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createIndex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ createdAt: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }, { expireAfterSeconds: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;86400&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;transactions-40-42-sharded&quot;&gt;Transactions (4.0+, 4.2+ sharded)&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;js&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; session&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startSession&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;session.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startTransaction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; coll1.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;updateOne&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ _id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }, { $inc: { x: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } }, { session });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; coll2.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;insertOne&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; }, { session });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;commitTransaction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (e) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;abortTransaction&lt;/span&gt;&lt;span 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;[!CAUTION]
&lt;em&gt;과거 MongoDB = 트랜잭션 없음&lt;/em&gt; 이미지가 강했으나, 4.0+ 부터 &lt;em&gt;multi-document ACID&lt;/em&gt;. &lt;em&gt;Sharded 트랜잭션은 비용 큼&lt;/em&gt;, &lt;em&gt;document 안에 묶을 수 있으면 그게 정통&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;&lt;code&gt;_id&lt;/code&gt; 의 ObjectId 가 &lt;em&gt;random&lt;/em&gt; 으로 &lt;em&gt;clustered index 미친 분포&lt;/em&gt;&lt;/strong&gt; = MongoDB 는 &lt;em&gt;_id index 의 random 분포&lt;/em&gt; 가 의도. 옛 PK 사고 패턴.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;join 이 무거움&lt;/strong&gt;&lt;/em&gt; = &lt;code&gt;$lookup&lt;/code&gt; 은 NoSQL 답지 않은 비용. &lt;em&gt;임베드 (embedding) 가 정통 패턴&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transaction 남용&lt;/strong&gt; = single document 안의 동작은 &lt;em&gt;항상 atomic&lt;/em&gt;. transaction 은 &lt;em&gt;진짜 필요할 때만&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shard key 잘못 선택&lt;/strong&gt; = &lt;em&gt;재선택 가능&lt;/em&gt; 했지만 옛 버전은 &lt;em&gt;불가&lt;/em&gt;. &lt;em&gt;write 분포가 균등하지 않은 키&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;[[postgresql]] (대안)&lt;/li&gt;
&lt;li&gt;[[dynamodb]] (NoSQL 대안)&lt;/li&gt;
&lt;li&gt;[[Redis]] (KV)&lt;/li&gt;
&lt;li&gt;[[sharding-vs-partitioning]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>mongodb</category><category>nosql</category><category>document</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB] MySQL / InnoDB: clustered index, redo log, MVCC</title><link>https://shinkeonkim.com/wiki/database-internals/mysql-innodb/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/mysql-innodb/</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;MySQL&lt;/strong&gt; + &lt;strong&gt;InnoDB&lt;/strong&gt; (기본 스토리지 엔진) 의 &lt;em&gt;clustered index + redo log&lt;/em&gt; 기반 &lt;em&gt;행 기반 RDBMS&lt;/em&gt;. &lt;em&gt;웹 / SaaS&lt;/em&gt; 의 가장 흔한 backbone.&lt;/p&gt;
&lt;h2 id=&quot;아키텍처&quot;&gt;아키텍처&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt; Server[&quot;MySQL Server&amp;#x3C;br/&gt;(parser, optimizer)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server --&gt; InnoDB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph InnoDB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BufferPool[&quot;Buffer Pool&amp;#x3C;br/&gt;(메모리 캐시)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RedoLog[&quot;Redo Log&amp;#x3C;br/&gt;(WAL 류)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        UndoLog[&quot;Undo Log&amp;#x3C;br/&gt;(MVCC)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        DataFiles[(ibd 데이터 파일)]&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;    Server --&gt;|writes| Binlog[(Binlog&amp;#x3C;br/&gt;replication)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;clustered-index-vs-postgresql&quot;&gt;Clustered Index (vs PostgreSQL)&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;PostgreSQL&lt;/th&gt;&lt;th&gt;MySQL InnoDB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Primary 데이터 저장&lt;/td&gt;&lt;td&gt;heap + 별도 인덱스&lt;/td&gt;&lt;td&gt;&lt;em&gt;primary key 의 B-tree leaf&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Secondary index → 데이터&lt;/td&gt;&lt;td&gt;&lt;code&gt;tid&lt;/code&gt; (heap 위치)&lt;/td&gt;&lt;td&gt;&lt;em&gt;primary key 값&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;결과&lt;/td&gt;&lt;td&gt;secondary index lookup = &lt;em&gt;1 hop&lt;/em&gt;&lt;/td&gt;&lt;td&gt;secondary index lookup = &lt;em&gt;2 hop&lt;/em&gt; (인덱스 → PK → 데이터)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PK 영향&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;td&gt;&lt;em&gt;큼&lt;/em&gt; (변경 시 secondary index 도 갱신)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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 InnoDB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PK[Primary Key B-tree&amp;#x3C;br/&gt;= 실제 row 데이터]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Sec[Secondary Index B-tree&amp;#x3C;br/&gt;= PK 값을 leaf 에]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Sec --&gt;|PK lookup| PK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
InnoDB 의 PK 선택은 &lt;em&gt;결정적&lt;/em&gt;. &lt;em&gt;큰 PK&lt;/em&gt; (UUID) = 모든 secondary index 가 &lt;em&gt;큰 leaf&lt;/em&gt;. &lt;em&gt;AUTO_INCREMENT BIGINT&lt;/em&gt; 가 보통 정답.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;redo-log--undo-log&quot;&gt;Redo Log + Undo Log&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;    Client-&gt;&gt;InnoDB: UPDATE ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    InnoDB-&gt;&gt;UndoLog: 옛 값 보관 (MVCC + rollback)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    InnoDB-&gt;&gt;BufferPool: page dirty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    InnoDB-&gt;&gt;RedoLog: 변경 기록 (WAL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    InnoDB--&gt;&gt;Client: COMMIT (redo fsync 후)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over InnoDB: Checkpoint 시점에 page → disk&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;Redo Log&lt;/td&gt;&lt;td&gt;crash recovery, &lt;em&gt;WAL&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Undo Log&lt;/td&gt;&lt;td&gt;MVCC + rollback&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Binlog&lt;/td&gt;&lt;td&gt;replication, point-in-time recovery&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;isolation-level-innodb-기본&quot;&gt;Isolation Level (InnoDB 기본)&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Level&lt;/th&gt;&lt;th&gt;InnoDB 기본&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;READ UNCOMMITTED&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;dirty read&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;READ COMMITTED&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;다른 트랜잭션의 commit 된 것만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;기본&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;같은 쿼리는 같은 결과&lt;/em&gt; (snapshot)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SERIALIZABLE&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;완전 격리&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;InnoDB 의 &lt;em&gt;REPEATABLE READ&lt;/em&gt; 는 &lt;em&gt;next-key lock&lt;/em&gt; 으로 &lt;em&gt;phantom read 방지&lt;/em&gt;. PostgreSQL 의 &lt;em&gt;Read Committed&lt;/em&gt; 기본과 &lt;em&gt;기본값이 다름&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;자세한 건 [[transaction-isolation-levels]].&lt;/p&gt;
&lt;h2 id=&quot;buffer-pool&quot;&gt;Buffer 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;    Disk[(ibd files)] --&gt; BP[Buffer Pool]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BP --&gt;|hit| Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BP -.-&gt;|LRU eviction| Disk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;메모리에 &lt;em&gt;page 단위 (16KB) 캐시&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; = &lt;em&gt;총 RAM 의 70-80%&lt;/em&gt; 가 운영 표준.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Hit ratio&lt;/em&gt; &lt;code&gt;Innodb_buffer_pool_read_requests / Innodb_buffer_pool_reads&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;lock&quot;&gt;Lock&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lock&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Shared (S)&lt;/td&gt;&lt;td&gt;다른 S 와 호환, X 와 충돌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Exclusive (X)&lt;/td&gt;&lt;td&gt;단독&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Intention (IS, IX)&lt;/td&gt;&lt;td&gt;table-level 의도 표시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Record lock&lt;/td&gt;&lt;td&gt;row 단일&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Gap lock&lt;/td&gt;&lt;td&gt;row 사이 간격&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Next-key lock&lt;/td&gt;&lt;td&gt;record + gap (phantom 방지)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Auto-inc lock&lt;/td&gt;&lt;td&gt;AUTO_INCREMENT 동기&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;next-key lock&lt;/em&gt; 이 의도치 않게 &lt;em&gt;넓은 범위&lt;/em&gt; 를 lock → deadlock 잦음. &lt;code&gt;SHOW ENGINE INNODB STATUS\G&lt;/code&gt; 의 &lt;em&gt;deadlock log&lt;/em&gt; 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;replication&quot;&gt;Replication&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;    Primary --&gt; Binlog[(binlog)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Binlog --&gt; Replica1[Replica 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Binlog --&gt; Replica2[Replica 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Binlog --&gt; Replica3[Replica 3]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Async&lt;/em&gt; (기본): primary 가 즉시 OK&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Semi-sync&lt;/em&gt;: 적어도 1개 replica 가 receive 후 OK&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Group Replication&lt;/em&gt;: 다수 노드 합의&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mysql-vs-postgresql&quot;&gt;MySQL vs PostgreSQL&lt;/h2&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;MySQL&lt;/th&gt;&lt;th&gt;PostgreSQL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;스토리지 엔진&lt;/td&gt;&lt;td&gt;InnoDB (+ MyISAM legacy)&lt;/td&gt;&lt;td&gt;단일&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;인덱스&lt;/td&gt;&lt;td&gt;clustered (PK = data)&lt;/td&gt;&lt;td&gt;heap + index&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;확장성 (extension)&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;JSON 인덱싱&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;td&gt;&lt;em&gt;JSONB + GIN 우수&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;복제&lt;/td&gt;&lt;td&gt;binlog 기반&lt;/td&gt;&lt;td&gt;streaming WAL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;트랜잭션 DDL&lt;/td&gt;&lt;td&gt;옛 버전 X (8.0+ 부분)&lt;/td&gt;&lt;td&gt;&lt;em&gt;대부분 트랜잭션&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 도구&lt;/td&gt;&lt;td&gt;percona, mysql workbench&lt;/td&gt;&lt;td&gt;psql + 풍부한 ecosystem&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;라이센스&lt;/td&gt;&lt;td&gt;GPL (commercial 도)&lt;/td&gt;&lt;td&gt;PostgreSQL License&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PK = UUID v4&lt;/strong&gt; = clustered index &lt;em&gt;random write&lt;/em&gt; → 페이지 분할 폭증. ULID / 정렬 가능 UUID 또는 &lt;em&gt;BIGINT&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SELECT * &lt;/code&gt;의 &lt;em&gt;MVCC&lt;/em&gt; 비용&lt;/strong&gt; = old version chain 따라가기. unused 컬럼 안 가져오기.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deadlock 무시&lt;/strong&gt; = &lt;code&gt;SHOW ENGINE INNODB STATUS&lt;/code&gt; 의 &lt;em&gt;LATEST DEADLOCK&lt;/em&gt; 모니터링.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;binlog format 이 &lt;code&gt;STATEMENT&lt;/code&gt;&lt;/strong&gt; = 비결정적 함수 (NOW(), UUID()) → replica 와 &lt;em&gt;다른 결과&lt;/em&gt;. ROW 또는 MIXED 권장.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[postgresql]] (비교)&lt;/li&gt;
&lt;li&gt;[[mvcc]]&lt;/li&gt;
&lt;li&gt;[[btree-indexing]]&lt;/li&gt;
&lt;li&gt;[[transaction-isolation-levels]]&lt;/li&gt;
&lt;li&gt;[[wal-write-ahead-log]]&lt;/li&gt;
&lt;li&gt;[[query-explain-plan]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>mysql</category><category>innodb</category><category>database</category><category>sql</category><author>koa (김신건)</author></item><item><title>[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성</title><link>https://shinkeonkim.com/wiki/database-internals/postgresql/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/postgresql/</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;PostgreSQL&lt;/strong&gt; 은 &lt;em&gt;오픈소스 ORDBMS&lt;/em&gt;. 1986 UC Berkeley &lt;em&gt;POSTGRES&lt;/em&gt; 의 후예. &lt;em&gt;MVCC, 확장 가능 타입, JSONB, full-text search, GIS (PostGIS), 벡터 (pgvector)&lt;/em&gt; 등 &lt;em&gt;무거운 데이터 시스템&lt;/em&gt; 의 표준.&lt;/p&gt;
&lt;h2 id=&quot;프로세스-모델&quot;&gt;프로세스 모델&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Postmaster[postmaster&amp;#x3C;br/&gt;master process] --&gt; BG[Background workers]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BG --&gt; WAL_W[WAL writer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BG --&gt; BG_W[Background writer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BG --&gt; Checkpointer[Checkpointer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BG --&gt; Autovacuum[Autovacuum launcher]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BG --&gt; Stats[Stats collector]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Postmaster --&gt;|fork| C1[&quot;Backend 1&amp;#x3C;br/&gt;(connection)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Postmaster --&gt;|fork| C2[Backend 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Postmaster --&gt;|fork| C3[Backend N]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;프로세스 per connection&lt;/em&gt;. 1000 연결 = 1000 OS 프로세스. &lt;em&gt;connection pool (PgBouncer)&lt;/em&gt; 가 &lt;em&gt;거의 필수&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;mvcc--vacuum&quot;&gt;MVCC + VACUUM&lt;/h2&gt;
&lt;p&gt;자세한 건 [[mvcc]] 참고. PostgreSQL 의 &lt;em&gt;dead tuple&lt;/em&gt; 누적 → &lt;em&gt;VACUUM&lt;/em&gt; 으로 회수.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Update[UPDATE 한 행] --&gt; NewVer[새 버전 tuple]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Update --&gt; DeadVer[&quot;옛 버전 tuple&amp;#x3C;br/&gt;(dead)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auto[autovacuum&amp;#x3C;br/&gt;주기적 실행] --&gt; DeadVer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auto --&gt;|회수| Space[빈 공간]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;VACUUM 종류&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;VACUUM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;dead tuple 표시, 공간 회수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;VACUUM ANALYZE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;+ 통계 갱신&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;VACUUM FULL&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;table rewrite&lt;/em&gt;. LOCK 필요. 운영 중 금지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;autovacuum&lt;/td&gt;&lt;td&gt;자동&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;autovacuum 이 따라잡지 못하면&lt;/em&gt; &lt;code&gt;pg_stat_user_tables.n_dead_tup&lt;/code&gt; 증가 → &lt;em&gt;쿼리 느려짐 + transaction ID wrap-around&lt;/em&gt; 위험.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;wal-write-ahead-log&quot;&gt;WAL (Write-Ahead Log)&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;    Client-&gt;&gt;Backend: UPDATE ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Backend-&gt;&gt;WAL: write log record&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Backend-&gt;&gt;SharedBuffer: page dirty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Backend--&gt;&gt;Client: COMMIT OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Backend: WAL fsync 후 commit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Checkpointer-&gt;&gt;Disk: dirty page → 데이터 파일&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;COMMIT = &lt;em&gt;WAL fsync&lt;/em&gt; 면 OK. 데이터 파일은 &lt;em&gt;나중에 checkpoint 시&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;crash recovery&lt;/em&gt; = WAL replay.&lt;/li&gt;
&lt;li&gt;replication 도 &lt;em&gt;WAL streaming&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;toast-큰-컬럼-압축&quot;&gt;TOAST (큰 컬럼 압축)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The Oversized-Attribute Storage Technique&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;row size &gt; 2KB 이면:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. 압축 (LZ4 / pglz)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 그래도 크면 → toast 테이블에 *분할 저장*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;대용량 텍스트 / JSONB / bytea 가 &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;&lt;code&gt;psql&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CLI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pg_dump&lt;/code&gt; / &lt;code&gt;pg_restore&lt;/code&gt;&lt;/td&gt;&lt;td&gt;백업&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pg_basebackup&lt;/code&gt;&lt;/td&gt;&lt;td&gt;physical 백업&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pg_rewind&lt;/code&gt;&lt;/td&gt;&lt;td&gt;replica → primary 빠른 동기화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pgbench&lt;/code&gt;&lt;/td&gt;&lt;td&gt;벤치마크&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EXPLAIN&lt;/code&gt;&lt;/td&gt;&lt;td&gt;쿼리 plan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pg_stat_*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;통계 view&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;explain-analyze&quot;&gt;EXPLAIN ANALYZE&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;EXPLAIN (ANALYZE, BUFFERS) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; email &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;koa@x.com&apos;&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;자세한 건 [[query-explain-plan]] 참고.&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;B-tree&lt;/td&gt;&lt;td&gt;일반 (default)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hash&lt;/td&gt;&lt;td&gt;등가 검색만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GIN&lt;/td&gt;&lt;td&gt;다값 (배열, JSONB, full-text)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GiST&lt;/td&gt;&lt;td&gt;공간, 범위&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SP-GiST&lt;/td&gt;&lt;td&gt;공간 partitioning&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BRIN&lt;/td&gt;&lt;td&gt;큰 시계열 (작은 인덱스)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bloom&lt;/td&gt;&lt;td&gt;다컬럼 (확률적)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자세한 건 [[gin-gist-hash-indexes]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;확장-extension&quot;&gt;확장 (Extension)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION postgis;      &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;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION pgvector;     &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;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION pg_stat_statements;  &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;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION pg_trgm;      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 트라이그램 (LIKE 최적화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION timescaledb;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 시계열&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;PostgreSQL 의 최대 강점&lt;/em&gt;. 코어를 &lt;em&gt;확장 으로 거대화&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SELECT *&lt;/code&gt; + 인덱스 only scan 기대&lt;/strong&gt; = TOAST 컬럼 있으면 &lt;em&gt;table fetch 강제&lt;/em&gt;. 필요 컬럼만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;너무 많은 인덱스&lt;/strong&gt; = INSERT/UPDATE 느려짐. 사용 안 되는 인덱스 &lt;code&gt;pg_stat_user_indexes.idx_scan = 0&lt;/code&gt; 으로 식별.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pg_dump&lt;/code&gt; 의 &lt;em&gt;논리 백업&lt;/em&gt; 만 의존&lt;/strong&gt; = 대용량은 &lt;em&gt;시간 폭증&lt;/em&gt;. &lt;em&gt;pg_basebackup + WAL archive&lt;/em&gt; 가 정통.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;autovacuum disable&lt;/strong&gt; = transaction ID wrap-around → DB 정지. 절대 끄지 말 것.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[mvcc]], [[btree-indexing]]&lt;/li&gt;
&lt;li&gt;[[gin-gist-hash-indexes]]&lt;/li&gt;
&lt;li&gt;[[query-explain-plan]]&lt;/li&gt;
&lt;li&gt;[[transaction-isolation-levels]]&lt;/li&gt;
&lt;li&gt;[[wal-write-ahead-log]]&lt;/li&gt;
&lt;li&gt;[[mysql-innodb]] (비교)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>database</category><category>sql</category><category>mvcc</category><category>wal</category><author>koa (김신건)</author></item><item><title>[DB] Sharding vs Partitioning: 수평 확장의 두 얼굴</title><link>https://shinkeonkim.com/wiki/database-internals/sharding-vs-partitioning/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/sharding-vs-partitioning/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Partitioning&lt;/th&gt;&lt;th&gt;Sharding&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;범위&lt;/td&gt;&lt;td&gt;&lt;em&gt;한 DB 안&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;여러 DB 노드&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;목적&lt;/td&gt;&lt;td&gt;큰 테이블 관리&lt;/td&gt;&lt;td&gt;수평 확장 (write/storage)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;투명성&lt;/td&gt;&lt;td&gt;DB 가 처리&lt;/td&gt;&lt;td&gt;보통 &lt;em&gt;애플리케이션 인지&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;예&lt;/td&gt;&lt;td&gt;PostgreSQL &lt;code&gt;PARTITION BY&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Citus, Vitess, MongoDB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;둘 다 &lt;em&gt;“데이터를 여러 조각으로 나눈다”&lt;/em&gt;. &lt;em&gt;경계&lt;/em&gt; 가 &lt;em&gt;DB 노드 안인지 밖인지&lt;/em&gt; 차이.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;partitioning-한-db-안&quot;&gt;Partitioning (한 DB 안)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- PostgreSQL: 시간 기준 range partition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span 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;  body JSONB&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;PARTITION&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; BY&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; RANGE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (created_at);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events_2026_06&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PARTITION&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OF events&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:#F97583&quot;&gt; VALUES&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;2026-06-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&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;2026-07-01&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:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events_2026_07&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PARTITION&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OF events&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:#F97583&quot;&gt; VALUES&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;2026-07-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&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;2026-08-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;종류&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;Range&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;시간, 숫자 범위&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;List&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;미리 정한 값 (region 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hash&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;해시 (균등 분포)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;큰 테이블 → 작은 partition&lt;/em&gt; (VACUUM, index 작음)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;오래된 partition drop&lt;/em&gt; = 빠른 보관 정리&lt;/li&gt;
&lt;li&gt;&lt;em&gt;partition pruning&lt;/em&gt; = WHERE 조건이 partition 키면 &lt;em&gt;그것만 스캔&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sharding-여러-db-노드&quot;&gt;Sharding (여러 DB 노드)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt; Router[Router / Coordinator]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt;|shard_key % N| S1[(Shard 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt;|shard_key % N| S2[(Shard 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router --&gt;|shard_key % N| S3[(Shard 3)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;shard-key-선택&quot;&gt;Shard Key 선택&lt;/h3&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;user_id&lt;/code&gt; hash&lt;/td&gt;&lt;td&gt;&lt;em&gt;고른 분포&lt;/em&gt;, 한 user 가 한 shard&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt; range&lt;/td&gt;&lt;td&gt;hot shard (최근만 트래픽)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tenant_id&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;멀티 테넌트&lt;/em&gt;. 큰 테넌트 = hot&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;geographic&lt;/code&gt;&lt;/td&gt;&lt;td&gt;data residency&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Shard key 는 &lt;em&gt;바꾸기 어렵다&lt;/em&gt;. &lt;em&gt;모든 query 가 shard key 포함 가능한지&lt;/em&gt; 먼저 검토.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;consistent-hashing&quot;&gt;Consistent Hashing&lt;/h3&gt;
&lt;p&gt;자세한 건 [[Load Balancer]] 의 consistent hashing 절.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Ring((Hash Ring)) --&gt; N1[Node A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Ring --&gt; N2[Node B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Ring --&gt; N3[Node C]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Key1[&quot;hash(user:42)&quot;] --&gt;|시계방향| N2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;노드 추가/제거 시 N/M 만 재분배&lt;/em&gt;. &lt;em&gt;Cassandra, DynamoDB, Redis Cluster&lt;/em&gt; 의 토대.&lt;/p&gt;
&lt;h2 id=&quot;cross-shard-query-의-비용&quot;&gt;Cross-shard Query 의 비용&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;    Client-&gt;&gt;Router: SELECT count(*) FROM users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par fan-out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Router-&gt;&gt;S1: SELECT count(*)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Router-&gt;&gt;S2: SELECT count(*)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Router-&gt;&gt;S3: SELECT count(*)&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;    S1--&gt;&gt;Router: 1234&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2--&gt;&gt;Router: 1567&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S3--&gt;&gt;Router: 1198&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router-&gt;&gt;Router: SUM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Router--&gt;&gt;Client: 3999&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;쿼리&lt;/th&gt;&lt;th&gt;비용&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;WHERE shard_key = X&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;1 shard&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;WHERE shard_key IN (X, Y)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;2 shard&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;WHERE other_field = ...&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;전 shard fan-out&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;JOIN&lt;/code&gt; cross-shard&lt;/td&gt;&lt;td&gt;&lt;em&gt;매우 비쌈&lt;/em&gt; (피하기)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GROUP BY&lt;/code&gt; cross-shard&lt;/td&gt;&lt;td&gt;&lt;em&gt;router 가 merge&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;대부분 쿼리가 shard key 포함 안 됨&lt;/em&gt; = 디자인 실패. shard key 결정 &lt;em&gt;전에&lt;/em&gt; access pattern 정의.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;vitess-mysql-sharding-아키텍처&quot;&gt;Vitess (MySQL sharding) 아키텍처&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;    App[App] --&gt; VTGate[&quot;VTGate (router)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    VTGate --&gt; VTTablet1[VTTablet 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    VTTablet1 --&gt; MySQL1[(MySQL)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    VTGate --&gt; VTTablet2[VTTablet 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    VTTablet2 --&gt; MySQL2[(MySQL)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    VTGate --&gt; Topo[&quot;Topology Service&amp;#x3C;br/&gt;(etcd / Zookeeper)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;YouTube&lt;/em&gt; 가 만들고 기여.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;MySQL 호환&lt;/em&gt; (Vitess client 가 &lt;em&gt;MySQL 프로토콜&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;re-sharding 자동화&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;citus-postgresql-extension&quot;&gt;Citus (PostgreSQL extension)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_distributed_table(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;events&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_id&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_reference_table(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;countries&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;PostgreSQL 의 &lt;em&gt;extension&lt;/em&gt; 으로 동작.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;PG 의 모든 기능 + sharding&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Microsoft Azure Cosmos DB for PostgreSQL&lt;/em&gt; 이 기반.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;다른-sharding-전략&quot;&gt;다른 sharding 전략&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;전략&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hash-based&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;hash(key) % N. 균등하지만 &lt;em&gt;N 변경 어려움&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Range-based&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;키 범위. &lt;em&gt;hot shard 위험&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Directory-based&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;lookup table. &lt;em&gt;유연 + 단일 장애&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Consistent hash&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;hash ring. &lt;em&gt;N 변경 friendly&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;re-sharding&quot;&gt;Re-sharding&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;shard 추가/제거 시 데이터 이동&lt;/em&gt;. 가장 어려운 운영.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Old[기존 3 shard] --&gt;|N=4 추가| Migrate[데이터 이동]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Migrate --&gt; New[4 shard 분배]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note[&quot;traffic 처리 *지속* 가운데&amp;#x3C;br/&gt;일관성 유지 어려움&quot;] -.-&gt; Migrate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;온라인 re-sharding&lt;/em&gt; 은 &lt;em&gt;몇 주 ~ 몇 달&lt;/em&gt; 운영.&lt;/li&gt;
&lt;li&gt;Vitess, Citus, MongoDB 가 &lt;em&gt;자동 도구 제공&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;수동 sharding (애플리케이션 레벨)&lt;/em&gt; 은 &lt;em&gt;매우 고통&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sharding 너무 일찍&lt;/strong&gt; = 운영 비용 폭증. &lt;em&gt;수직 확장 + replica&lt;/em&gt; 가 &lt;em&gt;수십 TB 까지&lt;/em&gt; 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-shard transaction&lt;/strong&gt; = &lt;em&gt;2PC&lt;/em&gt; 또는 &lt;em&gt;saga&lt;/em&gt; 필요. 비용 큼.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hot shard 무시&lt;/strong&gt; = 한 노드만 100% CPU. shard key 재선택 필요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partition pruning 작동 안 함&lt;/strong&gt; = partition key 가 WHERE 에 없으면 &lt;em&gt;모든 partition 스캔&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;[[postgresql]], [[mysql-innodb]]&lt;/li&gt;
&lt;li&gt;[[dynamodb]], [[mongodb]]&lt;/li&gt;
&lt;li&gt;[[Load Balancer]] (consistent hashing)&lt;/li&gt;
&lt;li&gt;[[distributed-systems-consensus]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>sharding</category><category>partitioning</category><category>scaling</category><category>database</category><author>koa (김신건)</author></item><item><title>[DB] SQLite: 단일 파일, WAL 모드, edge / mobile</title><link>https://shinkeonkim.com/wiki/database-internals/sqlite/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/sqlite/</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;SQLite&lt;/strong&gt; 는 &lt;em&gt;세계에서 가장 많이 배포된 DB&lt;/em&gt; (모든 스마트폰, 브라우저, OS). &lt;em&gt;서버 없이 라이브러리&lt;/em&gt;, &lt;em&gt;단일 파일&lt;/em&gt;, &lt;em&gt;zero-config&lt;/em&gt;. &lt;em&gt;embedded&lt;/em&gt; + &lt;em&gt;edge&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;SQLite&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;구조&lt;/td&gt;&lt;td&gt;단일 파일 (&lt;code&gt;.db&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;프로세스&lt;/td&gt;&lt;td&gt;없음 (앱 안 라이브러리)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;동시성&lt;/td&gt;&lt;td&gt;&lt;em&gt;동시 reader 다수, writer 1개&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;트랜잭션&lt;/td&gt;&lt;td&gt;ACID&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;크기&lt;/td&gt;&lt;td&gt;라이브러리 ~600KB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SQL 표준&lt;/td&gt;&lt;td&gt;부분 (SQL-92 의 일부)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Type&lt;/td&gt;&lt;td&gt;&lt;em&gt;dynamic typing&lt;/em&gt; (TYPE AFFINITY)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;journal-mode&quot;&gt;Journal Mode&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{journal_mode}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; DELETE[&quot;DELETE (기본)&amp;#x3C;br/&gt;rollback journal 사용&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; WAL[&quot;WAL (권장)&amp;#x3C;br/&gt;write-ahead log&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; MEMORY[&quot;MEMORY&amp;#x3C;br/&gt;rollback 메모리&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; OFF[&quot;OFF&amp;#x3C;br/&gt;crash 위험&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;wal-모드-운영-권장&quot;&gt;WAL 모드 (운영 권장)&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PRAGMA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; journal_mode=WAL&lt;/span&gt;&lt;span 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;PRAGMA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; synchronous=NORMAL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;모드&lt;/th&gt;&lt;th&gt;reader vs writer&lt;/th&gt;&lt;th&gt;동시성&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;DELETE (기본)&lt;/td&gt;&lt;td&gt;writer 가 reader 막음&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WAL&lt;/td&gt;&lt;td&gt;&lt;em&gt;분리&lt;/em&gt;. reader 가 옛 snapshot 봄&lt;/td&gt;&lt;td&gt;&lt;em&gt;높음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;프로덕션 SQLite 는 거의 항상 WAL&lt;/em&gt;. 동시 reader 폭증.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;적합한-사용처&quot;&gt;적합한 사용처&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{환경}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; A[모바일 / 데스크톱 앱]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; B[Edge / Serverless]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; C[테스트 DB]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; D[&quot;작은 웹 사이트&amp;#x3C;br/&gt;(write 적음)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; E[설정 / 캐시 파일]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; F[Local-first 앱]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;부적합:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;write throughput&lt;/em&gt; 큰 멀티 노드&lt;/li&gt;
&lt;li&gt;&lt;em&gt;cross-server 트랜잭션&lt;/em&gt; 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;옛-평가와-현재-2026&quot;&gt;옛 평가와 현재 (2026)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;“&lt;em&gt;SQLite 는 장난감&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;옛 주장&lt;/th&gt;&lt;th&gt;현재 (2026)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;writer 1개&lt;/td&gt;&lt;td&gt;1 writer * 분 단위 1만 commit. 대부분 충분&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;백업 어려움&lt;/td&gt;&lt;td&gt;Litestream, LiteFS, Turso 가 &lt;em&gt;실시간 streaming&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;분산 안 됨&lt;/td&gt;&lt;td&gt;Turso (libSQL), Cloudflare D1 = &lt;em&gt;edge 분산&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;큰 쿼리 느림&lt;/td&gt;&lt;td&gt;인덱스 잘 박으면 PostgreSQL 비교 가능&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;litestream--litefs&quot;&gt;Litestream / LiteFS&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App[App] --&gt; SQLite[(local SQLite)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SQLite --&gt;|WAL stream| Litestream[Litestream]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Litestream --&gt; S3[(S3 / R2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Replica[다른 노드] -.replicate.-&gt; S3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;실시간 streaming 백업&lt;/em&gt; (1초 지연).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;S3, GCS, ABS, R2&lt;/em&gt; 어디든.&lt;/li&gt;
&lt;li&gt;마치 &lt;em&gt;Postgres WAL streaming&lt;/em&gt; 처럼.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;libsql--turso&quot;&gt;libSQL / Turso&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;libSQL = SQLite fork. &lt;em&gt;async/await&lt;/em&gt;, &lt;em&gt;embedded replica&lt;/em&gt;, &lt;em&gt;vector search&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Turso = libSQL 의 &lt;em&gt;서버리스 클라우드&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;2026 시점 &lt;em&gt;edge DB 의 표준&lt;/em&gt; 으로 빠르게 성장.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;동시성-begin-immediate&quot;&gt;동시성: BEGIN IMMEDIATE&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 기본 (DEFERRED): 첫 write 명령 시 lock 시도 → SQLITE_BUSY 위험&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ...;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- shared lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ...;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- exclusive lock 시도 → 다른 writer 와 충돌 가능&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- IMMEDIATE: 시작 시 이미 write lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; IMMEDIATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;write 가 섞인 트랜잭션&lt;/em&gt; 은 &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; 가 정통. &lt;em&gt;SQLITE_BUSY&lt;/em&gt; 폭증을 막는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;type-affinity-numeric-integer-real-text-blob&quot;&gt;Type Affinity (NUMERIC, INTEGER, REAL, TEXT, BLOB)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- SQLite 는 *dynamic typing*. 컬럼 타입은 *affinity hint*.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INTEGER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;not number&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 동작! (strict 모드 끄면)&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;-- 3.37+ STRICT 모드&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INTEGER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) STRICT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;not number&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&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;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;Production 은 STRICT 모드 권장&lt;/em&gt;. 옛 SQLite 코드는 &lt;em&gt;implicit conversion&lt;/em&gt; 으로 &lt;em&gt;조용한 버그&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;WAL 모드 안 켬&lt;/strong&gt; = &lt;em&gt;writer 가 reader 막음&lt;/em&gt;. 첫 운영 사고.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PRAGMA synchronous=OFF&lt;/code&gt;&lt;/strong&gt; = crash 시 &lt;em&gt;DB 손상 가능&lt;/em&gt;. NORMAL 이 권장 (성능 + 안전 균형).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;여러 프로세스가 같은 파일&lt;/strong&gt; = &lt;em&gt;NFS / 네트워크 파일시스템&lt;/em&gt; 위에서 &lt;em&gt;lock 실패&lt;/em&gt;. 로컬 디스크 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;백업이 &lt;code&gt;cp file.db&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;진행 중 쿼리&lt;/em&gt; 때문에 &lt;em&gt;손상&lt;/em&gt;. &lt;code&gt;sqlite3 .backup&lt;/code&gt; 또는 Litestream.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&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;[[wal-write-ahead-log]]&lt;/li&gt;
&lt;li&gt;[[mvcc]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>sqlite</category><category>database</category><category>embedded</category><category>edge</category><author>koa (김신건)</author></item><item><title>[DB] WAL: Write-Ahead Log, crash recovery, replication 의 토대</title><link>https://shinkeonkim.com/wiki/database-internals/wal-write-ahead-log/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/wal-write-ahead-log/</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;Write-Ahead Log (WAL)&lt;/strong&gt; = &lt;em&gt;데이터 변경 전 로그를 먼저 디스크에 기록&lt;/em&gt;. 모든 모던 RDB / KV / 분산 시스템의 &lt;em&gt;내구성 기반&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;핵심 약속: &lt;strong&gt;로그가 디스크에 있으면 → 데이터도 복구 가능&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;왜-wal&quot;&gt;왜 WAL?&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;    Op[UPDATE row] --&gt; Decide{쓰는 순서}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Decide --&gt;|&quot;WAL first&quot;| Safe[안전: crash 시 replay]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Decide --&gt;|&quot;Data first&quot;| Unsafe[위험: 부분 쓰기 후 crash → 불일치]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;데이터 페이지를 디스크에 쓰는 건 비쌈&lt;/em&gt;. &lt;em&gt;로그는 sequential append 라 빠름&lt;/em&gt;. &lt;em&gt;commit = 로그 fsync&lt;/em&gt; 만 보장하면 &lt;em&gt;crash 후에도 복구 가능&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;일반-흐름&quot;&gt;일반 흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant App as DB Process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant WAL as WAL Buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Disk as Disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Page as Page Buffer&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;App: UPDATE x SET v = 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;WAL: 로그 record append&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Page: page in memory 변경 (dirty)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over App: COMMIT 요청&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;WAL: fsync WAL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Disk--&gt;&gt;App: ack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App--&gt;&gt;C: COMMIT OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over App: 백그라운드에서&amp;#x3C;br/&gt;checkpointer 가 dirty page → disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Page-.-&gt;Disk: 나중에 flush&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;crash-recovery-aries-기반&quot;&gt;Crash Recovery (ARIES 기반)&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;    Crash[Crash 발생] --&gt; Analyze[Analysis&amp;#x3C;br/&gt;마지막 checkpoint 이후 로그 스캔]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Analyze --&gt; Redo[Redo&amp;#x3C;br/&gt;committed 트랜잭션 적용]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Redo --&gt; Undo[Undo&amp;#x3C;br/&gt;uncommitted 트랜잭션 롤백]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Undo --&gt; Ready[(서비스 가능)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Analysis&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;마지막 checkpoint 이후의 로그 스캔. 트랜잭션 상태 복원&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Redo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;committed 트랜잭션의 변경 &lt;em&gt;재적용&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Undo&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;uncommitted 트랜잭션 &lt;em&gt;롤백&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;모든 modern DB&lt;/em&gt; 가 ARIES 또는 변형. &lt;em&gt;최초 commit 후 즉시 fsync&lt;/em&gt; 가 &lt;em&gt;D (Durability) 의 정의&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;checkpoint&quot;&gt;Checkpoint&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;    Checkpointer-&gt;&gt;BufferPool: 모든 dirty page 식별&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loop dirty pages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Checkpointer-&gt;&gt;Disk: flush dirty page&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;    Checkpointer-&gt;&gt;WAL: checkpoint record 기록&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over WAL: checkpoint 이전 WAL 회수 가능&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;checkpoint = recovery 시작점&lt;/em&gt;. 자주 하면 &lt;em&gt;recovery 빠름 + I/O 부담&lt;/em&gt;. 드물게 하면 &lt;em&gt;I/O 적음 + recovery 길음&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fsync-의-비용과-group-commit&quot;&gt;fsync 의 비용과 group commit&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;    T1-&gt;&gt;DB: COMMIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T2-&gt;&gt;DB: COMMIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    T3-&gt;&gt;DB: COMMIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB-&gt;&gt;DB: WAL buffer 모음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB-&gt;&gt;Disk: 한 번의 fsync (3 트랜잭션 동시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB--&gt;&gt;T1: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB--&gt;&gt;T2: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB--&gt;&gt;T3: OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Group commit&lt;/em&gt;: 동시 commit 들을 &lt;em&gt;한 fsync 로 묶음&lt;/em&gt;. throughput 큰 향상.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;정책&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;th&gt;손실&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;fsync = on&lt;/code&gt;&lt;/td&gt;&lt;td&gt;매 commit fsync&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;synchronous_commit = off&lt;/code&gt;&lt;/td&gt;&lt;td&gt;WAL 버퍼만, fsync 지연&lt;/td&gt;&lt;td&gt;~수 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;fsync = off&lt;/code&gt;&lt;/td&gt;&lt;td&gt;fsync 안함&lt;/td&gt;&lt;td&gt;&lt;em&gt;crash 시 손실 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;&lt;code&gt;fsync = off&lt;/code&gt;&lt;/em&gt; 는 &lt;em&gt;벤치마크 외 절대 금지&lt;/em&gt;. crash 시 &lt;em&gt;DB 복구 불가&lt;/em&gt; 가능.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;wal--replication&quot;&gt;WAL → Replication&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;    Primary --&gt; WAL[(WAL)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WAL --&gt;|streaming| R1[Replica 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WAL --&gt;|streaming| R2[Replica 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    WAL --&gt;|archive| Archive[(S3 archive)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Archive --&gt;|PITR| Restore[Point-in-Time Recovery]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Physical replication&lt;/em&gt;: WAL 그대로 전송&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Logical replication&lt;/em&gt;: 행 단위 변경 (필터링 / 변환 가능)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Point-in-Time Recovery&lt;/em&gt;: archive + WAL = 임의 시점 복원&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자세한 건 [[postgresql]] / [[mysql-innodb]] / [[Redis Replication]].&lt;/p&gt;
&lt;h2 id=&quot;wal-의-형태별-비교&quot;&gt;WAL 의 형태별 비교&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;DB&lt;/th&gt;&lt;th&gt;WAL 이름&lt;/th&gt;&lt;th&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;PostgreSQL&lt;/td&gt;&lt;td&gt;WAL&lt;/td&gt;&lt;td&gt;physical + logical&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MySQL InnoDB&lt;/td&gt;&lt;td&gt;redo log + undo log + binlog&lt;/td&gt;&lt;td&gt;3 종 분리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SQLite&lt;/td&gt;&lt;td&gt;journal / WAL mode&lt;/td&gt;&lt;td&gt;journal_mode=WAL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis&lt;/td&gt;&lt;td&gt;AOF&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LMDB&lt;/td&gt;&lt;td&gt;(copy-on-write, no WAL)&lt;/td&gt;&lt;td&gt;다른 모델&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RocksDB&lt;/td&gt;&lt;td&gt;WAL&lt;/td&gt;&lt;td&gt;LSM-tree&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;etcd / CockroachDB&lt;/td&gt;&lt;td&gt;Raft log&lt;/td&gt;&lt;td&gt;consensus + WAL&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;fsync-만--안전&quot;&gt;fsync 만 = 안전?&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App[App] --&gt;|write| OS[OS Page Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    OS --&gt;|fsync| Disk[Disk Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Disk --&gt;|Battery / FUA| Platter[Persistent]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Disk cache&lt;/em&gt; 까지가 전제. &lt;em&gt;power loss 시 disk cache 손실&lt;/em&gt; → &lt;em&gt;battery-backed cache 또는 SSD 의 power-loss-protection (PLP)&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;fsync 안 함 + 빠르다고 자랑&lt;/strong&gt; = 사실 &lt;em&gt;D 가 사라진&lt;/em&gt; 상태. 벤치마크 무의미.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WAL archive 보관 부족&lt;/strong&gt; = PITR 시 &lt;em&gt;복원 시점 없음&lt;/em&gt;. &lt;em&gt;최소 3-7일&lt;/em&gt; 보관.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checkpoint 너무 자주&lt;/strong&gt; = I/O spike. PG &lt;code&gt;checkpoint_timeout&lt;/code&gt;, &lt;code&gt;max_wal_size&lt;/code&gt; 튜닝.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group commit 안 켬 (MySQL)&lt;/strong&gt; = throughput 1/N. &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; + binlog group commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&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;[[mvcc]]&lt;/li&gt;
&lt;li&gt;[[Redis Persistence]] (Redis 의 AOF)&lt;/li&gt;
&lt;li&gt;[[Redis Replication]]&lt;/li&gt;
&lt;li&gt;[[distributed-systems-consensus]] (Raft)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>wal</category><category>durability</category><category>recovery</category><category>replication</category><category>database</category><author>koa (김신건)</author></item><item><title>[PostgreSQL] JSONB: 장점과 단점, 인덱싱, 사용 패턴</title><link>https://shinkeonkim.com/wiki/database-internals/postgresql-jsonb/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/postgresql-jsonb/</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;JSONB&lt;/strong&gt; (Binary JSON, PG 9.4+) = PostgreSQL 의 &lt;em&gt;binary 직렬화된 JSON&lt;/em&gt;. &lt;em&gt;인덱싱 가능 + 빠른 query&lt;/em&gt; 가 &lt;code&gt;json&lt;/code&gt; 과의 핵심 차이.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
2026 시점 &lt;em&gt;PostgreSQL 에서 schema-less + 관계형 결합&lt;/em&gt; 의 표준. &lt;em&gt;MongoDB 대안&lt;/em&gt; + &lt;em&gt;NoSQL/SQL 혼합&lt;/em&gt; 의 답.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;json-vs-jsonb&quot;&gt;json vs jsonb&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;json&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;jsonb&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;저장&lt;/td&gt;&lt;td&gt;&lt;em&gt;텍스트 그대로&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;binary parsed&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;입력 속도&lt;/td&gt;&lt;td&gt;빠름&lt;/td&gt;&lt;td&gt;느림 (parse 비용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Query 속도&lt;/td&gt;&lt;td&gt;매번 parse&lt;/td&gt;&lt;td&gt;&lt;em&gt;빠름&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;인덱싱&lt;/td&gt;&lt;td&gt;제한&lt;/td&gt;&lt;td&gt;&lt;em&gt;GIN 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Key 순서&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;Whitespace&lt;/td&gt;&lt;td&gt;보존&lt;/td&gt;&lt;td&gt;손실&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;중복 key&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;blockquote&gt;
&lt;p&gt;거의 항상 &lt;em&gt;&lt;code&gt;jsonb&lt;/code&gt;&lt;/em&gt;. &lt;code&gt;json&lt;/code&gt; 은 &lt;em&gt;원본 형식 보존&lt;/em&gt; 필요 (서명, 감사) 시만.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;장점&quot;&gt;장점&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro[JSONB 장점]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro --&gt; S[Schemaless flexibility]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro --&gt; I[GIN 인덱스로 빠른 검색]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro --&gt; O[관계형 + 문서형 혼합]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro --&gt; T[트랜잭션 + ACID]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pro --&gt; F[다양한 연산자 / 함수]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-schemaless-유연성&quot;&gt;1. Schemaless 유연성&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  event_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  payload JSONB,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOW&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events (event_type, payload) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;user_id&quot;: 42, &quot;page&quot;: &quot;/home&quot;, &quot;duration&quot;: 1500}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;purchase&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;user_id&quot;: 42, &quot;product_id&quot;: 99, &quot;amount&quot;: 50000, &quot;items&quot;: [{&quot;sku&quot;: &quot;A&quot;}, {&quot;sku&quot;: &quot;B&quot;}]}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;error&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;code&quot;: 500, &quot;stack&quot;: &quot;...&quot;, &quot;context&quot;: {&quot;db&quot;: &quot;down&quot;}}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-gin-인덱스--풍부한-query&quot;&gt;2. GIN 인덱스 + 풍부한 query&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 인덱스 (전체 JSONB)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_payload_gin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload);&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;-- jsonb_path_ops: @&gt; 만 빠르게 (더 작은 인덱스)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_payload_path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload jsonb_path_ops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 특정 키만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_user_id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events ((payload&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_id&apos;&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;자세한 인덱스 비교는 [[gin-gist-hash-indexes]].&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- containment (@&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload @&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;user_id&quot;: 42}&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;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload @&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;items&quot;: [{&quot;sku&quot;: &quot;A&quot;}]}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- key 존재 (?)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload ? &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;error_code&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;-- key 다수 (?| 또는 ?&amp;#x26;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload ?| &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&apos;error_code&apos;, &apos;warning_code&apos;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 경로 추출&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_id&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; uid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, payload&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;items&apos;&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:#F97583&quot;&gt;-&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sku&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- JSONPath (PG 12+)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload @? &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;$.items[*].sku ? (@ == &quot;A&quot;)&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 갱신&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;processed&quot;: true}&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jsonb_set(payload, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{user_id}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;99&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;temp_field&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-트랜잭션--acid&quot;&gt;4. 트랜잭션 + ACID&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; accounts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; metadata &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jsonb_set(metadata, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{last_transfer}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&quot;2026-06-25&quot;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#F97583&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;MongoDB 에서는 &lt;em&gt;문서 안&lt;/em&gt; 트랜잭션 OK, &lt;em&gt;컬렉션 간&lt;/em&gt; 은 느림. &lt;em&gt;PostgreSQL = 어디든 ACID&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;단점&quot;&gt;단점&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con[JSONB 단점]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con --&gt; Size[저장 크기 큼]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con --&gt; NoSchema[스키마 검증 부재]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con --&gt; Slower[관계형 비교 느림]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con --&gt; Update[부분 update 비효율]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Con --&gt; Type[타입 변환 비용]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t1&lt;/span&gt;&lt;span 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;  a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; t1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span 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 style=&quot;color:#6A737D&quot;&gt;-- row size: ~40 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t2&lt;/span&gt;&lt;span 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;  data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JSONB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; t2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;a&quot;: 1, &quot;b&quot;: 2, &quot;c&quot;: &quot;hello&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- row size: ~80 bytes (key 이름이 반복 저장)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;JSONB 는 key 이름이 매 row 마다 반복&lt;/em&gt;. 1억 row * 50 byte key 이름 = 5GB 낭비.&lt;/p&gt;
&lt;/blockquote&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (..., &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&quot;user_id&quot;: &quot;not-a-number&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 통과! (string 도 OK)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 컬럼 분리면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (..., &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;not-a-number&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:#6A737D&quot;&gt;-- 에러&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;typo, 잘못된 타입 silent 통과&lt;/em&gt;. 운영 사고 단골.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-부분-update-의-비효율&quot;&gt;3. 부분 update 의 비효율&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 작은 변경도 *전체 row 재작성* (MVCC)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jsonb_set(payload, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{processed}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &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:#6A737D&quot;&gt;-- → 옛 row 삭제 마크 + 새 row 작성. 큰 JSONB 일수록 비쌈.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 MVCC 는 [[mvcc]].&lt;/p&gt;
&lt;h3 id=&quot;4-join-비효율&quot;&gt;4. JOIN 비효율&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- payload-&gt;&gt;&apos;user_id&apos; 로 join 하면 *인덱스 활용 어려움*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; e&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:#F97583&quot;&gt;-&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_id&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:#6A737D&quot;&gt;-- → 매 row JSON path 추출 + 타입 cast&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;자주 JOIN 하는 필드는 &lt;em&gt;별도 컬럼&lt;/em&gt; 으로 분리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;사용-패턴-좋은-vs-나쁜&quot;&gt;사용 패턴 (좋은 vs 나쁜)&lt;/h2&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;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 이벤트 / 로그 / 감사&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;events (event_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, payload JSONB)&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;form_responses (form_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, answers JSONB)&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;-- 외부 API 응답 캐시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;api_cache (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;endpoint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, response JSONB, expires_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&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;-- 자주 안 변하는 metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;products (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, sku &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, metadata JSONB)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-나쁜-사용&quot;&gt;✗ 나쁜 사용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 자주 조회 + 매번 같은 필드만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;users (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;profile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JSONB)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- → name, email 등을 *컬럼으로* 분리해야 함&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;-- 자주 update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;counters (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JSONB &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* { &quot;count&quot;: 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;-- → INT 컬럼이 *수십배 빠름*&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;-- JOIN 자주&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;orders (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user_data JSONB &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* { &quot;user_id&quot;: ... } */&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- → user_id 별도 컬럼 + FK&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;payload @&gt; &apos;{...}&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;gin(payload jsonb_path_ops)&lt;/code&gt; (작고 빠름)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;payload ? &apos;key&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;gin(payload)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;payload-&gt;&gt;&apos;user_id&apos; = &apos;42&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;((payload-&gt;&gt;&apos;user_id&apos;))&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;(payload-&gt;&gt;&apos;user_id&apos;)::int &gt; 1000&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;(((payload-&gt;&gt;&apos;user_id&apos;)::int))&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;자주 쓰는 필드&lt;/td&gt;&lt;td&gt;&lt;em&gt;별도 컬럼 + 일반 인덱스&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;hybrid-패턴-권장&quot;&gt;Hybrid 패턴 (권장)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  event_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGINT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 자주 JOIN/검색 → 별도 컬럼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  payload JSONB                    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 나머지 schemaless&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_user_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events (user_id, created_at);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_event_type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events (event_type);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_payload&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gin(payload jsonb_path_ops);&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;나머지 = JSONB&lt;/em&gt;. 양쪽 장점.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;jsonb-vs-mongodb&quot;&gt;JSONB vs MongoDB&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;PG JSONB&lt;/th&gt;&lt;th&gt;MongoDB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ACID&lt;/td&gt;&lt;td&gt;&lt;em&gt;Yes&lt;/em&gt;&lt;/td&gt;&lt;td&gt;문서 단위&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JOIN&lt;/td&gt;&lt;td&gt;&lt;em&gt;Yes&lt;/em&gt;&lt;/td&gt;&lt;td&gt;$lookup (느림)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Schema&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;flexible&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Indexing&lt;/td&gt;&lt;td&gt;GIN + B-tree&lt;/td&gt;&lt;td&gt;다양&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Aggregation&lt;/td&gt;&lt;td&gt;SQL + JSONB&lt;/td&gt;&lt;td&gt;aggregation pipeline&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Scale&lt;/td&gt;&lt;td&gt;큰 단일 + read replica&lt;/td&gt;&lt;td&gt;sharded native&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;관계형 + 문서 혼합&lt;/em&gt; + &lt;em&gt;&amp;#x3C;1TB&lt;/em&gt; = PG JSONB. &lt;em&gt;수십 TB 분산&lt;/em&gt; = MongoDB.&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;모든 데이터 JSONB&lt;/strong&gt; = JOIN 폭증 + 인덱스 폭증. &lt;em&gt;자주 쓰는 건 컬럼&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GIN 인덱스 빌드 시간&lt;/strong&gt; = 큰 테이블 30분-수 시간. CONCURRENTLY 옵션 + 운영 시간.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;jsonb_set 의 NULL&lt;/strong&gt; = &lt;code&gt;jsonb_set(payload, &apos;{a}&apos;, null::jsonb)&lt;/code&gt; 가 &lt;em&gt;key 자체 삭제 안 함&lt;/em&gt;. &lt;code&gt;payload - &apos;a&apos;&lt;/code&gt; 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;숫자 key 검색의 타입&lt;/strong&gt; = &lt;code&gt;payload-&gt;&gt;&apos;id&apos; = &apos;42&apos;&lt;/code&gt; (text). &lt;code&gt;(payload-&gt;&gt;&apos;id&apos;)::int = 42&lt;/code&gt; (cast).&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[postgresql]]&lt;/li&gt;
&lt;li&gt;[[mvcc]]&lt;/li&gt;
&lt;li&gt;[[gin-gist-hash-indexes]]&lt;/li&gt;
&lt;li&gt;[[mongodb]] (대안)&lt;/li&gt;
&lt;li&gt;[[query-explain-plan]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>jsonb</category><category>json</category><category>schemaless</category><category>indexing</category><author>koa (김신건)</author></item><item><title>[Redis] Bloom / Cuckoo / CMS / TopK: 확률 자료구조</title><link>https://shinkeonkim.com/wiki/database-internals/redis-probabilistic/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-probabilistic/</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;확률 자료구조 (Probabilistic Data Structures)&lt;/strong&gt; 는 &lt;em&gt;작은 메모리&lt;/em&gt; 로 &lt;em&gt;대규모 데이터의 통계적 답&lt;/em&gt; 을 &lt;em&gt;제어된 오차&lt;/em&gt; 로 내는 자료구조군. Redis 는 &lt;em&gt;Bloom 모듈 (Redis 8 부터 코어)&lt;/em&gt; 로 &lt;em&gt;Bloom Filter / Cuckoo Filter / Count-Min Sketch / TopK / t-digest&lt;/em&gt; 를 제공한다.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;자료구조&lt;/th&gt;&lt;th&gt;답하는 질문&lt;/th&gt;&lt;th&gt;대표 사용처&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bloom Filter&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”이 멤버를 본 적 있나?” (yes/no, &lt;em&gt;false positive 가능&lt;/em&gt;)&lt;/td&gt;&lt;td&gt;캐시 미스 차단, 중복 URL 필터&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cuckoo Filter&lt;/strong&gt;&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;&lt;strong&gt;Count-Min Sketch&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”이 멤버의 빈도?” (overcount 가능)&lt;/td&gt;&lt;td&gt;트래픽 통계, 광고 노출&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TopK&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”가장 자주 나오는 K 개”&lt;/td&gt;&lt;td&gt;핫 키, 인기 검색어&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;t-digest&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”p99, p95, 분위수”&lt;/td&gt;&lt;td&gt;latency 분포&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
이들의 공통점: *&lt;em&gt;&lt;em&gt;고정 또는 거의 고정 메모리&lt;/em&gt; 로 &lt;em&gt;멤버 수가 무한히 늘어도&lt;/em&gt; 안정적인 답&lt;/em&gt;. Set / Hash 같은 &lt;em&gt;정확한 자료구조&lt;/em&gt; 가 &lt;em&gt;불가능한 크기&lt;/em&gt; 에서 빛난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;bloom-filter&quot;&gt;Bloom Filter&lt;/h2&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;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;    Item[&quot;item: &apos;user:42&apos;&quot;] --&gt; H[&quot;k개 hash 함수&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h1| B1[&quot;bit 7&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h2| B2[&quot;bit 31&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h3| B3[&quot;bit 64&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B1 &amp;#x26; B2 &amp;#x26; B3 --&gt;|모두 set| BloomBits[(&quot;Bloom 비트 배열&quot;)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query[&quot;query: &apos;user:99&apos;&quot;] --&gt; H2[&quot;같은 k개 hash&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H2 --&gt;|h1| Q1[&quot;bit 12&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H2 --&gt;|h2| Q2[&quot;bit 64&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H2 --&gt;|h3| Q3[&quot;bit 100&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 &amp;#x26; Q2 &amp;#x26; Q3 --&gt;|모두 1?| Check{&quot;있다고 답&amp;#x3C;br/&gt;(false positive 가능)&quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추가 (BF.ADD)&lt;/strong&gt;: &lt;em&gt;k 개 hash 의 비트&lt;/em&gt; 를 1 로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;존재 확인 (BF.EXISTS)&lt;/strong&gt;: &lt;em&gt;k 개 hash 의 비트 모두 1&lt;/em&gt; 이면 &lt;em&gt;있다&lt;/em&gt; (단, &lt;em&gt;false positive&lt;/em&gt; 있음)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삭제&lt;/strong&gt;: &lt;em&gt;불가능&lt;/em&gt; (다른 멤버의 비트와 겹치면 누구 비트인지 모름)&lt;/li&gt;
&lt;/ul&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 자동 reserve (기본 capacity 100, error 0.01)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.ADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.EXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://example.com&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         # 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.EXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://other.com&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;           # 0 또는 1 (false positive)&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;# 명시 reserve (capacity 1M, error 0.001)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.RESERVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.001&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.ADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.MADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url3&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;BF.MEXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url3&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:#B392F0&quot;&gt;BF.INFO&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; urls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;메모리-vs-오차율&quot;&gt;메모리 vs 오차율&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
1M 멤버에 &lt;em&gt;오차율 0.001 (0.1%) ≈ 1.8 MB&lt;/em&gt;. Set 으로 1M URL (각 50B) 저장하면 &lt;em&gt;50 MB+&lt;/em&gt;. &lt;em&gt;25배 효율&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;활용-패턴&quot;&gt;활용 패턴&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 캐시 stampede 방지&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# DB 에 없는 키를 자주 미스 → DB 부담&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Bloom 으로 *DB 에 있는 키 집합* 추적&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; r.bfExists(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;known_users&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user_id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # DB 조회 (있을 *확률* 높음)&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; db.query(user_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Bloom 이 false 면 *100% 없음* 보장&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;Bloom 의 false negative 는 절대 없다&lt;/em&gt;. &lt;em&gt;false positive 만 있다&lt;/em&gt;. 따라서 &lt;em&gt;“없다고 한 건 100% 없다”&lt;/em&gt; 는 강한 보장. 이것이 &lt;em&gt;negative cache&lt;/em&gt; 와 다른 점.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;2. 중복 URL / 중복 ID 차단&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.RESERVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen_urls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.0001&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 크롤러: 새 URL 인지 확인&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.ADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen_urls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $url           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 추가 + 추가 전 false 였는지 반환&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 보안: 알려진 악성 IP / 토큰 차단&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BF.RESERVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bad_ips&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.001&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 차단 목록 1000만개에서 *직접 메모리 1.8 MB*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;cuckoo-filter-bloom-의-후계자&quot;&gt;Cuckoo Filter (Bloom 의 후계자)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Bloom 의 단점&lt;/em&gt; (삭제 불가) 을 해결. &lt;em&gt;추가 / 삭제 / 존재 확인&lt;/em&gt; 모두 가능. &lt;em&gt;false positive&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CF.RESERVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CF.ADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; item1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CF.EXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; item1&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CF.DEL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; item1&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;CF.EXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; item1&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bloom-vs-cuckoo-비교&quot;&gt;Bloom vs Cuckoo 비교&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Bloom&lt;/th&gt;&lt;th&gt;Cuckoo&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;추가 / 조회&lt;/td&gt;&lt;td&gt;O(k)&lt;/td&gt;&lt;td&gt;O(1) (대부분)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;삭제&lt;/td&gt;&lt;td&gt;&lt;em&gt;불가&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;채우기 임계&lt;/td&gt;&lt;td&gt;75% 부근부터 false positive 증가&lt;/td&gt;&lt;td&gt;90%+ 채워도 잘 동작&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모리 (같은 오차율)&lt;/td&gt;&lt;td&gt;보통 &lt;em&gt;Cuckoo 가 약간 효율&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;멤버 수 추정&lt;/td&gt;&lt;td&gt;어려움&lt;/td&gt;&lt;td&gt;&lt;em&gt;부정확하지만 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
동적 멤버십 (추가 + 삭제 모두) 이면 &lt;em&gt;Cuckoo&lt;/em&gt;. &lt;em&gt;추가만&lt;/em&gt; 이면 &lt;em&gt;Bloom&lt;/em&gt; (더 단순, 더 안정).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;count-min-sketch-cms-빈도-추정&quot;&gt;Count-Min Sketch (CMS): 빈도 추정&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;“이 키의 빈도가 몇 번?”&lt;/em&gt; 에 대한 &lt;em&gt;overcount 가능한&lt;/em&gt; 추정. &lt;em&gt;d × w 의 2D 카운터 배열&lt;/em&gt; 에 &lt;em&gt;d 개 hash 함수&lt;/em&gt; 로 &lt;em&gt;모든 hash 행 의 min&lt;/em&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;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;    Item[&quot;item: &apos;url:a&apos;&quot;] --&gt; H[&quot;d개 hash&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h1| C1[&quot;row 1 col 12 +1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h2| C2[&quot;row 2 col 33 +1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h3| C3[&quot;row 3 col 7 +1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|h4| C4[&quot;row 4 col 88 +1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Query[&quot;count(&apos;url:a&apos;)&quot;] --&gt; H2[&quot;같은 4개 hash&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H2 --&gt; Cells[&quot;같은 4 cell 값&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cells --&gt;|min| Estimate[&quot;frequency 추정&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;overcount 가능&lt;/em&gt; (다른 멤버와 충돌하면 위로 셈), &lt;em&gt;undercount 없음&lt;/em&gt;. min 이 &lt;em&gt;진짜 빈도 ≤ 답&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;명령-1&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 폭과 깊이로 초기화&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CMS.INITBYDIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; trafic&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2000&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:#6A737D&quot;&gt;# 또는 오차/확률로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CMS.INITBYPROB&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.001&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.01&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;CMS.INCRBY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url:a&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url:b&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:#B392F0&quot;&gt;CMS.QUERY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url:a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url:b&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              # [1, 5]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CMS.MERGE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; total&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; traffic3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WEIGHTS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;활용&quot;&gt;활용&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;트래픽 빈도 통계&lt;/strong&gt;: API endpoint 호출 빈도&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;광고 노출 카운터&lt;/strong&gt;: ad → 노출 횟수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DDoS 탐지&lt;/strong&gt;: IP → 요청 빈도&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인기 상품 추정&lt;/strong&gt;: product → 조회 빈도&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;정확한 빈도&lt;/em&gt; 가 필요하면 &lt;em&gt;Hash + HINCRBY&lt;/em&gt;. &lt;em&gt;수십억 키의 빈도 통계&lt;/em&gt; 같이 &lt;em&gt;Hash 가 메모리 부담&lt;/em&gt; 일 때 CMS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;topk-인기-k-개-추정&quot;&gt;TopK: 인기 K 개 추정&lt;/h2&gt;
&lt;p&gt;streaming 데이터에서 &lt;em&gt;Top K&lt;/em&gt; 를 &lt;em&gt;작은 메모리&lt;/em&gt; 로. &lt;em&gt;Heavy Keepers&lt;/em&gt; 알고리즘 기반.&lt;/p&gt;
&lt;h3 id=&quot;명령-2&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.RESERVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.9&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # k=10, width=1000, depth=5, decay=0.9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.ADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.INCRBY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.LIST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                          # 현재 top-K&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.LIST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WITHCOUNT&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;TOPK.QUERY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url2&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;               # top-K 안에 있는지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TOPK.COUNT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hot_urls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; url1&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;활용-1&quot;&gt;활용&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인기 검색어 실시간&lt;/strong&gt;: 검색 query 스트림 → TopK 100&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;핫 키 탐지&lt;/strong&gt;: cache key 접근 빈도 → 가장 자주 쓰이는 100개&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트렌딩 해시태그&lt;/strong&gt;: 최근 N분 윈도우의 TopK&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DDoS attacker top-K&lt;/strong&gt;: 의심 IP 우선순위&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
정확한 &lt;em&gt;Top-K&lt;/em&gt; 가 필요하면 &lt;em&gt;Sorted Set 의 ZRANGE&lt;/em&gt;. &lt;em&gt;수십억 unique 가 들어오는 streaming&lt;/em&gt; 에서 &lt;em&gt;고정 메모리 안&lt;/em&gt; 에 끝내려면 TopK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;자료구조별-메모리-비교&quot;&gt;자료구조별 메모리 비교&lt;/h2&gt;
&lt;p&gt;같은 &lt;em&gt;1억 unique 멤버&lt;/em&gt; 추적할 때:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;결정-매트릭스&quot;&gt;결정 매트릭스&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{필요한 답?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|존재 여부| Q2{삭제도 필요?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|예| CF[Cuckoo Filter]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|아니오| BF[Bloom Filter]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|cardinality 만| HLL[HyperLogLog]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|개별 빈도| CMS[Count-Min Sketch]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|Top K 인기| TK[TopK]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|분위수 p95/p99| TD[t-digest]&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;Bloom 의 false negative 가 &lt;em&gt;없다는 보장&lt;/em&gt; 을 &lt;em&gt;false positive 가 없다고 오해&lt;/em&gt;&lt;/strong&gt; = &lt;em&gt;없다&lt;/em&gt; 라고 한 건 100% 없음. &lt;em&gt;있다&lt;/em&gt; 라고 한 건 &lt;em&gt;확률적&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CMS 의 overcount 를 &lt;em&gt;실제 빈도&lt;/em&gt; 로 사용&lt;/strong&gt; = 항상 &lt;em&gt;추정 ≥ 진짜&lt;/em&gt;. &lt;em&gt;비교 / 상위 K&lt;/em&gt; 같은 &lt;em&gt;순위&lt;/em&gt; 용도로만 신뢰.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고정 capacity Bloom 의 over-fill&lt;/strong&gt; = capacity 를 &lt;em&gt;넘기면&lt;/em&gt; false positive rate 가 &lt;em&gt;급격 악화&lt;/em&gt;. &lt;em&gt;주기적 reset + 새 키&lt;/em&gt; 또는 &lt;em&gt;scaling Bloom&lt;/em&gt; 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확률 자료구조의 합집합&lt;/strong&gt; = HLL &lt;code&gt;PFMERGE&lt;/code&gt; 와 CMS &lt;code&gt;CMS.MERGE&lt;/code&gt; 가능. Bloom 은 &lt;em&gt;비트 OR&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;&lt;em&gt;역량 리포트&lt;/em&gt; 의 &lt;em&gt;학생 활동 통계&lt;/em&gt; 같은 &lt;em&gt;방대한 streaming 통계&lt;/em&gt; 에서 &lt;em&gt;Hash + HINCRBY 가 너무 무거우면&lt;/em&gt; &lt;em&gt;CMS&lt;/em&gt; + &lt;em&gt;정확한 TopK 만 Sorted Set&lt;/em&gt; 의 &lt;em&gt;하이브리드&lt;/em&gt; 가 &lt;em&gt;메모리 / 정확도 균형&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Bloom 으로 cache miss 차단&lt;/em&gt; 은 &lt;em&gt;Hot Path 의 가장 가벼운 가속&lt;/em&gt;. &lt;em&gt;없는 키를 매번 DB 조회&lt;/em&gt; 하는 비용이 &lt;em&gt;Bloom 메모리보다 압도적으로 큼&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;TopK 의 decay 파라미터 (0.9)&lt;/em&gt; 가 &lt;em&gt;최근 트래픽 가중치&lt;/em&gt;. 너무 작으면 (0.5) 옛 데이터 무시, 너무 크면 (0.99) 변화 느림. &lt;em&gt;대시보드 갱신 주기에 맞춰 튜닝&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Redis 8 의 코어 통합&lt;/em&gt; 으로 &lt;em&gt;별도 모듈 설치 없이&lt;/em&gt; 사용. &lt;em&gt;프로덕션 도입 진입 장벽&lt;/em&gt; 이 &lt;em&gt;대폭 낮아짐&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (자료구조 카탈로그)&lt;/li&gt;
&lt;li&gt;[[Redis Sets]] (정확한 멤버십 대안)&lt;/li&gt;
&lt;li&gt;[[Redis HyperLogLog Geo]] (HLL 의 cardinality 추정)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (정확한 TopK)&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]] (Bloom 을 캐시 앞에)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/probabilistic/&quot;&gt;Probabilistic Data Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bloom 원논문: &lt;a href=&quot;https://dl.acm.org/doi/10.1145/362686.362692&quot;&gt;Burton Bloom, 1970&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cuckoo Filter: &lt;a href=&quot;https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf&quot;&gt;Fan et al.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CMS: &lt;a href=&quot;http://dimacs.rutgers.edu/~graham/pubs/papers/cmencyc.pdf&quot;&gt;Cormode &amp;#x26; Muthukrishnan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>data-structure</category><category>probabilistic</category><category>bloom</category><category>cuckoo</category><category>count-min</category><category>topk</category><author>koa (김신건)</author></item><item><title>[Redis] Cache Patterns: Cache-Aside, Stampede, Eviction</title><link>https://shinkeonkim.com/wiki/database-internals/redis-cache-patterns/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-cache-patterns/</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;Cache Pattern&lt;/strong&gt; 은 &lt;em&gt;Redis (또는 다른 캐시)&lt;/em&gt; 를 &lt;em&gt;어떤 경로로 읽고 쓰는지&lt;/em&gt; 의 디자인 결정. 같은 인프라로도 &lt;em&gt;패턴 선택에 따라 일관성 / 지연 / 부하&lt;/em&gt; 가 완전히 달라진다.&lt;/p&gt;
&lt;p&gt;가장 흔한 다섯 가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cache-aside&lt;/strong&gt; (look-aside): &lt;em&gt;애플리케이션이&lt;/em&gt; 캐시를 &lt;em&gt;직접&lt;/em&gt; 본다. miss 면 DB 로 가서 &lt;em&gt;직접&lt;/em&gt; 캐시에 채운다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-through&lt;/strong&gt;: &lt;em&gt;캐시 라이브러리&lt;/em&gt; 가 miss 시 &lt;em&gt;자동&lt;/em&gt; 으로 backing store 를 조회.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write-through&lt;/strong&gt;: write 시 &lt;em&gt;캐시 + DB 동시&lt;/em&gt; 갱신. 일관성 ↑.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write-behind / Write-back&lt;/strong&gt;: 캐시 먼저 쓰고 &lt;em&gt;비동기&lt;/em&gt; 로 DB 반영. 쓰기 빠름, 손실 위험.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refresh-ahead&lt;/strong&gt;: &lt;em&gt;TTL 만료 직전&lt;/em&gt; 에 &lt;em&gt;백그라운드 refresh&lt;/em&gt;. miss 자체를 줄임.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;cache-hit--miss-의-시각&quot;&gt;Cache Hit / Miss 의 시각&lt;/h2&gt;
&lt;p&gt;행복 경로 (HIT):&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;캐시 채우는 경로 (MISS → DB → set):&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;[!NOTE]
위 두 애니메이션은 &lt;em&gt;왼쪽 Client → Cache → DB&lt;/em&gt; 의 표준 &lt;em&gt;cache-aside&lt;/em&gt; 패턴. HIT 는 ~6ms, MISS 는 DB 왕복까지 더해 ~72ms 의 &lt;em&gt;직관 차이&lt;/em&gt; 를 보여준다.&lt;/p&gt;
&lt;/blockquote&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;    subgraph CA[Cache-Aside]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A1[App] --&gt;|GET| C1[Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1 --&gt;|miss| A1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A1 --&gt;|SELECT| D1[(DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1 --&gt; A1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A1 --&gt;|SET| C1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph WT[Write-Through]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A2[App] --&gt;|UPDATE| L2[Cache lib]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L2 --&gt;|동시| C2[Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L2 --&gt;|동시| D2[(DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph WB[Write-Behind]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A3[App] --&gt;|UPDATE| L3[Cache lib]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L3 --&gt;|즉시| C3[Cache]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L3 -.비동기.-&gt; D3[(DB)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매트릭스:&lt;/p&gt;





















































&lt;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;쓰기 지연&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;Cache-aside&lt;/td&gt;&lt;td&gt;약 (TTL 의존)&lt;/td&gt;&lt;td&gt;miss 만 느림&lt;/td&gt;&lt;td&gt;DB 만 영향&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;td&gt;&lt;em&gt;기본값&lt;/em&gt;. Rails &lt;code&gt;Rails.cache.fetch&lt;/code&gt;, Django&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Read-through&lt;/td&gt;&lt;td&gt;약 (TTL 의존)&lt;/td&gt;&lt;td&gt;miss 만 느림&lt;/td&gt;&lt;td&gt;DB 만 영향&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;td&gt;캐시 라이브러리 / Hibernate L2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Write-through&lt;/td&gt;&lt;td&gt;강 (캐시 ↔ DB 같은 순간)&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;Write-behind&lt;/td&gt;&lt;td&gt;약 (지연된 일관성)&lt;/td&gt;&lt;td&gt;캐시에서 즉시&lt;/td&gt;&lt;td&gt;캐시만 기다림&lt;/td&gt;&lt;td&gt;높음 (장애 시 손실)&lt;/td&gt;&lt;td&gt;메트릭 / 로그 집계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Refresh-ahead&lt;/td&gt;&lt;td&gt;약&lt;/td&gt;&lt;td&gt;항상 빠름&lt;/td&gt;&lt;td&gt;백그라운드&lt;/td&gt;&lt;td&gt;높음&lt;/td&gt;&lt;td&gt;인기 콘텐츠, 메인 페이지&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;cache-aside-의-정석&quot;&gt;Cache-aside 의 정석&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;GET → miss → SELECT → SET&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;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 Cache as Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant DB as PostgreSQL&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;Cache: GET user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt HIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;App: 데이터 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    else MISS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;App: (nil)&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 * FROM users WHERE id=42&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: row&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        App-&gt;&gt;Cache: SET user:42 ... EX 600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;App: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여러 언어로 같은 패턴:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
outputLanguage=“text”
outputLabel=“동일”
title=“Cache-aside 의 본질”
variants={[
{
label: ‘ruby’,
language: ‘ruby’,
code: &lt;code&gt;# Rails 의 표준 def find_user(id)   Rails.cache.fetch(&quot;user:#{id}&quot;, expires_in: 10.minutes) do     User.find(id)   end end&lt;/code&gt;,
},
{
label: ‘python’,
language: ‘python’,
code: `import redis, json
r = redis.Redis()&lt;/p&gt;
&lt;p&gt;def find_user(id: int):
key = f”user:{id}”
cached = r.get(key)
if cached:
return json.loads(cached)
user = db.query(“SELECT * FROM users WHERE id=%s”, id).fetchone()
r.set(key, json.dumps(user), ex=600)
return user&lt;code&gt;,     },     {       label: &apos;typescript&apos;,       language: &apos;typescript&apos;,       code: &lt;/code&gt;import { Redis } from ‘ioredis’;
const r = new Redis();&lt;/p&gt;
&lt;p&gt;export async function findUser(id: number) {
const key = `user:${id}`;
const cached = await r.get(key);
if (cached) return JSON.parse(cached);&lt;/p&gt;
&lt;p&gt;const user = await db.queryOne(‘SELECT * FROM users WHERE id=$1’, [id]);
await r.set(key, JSON.stringify(user), ‘EX’, 600);
return user;
}&lt;code&gt;,     },     {       label: &apos;java&apos;,       language: &apos;java&apos;,       code: &lt;/code&gt;@Service
class UserService {
private final RedisTemplate&amp;#x3C;String, User&gt; redis;
private final UserRepo repo;&lt;/p&gt;
&lt;p&gt;public User find(long id) {
var key = “user:” + id;
User cached = redis.opsForValue().get(key);
if (cached != null) return cached;&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;User user = repo.findById(id).orElseThrow();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;redis.opsForValue().set(key, user, Duration.ofMinutes(10));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return user;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}
}&lt;code&gt;,     },   ]}   output={&lt;/code&gt;동일 입력 → 동일 응답. miss 시 첫 호출만 DB 왕복.`}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;cache-stampede-thundering-herd--dogpile&quot;&gt;Cache Stampede (Thundering Herd / Dogpile)&lt;/h2&gt;
&lt;p&gt;TTL 이 &lt;em&gt;동시에 만료&lt;/em&gt; 되면 &lt;em&gt;수많은 클라이언트&lt;/em&gt; 가 &lt;em&gt;동시에 DB&lt;/em&gt; 를 친다. &lt;em&gt;원래 막아주던 캐시&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Cache&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;    Note over Cache: TTL 0, 만료된 순간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par C1, C2, C3 모두 동시에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1-&gt;&gt;Cache: GET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;C1: nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C2-&gt;&gt;Cache: GET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;C2: nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C3-&gt;&gt;Cache: GET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Cache--&gt;&gt;C3: nil&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;    par 모두 DB 로 (stampede!)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1-&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;        C2-&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;        C3-&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;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par 같은 결과를 캐시에 N 번 SET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C1-&gt;&gt;Cache: SET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C2-&gt;&gt;Cache: SET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C3-&gt;&gt;Cache: SET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;완화-패턴-4가지&quot;&gt;완화 패턴 4가지&lt;/h3&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;Single-flight (mutex)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;SET key:lock NX EX&lt;/code&gt; 잡은 &lt;em&gt;한 명&lt;/em&gt; 만 DB 조회, 나머지는 &lt;em&gt;기다림 / 옛 값&lt;/em&gt; 사용&lt;/td&gt;&lt;td&gt;락 노드 장애 시 데드락 위험&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Probabilistic Early Expiration (XFetch)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;남은 TTL 의 함수&lt;/em&gt; 로 &lt;em&gt;확률적 미리 갱신&lt;/em&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;&lt;strong&gt;Stale-while-revalidate&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;TTL 만료 후에도 &lt;em&gt;N 초간 옛 값 응답&lt;/em&gt; + &lt;em&gt;백그라운드 갱신&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;완전 stale&lt;/em&gt; 응답을 허용해야 함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Jitter on TTL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;TTL = base + random(±N%)&lt;/td&gt;&lt;td&gt;&lt;em&gt;동시 만료 자체&lt;/em&gt; 만 분산. 완전 해결 X&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;xfetch-probabilistic-early-expiration&quot;&gt;XFetch (Probabilistic Early Expiration)&lt;/h3&gt;
&lt;p&gt;논문 (&lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_stampede&quot;&gt;Vattani et al.&lt;/a&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;expiry = now + delta * beta * log(rand())&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;캐시에 &lt;em&gt;기록 시점에 평균 fetch 시간 &lt;code&gt;delta&lt;/code&gt;&lt;/em&gt; 를 함께 저장. 읽을 때 &lt;em&gt;위 식이 TTL 만료 시점을 넘으면&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; math, random, time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_with_xfetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r, key, fetch_fn, ttl_seconds, beta&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.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:#E1E4E8&quot;&gt;    payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.get(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; refresh(r, key, fetch_fn, ttl_seconds)&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;    value, delta, expiry &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decode(payload)&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:#F97583&quot;&gt;    if&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:#E1E4E8&quot;&gt; delta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; beta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; math.log(random.random()) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; expiry:&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;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; refresh(r, key, fetch_fn, ttl_seconds)&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; value&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;single-flight-set-nx-락&quot;&gt;Single-flight: SET NX 락&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cache_aside_with_lock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r, key, fetch_fn, ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.get(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&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;    lock_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;lock:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;key&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;    got &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.set(lock_key, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;nx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; got:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            fresh &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fetch_fn()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            r.set(key, fresh, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ttl)&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; fresh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        finally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            r.delete(lock_key)&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:#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;        time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.05&lt;/span&gt;&lt;span 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; cache_aside_with_lock(r, key, fetch_fn, ttl)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;eviction-policy-maxmemory-policy&quot;&gt;Eviction Policy (&lt;code&gt;maxmemory-policy&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;메모리 한도 (&lt;code&gt;maxmemory&lt;/code&gt;) 초과 시 &lt;em&gt;어떤 키부터 버릴지&lt;/em&gt;. 패턴 선택만큼 중요.&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Policy&lt;/th&gt;&lt;th&gt;동작&lt;/th&gt;&lt;th&gt;권장 시나리오&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;noeviction&lt;/code&gt; &lt;em&gt;(기본)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;메모리 초과 시 &lt;em&gt;write 에러&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;데이터 손실 절대 안 됨&lt;/em&gt; (primary store)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-lru&lt;/code&gt;&lt;/td&gt;&lt;td&gt;모든 키 중 &lt;em&gt;최근 덜 쓰인 (LRU)&lt;/em&gt; 제거&lt;/td&gt;&lt;td&gt;&lt;em&gt;순수 캐시&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-lfu&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;덜 자주 쓰인 (LFU)&lt;/em&gt; 제거&lt;/td&gt;&lt;td&gt;&lt;em&gt;인기 분포 편중&lt;/em&gt; 환경 (롱테일)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allkeys-random&lt;/code&gt;&lt;/td&gt;&lt;td&gt;무작위&lt;/td&gt;&lt;td&gt;LRU 비용도 아까울 때 (드물게)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;volatile-lru&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;TTL 설정된 키 중&lt;/em&gt; LRU&lt;/td&gt;&lt;td&gt;&lt;em&gt;세션 + 영구 데이터&lt;/em&gt; 혼합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;volatile-lfu&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TTL 키 중 LFU&lt;/td&gt;&lt;td&gt;동일, 분포 편중&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;volatile-ttl&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;남은 TTL 짧은 키부터&lt;/em&gt;&lt;/td&gt;&lt;td&gt;TTL 의 의미가 &lt;em&gt;우선순위&lt;/em&gt; 일 때&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;volatile-random&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TTL 키 중 무작위&lt;/td&gt;&lt;td&gt;드물게&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;lru-의-동작-직관&quot;&gt;LRU 의 동작 직관&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;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Redis 의 LRU 는 &lt;em&gt;근사 LRU&lt;/em&gt; (&lt;code&gt;maxmemory-samples&lt;/code&gt; 만큼 sampling). 정확한 LRU 가 아니다. &lt;code&gt;maxmemory-samples 10&lt;/code&gt; 정도가 정확도 / 비용의 보통 타협점.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;eviction-성능-비교-직관&quot;&gt;Eviction 성능 비교 (직관)&lt;/h3&gt;
&lt;p&gt;같은 워크로드, 다른 정책에서의 &lt;em&gt;히트율 차이&lt;/em&gt; 의 직관:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;negative-caching-404--null-도-캐시&quot;&gt;Negative Caching (404 / null 도 캐시)&lt;/h2&gt;
&lt;p&gt;DB miss 자체가 비싸면 &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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_user&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;    cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.get(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{id}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NULL_SENTINEL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&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; cached &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.loads(cached)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; db_lookup(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        r.set(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{id}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NULL_SENTINEL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 짧은 TTL&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:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        r.set(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{id}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, json.dumps(user), &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;600&lt;/span&gt;&lt;span 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; user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Negative cache TTL 은 &lt;em&gt;짧게&lt;/em&gt; (예: 60초). 안 그러면 &lt;em&gt;방금 가입한 사용자가 한참 동안 안 보이는&lt;/em&gt; 버그.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;무효화-invalidation&quot;&gt;무효화 (Invalidation)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;“There are only two hard things in Computer Science: cache invalidation and naming things.” (Phil Karlton)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;write-시-캐시-무효화-가장-흔함&quot;&gt;Write 시 캐시 &lt;em&gt;무효화&lt;/em&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; update_user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(id, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fields):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    db.update(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;UPDATE users SET ... WHERE id=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&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;fields)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    r.delete(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;user:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{id}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 다음 GET 이 miss → 새로 채움&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;SET&lt;/code&gt; 으로 갱신&lt;/em&gt; 보다 &lt;code&gt;DEL&lt;/code&gt; 이 일반적으로 안전. 멀티 키 / 파생 캐시까지 정확히 갱신하기 어렵기 때문에 &lt;em&gt;지우고 다시 빠뜨리는&lt;/em&gt; 패턴.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;tagged-invalidation-russian-doll&quot;&gt;Tagged Invalidation (Russian Doll)&lt;/h3&gt;
&lt;p&gt;특정 그룹의 캐시 &lt;em&gt;일괄 무효화&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;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(post_id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    version &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.get(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;post:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:version&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cached_get(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;post:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:v&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&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; invalidate_post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(post_id):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    r.incr(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;post:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:version&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;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Rails.cache.fetch&lt;/em&gt; 의 &lt;em&gt;race_condition_ttl&lt;/em&gt; 이 &lt;em&gt;single-flight + stale-while-revalidate&lt;/em&gt; 의 &lt;em&gt;내장 버전&lt;/em&gt;. 인기 페이지에 &lt;em&gt;반드시&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sidekiq job 결과&lt;/em&gt; 를 캐시할 때 &lt;em&gt;영구 캐시 + 명시적 invalidation&lt;/em&gt; 이 &lt;em&gt;짧은 TTL&lt;/em&gt; 보다 &lt;em&gt;DB / Redis 모두 가벼웠다&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Memcached 에서 Redis 로 갈아탄&lt;/em&gt; 가장 큰 이유: &lt;em&gt;negative cache&lt;/em&gt; 와 &lt;em&gt;데이터 구조&lt;/em&gt; 였다. &lt;em&gt;Memcached&lt;/em&gt; 는 &lt;em&gt;단순 캐시&lt;/em&gt; 의 &lt;em&gt;끝까지 단단&lt;/em&gt; 하지만, &lt;em&gt;해시 / 정렬셋 / 집합&lt;/em&gt; 이 필요해지면 결국 Redis.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;LFU 가 LRU 보다 좋은 경우&lt;/em&gt; 의 직관: &lt;em&gt;“대부분의 트래픽은 인기 콘텐츠 20%”&lt;/em&gt; 이면 LFU 가 강함. &lt;em&gt;균일 분포&lt;/em&gt; 에서는 LRU 와 LFU 가 비슷.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능)&lt;/li&gt;
&lt;li&gt;[[Redis Persistence]] (캐시 전용일 때 영속화 비활성)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (캐시 무효화 fan-out)&lt;/li&gt;
&lt;li&gt;[[Zero Downtime Deployment]] (배포 시 캐시 워밍)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/reference/eviction/&quot;&gt;Eviction policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AWS: &lt;a href=&quot;https://aws.amazon.com/caching/best-practices/&quot;&gt;Caching Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wikipedia: &lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_stampede&quot;&gt;Cache stampede&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare: &lt;a href=&quot;https://blog.cloudflare.com/origin-cache-control/&quot;&gt;SWR 적용&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>cache</category><category>pattern</category><category>eviction</category><category>stampede</category><category>performance</category><author>koa (김신건)</author></item><item><title>[Redis] Cluster: 16384 슬롯, MOVED/ASK, Atomic Slot Migration</title><link>https://shinkeonkim.com/wiki/database-internals/redis-cluster/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-cluster/</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;Redis Cluster&lt;/strong&gt; 는 &lt;em&gt;수평 확장&lt;/em&gt; 을 위한 &lt;em&gt;내장된 sharding&lt;/em&gt; + &lt;em&gt;자동 failover&lt;/em&gt;. &lt;strong&gt;16384 개의 hash slot&lt;/strong&gt; 으로 &lt;em&gt;키 공간&lt;/em&gt; 을 잘라 &lt;em&gt;여러 노드&lt;/em&gt; 에 분배. 각 슬롯은 &lt;em&gt;replica&lt;/em&gt; 를 가질 수 있어 &lt;em&gt;고가용성&lt;/em&gt; 도 함께.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;Sentinel&lt;/em&gt; 은 &lt;em&gt;분할 없음 + 전체 데이터셋이 한 노드&lt;/em&gt;. &lt;em&gt;Cluster&lt;/em&gt; 는 &lt;em&gt;분할 + 자동 failover&lt;/em&gt;. &lt;em&gt;데이터셋 &gt; 한 노드 메모리&lt;/em&gt; 면 Cluster 가 답.&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;slot = CRC16(key) mod 16384&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;키마다 &lt;em&gt;결정적&lt;/em&gt; 으로 0 ~ 16383 사이 슬롯 번호. 노드는 &lt;em&gt;연속된 슬롯 범위&lt;/em&gt; 들을 담당.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    K1[&quot;key: user:42&quot;] --&gt;|CRC16 mod 16384| S1[slot 5793]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    K2[&quot;key: order:1&quot;] --&gt;|CRC16 mod 16384| S2[slot 11923]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    K3[&quot;key: cart:99&quot;] --&gt;|CRC16 mod 16384| S3[slot 2010]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1 --&gt;|&quot;0..5460&quot;| N1[(Node A)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2 --&gt;|&quot;10923..16383&quot;| N3[(Node C)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S3 --&gt;|&quot;5461..10922&quot;| N2[(Node B)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;hash-tag-같은-슬롯에-묶고-싶을-때&quot;&gt;Hash Tag: 같은 슬롯에 묶고 싶을 때&lt;/h3&gt;
&lt;p&gt;기본은 &lt;em&gt;키 전체&lt;/em&gt; 가 hash 입력. &lt;em&gt;중괄호로 일부만 감싸면&lt;/em&gt; 그 부분만 hash 됨.&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;{user:42}:cart        → CRC16(&quot;user:42&quot;) % 16384&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{user:42}:profile     → CRC16(&quot;user:42&quot;) % 16384   ← 같은 슬롯&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{user:42}:history     → CRC16(&quot;user:42&quot;) % 16384   ← 같은 슬롯&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;멀티 키 명령 (&lt;code&gt;MGET&lt;/code&gt;, &lt;code&gt;MSET&lt;/code&gt;, &lt;code&gt;SUNION&lt;/code&gt;, transaction MULTI/EXEC, Lua KEYS)&lt;/em&gt; 은 &lt;em&gt;모든 키가 같은 슬롯&lt;/em&gt; 에 있어야 동작. &lt;em&gt;항상 같이 묶어 다닐&lt;/em&gt; 키들에 hash tag 를 미리 박아두는 게 &lt;em&gt;유일한 처방&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;토폴로지&quot;&gt;토폴로지&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Cluster&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      subgraph ShardA[&quot;Shard A (slots 0..5460)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PA[(Primary A)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RA1[(Replica A1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RA2[(Replica A2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      subgraph ShardB[&quot;Shard B (slots 5461..10922)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PB[(Primary B)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RB1[(Replica B1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      subgraph ShardC[&quot;Shard C (slots 10923..16383)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PC[(Primary C)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RC1[(Replica C1)]&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;      PA -.gossip.-&gt; PB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      PB -.gossip.-&gt; PC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      PA -.gossip.-&gt; PC&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;    Client --&gt;|MOVED redirect| ShardA &amp;#x26; ShardB &amp;#x26; ShardC&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;모든 &lt;em&gt;primary&lt;/em&gt; 끼리 &lt;em&gt;gossip&lt;/em&gt; (Cluster Bus port = &lt;code&gt;client_port + 10000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;클라이언트는 &lt;em&gt;cluster-aware&lt;/em&gt;. &lt;em&gt;처음 한 번&lt;/em&gt; 토폴로지 조회 후 &lt;em&gt;MOVED 받으면&lt;/em&gt; 캐시 갱신.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;moved--ask-리디렉션&quot;&gt;MOVED / ASK 리디렉션&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;클라이언트가 잘못된 노드&lt;/em&gt; 에 명령을 보내면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant A as Node A (slot 5793 주인)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant B as Node B (slot 5793 주인 아님)&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;B: GET user:42 (slot 5793)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B--&gt;&gt;C: -MOVED 5793 10.0.0.1:6379&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 클라이언트가 토폴로지 캐시 갱신&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;A: GET user:42 (slot 5793)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A--&gt;&gt;C: &quot;kim&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마이그레이션 중인 슬롯은 다르다. &lt;code&gt;ASK&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Src as 출발 노드&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Dst as 도착 노드&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 Src,Dst: slot 5793 가 Src → Dst 마이그레이션 중&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Src: GET user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt 키가 아직 Src 에 있음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Src--&gt;&gt;C: 값 반환&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;        Src--&gt;&gt;C: -ASK 5793 dst-ip:port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;Dst: ASKING + GET user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Dst--&gt;&gt;C: 값 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;차이:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Redirect&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;th&gt;클라이언트 동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MOVED&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;영구적&lt;/em&gt; 으로 슬롯의 주인이 바뀜&lt;/td&gt;&lt;td&gt;토폴로지 캐시 &lt;em&gt;갱신&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ASK&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;마이그레이션 일시적&lt;/em&gt; 으로 &lt;em&gt;이번 요청만&lt;/em&gt; 도착 노드&lt;/td&gt;&lt;td&gt;&lt;code&gt;ASKING&lt;/code&gt; prefix 후 &lt;em&gt;한 번&lt;/em&gt; 만 보냄. 캐시 안 바꿈&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;슬롯-마이그레이션-resharding&quot;&gt;슬롯 마이그레이션 (Resharding)&lt;/h2&gt;
&lt;p&gt;전통적 흐름 (Redis 7 이전 + 현재 Redis 호환):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stateDiagram-v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    [*] --&gt; STABLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    STABLE --&gt; IMPORTING: CLUSTER SETSLOT N IMPORTING &amp;#x3C;src&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    STABLE --&gt; MIGRATING: CLUSTER SETSLOT N MIGRATING &amp;#x3C;dst&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    MIGRATING --&gt; KeysMoving: MIGRATE 명령 반복&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    KeysMoving --&gt; KeysMoving: ASK 응답으로 클라이언트 안내&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    KeysMoving --&gt; STABLE: 모든 키 이전 + CLUSTER SETSLOT N NODE &amp;#x3C;dst&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IMPORTING --&gt; STABLE: 같이 STABLE 로&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;흐름의 &lt;em&gt;비원자성&lt;/em&gt; 이 가장 큰 함정. &lt;em&gt;중간&lt;/em&gt; 에 &lt;em&gt;재시작 / 네트워크 사고&lt;/em&gt; 가 발생하면 &lt;em&gt;손으로 복구&lt;/em&gt; 가 필요.&lt;/p&gt;
&lt;h3 id=&quot;valkey-90-atomic-slot-migration&quot;&gt;Valkey 9.0: Atomic Slot Migration&lt;/h3&gt;
&lt;p&gt;Valkey 9.0 (2025-10) 의 가장 큰 셀링 포인트. &lt;em&gt;슬롯 단위로 원자적 마이그레이션&lt;/em&gt;. 클라이언트는 &lt;em&gt;MOVED 한 번&lt;/em&gt; 만 받고 끝. &lt;em&gt;MIGRATING/IMPORTING/ASK&lt;/em&gt; 의 &lt;em&gt;복잡한 중간 상태&lt;/em&gt; 가 단축된다 (&lt;a href=&quot;https://valkey.io/blog/atomic-slot-migration/&quot;&gt;blog&lt;/a&gt;).&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;전통 (Redis Cluster)&lt;/th&gt;&lt;th&gt;Atomic (Valkey 9.0+)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;슬롯 한 개 마이그레이션&lt;/td&gt;&lt;td&gt;다단계 (MIGRATING / IMPORTING / per-key MIGRATE / ASK 응답 / SETSLOT)&lt;/td&gt;&lt;td&gt;&lt;em&gt;원자적 단일 단계&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;클라이언트 redirect&lt;/td&gt;&lt;td&gt;ASK 다수 + MOVED 1개&lt;/td&gt;&lt;td&gt;MOVED 1개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;중간 실패 복구&lt;/td&gt;&lt;td&gt;&lt;em&gt;수동 SETSLOT 복구&lt;/em&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;p&gt;&lt;code&gt;scale out&lt;/code&gt; 이 &lt;em&gt;얼마나 잘 통하는지&lt;/em&gt; 의 직관 (Valkey 9.0 의 1B RPS 벤치마크):&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;멀티 키 명령&lt;/em&gt; / &lt;em&gt;transaction&lt;/em&gt; 이 많을수록 &lt;em&gt;선형성&lt;/em&gt; 이 깨진다. &lt;em&gt;cross-shard&lt;/em&gt; 요청은 &lt;em&gt;클라이언트가 N 번 분할&lt;/em&gt; 해서 보내야 하기 때문.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;쓰기--읽기-라우팅&quot;&gt;쓰기 / 읽기 라우팅&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client[Client] --&gt;|GET user:42| Pool[Cluster 클라이언트]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pool --&gt;|CRC16 + slot map| Decide{primary or replica?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Decide --&gt;|쓰기| Primary[(Primary)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Decide --&gt;|READONLY 켰을 때| Replica[(Replica)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Decide --&gt;|&quot;기본 (READONLY 안 켜면)&quot;| Primary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Primary --&gt;|복제 비동기| Replica&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;READONLY&lt;/code&gt; 명령으로 &lt;em&gt;해당 연결에서만&lt;/em&gt; replica 읽기 허용. &lt;em&gt;replica 가 stale&lt;/em&gt; 한 경우가 있어 &lt;em&gt;eventual consistent&lt;/em&gt; OK 한 쿼리에만.&lt;/p&gt;
&lt;h2 id=&quot;셋업-redis-cli-의-cluster-도구&quot;&gt;셋업: redis-cli 의 cluster 도구&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“bash”
label=”$ bash”
outputLanguage=“text”
outputLabel=“redis-cli”
title=“6 노드 클러스터 생성 + 슬롯 분배”
code={&lt;code&gt;# 3 primary + 3 replica redis-cli --cluster create \\   10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \\   10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \\   --cluster-replicas 1&lt;/code&gt;}
output={`&gt;&gt;&gt; Performing hash slots allocation on 6 nodes…
Master[0] -&gt; Slots 0 - 5460
Master[1] -&gt; Slots 5461 - 10922
Master[2] -&gt; Slots 10923 - 16383
Adding replica 10.0.0.5:6379 to 10.0.0.1:6379
Adding replica 10.0.0.6:6379 to 10.0.0.2:6379
Adding replica 10.0.0.4:6379 to 10.0.0.3:6379
Can I set the above configuration? (type ‘yes’ to accept): yes&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Nodes configuration updated
Assign a different config epoch to each node
Sending CLUSTER MEET messages to join the cluster
Performing Cluster Check (using node 10.0.0.1:6379)
[OK] All 16384 slots covered.`}
/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;상태 점검:&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;&lt;code&gt;cluster_state:ok&lt;/code&gt;&lt;/em&gt; 이 &lt;em&gt;유일하게 신뢰할 수 있는 상태 비트&lt;/em&gt;. 다른 노드는 &lt;em&gt;gossip 으로 곧 알게 된다&lt;/em&gt; 라는 &lt;em&gt;희망 메시지&lt;/em&gt; 는 무시하고 &lt;em&gt;모든 노드에서 ok 가 떠야&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;strong&gt;Hot slot&lt;/strong&gt;: 키 분포가 &lt;em&gt;균등하지 않으면&lt;/em&gt; 특정 슬롯이 &lt;em&gt;전체 처리량의 대부분&lt;/em&gt; 을 가져간다. &lt;em&gt;해시태그 남발&lt;/em&gt; 의 가장 흔한 부작용. (&lt;code&gt;{tenant:1}&lt;/code&gt; 으로 &lt;em&gt;전체 테넌트 1 의 데이터를 한 슬롯&lt;/em&gt; 으로 묶으면, 테넌트 1 이 &lt;em&gt;가장 큰 고객&lt;/em&gt; 일 때 노드 1개가 다 받는다.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;증상:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLUSTER COUNTKEYSINSLOT &amp;#x3C;slot&gt;&lt;/code&gt; 으로 슬롯별 키 수 확인 → 편중 발견.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;redis-cli -h &amp;#x3C;node&gt; --hotkeys&lt;/code&gt; (Redis 4+) 로 특정 노드의 &lt;em&gt;hot key&lt;/em&gt; 추출.&lt;/li&gt;
&lt;li&gt;운영 시: &lt;em&gt;hot slot&lt;/em&gt; 만 &lt;em&gt;수동으로 같은 노드 안의 다른 슬롯으로 옮기는 게 의미 없다&lt;/em&gt;. &lt;em&gt;키 디자인 재검토&lt;/em&gt; 가 진짜 답.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cluster-vs-sentinel-결정-트리&quot;&gt;Cluster vs Sentinel: 결정 트리&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;    A{전체 데이터셋이&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;    A --&gt;|예| Sentinel[Sentinel 충분]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|아니오| B{멀티 키 명령&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;    B --&gt;|많음| Refactor[키 디자인&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;    B --&gt;|적음| Cluster[Cluster 가 답]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Refactor --&gt; Cluster&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능)&lt;/li&gt;
&lt;li&gt;[[Redis Replication]] (Sentinel / replica 메커니즘)&lt;/li&gt;
&lt;li&gt;[[Redis Persistence]] (각 노드의 RDB / AOF)&lt;/li&gt;
&lt;li&gt;[[Distributed Lock]] (cluster 모드에서의 Redlock 주의점)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;</content:encoded><category>redis</category><category>cluster</category><category>sharding</category><category>scaling</category><category>valkey</category><category>gossip</category><category>slot</category><author>koa (김신건)</author></item><item><title>[Redis] Distributed Lock: SET NX, Redlock, Fencing Token</title><link>https://shinkeonkim.com/wiki/database-internals/redis-distributed-lock/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-distributed-lock/</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;분산 락 (Distributed Lock)&lt;/strong&gt; 은 &lt;em&gt;여러 프로세스 / 머신&lt;/em&gt; 에서 &lt;em&gt;한 시점에 하나만&lt;/em&gt; 임계 영역에 진입하도록 보장하는 동기화 도구. Redis 는 &lt;em&gt;간단한 SETNX + EX&lt;/em&gt; 부터 &lt;em&gt;Redlock (5-node 합의)&lt;/em&gt; 까지 &lt;em&gt;spectrum&lt;/em&gt; 을 제공한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;분산 락은 일반적인 mutex 의 직접 대체가 아니다&lt;/em&gt;. &lt;em&gt;GC pause, 네트워크 분할, 시계 어긋남&lt;/em&gt; 같은 분산 환경의 &lt;em&gt;현실&lt;/em&gt; 이 &lt;em&gt;어떤 락도 100% 안전하지 않게&lt;/em&gt; 한다. &lt;em&gt;idempotent + fencing token&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;단일-인스턴스-set-nx-ex--lua&quot;&gt;단일 인스턴스: SET NX EX + Lua&lt;/h2&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SETNX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;owner-1&quot;&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;EXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # ❌ 두 명령 사이에 죽으면 *영구 락*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SETNX&lt;/code&gt; 와 &lt;code&gt;EXPIRE&lt;/code&gt; 가 &lt;em&gt;원자적이지 않다&lt;/em&gt;. 사이에 &lt;em&gt;프로세스 다운&lt;/em&gt; 시 &lt;em&gt;TTL 없는 락&lt;/em&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;uuid-abc&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# OK   → 락 획득&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# nil  → 누가 이미 잡고 있음&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;옵션&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;NX&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;존재하지 않을 때만&lt;/em&gt; set&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;PX 30000&lt;/code&gt;&lt;/td&gt;&lt;td&gt;30000 ms 후 만료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;value = &lt;em&gt;고유 ID&lt;/em&gt;&lt;/td&gt;&lt;td&gt;release 시 &lt;em&gt;내가 잡은 락만&lt;/em&gt; 풀기 위함&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;release-get--del-의-함정&quot;&gt;Release: GET + DEL 의 함정&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # &quot;uuid-abc&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DEL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # ❌ 사이에 *TTL 만료 + 다른 owner* 가 잡으면 *남의 락을 푼다*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;정석-lua-로-compare-and-delete&quot;&gt;정석: Lua 로 &lt;em&gt;compare-and-delete&lt;/em&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;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- KEYS[1] = lock 키, ARGV[1] = 내 토큰&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; redis.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, KEYS[&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:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ARGV[&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:#F97583&quot;&gt;then&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; redis.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;DEL&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, KEYS[&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:#F97583&quot;&gt;else&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; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;EVAL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;if redis.call(&apos;GET&apos;, KEYS[1]) == ARGV[1] then return redis.call(&apos;DEL&apos;, KEYS[1]) else return 0 end&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uuid-abc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;전체-흐름&quot;&gt;전체 흐름&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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 Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;Client: token = uuid()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;Redis: SET lock:order:42 token NX PX 30000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt OK 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over Client: 임계 영역 진입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Client-&gt;&gt;Client: 작업 수행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Client-&gt;&gt;Redis: EVAL CAS-delete (token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Redis--&gt;&gt;Client: 1 (내 락 풀림)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    else nil 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over Client: 누가 잡음 → 대기 / 재시도 / 실패 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;ttl-의-함정과-watchdog&quot;&gt;TTL 의 함정과 watchdog&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client (락 보유)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 30s 락 획득&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: 작업 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: GC pause / 큰 IO 25s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 작업 아직 안 끝남&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R: 30s 후 TTL 만료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R-&gt;&gt;R: 락 자동 해제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant D as 다른 Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D-&gt;&gt;R: SET ... NX PX 30000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R--&gt;&gt;D: OK (락 획득)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: GC pause 끝남, 작업 끝, &quot;내 락&quot; 푼다고 EVAL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;R: EVAL CAS-delete (token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R: token 안 맞음 → 0 반환 (남의 락 보호됨)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C,D: 그런데 *둘 다 동시에 critical section* 에 있었다!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;핵심: &lt;em&gt;TTL &amp;#x3C; 실제 작업 시간&lt;/em&gt; 이면 &lt;em&gt;두 클라이언트가 동시에 critical section&lt;/em&gt; 진입 가능. &lt;em&gt;Redlock&lt;/em&gt; 도 이 문제를 &lt;em&gt;완전히 해결하지 못한다&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;watchdog-자동-갱신&quot;&gt;Watchdog (자동 갱신)&lt;/h3&gt;
&lt;p&gt;작업이 &lt;em&gt;돌고 있는 동안&lt;/em&gt; 락 TTL 을 &lt;em&gt;주기적으로 갱신&lt;/em&gt;. Java 의 &lt;em&gt;Redisson&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant W as Watchdog Thread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;R: SET lock NX PX 30000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W: 10초마다 PEXPIRE 갱신&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loop work in progress&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        W-&gt;&gt;R: PEXPIRE lock 30000&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 over C: 작업 완료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;W: 중지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;R: EVAL CAS-delete&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Watchdog 도 &lt;em&gt;프로세스가 응답 안 하면 작동 안 함&lt;/em&gt;. &lt;em&gt;GC pause / OS 스왑 / VM 정지&lt;/em&gt; 같은 &lt;em&gt;프로세스가 멈춘 시간&lt;/em&gt; 은 watchdog 도 멈춰 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;redlock-5-node-합의-알고리즘&quot;&gt;Redlock: 5-node 합의 알고리즘&lt;/h2&gt;
&lt;p&gt;antirez 가 제안한 &lt;em&gt;다수 노드&lt;/em&gt; 알고리즘. &lt;em&gt;N 개의 독립 Redis 인스턴스&lt;/em&gt; (보통 5개) 에 &lt;em&gt;과반수 (3개 이상) 가 락을 동의&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R1 as Redis A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R2 as Redis B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R3 as Redis C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R4 as Redis D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R5 as Redis E&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;C: token = uuid(), t0 = now()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par 모든 인스턴스에 동시에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R1: SET lock token NX PX 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R2: SET lock token NX PX 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R3: SET lock token NX PX 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R4: SET lock token NX PX 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R5: SET lock token NX PX 10000&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;    R1--&gt;&gt;C: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R2--&gt;&gt;C: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R3--&gt;&gt;C: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R4--&gt;&gt;C: nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R5--&gt;&gt;C: nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: count = 3, elapsed = now() - t0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt count &gt;= 3 AND elapsed &amp;#x3C; TTL/2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over C: 락 획득. 유효 TTL = TTL - elapsed&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;        C-&gt;&gt;R1: EVAL release (token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R2: EVAL release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C-&gt;&gt;R3: EVAL release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over C: 실패. 모든 노드에 release.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;알고리즘-요약&quot;&gt;알고리즘 요약&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;token = unique-id&lt;/code&gt;, &lt;code&gt;t0 = now()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;모든 N 개 인스턴스&lt;/em&gt; 에 &lt;em&gt;동시에&lt;/em&gt; &lt;code&gt;SET lock token NX PX &amp;#x3C;TTL&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OK&lt;/code&gt; 응답 수 = &lt;code&gt;count&lt;/code&gt;. &lt;code&gt;elapsed = now() - t0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;모두 만족&lt;/em&gt; 해야 락 획득:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;count &gt;= (N/2)+1&lt;/code&gt; (다수)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;elapsed &amp;#x3C; TTL&lt;/code&gt; (의미 있는 시간 안에)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;유효 락 TTL&lt;/em&gt; = &lt;code&gt;TTL - elapsed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;실패하면 &lt;em&gt;모든 노드에 release&lt;/em&gt; (받았던 곳이 일부라도).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;5-node-노드-수의-정당성&quot;&gt;5-node 노드 수의 정당성&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;kleppmann의-비판과-fencing-token&quot;&gt;Kleppmann의 비판과 fencing token&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;Martin Kleppmann 의 글&lt;/a&gt; 은 &lt;em&gt;Redlock 이 &lt;em&gt;strong mutual exclusion&lt;/em&gt; 을 보장하지 못한다&lt;/em&gt; 고 지적. 핵심 시나리오:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C1 as Client 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Lock as Redlock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Storage as 보호 대상 (DB / S3 / ...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Lock: acquire (TTL 10s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lock--&gt;&gt;C1: OK (token A)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: GC pause 12s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Lock: 10s 만료, 락 풀림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C2 as Client 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;Lock: acquire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lock--&gt;&gt;C2: OK (token B)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;Storage: 데이터 쓰기 (token B 의 책임)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: GC pause 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Storage: 데이터 쓰기 (이미 만료된 token A 의 책임)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Storage: 두 client 의 write 가 *동시에* 발생&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;em&gt;clock drift&lt;/em&gt;, &lt;em&gt;GC pause&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;h3 id=&quot;fencing-token-가장-강력한-보강&quot;&gt;Fencing token: 가장 강력한 보강&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;락을 잡을 때 monotonic increasing token 을 함께 받는다&lt;/em&gt;. &lt;em&gt;보호 대상&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C1 as Client 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Storage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Lock: acquire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lock--&gt;&gt;C1: OK, token=33&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: GC pause&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Lock: 만료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C2 as Client 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;Lock: acquire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lock--&gt;&gt;C2: OK, token=34&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2-&gt;&gt;Storage: write(token=34)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Storage-&gt;&gt;Storage: last_token = 34&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Storage--&gt;&gt;C2: OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C1: GC pause 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C1-&gt;&gt;Storage: write(token=33)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Storage-&gt;&gt;Storage: 33 &amp;#x3C; last_token (34) → 거절&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Storage--&gt;&gt;C1: REJECTED&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Redis 자체&lt;/em&gt; 는 &lt;em&gt;fencing token monotonicity 를 보장하지 않는다&lt;/em&gt;. &lt;em&gt;별도 monotonic 카운터&lt;/em&gt; (&lt;code&gt;INCR&lt;/code&gt;) 또는 &lt;em&gt;Zookeeper / etcd 같은 합의 시스템&lt;/em&gt; 이 필요. Kleppmann 의 입장: &lt;em&gt;fencing 이 가능하면 분산 락 자체가 필요 없는 경우가 많다&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;antirez-의-반박-요약&quot;&gt;antirez 의 반박 요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Redlock 은 &lt;em&gt;모든 mutual exclusion 사고를 막는다&lt;/em&gt; 라고 주장한 적 없다&lt;/em&gt;. &lt;em&gt;실용적 안전 마진&lt;/em&gt; + &lt;em&gt;idempotent 작업&lt;/em&gt; 위에 &lt;em&gt;충분한 도구&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;clock drift&lt;/em&gt; 는 &lt;em&gt;현대 OS / NTP&lt;/em&gt; 에서 &lt;em&gt;수 ms 수준&lt;/em&gt; 이고, &lt;em&gt;Redlock TTL &gt; 클럭 drift 의 최악 케이스&lt;/em&gt; 면 &lt;em&gt;문제 없다&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;→ 결론: **&lt;em&gt;Redlock 만으로 결제 / 잔액 / 인벤토리 의 &lt;em&gt;불변량&lt;/em&gt; 보호는 위험&lt;/em&gt;. 그런 워크로드는 &lt;em&gt;DB transaction / row lock 으로 묶거나&lt;/em&gt;, &lt;em&gt;fencing token + idempotent operation&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;TTL &gt; &lt;em&gt;최악 작업 시간 + 안전 마진 (예: 5x)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;짧으면 &lt;em&gt;동시 진입&lt;/em&gt; 위험&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;token = &lt;em&gt;고유 UUID&lt;/em&gt;&lt;/td&gt;&lt;td&gt;release 시 &lt;em&gt;남의 락 풀기&lt;/em&gt; 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;release 는 &lt;em&gt;Lua CAS&lt;/em&gt;&lt;/td&gt;&lt;td&gt;절대 &lt;code&gt;GET → DEL&lt;/code&gt; 분리하지 말 것&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;락은 &lt;em&gt;idempotent 작업&lt;/em&gt; 보호용으로 사용&lt;/td&gt;&lt;td&gt;인벤토리 같은 &lt;em&gt;불변량&lt;/em&gt; 은 DB transaction 으로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;실패 시 &lt;em&gt;retry 정책 정의&lt;/em&gt;&lt;/td&gt;&lt;td&gt;무한 retry → starvation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;fencing token&lt;/em&gt; 필요시 &lt;em&gt;별도 카운터&lt;/em&gt;&lt;/td&gt;&lt;td&gt;또는 &lt;em&gt;etcd / Zookeeper&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;분산 락 = 만능&lt;/em&gt; 이라고 생각하지 말 것&lt;/td&gt;&lt;td&gt;&lt;em&gt;항상 race condition 가정&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;Java&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/redisson/redisson&quot;&gt;Redisson&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;watchdog 자동&lt;/em&gt;, fairness lock, semaphore, multi-lock 모두&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://redis.io/docs/latest/develop/clients/redis-py/queries-and-commands/distributed-locking/&quot;&gt;redis-py&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Lock&lt;/code&gt; 클래스 (단일 인스턴스), &lt;code&gt;redlock-py&lt;/code&gt; (Redlock)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/leandromoreira/redlock-rb&quot;&gt;redlock-rb&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Redlock 알고리즘 직접&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Node&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/mike-marcacci/node-redlock&quot;&gt;redlock&lt;/a&gt;&lt;/td&gt;&lt;td&gt;abort signal, extension 자동 갱신&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Go&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/go-redsync/redsync&quot;&gt;redsync&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Redlock 표준 구현&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;redisson-예시&quot;&gt;Redisson 예시&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;RLock lock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redisson.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getLock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;order:42&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&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; (lock.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tryLock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, TimeUnit.SECONDS)) {   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// wait 10s, lease 30s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#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;finally&lt;/span&gt;&lt;span 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;        lock.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unlock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;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;watchdog 으로 &lt;em&gt;작업 진행 중 자동 갱신&lt;/em&gt;. &lt;em&gt;프로세스 다운 시 락 회수&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;hera-webapp 의 &lt;em&gt;이메일 일괄 발송 batch&lt;/em&gt; 는 &lt;em&gt;분산 락 + idempotent insert&lt;/em&gt; 두 겹. 같은 batch_id 가 &lt;em&gt;DB unique constraint&lt;/em&gt; 에 막혀서 &lt;em&gt;분산 락 이 풀려도 중복 발송이 없다&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Redisson watchdog&lt;/em&gt; 은 &lt;em&gt;Spring + Redis 환경&lt;/em&gt; 에서 &lt;em&gt;최고의 ROI&lt;/em&gt;. 단일 인스턴스 + 자동 갱신 + Spring 친화.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sidekiq unique jobs&lt;/em&gt; (분산 락 기반) 은 &lt;em&gt;short window 에서만&lt;/em&gt; 안전. &lt;em&gt;결제 / 잔액&lt;/em&gt; 같은 &lt;em&gt;불변량 보호&lt;/em&gt; 는 &lt;em&gt;DB row lock&lt;/em&gt; 만 안전.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Lock 으로 throughput 을 짠다&lt;/em&gt; 는 발상이 &lt;em&gt;가장 위험&lt;/em&gt;. &lt;em&gt;분산 락을 매번 잡는 워크플로&lt;/em&gt; 는 &lt;em&gt;재설계&lt;/em&gt; 가 답.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능)&lt;/li&gt;
&lt;li&gt;[[Redis Cluster]] (cluster 모드에서의 Redlock 주의)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (큐 + 분산 락 패턴)&lt;/li&gt;
&lt;li&gt;[[Zero Downtime Deployment]] (배포 중 락 풀림 보호)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/use/patterns/distributed-locks/&quot;&gt;Distributed Locks with Redis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kleppmann: &lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;How to do distributed locking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;antirez 반박: &lt;a href=&quot;http://antirez.com/news/101&quot;&gt;Is Redlock safe?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>lock</category><category>mutex</category><category>distributed-systems</category><category>redlock</category><category>safety</category><author>koa (김신건)</author></item><item><title>[Redis] Hash: 객체 저장, Field TTL (7.4+), Listpack vs Hashtable</title><link>https://shinkeonkim.com/wiki/database-internals/redis-hashes/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-hashes/</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;Redis Hash&lt;/strong&gt; 는 &lt;em&gt;키 안의 작은 dict&lt;/em&gt;. 하나의 키 (&lt;code&gt;user:42&lt;/code&gt;) 가 &lt;em&gt;여러 field → value&lt;/em&gt; 쌍을 가진다. &lt;em&gt;부분 갱신 / 부분 조회&lt;/em&gt; 가 &lt;em&gt;atomic + 효율적&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;= “&lt;em&gt;Redis 의 객체&lt;/em&gt;”. 객체 저장이 &lt;em&gt;String + JSON&lt;/em&gt; 보다 효율적일 때의 표준 선택.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
2024-04 부터 &lt;em&gt;Redis 7.4&lt;/em&gt; 가, 2025-10 부터 &lt;em&gt;Valkey 9.0&lt;/em&gt; 이 &lt;strong&gt;Hash Field 단위 TTL&lt;/strong&gt; 을 지원 (&lt;code&gt;HEXPIRE&lt;/code&gt;). 더 이상 &lt;em&gt;키 전체 TTL&lt;/em&gt; 만의 시대가 아니다. 세션 / 토큰 / 캐시 entry 패턴이 &lt;em&gt;크게 단순해진다&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;내부-인코딩&quot;&gt;내부 인코딩&lt;/h2&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;인코딩&lt;/th&gt;&lt;th&gt;조건 (기본)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;listpack&lt;/code&gt;&lt;/td&gt;&lt;td&gt;필드 수 ≤ &lt;code&gt;hash-max-listpack-entries&lt;/code&gt; (128) &lt;strong&gt;AND&lt;/strong&gt; 각 값 크기 ≤ &lt;code&gt;hash-max-listpack-value&lt;/code&gt; (64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;hashtable&lt;/code&gt;&lt;/td&gt;&lt;td&gt;그 외&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hash-max-listpack-entries 128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hash-max-listpack-value 64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;작은 hash 는 &lt;em&gt;연속 메모리 배열&lt;/em&gt;. &lt;em&gt;cache locality 가 압도적&lt;/em&gt;. &lt;em&gt;큰 hash&lt;/em&gt; 는 &lt;em&gt;dict + chaining&lt;/em&gt; 의 &lt;em&gt;진짜 hashtable&lt;/em&gt; 로 자동 전환.&lt;/p&gt;
&lt;h3 id=&quot;listpack-flat-배열&quot;&gt;Listpack: flat 배열&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;    Hdr[&quot;[헤더]&quot;] --&gt; F1[field1] --&gt; V1[value1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V1 --&gt; F2[field2] --&gt; V2[value2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V2 --&gt; F3[field3] --&gt; V3[value3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V3 --&gt; End[&quot;[end]&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;총 길이가 작을 때&lt;/em&gt; 의 &lt;em&gt;최적&lt;/em&gt;. 메모리 ~ String 한 줄 수준.&lt;/p&gt;
&lt;h3 id=&quot;hashtable-chaining&quot;&gt;Hashtable: chaining&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;/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;p&gt;&lt;em&gt;큰 hash&lt;/em&gt; 에서는 &lt;em&gt;bucket 배열&lt;/em&gt; + &lt;em&gt;동일 hash 에 충돌하는 노드의 연결 리스트&lt;/em&gt;. 위 두 애니메이션이 그 내부 동작을 시각화한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Redis 의 dict 는 &lt;em&gt;progressive rehashing&lt;/em&gt; (두 개의 hashtable 을 &lt;em&gt;점진적으로&lt;/em&gt; 옮긴다). 큰 hash 의 &lt;em&gt;resize 가 짧게 끊어 분산&lt;/em&gt; 되어 &lt;em&gt;single-thread 차단&lt;/em&gt; 을 피한다. 자세한 건 [[Redis]] 의 &lt;em&gt;내부&lt;/em&gt; 절.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;핵심-명령&quot;&gt;핵심 명령&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HSET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;koa&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; email&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;koa@x.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tier&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;pro&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HGET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                  # &quot;koa&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HMGET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; email&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;HGETALL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&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;HKEYS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                      # field 이름들&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HVALS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                      # 값들&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HDEL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; email&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;HEXISTS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # 존재 여부&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HLEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&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;HINCRBY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; visits&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # atomic 정수 증가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HINCRBYFLOAT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; balance&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.5&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # float 증가&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;HRANDFIELD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WITHVALUES&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     # 랜덤 (Redis 6.2+)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HSCAN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MATCH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;name*&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&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;redis-8-의-새-명령&quot;&gt;Redis 8 의 새 명령&lt;/h3&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;HGETEX key field [EX seconds]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;가져오면서 &lt;em&gt;키 전체 TTL 갱신&lt;/em&gt; (String 의 &lt;code&gt;GETEX&lt;/code&gt; 와 동일)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HSETEX key seconds field value&lt;/code&gt;&lt;/td&gt;&lt;td&gt;한 번에 &lt;em&gt;set + 키 TTL&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HGETDEL key FIELDS N f1 f2 ...&lt;/code&gt;&lt;/td&gt;&lt;td&gt;가져오면서 &lt;em&gt;해당 필드 즉시 삭제&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
Redis 7.4+ 의 &lt;em&gt;필드 단위 TTL&lt;/em&gt; 과 함께 &lt;em&gt;세션 store&lt;/em&gt; / &lt;em&gt;one-time token&lt;/em&gt; 같은 패턴의 &lt;em&gt;atomic&lt;/em&gt; 동작이 매우 간결.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;hash-field-ttl-redis-74--valkey-90&quot;&gt;Hash Field TTL (Redis 7.4 / Valkey 9.0)&lt;/h2&gt;
&lt;p&gt;기존: &lt;em&gt;키 전체 TTL&lt;/em&gt; 만. 한 hash 안에 &lt;em&gt;서로 다른 만료 시점이 섞이면&lt;/em&gt; 여러 키로 &lt;em&gt;분리&lt;/em&gt; 해야 했다.&lt;/p&gt;
&lt;p&gt;7.4 부터:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seconds&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HPEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; milliseconds&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIREAT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unixtime&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HPEXPIREAT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unixtime-ms&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRETIME&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&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;HPEXPIRETIME&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # ms 단위&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HTTL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&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;HPTTL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;           # 남은 ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HPERSIST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; f1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [f2 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;...]&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # TTL 제거 (영구)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;활용-세션-store&quot;&gt;활용: 세션 store&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123:user_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1800&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123:csrf_token&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; xyz&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1800&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123:ip&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1800&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 같은 세션의 3개 키, 모두 EX 1800&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;7.4+:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HSET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; csrf_token&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; xyz&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ip&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1800&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user_id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; csrf_token&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 또는 키 단위 TTL 만 잡고 필드 단위는 *더 짧게*:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;EXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3600&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                    # 세션 자체 1시간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; session:abc123&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; csrf_token&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # CSRF 토큰만 5분&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;키 단위 TTL&lt;/em&gt; 과 &lt;em&gt;필드 단위 TTL&lt;/em&gt; 은 &lt;em&gt;공존&lt;/em&gt;. &lt;em&gt;둘 다 적용되면 더 짧은 쪽이 이긴다&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;활용-jwt-blacklist&quot;&gt;활용: JWT blacklist&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# token 마다 다른 만료 시점&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HSET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; jwt:blacklist&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sig_aaa&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;revoked&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sig_bbb&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;revoked&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sig_ccc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;revoked&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; jwt:blacklist&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3600&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FIELDS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sig_aaa&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # aaa 는 1시간 후 자동 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HEXPIRE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; jwt:blacklist&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 600&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  FIELDS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sig_bbb&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # bbb 는 10분&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 한 hash 가 &lt;em&gt;수만 개의 작은 entry 와 각자 다른 TTL&lt;/em&gt; 을 &lt;em&gt;원자적으로 관리&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;활용-캐시-entry-per-field&quot;&gt;활용: 캐시 entry per-field&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;    Cache[(cache:user:42)] --&gt;|HEXPIRE 60| F1[profile field]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cache --&gt;|HEXPIRE 600| F2[preferences field]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cache --&gt;|HEXPIRE 3600| F3[settings field]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cache --&gt;|HPERSIST| F4[immutable_id field]&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; &lt;em&gt;각자 TTL&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;객체-저장-hash-vs-stringjson&quot;&gt;객체 저장: Hash vs String+JSON&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;    Use{사용 패턴}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Use --&gt;|전체 갱신 위주&amp;#x3C;br/&gt;작은 객체| Str[String + JSON]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Use --&gt;|부분 갱신 위주&amp;#x3C;br/&gt;큰 객체| Hash[Hash]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Use --&gt;|필드별 TTL 필요| Hash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Use --&gt;|언어 간 호환성&amp;#x3C;br/&gt;중요| Str&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Use --&gt;|atomic 카운터&amp;#x3C;br/&gt;필드 중 일부에| Hash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비교 매트릭스:&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;String + JSON&lt;/th&gt;&lt;th&gt;Hash&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;단일 필드 갱신&lt;/td&gt;&lt;td&gt;&lt;em&gt;전체 직렬화 다시&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HSET field value&lt;/code&gt; (O(1))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;단일 필드 조회&lt;/td&gt;&lt;td&gt;&lt;em&gt;전체 파싱&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HGET field&lt;/code&gt; (O(1))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;전체 조회&lt;/td&gt;&lt;td&gt;&lt;code&gt;GET&lt;/code&gt; 1번&lt;/td&gt;&lt;td&gt;&lt;code&gt;HGETALL&lt;/code&gt; (작으면 OK, 크면 위험)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;atomic 카운터&lt;/td&gt;&lt;td&gt;&lt;em&gt;Lua 필요&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HINCRBY field&lt;/code&gt; (O(1) atomic)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;직렬화 비용&lt;/td&gt;&lt;td&gt;매번&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;필드별 TTL&lt;/td&gt;&lt;td&gt;&lt;em&gt;불가능&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;7.4+ 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모리 (작은 객체)&lt;/td&gt;&lt;td&gt;비슷 / 약간 손해&lt;/td&gt;&lt;td&gt;listpack 으로 &lt;em&gt;작은 hash 는 매우 효율&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;성능-표&quot;&gt;성능 표&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;명령&lt;/th&gt;&lt;th&gt;복잡도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HSET&lt;/code&gt;, &lt;code&gt;HGET&lt;/code&gt;, &lt;code&gt;HEXISTS&lt;/code&gt;, &lt;code&gt;HDEL&lt;/code&gt;, &lt;code&gt;HINCRBY&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HMGET (k)&lt;/code&gt;, &lt;code&gt;HSET (k fields)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(k)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HKEYS&lt;/code&gt;, &lt;code&gt;HVALS&lt;/code&gt;, &lt;code&gt;HGETALL&lt;/code&gt;, &lt;code&gt;HLEN&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HSCAN&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;amortized&lt;/em&gt; O(N) (cursor 분할)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HEXPIRE FIELDS k&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(k)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HRANDFIELD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(1) (count=1) / O(N) (큰 count + unique)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;code&gt;HGETALL&lt;/code&gt; / &lt;code&gt;HKEYS&lt;/code&gt; / &lt;code&gt;HVALS&lt;/code&gt; 는 &lt;em&gt;큰 hash&lt;/em&gt; 에서 &lt;em&gt;single thread 차단&lt;/em&gt;. 큰 hash 의 &lt;em&gt;부분 순회&lt;/em&gt; 는 &lt;em&gt;&lt;code&gt;HSCAN&lt;/code&gt;&lt;/em&gt; 으로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;메모리-효율-비교&quot;&gt;메모리 효율 비교&lt;/h2&gt;
&lt;p&gt;같은 데이터 (1만 개 사용자 객체, 각 5 필드) 의 저장 방식별 메모리:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;왼쪽 두 번째 (Hash listpack)&lt;/em&gt; 가 &lt;em&gt;가장 효율&lt;/em&gt;. 자동으로 &lt;em&gt;hashtable 로 넘어가는 임계점&lt;/em&gt; 을 알고 운영. 큰 객체 (필드 100+) 면 listpack 의 이점은 사라진다.&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;&lt;code&gt;HGETALL&lt;/code&gt; 의 큰 hash&lt;/strong&gt; = 모든 명령 차단. 운영 사고 단골 1위.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HMSET&lt;/code&gt; 는 deprecated&lt;/strong&gt; (Redis 4+). &lt;code&gt;HSET&lt;/code&gt; 가 &lt;em&gt;여러 인자&lt;/em&gt; 받음. 새 코드는 &lt;code&gt;HSET&lt;/code&gt; 로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필드 단위 TTL 의 만료 알림&lt;/strong&gt; = &lt;code&gt;notify-keyspace-events Hg&lt;/code&gt; 또는 &lt;code&gt;Hh&lt;/code&gt; 필요. 기본은 &lt;em&gt;조용히&lt;/em&gt; 사라짐.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;listpack 임계 조정&lt;/strong&gt; = &lt;code&gt;hash-max-listpack-entries&lt;/code&gt; 를 &lt;em&gt;너무 크게&lt;/em&gt; 잡으면 &lt;em&gt;listpack 의 O(N) 검색&lt;/em&gt; 이 &lt;em&gt;느려진다&lt;/em&gt;. 기본 128 이 &lt;em&gt;조용한 sweet spot&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;&lt;em&gt;세션 store&lt;/em&gt; 를 Hash + Field TTL 로 옮긴 뒤 &lt;em&gt;Redis 키 수&lt;/em&gt; 가 &lt;em&gt;대폭 감소&lt;/em&gt;. 세션 마다 &lt;em&gt;4-5 키 → 1 키&lt;/em&gt;. KEYS / SCAN 도 &lt;em&gt;훨씬 가벼움&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;작은 객체 1만개&lt;/em&gt; 시나리오에서 &lt;em&gt;listpack 임계 안에 들도록&lt;/em&gt; 필드 수를 의도적으로 제한했더니 &lt;em&gt;메모리 거의 2배 효율&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Redis 8 의 &lt;code&gt;HSETEX&lt;/code&gt; + &lt;code&gt;HGETDEL&lt;/code&gt;&lt;/em&gt; 은 &lt;em&gt;Rails session_id one-time token&lt;/em&gt; 같은 흐름에 &lt;em&gt;완벽한 atomic 한 줄&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HGETALL&lt;/code&gt; 사고 회피 패턴: &lt;em&gt;클라이언트 라이브러리에 &lt;code&gt;HGETALL&lt;/code&gt; 호출 시 size 임계 초과면 자동으로 HSCAN 으로 전환&lt;/em&gt; 하도록 &lt;em&gt;공통 helper&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (자료구조 카탈로그)&lt;/li&gt;
&lt;li&gt;[[Redis Strings]] (객체 저장 대안)&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]] (캐시로서의 hash)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (정렬이 필요할 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/hashes/&quot;&gt;Hashes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hash Field Expiration: &lt;a href=&quot;https://github.com/redis/redis/pull/13172&quot;&gt;redis/redis #13172&lt;/a&gt;, &lt;a href=&quot;https://valkey.io/blog/hash-fields-expiration/&quot;&gt;Valkey blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>data-structure</category><category>hash</category><category>object</category><category>field-ttl</category><category>listpack</category><category>hashtable</category><author>koa (김신건)</author></item><item><title>[Redis] HyperLogLog &amp; Geo: 확률 카운팅 + 공간 인덱스</title><link>https://shinkeonkim.com/wiki/database-internals/redis-hyperloglog-geo/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-hyperloglog-geo/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;이 페이지는 &lt;em&gt;작지만 강력한 두 자료구조&lt;/em&gt; 를 묶는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HyperLogLog (HLL)&lt;/strong&gt;: &lt;em&gt;대규모 unique count&lt;/em&gt; 를 &lt;em&gt;0.81% 오차로 12 KB 안에&lt;/em&gt; 추정.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geo&lt;/strong&gt;: &lt;em&gt;위경도 + 멤버&lt;/em&gt; 의 &lt;em&gt;공간 인덱스&lt;/em&gt;. 내부는 &lt;em&gt;Sorted Set + geohash 인코딩&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
둘 다 &lt;em&gt;Sorted Set / Set / Hash 같은 일반 자료구조 위&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;hr&gt;
&lt;h2 id=&quot;hyperloglog-081-오차로-unique-추정&quot;&gt;HyperLogLog: 0.81% 오차로 unique 추정&lt;/h2&gt;
&lt;h3 id=&quot;왜-필요한가&quot;&gt;왜 필요한가?&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;일별 UV (unique visitor)&lt;/em&gt; 같은 &lt;em&gt;대규모 cardinality&lt;/em&gt;. Set 으로 정확히 세려면 &lt;em&gt;모든 ID 가 메모리에&lt;/em&gt;. 1억 명이면 GB 단위.&lt;/p&gt;
&lt;p&gt;HLL 은 &lt;em&gt;고정 12 KB&lt;/em&gt; 로 1억 unique 도 &lt;em&gt;0.81% 오차&lt;/em&gt; 안에 추정한다.&lt;/p&gt;
&lt;h3 id=&quot;알고리즘-직관&quot;&gt;알고리즘 직관&lt;/h3&gt;
&lt;p&gt;각 입력의 &lt;em&gt;hash 의 첫 N 비트&lt;/em&gt; 로 &lt;em&gt;bucket 결정&lt;/em&gt;, &lt;em&gt;나머지 비트의 &lt;em&gt;leading zeros 의 max&lt;/em&gt;&lt;/em&gt; 를 bucket 에 저장. &lt;em&gt;전체 unique 수가 많을수록 leading zeros 가 클 확률 증가&lt;/em&gt;. 모든 bucket 의 &lt;em&gt;조화 평균&lt;/em&gt; 으로 cardinality 역산.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Input[&quot;item: &apos;user:42&apos;&quot;] --&gt;|hash| H[&quot;64-bit hash: 0b01000...10...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|&quot;top 14 bits&quot;| Bucket[&quot;bucket index (0..16383)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt;|&quot;나머지 50 bits&quot;| LZ[&quot;leading zeros count&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LZ --&gt;|max| Reg[&quot;register at bucket&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Reg --&gt; Estimate[&quot;전체 estimate ≈ α × m² / Σ 2^-Reg[i]&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;Redis 값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Buckets (m)&lt;/td&gt;&lt;td&gt;16384 (2^14)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모리&lt;/td&gt;&lt;td&gt;12 KB (sparse 인코딩이면 더 작음)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;표준 오차&lt;/td&gt;&lt;td&gt;0.81%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:home&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:99&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:home&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:777&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 이미 있는 user:42 는 무시 (확률적으로)&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;PFCOUNT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:home&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                        # 추정 unique 수&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFCOUNT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:home&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:about&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 합집합의 unique (별도 키 동시 계산)&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:#B392F0&quot;&gt;PFMERGE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:weekly&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:2026-06-25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:2026-06-24&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFCOUNT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pageviews:weekly&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                       # 7일 unique 합집합&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;set-vs-hll-메모리-비교&quot;&gt;Set vs HLL 메모리 비교&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;1억 unique 에 &lt;em&gt;12 KB&lt;/em&gt; 라는 게 &lt;em&gt;직관에 반함&lt;/em&gt;. 처음 본 사람이 항상 “&lt;em&gt;진짜?&lt;/em&gt;” 라고 묻는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;정확도-vs-오차&quot;&gt;정확도 vs 오차&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:#6A737D&quot;&gt;# 1만 개 unique 시뮬레이션 (10번 반복)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; redis.Redis()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r.delete(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;test:hll&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    r.pfadd(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;test:hll&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&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;user:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r.pfcount(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;test:hll&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;# → 보통 9920 ~ 10080 범위 (0.8% 오차)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;정확한 cardinality&lt;/em&gt; 가 필요한 경우 (예: 결제 unique 사용자) 면 &lt;em&gt;Set + 별도 보관&lt;/em&gt;. &lt;em&gt;대시보드 / 분석&lt;/em&gt; 이면 &lt;em&gt;HLL 가 압도적&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;활용-패턴&quot;&gt;활용 패턴&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;일/주/월 UV&lt;/strong&gt;: 하루마다 새 HLL, &lt;em&gt;PFMERGE&lt;/em&gt; 로 누적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;광고 reach&lt;/strong&gt;: &lt;em&gt;광고에 노출된 unique 사용자&lt;/em&gt; 추정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검색 query 의 unique users&lt;/strong&gt;: 검색어별 HLL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API endpoint 의 unique 호출자&lt;/strong&gt;: rate limiting 의 &lt;em&gt;cardinality 통계&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;HLL 의 sparse → dense 자동 전환&lt;/em&gt;. 처음 채워질 때는 &lt;em&gt;sparse 인코딩&lt;/em&gt; (수십 B 만 사용), 차오를수록 &lt;em&gt;dense 12 KB&lt;/em&gt; 가 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;set-과의-조합&quot;&gt;Set 과의 조합&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 정확 cardinality 가 필요할 때는 Set, 추정으로 충분할 때는 HLL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users:premium&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:99&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;PFADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users:active&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:99&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:200&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 추정&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;geo-공간-인덱스&quot;&gt;Geo: 공간 인덱스&lt;/h2&gt;
&lt;h3 id=&quot;내부-구조&quot;&gt;내부 구조&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Sorted Set + 52-bit geohash&lt;/em&gt;. 즉 &lt;em&gt;Geo 자료형&lt;/em&gt; 은 &lt;em&gt;별도 자료구조가 아니라&lt;/em&gt; &lt;em&gt;Sorted Set 의 score 가 geohash 인 것&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;GEOADD stores 126.97 37.55 &apos;seoul&apos;&quot;] --&gt;|&quot;geohash → score&quot;| B[&quot;ZADD stores 4067218530100 &apos;seoul&apos;&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;GEOSEARCH stores FROMLONLAT ...&quot;] --&gt;|&quot;score 범위 + 후처리&quot;| D[&quot;가까운 멤버 목록&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;내부&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Score&lt;/td&gt;&lt;td&gt;52-bit geohash (인터리브 비트로 &lt;em&gt;공간 locality 유지&lt;/em&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Member&lt;/td&gt;&lt;td&gt;사용자 정의 ID&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;거리 계산&lt;/td&gt;&lt;td&gt;&lt;em&gt;Haversine 공식&lt;/em&gt; (구면 거리)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;geohash-의-grid-분할&quot;&gt;Geohash 의 grid 분할&lt;/h3&gt;
&lt;p&gt;지도를 &lt;em&gt;재귀적 격자&lt;/em&gt; 로 나누어 &lt;em&gt;각 격자에 base32 문자&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;1차: 지구를 8개 영역&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2차: 각 영역을 다시 8개&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;12자 까지 가면 ≈ 3.7 cm 정밀&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 prefix 의 geohash 는 &lt;em&gt;공간적으로 가깝다&lt;/em&gt;. ZSet 의 &lt;em&gt;score 범위 검색&lt;/em&gt; 이 &lt;em&gt;대략적 인접 검색&lt;/em&gt; 이 된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Geohash 의 &lt;em&gt;경계 함정&lt;/em&gt;. 두 점이 &lt;em&gt;공간적으로 가까워도 다른 cell&lt;/em&gt; 에 떨어질 수 있다 (예: 위/아래 cell 의 경계). Redis 는 &lt;em&gt;주변 cell 도 함께 검색&lt;/em&gt; 해서 자동 보정.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;명령-1&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;seoul-gangnam&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.92&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.56&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;seoul-myeongdong&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 139.69&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 35.69&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;tokyo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 좌표 + 멤버 동시 (Redis 6.2+)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 121.47&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 31.23&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;shanghai&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 위치&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOPOS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seoul-gangnam&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tokyo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → [[126.97, 37.55], [139.69, 35.69]]&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;# 거리 (단위 옵션: m/km/mi/ft)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEODIST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seoul-gangnam&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tokyo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → 1158.42&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;# Geohash 인코딩 문자열&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOHASH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seoul-gangnam&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → wydn... (base32)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;검색&quot;&gt;검색&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 좌표 기준 반경 검색 (Redis 6.2+ 표준)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOSEARCH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  FROMLONLAT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  BYRADIUS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  ASC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  COUNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  WITHCOORD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WITHDIST&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:#B392F0&quot;&gt;GEOSEARCH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FROMMEMBER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seoul-gangnam&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BYRADIUS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ASC&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;# Bounding box (사각)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOSEARCH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  FROMLONLAT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  BYBOX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  ASC&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;# 결과를 다른 키로 (BYSCORE / BYHASH)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOSEARCHSTORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; result_key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stores&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FROMLONLAT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BYRADIUS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
옛 명령 &lt;code&gt;GEORADIUS&lt;/code&gt; / &lt;code&gt;GEORADIUSBYMEMBER&lt;/code&gt; 은 &lt;em&gt;deprecated&lt;/em&gt;. 새 코드는 &lt;em&gt;&lt;code&gt;GEOSEARCH&lt;/code&gt; + 옵션 (FROMLONLAT/FROMMEMBER, BYRADIUS/BYBOX)&lt;/em&gt; 으로.&lt;/p&gt;
&lt;/blockquote&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;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 Grid[&quot;geohash grid&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A[A] --- B[B] --- C[C]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D[D] --- E[&quot;E (검색 중심)&quot;] --- F[F]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        G[G] --- H[H] --- I[I]&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;    Query[Query: 중심 E + 반경 R] --&gt;|9 cell 합집합| Cells[&quot;E 와 8 인접 cell 의 멤버&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cells --&gt; Filter[&quot;Haversine 거리 ≤ R 인 것만&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Filter --&gt; Result[최종 결과]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis 가 &lt;em&gt;9 개 cell (자기 + 8 주변)&lt;/em&gt; 의 멤버를 &lt;em&gt;후보군&lt;/em&gt; 으로 모은 뒤, &lt;em&gt;Haversine 거리&lt;/em&gt; 로 &lt;em&gt;정확히 filter&lt;/em&gt;. 거리 정확성과 검색 효율의 균형.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;활용-패턴-1&quot;&gt;활용 패턴&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;근처 매장 / 식당 검색&lt;/strong&gt;: &lt;code&gt;GEOSEARCH FROMLONLAT BYRADIUS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배달 라이더 매칭&lt;/strong&gt;: 주문 위치 → 가까운 라이더 N명&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;친구 근처 알림&lt;/strong&gt;: &lt;code&gt;GEOSEARCH FROMMEMBER BYRADIUS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지오펜싱&lt;/strong&gt;: bounding box 안에 들어왔는지 주기적 체크&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POI 클러스터링&lt;/strong&gt;: geohash prefix 별 grouping&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;성능-표&quot;&gt;성능 표&lt;/h3&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;GEOADD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GEOPOS&lt;/code&gt;, &lt;code&gt;GEOHASH&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GEODIST&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GEOSEARCH BYRADIUS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N + log M) (cell 안 멤버 수 N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GEOSEARCH BYBOX&lt;/code&gt;&lt;/td&gt;&lt;td&gt;동일&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GEOSEARCHSTORE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N) + ZADD 비용&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&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;em&gt;후보 수 감소 + 검색 속도&lt;/em&gt; 의 표준 패턴.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;메모리-비교-1만-매장--위치&quot;&gt;메모리 비교 (1만 매장 + 위치)&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;메모리는 비슷하지만 &lt;em&gt;Hash 나 List&lt;/em&gt; 는 &lt;em&gt;공간 인덱스 검색&lt;/em&gt; 이 &lt;em&gt;클라이언트 코드 + O(N)&lt;/em&gt;. Geo 는 &lt;em&gt;서버에서 log time&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;두-자료구조-결합-시나리오&quot;&gt;두 자료구조 결합 시나리오&lt;/h2&gt;
&lt;h3 id=&quot;광고-reach--지역-분포&quot;&gt;광고 reach + 지역 분포&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 광고 ad:1234 가 노출된 *unique 사용자* (HLL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ad:1234:reach&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:99&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PFCOUNT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ad:1234:reach&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;# 각 사용자의 *현재 위치* (Geo)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users:loc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users:loc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 139.69&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 35.69&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:99&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;# *서울 반경 30km 의 *unique 노출 사용자* 추정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GEOSEARCHSTORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seoul_users&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users:loc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FROMLONLAT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 126.97&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37.55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BYRADIUS&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; km&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → seoul_users 의 멤버를 ad:1234:reach 와 *비교* 하려면 별도 Set 도 필요&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;HLL 은 부분집합 cardinality 추정이 어려움&lt;/em&gt;. &lt;em&gt;HLL 만으로 “서울에서 광고 노출된 unique 수”&lt;/em&gt; 를 정확히 계산 불가. &lt;em&gt;정확한 교집합 cardinality&lt;/em&gt; 가 필요하면 &lt;em&gt;Set + Geo&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;HLL 의 작은 cardinality 에서 큰 오차&lt;/strong&gt; = 100 명 unique 일 때 ±20% 까지 흔들림. &lt;em&gt;PFCOUNT 가 작은 수 일 때는 신뢰성 낮음&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PFMERGE 의 &lt;em&gt;지속적 누적&lt;/em&gt;&lt;/strong&gt; = 12 KB 가 고정이지만 &lt;em&gt;합집합 후&lt;/em&gt; 정확도 떨어지지 않음. 그러나 &lt;em&gt;Set 처럼 멤버를 다시 빼내지 못함&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geo 의 위경도 순서&lt;/strong&gt; = &lt;code&gt;GEOADD lng lat member&lt;/code&gt; 순서. &lt;em&gt;lat / lng 헷갈리는 사고&lt;/em&gt; 빈번.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geohash 셀 경계&lt;/strong&gt; = 직접 &lt;em&gt;binary geohash prefix 매칭&lt;/em&gt; 으로 검색하면 &lt;em&gt;경계에서 가까운 점 누락&lt;/em&gt;. 항상 &lt;em&gt;GEOSEARCH 사용&lt;/em&gt; (자동 보정).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고밀도 영역의 GEOSEARCH&lt;/strong&gt; = 도심지처럼 &lt;em&gt;한 cell 에 수십만 개&lt;/em&gt; 면 &lt;em&gt;후보군 자체가 큼&lt;/em&gt;. &lt;em&gt;국가/도시별 분할&lt;/em&gt; 필요.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;광고 reach&lt;/em&gt; 같은 &lt;em&gt;비즈니스 대시보드&lt;/em&gt; 는 &lt;em&gt;HLL 의 0.81% 오차&lt;/em&gt; 가 &lt;em&gt;완전히 무시 가능&lt;/em&gt;. 정확한 cardinality 가 &lt;em&gt;비즈니스 가치를 더 가져다 주지 않음&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Geo 의 BYBOX&lt;/em&gt; 가 &lt;em&gt;BYRADIUS&lt;/em&gt; 보다 &lt;em&gt;대시보드 UI 친화&lt;/em&gt;. 지도 viewport 그대로 BYBOX.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;HLL + Set&lt;/em&gt; 의 hybrid: &lt;em&gt;일반 사용자&lt;/em&gt; 는 HLL, &lt;em&gt;프리미엄 / 결제 사용자&lt;/em&gt; 는 Set 으로 정확히. &lt;em&gt;비용 / 정확도&lt;/em&gt; 의 분할.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Geo 의 &lt;em&gt;세로 의미 (고도)&lt;/em&gt; 가 없다&lt;/em&gt;. 단일 평면 (위경도 만). 3D 가 필요하면 &lt;em&gt;별도 인덱싱 (R-tree 같은)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (자료구조 카탈로그)&lt;/li&gt;
&lt;li&gt;[[Redis Sets]] (정확한 cardinality 대안)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (Geo 의 내부 자료구조)&lt;/li&gt;
&lt;li&gt;[[Redis Probabilistic]] (Bloom / CMS 등 다른 확률 자료구조)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/hyperloglogs/&quot;&gt;HyperLogLog&lt;/a&gt;, &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/geospatial/&quot;&gt;Geo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HLL 원논문: &lt;a href=&quot;http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf&quot;&gt;Flajolet et al.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Geohash: &lt;a href=&quot;https://en.wikipedia.org/wiki/Geohash&quot;&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>data-structure</category><category>hyperloglog</category><category>geo</category><category>cardinality</category><category>spatial</category><category>probabilistic</category><author>koa (김신건)</author></item><item><title>[Redis] List: Quicklist / Listpack, BLPOP, Sidekiq 큐</title><link>https://shinkeonkim.com/wiki/database-internals/redis-lists/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-lists/</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;Redis List&lt;/strong&gt; 는 &lt;em&gt;양끝 push / pop 이 O(1)&lt;/em&gt; 인 &lt;em&gt;순서 보존 시퀀스&lt;/em&gt;. &lt;em&gt;큐 (FIFO)&lt;/em&gt;, &lt;em&gt;스택 (LIFO)&lt;/em&gt;, &lt;em&gt;최근 N개 목록&lt;/em&gt;, &lt;em&gt;Sidekiq 같은 큐 시스템&lt;/em&gt; 의 토대.&lt;/p&gt;
&lt;p&gt;겉으로는 &lt;em&gt;연결 리스트&lt;/em&gt; 같지만 &lt;em&gt;내부적으로는 quicklist + listpack&lt;/em&gt; 의 &lt;em&gt;공간 효율 + cache locality&lt;/em&gt; 친화 구조.&lt;/p&gt;
&lt;h2 id=&quot;내부-구조-직관&quot;&gt;내부 구조 직관&lt;/h2&gt;
&lt;p&gt;순수 &lt;em&gt;연결 리스트&lt;/em&gt; 의 동작:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;위 애니메이션은 &lt;em&gt;연결 리스트&lt;/em&gt; 의 &lt;em&gt;insert / delete / traverse&lt;/em&gt; 직관. Redis List 의 &lt;em&gt;논리적 동작&lt;/em&gt; 은 정확히 이렇다.&lt;/p&gt;
&lt;/blockquote&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;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스택으로 쓰는 경우:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Redis List 는 &lt;em&gt;push / pop&lt;/em&gt; 의 &lt;em&gt;양끝 방향&lt;/em&gt; 만 정해주면 &lt;em&gt;큐 (push 한쪽, pop 반대쪽)&lt;/em&gt; 도 되고 &lt;em&gt;스택 (push 와 pop 이 같은 쪽)&lt;/em&gt; 도 된다. 일반적인 &lt;em&gt;큐&lt;/em&gt; 구현은 &lt;code&gt;RPUSH ... + BLPOP&lt;/code&gt; 또는 &lt;code&gt;LPUSH ... + BRPOP&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;quicklist--listpack-실제-내부&quot;&gt;Quicklist + Listpack: 실제 내부&lt;/h2&gt;
&lt;p&gt;Redis 3.2 이전: &lt;em&gt;순수 linked list&lt;/em&gt; 또는 &lt;em&gt;작은 리스트는 ziplist&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Redis 3.2 ~ 7: &lt;strong&gt;quicklist&lt;/strong&gt; = &lt;em&gt;linked list of ziplists&lt;/em&gt;. 노드마다 &lt;em&gt;작은 packed 배열&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Redis 7+: ziplist → &lt;strong&gt;listpack&lt;/strong&gt; 으로 교체. 더 단순한 &lt;em&gt;encoding overflow&lt;/em&gt; 검출.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Head[(head)] --&gt; N1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph N1[Node 1&amp;#x3C;br/&gt;listpack]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N1a[&quot;[a, b, c, d]&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;    N1 --&gt; N2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph N2[Node 2&amp;#x3C;br/&gt;listpack]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N2a[&quot;[e, f, g, h, i]&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;    N2 --&gt; N3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph N3[Node 3&amp;#x3C;br/&gt;listpack]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        N3a[&quot;[j, k]&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;    N3 --&gt; Tail[(tail)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;인코딩&lt;/th&gt;&lt;th&gt;조건&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;listpack&lt;/code&gt; (Redis 7+)&lt;/td&gt;&lt;td&gt;전체 리스트가 &lt;em&gt;작거나&lt;/em&gt; (&lt;code&gt;list-max-listpack-size&lt;/code&gt;), &lt;em&gt;각 노드의 listpack&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;quicklist&lt;/code&gt;&lt;/td&gt;&lt;td&gt;큰 리스트 (linked list of listpacks)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;linkedlist&lt;/code&gt; (deprecated)&lt;/td&gt;&lt;td&gt;옛 인코딩&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ziplist&lt;/code&gt; (deprecated)&lt;/td&gt;&lt;td&gt;옛 작은 인코딩&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;# 노드당 max 항목 수 (음수면 바이트 단위 한계)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;list-max-listpack-size -2     # -2 = 8KB per node (기본)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;list-compress-depth 0          # 0 = 압축 안함, 1+ = 양끝부터 N 개 노드 빼고 LZF 압축&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;큰 리스트가 메모리 압박&lt;/em&gt; 이면 &lt;code&gt;list-compress-depth 1&lt;/code&gt; 또는 &lt;code&gt;2&lt;/code&gt; 로 &lt;em&gt;중간 노드들 LZF 압축&lt;/em&gt;. 양끝 (자주 접근) 은 그대로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;핵심-명령&quot;&gt;핵심 명령&lt;/h2&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LPUSH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job3&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       # 왼쪽 (head) 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RPUSH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job4&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                  # 오른쪽 (tail) 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LLEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                        # 길이&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LRANGE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -1&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;LRANGE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 9&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LINDEX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&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;LSET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; newjob&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;LINSERT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BEFORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; new&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;LREM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; job2&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                 # 첫 번째 만나는 job2 제거&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LTRIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 9&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                   # 처음 10개만 남기고 자름&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pop&quot;&gt;Pop&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LPOP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                        # 왼쪽 1개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RPOP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                        # 오른쪽 1개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LPOP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                      # 왼쪽 5개 (Redis 6.2+)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;blocking-pop-workers-의-핵심&quot;&gt;Blocking Pop (Workers 의 핵심)&lt;/h3&gt;
&lt;p&gt;워커가 &lt;em&gt;큐가 빌 때까지 대기&lt;/em&gt;. &lt;em&gt;Sidekiq / Resque / Celery&lt;/em&gt; 의 &lt;em&gt;fetch loop&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BLPOP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:critical&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 두 큐 중 하나 + 최대 5초 대기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → 큐가 비면 5초 후 nil, 메시지 오면 즉시 반환&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;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 W as Worker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Q as queue:default&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W-&gt;&gt;Q: BLPOP queue:critical queue:default 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W,Q: 큐가 비어있음. 워커가 대기 (blocked)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;Q: RPUSH queue:default job-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q--&gt;&gt;W: (&quot;queue:default&quot;, &quot;job-1&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W: 작업 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W-&gt;&gt;Q: BLPOP queue:critical queue:default 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W,Q: 다시 대기&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;code&gt;BLPOP&lt;/code&gt; 는 &lt;em&gt;redis-cli 가 timeout 0 으로 영원히 대기&lt;/em&gt; 가능. &lt;em&gt;워커 프로세스&lt;/em&gt; 는 &lt;em&gt;idle 상태에서 CPU 0%&lt;/em&gt;. &lt;em&gt;polling 보다 압도적&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;신뢰성-fetch-lmove&quot;&gt;신뢰성 fetch: LMOVE&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;BRPOPLPUSH&lt;/code&gt; (deprecated) → &lt;code&gt;BLMOVE&lt;/code&gt; / &lt;code&gt;LMOVE&lt;/code&gt; 로. &lt;em&gt;fetch + 별도 처리 목록&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LMOVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; queue:default&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; processing:host:pid&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; LEFT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; RIGHT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# = LPOP queue:default + RPUSH processing:host:pid (한 명령으로 atomic)&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;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 W as Worker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Q as queue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant P as processing:host:pid&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;    W-&gt;&gt;Q: LMOVE queue processing LEFT RIGHT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q-&gt;&gt;P: (job-1 이동)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q--&gt;&gt;W: job-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W: 처리 시작&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;        W-&gt;&gt;P: LREM processing 1 job-1&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;        Note over P: job-1 이 processing 에 남음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over P: 다른 워커 / 운영자가 회수&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ Sidekiq 의 &lt;em&gt;reliable fetch&lt;/em&gt;. 자세한 건 [[Redis Pub Sub vs Streams]] 의 &lt;em&gt;Sidekiq 절&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;활용-패턴&quot;&gt;활용 패턴&lt;/h2&gt;
&lt;h3 id=&quot;1-최근-n개-알림-ltrim&quot;&gt;1. 최근 N개 알림 (LTRIM)&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LPUSH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; notifications:user:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;json...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LTRIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; notifications:user:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 99&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       # 최근 100개만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LRANGE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; notifications:user:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 19&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 처음 20개 가져오기&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;LTRIM&lt;/em&gt; 으로 &lt;em&gt;append 직후 자르기&lt;/em&gt;. &lt;code&gt;O(N)&lt;/code&gt; 이지만 &lt;em&gt;N 이 작아 무시 가능&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-큐-sidekiq-패턴&quot;&gt;2. 큐 (Sidekiq 패턴)&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;    P[Producer&amp;#x3C;br/&gt;Rails App] --&gt;|RPUSH queue:default| Q[(queue:default&amp;#x3C;br/&gt;List)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|BLPOP| W1[Worker 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|BLPOP| W2[Worker 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt;|BLPOP| W3[Worker 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W1 --&gt;|LMOVE| P1[(processing&amp;#x3C;br/&gt;:host:pid)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W2 --&gt;|LMOVE| P2[(processing&amp;#x3C;br/&gt;:host:pid)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W3 --&gt;|LMOVE| P3[(processing&amp;#x3C;br/&gt;:host:pid)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Sidekiq 큐 종류&lt;/th&gt;&lt;th&gt;List 키&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;기본 큐&lt;/td&gt;&lt;td&gt;&lt;code&gt;queue:default&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;우선순위 큐&lt;/td&gt;&lt;td&gt;&lt;code&gt;queue:critical&lt;/code&gt;, &lt;code&gt;queue:low&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;진행 중&lt;/td&gt;&lt;td&gt;&lt;code&gt;processing:host:pid&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;예약&lt;/td&gt;&lt;td&gt;&lt;code&gt;schedule&lt;/code&gt; (Sorted Set, List 아님)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;재시도&lt;/td&gt;&lt;td&gt;&lt;code&gt;retry&lt;/code&gt; (Sorted Set)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;죽은 잡&lt;/td&gt;&lt;td&gt;&lt;code&gt;dead&lt;/code&gt; (Sorted Set)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;3-circular-buffer-lpop--rpush&quot;&gt;3. Circular Buffer (LPOP + RPUSH)&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;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; push_recent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r, key, item, max_size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pipe &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.pipeline()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pipe.rpush(key, item)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pipe.ltrim(key, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;max_size, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 끝에서부터 max_size 만 남김&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pipe.execute()&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;LPUSH&lt;/code&gt; / &lt;code&gt;RPUSH&lt;/code&gt; / &lt;code&gt;LPOP&lt;/code&gt; / &lt;code&gt;RPOP&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LLEN&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LRANGE&lt;/code&gt; (k 개)&lt;/td&gt;&lt;td&gt;O(k)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LINDEX&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N) &lt;em&gt;worst&lt;/em&gt; (quicklist node 검색)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LSET&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N) &lt;em&gt;worst&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LINSERT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N) &lt;em&gt;(피벗 검색)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LREM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LTRIM&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;중간 접근 (&lt;code&gt;LINDEX&lt;/code&gt;, &lt;code&gt;LINSERT&lt;/code&gt;, &lt;code&gt;LSET&lt;/code&gt;)&lt;/em&gt; 은 &lt;em&gt;O(N)&lt;/em&gt;. &lt;em&gt;List 의 가치&lt;/em&gt; 는 &lt;em&gt;양끝&lt;/em&gt; 에서 나온다. &lt;em&gt;임의 접근&lt;/em&gt; 이 많으면 &lt;em&gt;Sorted Set&lt;/em&gt; 이나 &lt;em&gt;Hash + Sorted Set 인덱스&lt;/em&gt; 가 답.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;메모리-비교-인코딩별&quot;&gt;메모리 비교 (인코딩별)&lt;/h2&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h2 id=&quot;큐-시스템-비교-javarubynode-의-list-위-큐&quot;&gt;큐 시스템 비교 (Java/Ruby/Node 의 List 위 큐)&lt;/h2&gt;
&lt;p&gt;각 언어의 &lt;em&gt;blocking queue&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;producer / consumer 가 &lt;em&gt;큐 한 점&lt;/em&gt; 에서 만나는 일반 패턴. Redis List + &lt;code&gt;BLPOP/BRPOP&lt;/code&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;&lt;code&gt;LRANGE 0 -1&lt;/code&gt; 대용량&lt;/strong&gt; = O(N), single thread 차단. &lt;em&gt;큰 리스트는 페이지 단위&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;LINSERT BEFORE pivot&lt;/code&gt; 의 pivot 검색&lt;/strong&gt; = O(N). 자주 쓰면 &lt;em&gt;Sorted Set 으로 인덱싱&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;블록 명령은 connection 점유&lt;/strong&gt; = Sentinel failover 시 &lt;em&gt;재연결 후 BLPOP 다시 시작&lt;/em&gt; 해야 함. 클라이언트 라이브러리가 &lt;em&gt;자동&lt;/em&gt; 으로 처리하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List 가 &lt;em&gt;영구 큐&lt;/em&gt; 라는 인상&lt;/strong&gt; = Redis 가 &lt;em&gt;재시작 + AOF 없으면&lt;/em&gt; 그대로 손실. &lt;em&gt;Persistence&lt;/em&gt; 가 &lt;em&gt;큐의 신뢰성을 결정&lt;/em&gt;. [[Redis Persistence]] 참고.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;hera-webapp 의 &lt;em&gt;Sidekiq&lt;/em&gt; 운영에서 &lt;em&gt;큐 별 우선순위&lt;/em&gt; + &lt;em&gt;워커 풀 별 분리&lt;/em&gt; 가 &lt;em&gt;핵심&lt;/em&gt;. &lt;code&gt;queue:critical&lt;/code&gt; 에만 &lt;em&gt;전담 워커 5개&lt;/em&gt;, &lt;code&gt;queue:default&lt;/code&gt; 에 &lt;em&gt;공유 워커 10개&lt;/em&gt; 처럼.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;최근 알림 N개&lt;/em&gt; 같은 &lt;em&gt;읽기 중심 리스트&lt;/em&gt; 는 &lt;em&gt;LTRIM&lt;/em&gt; 으로 &lt;em&gt;고정 크기 유지&lt;/em&gt;. 그 외 &lt;em&gt;오래된 알림&lt;/em&gt; 은 &lt;em&gt;별도 archive&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;code&gt;LMOVE&lt;/code&gt;&lt;/em&gt; 가 도입되기 전 (Redis 6.2 이전) 의 &lt;em&gt;&lt;code&gt;BRPOPLPUSH&lt;/code&gt;&lt;/em&gt; 가 여전히 많이 보인다. &lt;em&gt;Sidekiq 호환&lt;/em&gt; 을 위해 &lt;em&gt;옛 명령도 살아있음&lt;/em&gt;. 새 코드는 &lt;code&gt;LMOVE&lt;/code&gt; 로.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;block 명령은 connection 풀 한 자리&lt;/em&gt; 를 차지. &lt;em&gt;워커 수 * 큐 수&lt;/em&gt; 가 &lt;em&gt;Redis connection 한도&lt;/em&gt; 와 만난다. &lt;code&gt;max-clients&lt;/code&gt; 와 &lt;em&gt;워커 fleet 크기&lt;/em&gt; 의 &lt;em&gt;곱셈&lt;/em&gt; 으로 미리 검산.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (자료구조 카탈로그)&lt;/li&gt;
&lt;li&gt;[[Redis Strings]] (가장 기본)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (인덱싱이 필요할 때의 대안)&lt;/li&gt;
&lt;li&gt;[[Redis Pub Sub vs Streams]] (List 큐 vs Streams 큐 결정)&lt;/li&gt;
&lt;li&gt;[[Sidekiq]] (Ruby 큐 운영자)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/lists/&quot;&gt;Lists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;listpack RFC: &lt;a href=&quot;https://github.com/redis/redis/blob/unstable/src/listpack.md&quot;&gt;redis/redis listpack.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>data-structure</category><category>list</category><category>queue</category><category>stack</category><category>quicklist</category><category>listpack</category><author>koa (김신건)</author></item><item><title>[Redis] Persistence: RDB / AOF / Hybrid</title><link>https://shinkeonkim.com/wiki/database-internals/redis-persistence/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-persistence/</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;Redis Persistence&lt;/strong&gt; 는 &lt;em&gt;in-memory&lt;/em&gt; 데이터를 &lt;em&gt;디스크&lt;/em&gt; 로 지속화하는 메커니즘. 세 가지 옵션:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;RDB&lt;/strong&gt; (Redis Database file): 주기적 &lt;em&gt;snapshot&lt;/em&gt;. 작고 빠른 부팅, &lt;em&gt;분 단위 손실&lt;/em&gt; 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AOF&lt;/strong&gt; (Append-Only File): 모든 write 명령을 로그처럼 &lt;em&gt;append&lt;/em&gt;. 손실 거의 없음, &lt;em&gt;큰 파일 + 부팅 느림&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid&lt;/strong&gt; (RDB + AOF, 권장): AOF rewrite 가 &lt;em&gt;내부에 RDB preamble&lt;/em&gt; 을 포함. &lt;em&gt;둘의 장점을 모두&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;선택은 &lt;em&gt;세 축의 트레이드오프&lt;/em&gt; 위에 놓인다: &lt;strong&gt;데이터 손실 허용 시간&lt;/strong&gt;, &lt;strong&gt;부팅 시간&lt;/strong&gt;, &lt;strong&gt;fsync 비용 (I/O / latency)&lt;/strong&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;th&gt;파일 크기&lt;/th&gt;&lt;th&gt;부팅 속도&lt;/th&gt;&lt;th&gt;I/O 부하&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;save &quot;&quot;&lt;/code&gt; (영속화 끔)&lt;/td&gt;&lt;td&gt;&lt;em&gt;재시작 = 전부 손실&lt;/em&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;즉시&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RDB 만&lt;/td&gt;&lt;td&gt;수 분&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;td&gt;빠름&lt;/td&gt;&lt;td&gt;낮음 (fork 시 spike)&lt;/td&gt;&lt;td&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AOF &lt;code&gt;everysec&lt;/code&gt; (기본)&lt;/td&gt;&lt;td&gt;최대 1초&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;AOF &lt;code&gt;always&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;큼&lt;/td&gt;&lt;td&gt;느림&lt;/td&gt;&lt;td&gt;&lt;em&gt;높음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hybrid (RDB + AOF)&lt;/td&gt;&lt;td&gt;최대 1초&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;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;quadrantChart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    title Persistence 옵션의 위치&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    x-axis &quot;디스크 I-O 비용 낮음&quot; --&gt; &quot;높음&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    y-axis &quot;데이터 손실 큼&quot; --&gt; &quot;작음&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    quadrant-1 &quot;안전하지만 비쌈&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    quadrant-2 &quot;안전 저렴 이상&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    quadrant-3 &quot;위험 저렴&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    quadrant-4 &quot;위험 비쌈&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;save 비활성&quot;: [0.05, 0.05]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;RDB only&quot;: [0.2, 0.35]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;AOF everysec&quot;: [0.55, 0.85]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;AOF always&quot;: [0.95, 0.98]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;Hybrid&quot;: [0.55, 0.90]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;fsync-정책별-처리량--손실-시각화&quot;&gt;fsync 정책별 처리량 / 손실 시각화&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;appendfsync&lt;/code&gt; 정책이 &lt;em&gt;latency 와 데이터 손실의 가장 큰 결정 변수&lt;/em&gt;. 동일 워크로드에서의 &lt;em&gt;상대적 처리량&lt;/em&gt; (베이스라인 = &lt;code&gt;always&lt;/code&gt;):&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;p&gt;손실 가능 시간:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;프로덕션 표준&lt;/em&gt; 은 거의 항상 &lt;code&gt;everysec&lt;/code&gt;. &lt;em&gt;결제 / 주문&lt;/em&gt; 같이 &lt;em&gt;어떤 손실도 안 됨&lt;/em&gt; 인 경우에만 &lt;code&gt;always&lt;/code&gt; 와 함께 &lt;em&gt;DB primary 로 안 쓰는 게&lt;/em&gt; 최선.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;rdb-bgsave-와-fork2&quot;&gt;RDB: BGSAVE 와 fork(2)&lt;/h2&gt;
&lt;p&gt;RDB 의 핵심은 &lt;em&gt;프로세스 fork 후 자식이 메모리 dump&lt;/em&gt;. fork(2) 의 &lt;em&gt;Copy-on-Write&lt;/em&gt; 가 핵심.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Redis (parent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Forked (child)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client-&gt;&gt;Redis (parent): BGSAVE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Redis (parent): SAVE 트리거 (저장점) 또는 명령&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Redis (parent)-&gt;&gt;Forked (child): fork()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Forked (child): 부모 메모리의 CoW 스냅샷&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par 부모는 명령 계속 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Client-&gt;&gt;Redis (parent): SET / GET (계속)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over Redis (parent): write 가 발생한 페이지만 *복제*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    and 자식은 dump 작성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Forked (child)-&gt;&gt;Disk: dump.rdb (임시 파일)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Forked (child)-&gt;&gt;Disk: rename (atomic)&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;    Forked (child)-&gt;&gt;Redis (parent): exit(0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Redis (parent): SAVE 완료, 다음 저장점 대기&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저장점 (&lt;code&gt;redis.conf&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# &quot;M 초 안에 K 개 이상의 키 변경&quot; 이면 자동 BGSAVE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;save 3600 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;save 300 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;save 60 10000&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;# save &quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;fork&lt;/em&gt; 는 &lt;em&gt;부모 메모리만큼의 가상 메모리 commit&lt;/em&gt; 이 필요해 보일 수 있다. Linux 의 &lt;code&gt;vm.overcommit_memory=1&lt;/code&gt; 필요. 안 그러면 &lt;em&gt;fork 실패 = BGSAVE 실패&lt;/em&gt;. Redis 가 부팅 시 경고를 띄운다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;aof-append-그리고-rewrite&quot;&gt;AOF: append 그리고 rewrite&lt;/h2&gt;
&lt;p&gt;모든 write 를 &lt;em&gt;RESP 프로토콜 그대로&lt;/em&gt; 파일에 append. 시간이 지나면 &lt;em&gt;redundant 명령&lt;/em&gt; 이 누적 → 주기적 &lt;em&gt;rewrite&lt;/em&gt; 로 압축.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[SET / DEL / EXPIRE / ...] --&gt; B[in-memory state]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; C{appendfsync}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|always| D[write + fsync&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;    C --&gt;|everysec| E[write 즉시&amp;#x3C;br/&gt;fsync 별도 스레드 1초]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|no| F[write 즉시&amp;#x3C;br/&gt;fsync OS 위임]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt; G[(appendonly.aof)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt; G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F --&gt; G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt;|크기 임계값| H{BGREWRITEAOF}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt; I[(새 AOF&amp;#x3C;br/&gt;RDB preamble +&amp;#x3C;br/&gt;증분 명령)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hybrid (&lt;code&gt;aof-use-rdb-preamble yes&lt;/code&gt;, 기본):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AOF rewrite 시작&lt;/strong&gt; → fork → child 가 &lt;em&gt;현재 메모리를 RDB 형태로 dump&lt;/em&gt; → 그 뒤에 &lt;em&gt;이후 들어온 명령&lt;/em&gt; 을 RESP 로 append.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;부팅 시&lt;/em&gt;: RDB 부분 빠르게 load → 나머지 RESP 명령 replay. &lt;em&gt;AOF 의 안정성 + RDB 의 빠른 부팅&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 현재 AOF 상태 확인&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;redis-cli&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INFO&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; persistence&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;code&gt;aof_delayed_fsync&lt;/code&gt; 는 &lt;em&gt;fsync 가 지연된 횟수&lt;/em&gt;. 0 이 아니면 &lt;em&gt;디스크 I/O 가 따라오지 못함&lt;/em&gt;. EBS, 느린 NFS 등에서 특히 자주 본다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;부팅-무엇이-먼저-읽히는가&quot;&gt;부팅: 무엇이 먼저 읽히는가?&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start[redis-server 시작] --&gt; Check{AOF enabled?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Check --&gt;|yes| AofPath&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Check --&gt;|no| RdbPath&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AofPath{aof-use-rdb-preamble yes?} --&gt;|yes| HybridLoad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AofPath --&gt;|no| AofLoad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    HybridLoad[appendonly.aof 의 RDB preamble 빠르게 load] --&gt; ReplayResp[이후 RESP 명령 replay]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AofLoad[모든 명령 RESP 로 replay] --&gt; Ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RdbPath[dump.rdb 만 load] --&gt; Ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ReplayResp --&gt; Ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Ready[(READY)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;AOF 가 활성&lt;/em&gt; 이면 RDB 는 &lt;em&gt;부팅에 사용되지 않는다&lt;/em&gt;. 둘 다 켜놓아도 &lt;em&gt;최종 진실은 AOF&lt;/em&gt;. RDB 는 &lt;em&gt;백업 / 복제&lt;/em&gt; 의 빠른 경로로 남는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;운영-결정-가이드-의사결정-트리&quot;&gt;운영 결정 가이드 (의사결정 트리)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{&quot;데이터가 캐시 전용?&amp;#x3C;br/&gt;(다시 데울 수 있음)&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|예| OFF[영속화 비활성&amp;#x3C;br/&gt;save &apos;&apos; + aof off]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|아니오| Q2{최대 허용 손실?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|수 분 OK| RDB[RDB only]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|1초 이하| Q3{쓰기 처리량 critical?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|아니오| HYB[Hybrid&amp;#x3C;br/&gt;aof everysec +&amp;#x3C;br/&gt;rdb-preamble]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|예| TUNE[Hybrid + 빠른 디스크&amp;#x3C;br/&gt;NVMe / io_uring]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|0초| WARN[AOF always&amp;#x3C;br/&gt;+ 다른 primary DB 권장]&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;순수 캐시 (auth token, page cache)&lt;/td&gt;&lt;td&gt;&lt;code&gt;save &quot;&quot;&lt;/code&gt; + &lt;code&gt;appendonly no&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;세션 store (1시간 미만 손실 OK)&lt;/td&gt;&lt;td&gt;RDB 만, &lt;code&gt;save 60 10000&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;큐 / 분산락 (1초 손실 OK)&lt;/td&gt;&lt;td&gt;Hybrid, &lt;code&gt;appendfsync everysec&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;운영 critical KV (트랜잭션)&lt;/td&gt;&lt;td&gt;&lt;em&gt;Redis 단독 비추천&lt;/em&gt;. PostgreSQL 등을 primary 로&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;디스크와-메모리-함정&quot;&gt;디스크와 메모리: 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;em&gt;RDB BGSAVE 의 fork 가 비싸지는 환경&lt;/em&gt; (큰 데이터셋, 잦은 write):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Transparent Huge Pages (THP)&lt;/em&gt; 켜져 있으면 CoW &lt;em&gt;latency spike&lt;/em&gt;. &lt;code&gt;transparent_hugepage=never&lt;/code&gt; 권장.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;vm.overcommit_memory=1&lt;/em&gt; 안 되어 있으면 &lt;em&gt;fork 실패&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;메모리의 &lt;em&gt;fork 시점 free 가 적으면&lt;/em&gt; CoW 가 빠르게 &lt;em&gt;물리 메모리 2배 차지&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;EBS GP3 처럼 &lt;em&gt;throughput 한계가 명확한&lt;/em&gt; 디스크에서는 BGSAVE 와 AOF rewrite &lt;em&gt;동시&lt;/em&gt; 실행 시 &lt;em&gt;지연 spike&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;thp-끄기&quot;&gt;THP 끄기&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; never&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /sys/kernel/mm/transparent_hugepage/enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; never&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /sys/kernel/mm/transparent_hugepage/defrag&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Sidekiq + Redis&lt;/em&gt; 운영에서 가장 자주 본 알람은 &lt;em&gt;AOF rewrite 중 latency spike&lt;/em&gt;. 새벽 &lt;em&gt;트래픽 적은 시간&lt;/em&gt; 에 rewrite 가 일어나도록 &lt;code&gt;auto-aof-rewrite-percentage 100&lt;/code&gt; + &lt;code&gt;auto-aof-rewrite-min-size 1gb&lt;/code&gt; 조정.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;replica 에서만 BGSAVE&lt;/em&gt; 하는 패턴: primary 는 &lt;code&gt;save &quot;&quot;&lt;/code&gt; + AOF, replica 는 RDB. &lt;em&gt;snapshot 부담&lt;/em&gt; 을 분리.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;재해 복구 연습&lt;/em&gt; 을 &lt;em&gt;분기 1회&lt;/em&gt; 정도는 한다. &lt;code&gt;dump.rdb&lt;/code&gt; 만 들고 &lt;em&gt;깨끗한 인스턴스&lt;/em&gt; 에서 부팅이 &lt;em&gt;몇 분&lt;/em&gt; 인지 측정 → 부팅 시간이 &lt;em&gt;RTO&lt;/em&gt; 에 맞는지 확인.&lt;/li&gt;
&lt;li&gt;AOF only 환경에서 &lt;em&gt;오랫동안 rewrite 실패&lt;/em&gt; 가 누적되면 &lt;em&gt;appendonly.aof&lt;/em&gt; 가 &lt;em&gt;수십 GB&lt;/em&gt; 까지 가는 사고를 본 적이 있다. &lt;em&gt;디스크 모니터링&lt;/em&gt; 이 &lt;em&gt;redis 메트릭&lt;/em&gt; 만큼 중요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능 / 데이터 구조)&lt;/li&gt;
&lt;li&gt;[[Redis Replication]] (replica 에서의 RDB 활용)&lt;/li&gt;
&lt;li&gt;[[Redis Cluster]] (slot 마이그레이션과 persistence)&lt;/li&gt;
&lt;li&gt;[[Zero Downtime Deployment]] (BGSAVE 중 무중단)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;</content:encoded><category>redis</category><category>persistence</category><category>durability</category><category>rdb</category><category>aof</category><category>fsync</category><author>koa (김신건)</author></item><item><title>[Redis] Pub/Sub vs Streams: 휘발 신호 vs 영속 로그</title><link>https://shinkeonkim.com/wiki/database-internals/redis-pubsub-vs-streams/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-pubsub-vs-streams/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pub/Sub&lt;/strong&gt; (&lt;code&gt;PUBLISH&lt;/code&gt; / &lt;code&gt;SUBSCRIBE&lt;/code&gt;): &lt;em&gt;지금 듣고 있는 구독자&lt;/em&gt; 에게만 메시지가 전달되는 &lt;em&gt;휘발성&lt;/em&gt; 신호. &lt;em&gt;영속 없음&lt;/em&gt;, ACK 없음. &lt;em&gt;fan-out&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streams&lt;/strong&gt; (&lt;code&gt;XADD&lt;/code&gt; / &lt;code&gt;XREAD&lt;/code&gt; / &lt;code&gt;XREADGROUP&lt;/code&gt; / &lt;code&gt;XACK&lt;/code&gt;): 영속적인 &lt;em&gt;append-only 로그&lt;/em&gt;. &lt;em&gt;consumer group&lt;/em&gt; 으로 &lt;em&gt;작업 분배&lt;/em&gt;. Kafka 에 가장 가까운 Redis 자료구조.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Pub/Sub 는 메시지 큐가 아니다&lt;/em&gt;. 다운된 구독자가 &lt;em&gt;나중에 돌아와도 지난 메시지는 못 받는다&lt;/em&gt;. &lt;em&gt;처리 보장&lt;/em&gt; 이 필요하면 &lt;em&gt;Streams&lt;/em&gt; 또는 &lt;em&gt;List 큐&lt;/em&gt; 로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;pubsub-의-동작&quot;&gt;Pub/Sub 의 동작&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;pre class=&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 Publisher&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S1 as Subscriber 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S2 as Subscriber 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:#E1E4E8&quot;&gt;    S1-&gt;&gt;R: SUBSCRIBE chat:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2-&gt;&gt;R: SUBSCRIBE chat:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;R: PUBLISH chat:42 &quot;hello&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par 같은 메시지 fan-out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R--&gt;&gt;S1: message chat:42 &quot;hello&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R--&gt;&gt;S2: message chat:42 &quot;hello&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 over S2: 네트워크 끊김&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;R: PUBLISH chat:42 &quot;world&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R--&gt;&gt;S1: message chat:42 &quot;world&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R,S2: S2 는 못 받는다 (휘발). 다시 SUBSCRIBE 해도 &quot;world&quot; 는 영원히 사라짐&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;명령-셋&quot;&gt;명령 셋&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 구독&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SUBSCRIBE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:99&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;PSUBSCRIBE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&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;SSUBSCRIBE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; shardchannel&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # Redis 7+ Sharded Pub/Sub (Cluster 친화)&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:#B392F0&quot;&gt;PUBLISH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hello&quot;&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;SPUBLISH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; shardchannel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;msg&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         # Sharded&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:#B392F0&quot;&gt;PUBSUB&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&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;PUBSUB&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NUMSUB&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&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;PUBSUB&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NUMPAT&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;sharded-pubsub-redis-7&quot;&gt;Sharded Pub/Sub (Redis 7+)&lt;/h3&gt;
&lt;p&gt;전통 Pub/Sub 는 &lt;em&gt;Cluster 에서 모든 노드로 fan-out&lt;/em&gt; → 노드 수 늘수록 &lt;em&gt;낭비&lt;/em&gt;. &lt;em&gt;Sharded Pub/Sub&lt;/em&gt; 는 채널을 &lt;em&gt;슬롯에 매핑&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;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Publisher] --&gt;|PUBLISH| All[모든 노드로 fan-out&amp;#x3C;br/&gt;~ N nodes 비용]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P2[Publisher] --&gt;|SPUBLISH| Slot[CRC16 slot → 1 노드만]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Slot --&gt; Sub[해당 슬롯 구독자만]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;streams-영속-로그--consumer-group&quot;&gt;Streams: 영속 로그 + Consumer Group&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;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;em&gt;FIFO 큐 동작&lt;/em&gt; 의 일반 직관. Streams 는 &lt;em&gt;큐 + 영속 + 다중 consumer group&lt;/em&gt; 으로 확장된 형태.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[Producer] --&gt;|XADD chat:42 *&amp;#x3C;br/&gt;field=value| Log[(Stream chat:42)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log --&gt;|XREADGROUP| G1[Group A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log --&gt;|XREADGROUP| G2[Group B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph G1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C11[Consumer 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C12[Consumer 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph G2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C21[Consumer 1]&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;    G1 --&gt;|XACK| Log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G2 --&gt;|XACK| Log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 &lt;em&gt;group&lt;/em&gt; 은 &lt;em&gt;자기 진행 위치 (last-delivered-id)&lt;/em&gt; 를 &lt;em&gt;Stream 안에&lt;/em&gt; 저장. &lt;em&gt;consumer&lt;/em&gt; 하나가 죽어도 &lt;em&gt;다른 consumer&lt;/em&gt; 가 &lt;em&gt;XPENDING&lt;/em&gt; 으로 &lt;em&gt;못 ACK 된 메시지&lt;/em&gt; 를 &lt;em&gt;XCLAIM&lt;/em&gt; 으로 이어받음.&lt;/p&gt;
&lt;h3 id=&quot;명령-셋-1&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;alice&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hi&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;           # ID = millis-seq&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XLEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                                     # 길이&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 단일 consumer 읽기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XREAD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; STREAMS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&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;XREAD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BLOCK&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; STREAMS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 새 것만 5초 블록&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Consumer group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XGROUP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CREATE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;MKSTREAM&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;XREADGROUP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GROUP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; c1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; STREAMS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XACK&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 1709305822-0&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;XPENDING&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&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;XCLAIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; c2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 1709305822-0&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     # 60초 이상 응답 없는 메시지 빼앗아 옴&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XAUTOCLAIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; workers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; c2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60000&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 일괄 (Redis 6.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:#6A737D&quot;&gt;# 정리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XTRIM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MAXLEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100000&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                    # approximate, ~ 가 효율적&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; chat:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MAXLEN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100000&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; field&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       # add 와 함께&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;xadd--xack-의-전형적-흐름&quot;&gt;XADD ↔ XACK 의 전형적 흐름&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 Producer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant W1 as Worker 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant W2 as Worker 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:#E1E4E8&quot;&gt;    Producer-&gt;&gt;Stream: XADD orders * id 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Producer-&gt;&gt;Stream: XADD orders * id 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W1-&gt;&gt;Stream: XREADGROUP workers w1 COUNT 1 &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream--&gt;&gt;W1: (orders, &quot;1-0&quot;, {id:1})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W2-&gt;&gt;Stream: XREADGROUP workers w2 COUNT 1 &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream--&gt;&gt;W2: (orders, &quot;2-0&quot;, {id:2})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W1: 처리 성공&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W1-&gt;&gt;Stream: XACK orders workers 1-0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W2: 처리 중 크래시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W2-xStream: (응답 없음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Stream: 1분 후 운영자가 또는 다른 worker 가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W1-&gt;&gt;Stream: XAUTOCLAIM orders workers w1 60000 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream--&gt;&gt;W1: (&quot;2-0&quot;, ...) 가져옴&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W1-&gt;&gt;Stream: XACK orders workers 2-0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;8x-의-신규-명령&quot;&gt;8.x 의 신규 명령&lt;/h3&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;XDELEX&lt;/code&gt; &lt;em&gt;(Redis 8.2)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;지정 ID 를 &lt;em&gt;delete + 즉시 reclaim&lt;/em&gt;. consumer group ack 와 동시에 처리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;XACKDEL&lt;/code&gt; &lt;em&gt;(Redis 8.2)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;ack + delete 한 번에&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;XNACK&lt;/code&gt; &lt;em&gt;(Redis 8.8)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;consumer 가 &lt;em&gt;명시적으로&lt;/em&gt; 메시지를 &lt;em&gt;release&lt;/em&gt;. 재시도 가능&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;pubsub-vs-streams-결정-매트릭스&quot;&gt;Pub/Sub vs Streams: 결정 매트릭스&lt;/h2&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;특성&lt;/th&gt;&lt;th&gt;Pub/Sub&lt;/th&gt;&lt;th&gt;Streams&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;영속성&lt;/td&gt;&lt;td&gt;&lt;em&gt;없음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;있음&lt;/em&gt; (MAXLEN 으로 조절)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;다운된 구독자의 메시지&lt;/td&gt;&lt;td&gt;&lt;em&gt;영구 손실&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;복귀 후 수신 가능&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consumer group&lt;/td&gt;&lt;td&gt;없음 (broadcast 만)&lt;/td&gt;&lt;td&gt;&lt;em&gt;있음&lt;/em&gt; (작업 분배)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ACK / 재시도&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;&lt;em&gt;있음&lt;/em&gt; (XACK / XCLAIM)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fan-out&lt;/td&gt;&lt;td&gt;&lt;em&gt;N 구독자&lt;/em&gt; 에게 모두&lt;/td&gt;&lt;td&gt;&lt;em&gt;그룹 단위&lt;/em&gt; 1개&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cluster&lt;/td&gt;&lt;td&gt;전체 fan-out (Sharded 로 완화)&lt;/td&gt;&lt;td&gt;키 (Stream) 별 슬롯&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모리 비용&lt;/td&gt;&lt;td&gt;&lt;em&gt;0&lt;/em&gt; (즉시 버림)&lt;/td&gt;&lt;td&gt;&lt;em&gt;길이 비례&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합&lt;/td&gt;&lt;td&gt;알림, 무효화 신호, 라이브 채팅&lt;/td&gt;&lt;td&gt;메시지 큐, 이벤트 소싱&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;처리량--지연-직관&quot;&gt;처리량 / 지연 (직관)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&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;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 Sidekiq[Sidekiq, Ruby]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SK_LIST[List&amp;#x3C;br/&gt;queue:default]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SK_ZSET[ZSet&amp;#x3C;br/&gt;schedule]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SK_HASH[Hash&amp;#x3C;br/&gt;job hash]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph BullMQ[BullMQ, Node]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BMQ_STREAM[Stream&amp;#x3C;br/&gt;jobs]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BMQ_ZSET[ZSet&amp;#x3C;br/&gt;delayed]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph Celery[Celery, Python]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        CL_LIST[List&amp;#x3C;br/&gt;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;    subgraph RQ[RQ, Python]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RQ_LIST[List&amp;#x3C;br/&gt;rq:queue:default]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;라이브러리&lt;/th&gt;&lt;th&gt;기본 구현&lt;/th&gt;&lt;th&gt;신뢰성&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Sidekiq (OSS)&lt;/td&gt;&lt;td&gt;&lt;code&gt;BRPOPLPUSH&lt;/code&gt; (현 &lt;code&gt;LMOVE&lt;/code&gt;) 로 &lt;em&gt;fetch + 처리 중 표시&lt;/em&gt;, ACK 시 제거&lt;/td&gt;&lt;td&gt;crash 시 &lt;em&gt;처리 중 목록&lt;/em&gt; 에서 회수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sidekiq Pro&lt;/td&gt;&lt;td&gt;”reliable fetch” 강화&lt;/td&gt;&lt;td&gt;동일, 보강&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BullMQ&lt;/td&gt;&lt;td&gt;Streams 기반&lt;/td&gt;&lt;td&gt;XACK + retry 로 견고&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Celery (Redis broker)&lt;/td&gt;&lt;td&gt;List 기반 단순 fetch&lt;/td&gt;&lt;td&gt;crash 시 일부 손실 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RQ&lt;/td&gt;&lt;td&gt;List 기반 단순&lt;/td&gt;&lt;td&gt;동일&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;sidekiq-의-reliable-fetch-원리&quot;&gt;Sidekiq 의 reliable fetch 원리&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 W as Worker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Q as queue:default (List)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant P as processing:host:pid (List)&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;    W-&gt;&gt;Q: LMOVE queue:default processing:host:pid LEFT RIGHT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q--&gt;&gt;W: job-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over W: 처리 시작 (job 은 processing 목록에 남아있음)&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;        W-&gt;&gt;P: LREM processing:host:pid 1 job-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    else crash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over P: 워커 다운 감지 (heartbeat)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Note over P: 운영자 / 다른 워커가 processing 의 job 을 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;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;영원히 &lt;em&gt;List&lt;/em&gt; 기반인 이유&lt;/em&gt; 는 &lt;em&gt;호환성&lt;/em&gt;. 이론적으로는 &lt;em&gt;Streams 로 다시 짤 수&lt;/em&gt; 있지만 &lt;em&gt;모든 user 의 큐 키 / 미들웨어 / 콘솔&lt;/em&gt; 이 &lt;em&gt;List 인 것을 가정&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;keyspace-notifications&quot;&gt;Keyspace Notifications&lt;/h2&gt;
&lt;p&gt;키 변경을 &lt;em&gt;Pub/Sub 채널&lt;/em&gt; 로 받는 기능. &lt;em&gt;invalidation broadcast&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;notify-keyspace-events &quot;KEA&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;플래그 (조합):&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;플래그&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;K&lt;/code&gt;&lt;/td&gt;&lt;td&gt;keyspace 채널 (&lt;code&gt;__keyspace@0__:key&lt;/code&gt; 로 &lt;em&gt;이벤트 이름&lt;/em&gt; 발행)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;E&lt;/code&gt;&lt;/td&gt;&lt;td&gt;keyevent 채널 (&lt;code&gt;__keyevent@0__:event&lt;/code&gt; 로 &lt;em&gt;키 이름&lt;/em&gt; 발행)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;&lt;td&gt;일반 (DEL, EXPIRE, RENAME)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;List&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Set&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;h&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Hash&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;z&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ZSet&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;&lt;td&gt;expired&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;&lt;td&gt;evicted&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;g$lshzxe&lt;/code&gt; 의 alias (all)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PSUBSCRIBE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;__keyevent@0__:expired&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# expired 가 발생할 때마다 키 이름이 메시지로 옴&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;expired 이벤트&lt;/em&gt; 는 &lt;em&gt;실제 키 만료 시점&lt;/em&gt; 이 아니라 &lt;em&gt;Redis 가 발견한 시점&lt;/em&gt;. lazy expiration 때문에 &lt;em&gt;수 초 지연&lt;/em&gt; 가능. 정확한 시각이 필요하면 &lt;em&gt;Streams + 스케줄러&lt;/em&gt; 가 답.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;실전-패턴&quot;&gt;실전 패턴&lt;/h2&gt;
&lt;h3 id=&quot;캐시-무효화-fan-out-pubsub&quot;&gt;캐시 무효화 fan-out (Pub/Sub)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;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 W as Writer (DB 갱신)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant A1 as App 인스턴스 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant A2 as App 인스턴스 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:#E1E4E8&quot;&gt;    A1-&gt;&gt;R: SUBSCRIBE cache:invalidate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2-&gt;&gt;R: SUBSCRIBE cache:invalidate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    W-&gt;&gt;R: PUBLISH cache:invalidate user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    par&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R--&gt;&gt;A1: cache:invalidate user:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R--&gt;&gt;A2: cache:invalidate user:42&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;    A1-&gt;&gt;A1: in-memory LRU 의 user:42 제거&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2-&gt;&gt;A2: in-memory LRU 의 user:42 제거&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;로컬 L1 캐시 + Redis L2 캐시&lt;/em&gt; 인 환경의 &lt;em&gt;L1 무효화&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;이벤트-소싱--projection-streams&quot;&gt;이벤트 소싱 + projection (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;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmd[Commands] --&gt;|XADD events| Stream[(events stream)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream --&gt;|XREADGROUP proj1| P1[Read Model A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream --&gt;|XREADGROUP proj2| P2[Read Model B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Stream --&gt;|XREADGROUP audit| P3[Audit Log]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P1 --&gt; View1[(Materialized View 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P2 --&gt; View2[(Materialized View 2)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P3 --&gt; Store[(Append-only audit)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 &lt;em&gt;projection&lt;/em&gt; 이 &lt;em&gt;독립 group&lt;/em&gt;. 한 projection 이 &lt;em&gt;과거 이벤트 다시 처리&lt;/em&gt; 가 필요하면 &lt;em&gt;XGROUP CREATE … 0&lt;/em&gt; 으로 처음부터.&lt;/p&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;hera-webapp 의 &lt;em&gt;알림 fan-out&lt;/em&gt; 은 &lt;em&gt;Pub/Sub&lt;/em&gt;. &lt;em&gt;놓쳐도 되는 라이브 신호&lt;/em&gt; 만. 영속이 필요한 &lt;em&gt;재발송 가능 이메일&lt;/em&gt; 은 &lt;em&gt;Sidekiq 의 List 큐&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Streams 로 큐를 직접 운영&lt;/em&gt; 한 경험: &lt;em&gt;XACK 누락 시 무한 retry&lt;/em&gt; 와 &lt;em&gt;DLQ (XCLAIM 한도 초과 시 별도 stream)&lt;/em&gt; 의 &lt;em&gt;수동 운영&lt;/em&gt; 부담이 크다. &lt;em&gt;Sidekiq&lt;/em&gt; 가 &lt;em&gt;훨씬 더 운영자 친화&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sharded Pub/Sub&lt;/em&gt; 는 &lt;em&gt;Cluster + Pub/Sub fan-out&lt;/em&gt; 환경에서 &lt;em&gt;네트워크 대역폭&lt;/em&gt; 을 &lt;em&gt;수 배 절감&lt;/em&gt;. 신규 시스템은 &lt;em&gt;처음부터 SPUBLISH/SSUBSCRIBE&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Keyspace notifications&lt;/em&gt; 의 &lt;em&gt;expired&lt;/em&gt; 는 &lt;em&gt;재미있지만 위험&lt;/em&gt;. 의존하면 안 된다. 항상 &lt;em&gt;별도 스케줄러&lt;/em&gt; 가 &lt;em&gt;재확인&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능)&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]] (캐시 무효화 fan-out)&lt;/li&gt;
&lt;li&gt;[[Redis Distributed Lock]] (큐 작업의 멱등성)&lt;/li&gt;
&lt;li&gt;[[Sidekiq]] (Ruby 큐 운영)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&quot;&gt;Pub/Sub&lt;/a&gt;, &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/streams/&quot;&gt;Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sidekiq Wiki: &lt;a href=&quot;https://github.com/sidekiq/sidekiq/wiki&quot;&gt;github.com/sidekiq/sidekiq/wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;BullMQ: &lt;a href=&quot;https://docs.bullmq.io/&quot;&gt;docs.bullmq.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>pubsub</category><category>streams</category><category>queue</category><category>messaging</category><category>consumer-group</category><author>koa (김신건)</author></item><item><title>[Redis] Replication &amp; Sentinel: 비동기 복제와 자동 Failover</title><link>https://shinkeonkim.com/wiki/database-internals/redis-replication/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-replication/</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;Redis Replication&lt;/strong&gt; 은 &lt;em&gt;primary (master) → replica (slave)&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;p&gt;&lt;strong&gt;Redis Sentinel&lt;/strong&gt; 은 &lt;em&gt;그 위에 얹는 watchdog&lt;/em&gt; 시스템. 일정 quorum 이상이 primary 가 &lt;em&gt;죽었다&lt;/em&gt; 라고 동의하면, &lt;em&gt;replica 하나를 새 primary 로 승격&lt;/em&gt; + &lt;em&gt;다른 replica 들을 재구성&lt;/em&gt; + &lt;em&gt;클라이언트에 알림&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;Sentinel ≠ Cluster&lt;/strong&gt;. Sentinel 은 &lt;em&gt;키 분할이 없다&lt;/em&gt;. &lt;em&gt;전체 데이터셋이 한 노드 메모리 안에 들어가야&lt;/em&gt; 한다. 분할이 필요하면 &lt;em&gt;Cluster&lt;/em&gt; 로. [[Redis Cluster]] 참고.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;replication-의-시각&quot;&gt;Replication 의 시각&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;토폴로지:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph DC1[데이터센터 / VPC]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P[(Primary&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;        R1[(Replica 1&amp;#x3C;br/&gt;읽기 only)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R2[(Replica 2&amp;#x3C;br/&gt;읽기 only)]&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;    P --&gt;|RESP 명령 스트림| R1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt;|RESP 명령 스트림| R2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client[App] --&gt;|Write| P&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|Read| R1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|Read| R2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;특징:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;비동기&lt;/strong&gt;: write 가 primary 에서 성공하면 &lt;em&gt;바로 응답&lt;/em&gt;. replica 에 &lt;em&gt;반영되었는지 모름&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단방향&lt;/strong&gt;: replica 에 직접 write 하면 &lt;em&gt;조용히 거부&lt;/em&gt; (또는 &lt;em&gt;replica 가 read-only 가 아니면 분기&lt;/em&gt;. 절대 금지).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PSYNC&lt;/strong&gt;: 처음 한 번은 RDB 풀 동기화, 이후엔 &lt;em&gt;backlog 의 offset 부터 증분&lt;/em&gt; 동기화.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;psync-풀--부분-동기화&quot;&gt;PSYNC: 풀 / 부분 동기화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant P as Primary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R-&gt;&gt;P: PSYNC ? -1 (replicationid 모름)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P--&gt;&gt;R: +FULLRESYNC &amp;#x3C;replid&gt; &amp;#x3C;offset&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over P: BGSAVE 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-&gt;&gt;P: dump.rdb 생성 (fork)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P--&gt;&gt;R: $&amp;#x3C;bytes&gt;\r\n&amp;#x3C;RDB binary&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R: RDB load → state 동기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P--&gt;&gt;R: 이후의 명령 스트림 (replication backlog 부터)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R: 이후 정상 streaming&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over R,P: 네트워크 끊김 시 ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R-&gt;&gt;P: PSYNC &amp;#x3C;replid&gt; &amp;#x3C;offset&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alt offset 이 backlog 안에 있음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        P--&gt;&gt;R: +CONTINUE (증분만 보냄)&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;        P--&gt;&gt;R: +FULLRESYNC ... (다시 RDB 풀 동기화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;replication backlog&lt;/code&gt; 가 &lt;em&gt;크기 (메모리)&lt;/em&gt; 만큼만 &lt;em&gt;부분 재동기화&lt;/em&gt; 가 가능. 끊김 시간이 backlog 보다 길면 &lt;em&gt;RDB 풀 동기화로 회귀&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;# redis.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;repl-backlog-size 64mb       # 끊김에 견딜 최대 시간 = backlog / 처리량&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;repl-backlog-ttl 3600        # replica 없을 때 backlog 유지 시간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;repl-diskless-sync yes       # primary 가 RDB 를 socket 으로 직접 전송 (디스크 우회)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;repl-diskless-sync-delay 5   # 더 많은 replica 가 모일 때까지 기다림&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;replica 다수가 동시에 재접속&lt;/em&gt; 하면 (예: 배포 후) &lt;code&gt;repl-diskless-sync-delay&lt;/code&gt; 가 &lt;em&gt;동일 BGSAVE 를 여러 replica 가 공유&lt;/em&gt; 하게 해준다. fork 비용 절감.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;replication-lag-의-시각화&quot;&gt;Replication Lag 의 시각화&lt;/h2&gt;
&lt;p&gt;primary 에 &lt;em&gt;지속적인 쓰기&lt;/em&gt; 가 있을 때 replica 가 &lt;em&gt;얼마나 늦었는지&lt;/em&gt;. 일반적인 &lt;em&gt;행복한 경우 vs 네트워크 혼잡&lt;/em&gt; 비교:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;p&gt;핵심 지표 (&lt;code&gt;INFO replication&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;master_repl_offset&lt;/code&gt; / &lt;code&gt;slave_repl_offset&lt;/code&gt; 의 &lt;em&gt;차이&lt;/em&gt; = &lt;em&gt;명령 단위&lt;/em&gt; lag.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;master_link_status&lt;/code&gt; = &lt;code&gt;up&lt;/code&gt; / &lt;code&gt;down&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;master_last_io_seconds_ago&lt;/code&gt; = primary 로부터 &lt;em&gt;마지막 명령 수신&lt;/em&gt; 후 경과.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;안전-가드-wait-와-min-replicas&quot;&gt;안전 가드: WAIT 와 min-replicas&lt;/h2&gt;
&lt;p&gt;비동기라도 &lt;em&gt;일부 동기 보장&lt;/em&gt; 을 흉내내는 두 장치:&lt;/p&gt;
&lt;h3 id=&quot;wait-numreplicas-timeout&quot;&gt;&lt;code&gt;WAIT numreplicas timeout&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;마지막 쓰기가 &lt;em&gt;numreplicas 개 이상&lt;/em&gt; 의 replica 에 도달할 때까지 &lt;em&gt;최대 timeout ms&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 1개 replica 이상 ACK 받을 때까지 100ms 대기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; payment:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; paid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WAIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → (integer) 1   # 1개에 도달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# → (integer) 0   # 어디에도 못 갔음 (네트워크 끊김 등)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
WAIT 는 &lt;em&gt;완벽한 동기 복제&lt;/em&gt; 가 아니다. &lt;em&gt;primary 가 죽어도 replica 가 못 받은 케이스&lt;/em&gt; 는 여전히 존재. &lt;em&gt;결제 / 주문&lt;/em&gt; 의 &lt;em&gt;primary store&lt;/em&gt; 로 Redis 를 쓰면 안 되는 이유.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;min-replicas-to-write--min-replicas-max-lag&quot;&gt;&lt;code&gt;min-replicas-to-write&lt;/code&gt; / &lt;code&gt;min-replicas-max-lag&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;min-replicas-to-write 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;min-replicas-max-lag 10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ “&lt;em&gt;최소 1개 이상&lt;/em&gt; 의 replica 가 &lt;em&gt;지난 10초 안에&lt;/em&gt; 응답했어야 &lt;em&gt;primary 가 write 를 받아준다&lt;/em&gt;”. &lt;em&gt;split-brain 의 write 손실&lt;/em&gt; 을 줄인다.&lt;/p&gt;
&lt;h2 id=&quot;sentinel-자동-failover&quot;&gt;Sentinel: 자동 Failover&lt;/h2&gt;
&lt;p&gt;Sentinel 은 &lt;em&gt;별도 프로세스&lt;/em&gt; (3 ~ 5개 짝수 피하기). 각 Sentinel 이 &lt;em&gt;모든 노드를 주기적으로 ping + INFO&lt;/em&gt;. &lt;em&gt;quorum 명이 동의&lt;/em&gt; 하면 &lt;em&gt;Subjective Down → Objective Down → Election → Promotion&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S1 as Sentinel 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S2 as Sentinel 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S3 as Sentinel 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant P as Primary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant R as Replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over P: 정상 동작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-xS1: PING 응답 없음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P-xS2: PING 응답 없음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over S1: S_DOWN 표시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over S2: S_DOWN 표시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;S2: SENTINEL is-master-down-by-addr ?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2--&gt;&gt;S1: 예&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;S3: SENTINEL is-master-down-by-addr ?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S3--&gt;&gt;S1: 예&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over S1: 정족수 도달 → O_DOWN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;S2: 리더 선출 (Raft 비슷)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;S3: 리더 선출&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over S1: 리더 = S1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;R: 후보 평가 (lag / priority)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;R: REPLICAOF NO ONE (승격)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R--&gt;&gt;S1: OK (이제 새 Primary)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;R: 다른 replica 들을 새 primary 로 REPLICAOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1-&gt;&gt;C: Pub/Sub +switch-master 알림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;R: 이후 트래픽&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심 단계:&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;S_DOWN&lt;/code&gt; (Subjective)&lt;/td&gt;&lt;td&gt;&lt;em&gt;나 (이 Sentinel) 보기엔&lt;/em&gt; primary 가 죽었다. 단독 판단&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;O_DOWN&lt;/code&gt; (Objective)&lt;/td&gt;&lt;td&gt;&lt;em&gt;quorum 명 이상&lt;/em&gt; 의 Sentinel 이 동의. 진짜 죽었다고 합의&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Election&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Sentinel 들이 &lt;em&gt;리더&lt;/em&gt; 1개를 선출&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Promote&lt;/code&gt;&lt;/td&gt;&lt;td&gt;리더가 &lt;em&gt;replica 중 하나를&lt;/em&gt; 새 primary 로 승격&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Reconfigure&lt;/code&gt;&lt;/td&gt;&lt;td&gt;나머지 replica 들을 새 primary 의 replica 로 &lt;em&gt;REPLICAOF&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Notify&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Sentinel 의 Pub/Sub 채널 (&lt;code&gt;+switch-master&lt;/code&gt;) 로 알림&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;설정 (&lt;code&gt;sentinel.conf&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;port 26379&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sentinel monitor mymaster 10.0.0.10 6379 2     # quorum = 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sentinel down-after-milliseconds mymaster 5000  # 5초 응답 없으면 S_DOWN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sentinel failover-timeout mymaster 60000        # failover 한 사이클 한도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sentinel parallel-syncs mymaster 1              # 동시에 새 primary 와 sync 할 replica 수&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;rto--rpo-의-직관&quot;&gt;RTO / RPO 의 직관&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;gantt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    title Failover 타임라인 (예시, 단위 = 초)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dateFormat ss&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    axisFormat %Ss&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;    section Primary 다운&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    실제 다운             :done, p1, 00, 1s&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;    section Sentinel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    down-after (5s)       :crit, s1, 01, 5s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Election             :active, s2, after s1, 2s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Promote              :active, s3, after s2, 1s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Replicas 재구성      :active, s4, after s3, 3s&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;    section Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sentinel Pub/Sub 수신 :milestone, m1, after s3, 0s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    재연결               :active, c1, after m1, 2s&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;Sentinel 의 보통 범위&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;RTO&lt;/strong&gt; (Recovery Time Objective)&lt;/td&gt;&lt;td&gt;서비스 복구까지&lt;/td&gt;&lt;td&gt;5초 (down-after) + 5 ~ 15초 (election + promote + reconnect)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;RPO&lt;/strong&gt; (Recovery Point Objective)&lt;/td&gt;&lt;td&gt;데이터 손실 허용 시점&lt;/td&gt;&lt;td&gt;비동기 복제 이므로 &lt;em&gt;마지막 ACK 안 된 write 만큼&lt;/em&gt; (수십 ms ~ 수 초)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
RTO 를 &lt;em&gt;수 초&lt;/em&gt; 로 줄이려면 &lt;code&gt;down-after-milliseconds&lt;/code&gt; 를 작게. 너무 작으면 &lt;em&gt;네트워크 jitter 만으로도 false failover&lt;/em&gt;. 보통 1 ~ 5초 사이에서 &lt;em&gt;false positive 와 RTO&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;em&gt;클라이언트 라이브러리&lt;/em&gt; 가 &lt;em&gt;Sentinel 의 알림&lt;/em&gt; 을 &lt;em&gt;제대로 처리&lt;/em&gt; 해야 한다. &lt;em&gt;connection pool&lt;/em&gt; 이 &lt;em&gt;옛 primary 의 IP&lt;/em&gt; 를 가지고 있으면 failover 후에도 &lt;em&gt;옛 노드&lt;/em&gt; 로 계속 시도. &lt;em&gt;Lettuce&lt;/em&gt;, &lt;em&gt;redis-py-cluster&lt;/em&gt;, &lt;em&gt;redis-rb&lt;/em&gt; 등 &lt;em&gt;Sentinel discovery&lt;/em&gt; 가 켜져 있는지 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;흔한 사고:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Split-brain&lt;/strong&gt;: 네트워크 분할로 &lt;em&gt;옛 primary 와 새 primary 가 동시&lt;/em&gt; 에 write 받음. &lt;code&gt;min-replicas-to-write&lt;/code&gt; 가 핵심 방어.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replica 가 너무 lag&lt;/strong&gt; 한 상태에서 승격 → &lt;em&gt;최근 write 손실 큼&lt;/em&gt;. Sentinel 의 &lt;em&gt;replica 우선순위&lt;/em&gt; 와 &lt;em&gt;replica-priority&lt;/em&gt; (낮은 값 우선) 로 &lt;em&gt;lag 적은 replica&lt;/em&gt; 가 선정되도록.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quorum = 노드 수와 동일&lt;/strong&gt; → &lt;em&gt;어떤 Sentinel 한 명이 죽어도 failover 못함&lt;/em&gt;. &lt;em&gt;quorum = (N/2)+1&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentinel 이 같은 호스트 / VM&lt;/strong&gt; 에서 동작 → &lt;em&gt;호스트 장애가 모든 Sentinel 을 한꺼번에 죽임&lt;/em&gt;. &lt;em&gt;반드시 분산&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;redis-자체-ha-standalone-vs-sentinel-vs-cluster&quot;&gt;Redis 자체 HA: Standalone vs Sentinel vs Cluster&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;자동 failover&lt;/th&gt;&lt;th&gt;클라이언트 복잡도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Standalone&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;&lt;em&gt;수동&lt;/em&gt;&lt;/td&gt;&lt;td&gt;가장 단순&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sentinel&lt;/td&gt;&lt;td&gt;없음 (전체 데이터 한 노드 메모리에)&lt;/td&gt;&lt;td&gt;&lt;em&gt;자동&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Sentinel 발견 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cluster&lt;/td&gt;&lt;td&gt;16384 슬롯 분할&lt;/td&gt;&lt;td&gt;&lt;em&gt;자동&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;cluster-aware&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;ul&gt;
&lt;li&gt;hera-webapp 에서는 &lt;em&gt;AWS ElastiCache Redis Cluster Mode Off + Multi-AZ&lt;/em&gt; 사용. &lt;em&gt;managed Sentinel&lt;/em&gt; 이라고 봐도 된다. 직접 Sentinel 운영은 &lt;em&gt;3 노드 분산 + Sentinel 모니터링 알람&lt;/em&gt; 이 필수.&lt;/li&gt;
&lt;li&gt;*failover 동안 &lt;em&gt;짧은 timeout&lt;/em&gt; 으로 &lt;em&gt;connection pool 이 즉시 새 primary 를 잡도록&lt;/em&gt; 셋업했더니, &lt;em&gt;간헐적 5xx&lt;/em&gt; 가 안 보이기 시작.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sidekiq&lt;/em&gt; 은 &lt;em&gt;Sentinel 친화적&lt;/em&gt;. &lt;code&gt;:sentinels&lt;/code&gt; 옵션 + &lt;code&gt;:role =&gt; :master&lt;/code&gt; 로 &lt;em&gt;항상 primary 로 라우팅&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;failover 연습&lt;/em&gt; 을 &lt;em&gt;카오스 엔지니어링 한 일과&lt;/em&gt; 로. &lt;em&gt;primary 에 일부러 &lt;code&gt;DEBUG SLEEP 30&lt;/code&gt;&lt;/em&gt; 을 날려 Sentinel 이 &lt;em&gt;실전처럼 동작&lt;/em&gt; 하는지 검증.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (라이센스 / 신 기능)&lt;/li&gt;
&lt;li&gt;[[Redis Persistence]] (replica 에서 RDB 활용)&lt;/li&gt;
&lt;li&gt;[[Redis Cluster]] (수평 확장이 필요할 때)&lt;/li&gt;
&lt;li&gt;[[Zero Downtime Deployment]] (failover 와 무중단 배포)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;</content:encoded><category>redis</category><category>replication</category><category>sentinel</category><category>ha</category><category>failover</category><author>koa (김신건)</author></item><item><title>[Redis] Set: 중복 없는 멤버십, SINTER / SUNION / SDIFF</title><link>https://shinkeonkim.com/wiki/database-internals/redis-sets/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/database-internals/redis-sets/</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;Redis Set&lt;/strong&gt; 은 &lt;em&gt;중복 없는 unordered 멤버 집합&lt;/em&gt;. &lt;em&gt;SADD / SREM / SISMEMBER&lt;/em&gt; 가 &lt;em&gt;O(1)&lt;/em&gt;, &lt;em&gt;SINTER / SUNION / SDIFF&lt;/em&gt; 의 &lt;em&gt;집합 연산&lt;/em&gt; 이 자료구조 자체에 박혀 있다.&lt;/p&gt;
&lt;p&gt;활용:&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; (게시물 → 태그들, 태그 → 게시물들)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;친구 / 팔로워&lt;/strong&gt; (양방향, 공통 친구 SINTER)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고유 카운트&lt;/strong&gt; (정확한, 작은 집합)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권한 / 역할&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;em&gt;순서가 필요&lt;/em&gt; 하면 [[Redis Sorted Sets]] 로. &lt;em&gt;대규모 cardinality (정확도 약간 희생 가능)&lt;/em&gt; 면 [[Redis HyperLogLog Geo]] 의 HLL 로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;내부-인코딩&quot;&gt;내부 인코딩&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;인코딩&lt;/th&gt;&lt;th&gt;조건 (기본)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;intset&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;모든 멤버가 정수&lt;/em&gt; 이고 &lt;em&gt;길이 ≤ &lt;code&gt;set-max-intset-entries&lt;/code&gt;&lt;/em&gt; (512)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;listpack&lt;/code&gt;&lt;/td&gt;&lt;td&gt;작은 set (Redis 7.2+) ≤ &lt;code&gt;set-max-listpack-entries&lt;/code&gt; (128) AND 멤버 크기 작음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;hashtable&lt;/code&gt;&lt;/td&gt;&lt;td&gt;그 외&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;set-max-intset-entries 512&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;set-max-listpack-entries 128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;set-max-listpack-value 64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;intset-의-마법&quot;&gt;intset 의 마법&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;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;위 애니메이션은 &lt;em&gt;Hash Set&lt;/em&gt; 의 &lt;em&gt;bucket + 충돌 해결&lt;/em&gt; 직관. Redis 의 &lt;em&gt;hashtable 인코딩&lt;/em&gt; 은 정확히 이 구조.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Hdr[&quot;[encoding: int16/int32/int64]&amp;#x3C;br/&gt;length: 5&quot;] --&gt; M[&quot;sorted: [-3, 1, 7, 42, 99]&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;정렬된 정수 배열&lt;/em&gt;. &lt;em&gt;binary search&lt;/em&gt; 로 SISMEMBER 가 &lt;em&gt;O(log N)&lt;/em&gt;. &lt;em&gt;최소 정수 비트 폭&lt;/em&gt; 으로 &lt;em&gt;극도로 압축&lt;/em&gt;. 1 만개 정수도 &lt;em&gt;수십 KB&lt;/em&gt; 면 충분.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;em&gt;순수 정수 ID 의 큰 멤버십&lt;/em&gt; 은 &lt;code&gt;intset&lt;/code&gt; 이 &lt;em&gt;비현실적으로 효율&lt;/em&gt;. 사용자 ID 같은 정수만 다룬다면 &lt;em&gt;Redis 가 자동&lt;/em&gt; 으로 가장 좋은 인코딩을 고른다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;hashtable-인코딩&quot;&gt;hashtable 인코딩&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;큰 set 또는 &lt;em&gt;문자열 멤버&lt;/em&gt; 면 &lt;em&gt;해시 테이블&lt;/em&gt; 로 전환. 같은 dict 구현체이지만 &lt;em&gt;value 가 없는 dict&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;핵심-명령&quot;&gt;핵심 명령&lt;/h2&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SREM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SISMEMBER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SMISMEMBER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # [1, 0, 1]  (Redis 6.2+)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SMEMBERS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&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;SCARD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&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;SRANDMEMBER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # 랜덤 3개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SPOP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                    # pop (제거하면서 반환)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SMOVE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; blocked:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 멤버 이동 (atomic)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SSCAN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MATCH&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;*&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&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;집합-연산&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SINTER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&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;SUNION&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&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;SDIFF&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 42 의 친구 중 99 의 친구가 아닌 사람&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;# STORE 변형: 결과를 키에 저장&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SINTERSTORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SUNIONSTORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SDIFFSTORE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&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;# SINTERCARD: 결과 크기만 (Redis 7+)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SINTERCARD&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&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[&quot;friends:42&amp;#x3C;br/&gt;{100, 200, 300, 400}&quot;] --&gt; Op&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;friends:99&amp;#x3C;br/&gt;{200, 300, 500, 600}&quot;] --&gt; Op&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Op{집합 연산} --&gt; SI[&quot;SINTER&amp;#x3C;br/&gt;{200, 300}&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Op --&gt; SU[&quot;SUNION&amp;#x3C;br/&gt;{100, 200, 300, 400, 500, 600}&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Op --&gt; SD[&quot;SDIFF (42 - 99)&amp;#x3C;br/&gt;{100, 400}&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
집합 연산은 &lt;em&gt;Cluster 에서 같은 슬롯&lt;/em&gt; 의 키여야 동작. &lt;em&gt;친구 / 팔로워&lt;/em&gt; 같이 &lt;em&gt;함께 다닐 키&lt;/em&gt; 는 &lt;code&gt;{user:42}:friends&lt;/code&gt; 처럼 &lt;em&gt;hash tag&lt;/em&gt; 로 같은 슬롯 보장.&lt;/p&gt;
&lt;/blockquote&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 게시물 1 의 태그&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; post:1:tags&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ruby&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; rails&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 태그 → 게시물 역인덱스&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tag:ruby:posts&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tag:rails:posts&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tag:redis:posts&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&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;# &quot;ruby 와 redis 가 둘 다 달린 게시물&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SINTER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tag:ruby:posts&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tag:redis:posts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;역인덱스 만들기 + atomic 검색&lt;/em&gt; 이 &lt;em&gt;한 줄&lt;/em&gt;. 검색엔진 없이 &lt;em&gt;Redis 만으로 작은 검색&lt;/em&gt; 가능.&lt;/p&gt;
&lt;/blockquote&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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 양방향 친구 = a 와 b 모두에 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MULTI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 99&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;EXEC&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:#B392F0&quot;&gt;SINTER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&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:#B392F0&quot;&gt;SUNION&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:99&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:100&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:101&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # 친구 99/100/101 의 모든 친구&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SDIFF&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; temp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; friends:42&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;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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42:roles&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; admin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; editor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reviewer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SISMEMBER&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42:roles&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; admin&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              # 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SMEMBERS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user:42:roles&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;# 모든 admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SADD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; role:admin:users&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 99&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SMEMBERS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; role:admin:users&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-중복-방지-deduplication&quot;&gt;4. 중복 방지 (deduplication)&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:#6A737D&quot;&gt;# 같은 ID 가 *처음* 들어왔는지 atomic 체크&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; first_time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r, event_id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;bool&lt;/span&gt;&lt;span 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; r.sadd(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seen:events&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, event_id) &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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;1 == 처음 (추가됨)&lt;/em&gt;, &lt;em&gt;0 == 이미 있음&lt;/em&gt;. 멱등성 보장의 &lt;em&gt;한 줄 패턴&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;5-분리-집합-disjoint-set&quot;&gt;5. 분리 집합 (Disjoint Set)&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;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Union-Find 의 &lt;em&gt;find / union&lt;/em&gt; 패턴. Redis Set 으로 &lt;em&gt;느슨한 disjoint&lt;/em&gt; 표현 가능 (정밀하게는 알고리즘 자료구조가 더 적합).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;성능-표&quot;&gt;성능 표&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;명령&lt;/th&gt;&lt;th&gt;복잡도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SADD&lt;/code&gt;, &lt;code&gt;SREM&lt;/code&gt;, &lt;code&gt;SISMEMBER&lt;/code&gt;, &lt;code&gt;SCARD&lt;/code&gt;, &lt;code&gt;SPOP&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SMISMEMBER (k)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(k)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SMEMBERS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SINTER (N1, N2, ..., Nk)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N * k) (최소 집합 기준)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SUNION (N1, N2, ..., Nk)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SDIFF (N1, N2, ..., Nk)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SRANDMEMBER (count)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;O(N) (큰 count 일 때)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SSCAN&lt;/code&gt;&lt;/td&gt;&lt;td&gt;amortized O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;SINTER 의 최적화&lt;/em&gt; = Redis 는 &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;메모리-효율-비교&quot;&gt;메모리 효율 비교&lt;/h2&gt;
&lt;p&gt;같은 1만 정수 멤버를 각 자료형에 저장:&lt;/p&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;blockquote&gt;
&lt;p&gt;intset 은 &lt;em&gt;수십 배 효율&lt;/em&gt;. &lt;em&gt;멤버가 정수 + 작은 set&lt;/em&gt; 이면 &lt;em&gt;자동&lt;/em&gt; 으로 intset 이 잡힌다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;set-vs-hyperloglog-결정&quot;&gt;Set vs HyperLogLog 결정&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{정확한 cardinality&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;    Q1 --&gt;|예| Q2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|아니오 0.81% 오차 OK| HLL[HyperLogLog&amp;#x3C;br/&gt;12KB 고정]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2{개별 멤버에&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;    Q2 --&gt;|&quot;예 (SISMEMBER 등)&quot;| Set[Set]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|아니오 cardinality 만| Q3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3{메모리 한계?}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|충분| Set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|타이트| HLL&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;code&gt;SMEMBERS&lt;/code&gt; 대용량&lt;/strong&gt; = O(N) single-thread 차단. &lt;em&gt;작은 set&lt;/em&gt; 에서만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SUNION/SINTER&lt;/code&gt; cross-slot&lt;/strong&gt; = Cluster 에서 &lt;em&gt;키들이 다른 슬롯&lt;/em&gt; 이면 에러. &lt;em&gt;hash tag&lt;/em&gt; 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SADD&lt;/code&gt; 의 매우 큰 batch&lt;/strong&gt; = 한 명령에 &lt;em&gt;수만 개 인자&lt;/em&gt; 면 &lt;em&gt;명령 자체&lt;/em&gt; 가 &lt;em&gt;single-thread 차단&lt;/em&gt;. &lt;em&gt;나눠서&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;intset → hashtable 전환&lt;/strong&gt; = 한 번 hashtable 가 되면 &lt;em&gt;되돌리지 않는다&lt;/em&gt;. 메모리 사용량이 &lt;em&gt;영구히 늘어남&lt;/em&gt;. &lt;em&gt;전환 임계&lt;/em&gt; 를 알고 운영.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;김신건의-현장-메모&quot;&gt;김신건의 현장 메모&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;태그 시스템&lt;/em&gt; 을 Set + 역인덱스로 만들면 &lt;em&gt;검색엔진 없이도&lt;/em&gt; &lt;em&gt;AND/OR/NOT&lt;/em&gt; 가 즉각 가능. 작은 / 중간 규모에서는 ElasticSearch 도입 &lt;em&gt;직전까지&lt;/em&gt; 충분.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;팔로워 / 팔로잉&lt;/em&gt; 같은 &lt;em&gt;대칭 관계&lt;/em&gt; 는 &lt;em&gt;MULTI/EXEC&lt;/em&gt; 로 묶어 &lt;em&gt;둘 다 정합 보장&lt;/em&gt;. &lt;em&gt;비대칭 (블록 같은)&lt;/em&gt; 은 한 쪽만.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;intset 의 자동 효율&lt;/em&gt; 이 &lt;em&gt;너무 좋아서&lt;/em&gt; 종종 &lt;em&gt;intset 이 잡혔는지 OBJECT ENCODING&lt;/em&gt; 으로 확인하는 습관이 들었다. &lt;em&gt;멤버가 비정수로 섞이면 자동 hashtable 로 영구 전환&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sidekiq unique jobs&lt;/em&gt; 같은 &lt;em&gt;멱등성 토큰&lt;/em&gt; 은 &lt;em&gt;Set 으로 simple 한 자기 멱등 보장&lt;/em&gt; 이 자연스럽다. &lt;code&gt;SADD&lt;/code&gt; 의 &lt;em&gt;0/1 반환&lt;/em&gt; 을 &lt;em&gt;조건&lt;/em&gt; 으로 직접 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Redis]] (자료구조 카탈로그)&lt;/li&gt;
&lt;li&gt;[[Redis Sorted Sets]] (순서가 필요할 때)&lt;/li&gt;
&lt;li&gt;[[Redis HyperLogLog Geo]] (대규모 cardinality)&lt;/li&gt;
&lt;li&gt;[[Redis Hashes]] (값이 필요할 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식: &lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/sets/&quot;&gt;Sets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;intset 소스: &lt;a href=&quot;https://github.com/redis/redis/blob/unstable/src/intset.c&quot;&gt;redis/redis/src/intset.c&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>redis</category><category>data-structure</category><category>set</category><category>membership</category><category>intset</category><category>hashtable</category><author>koa (김신건)</author></item></channel></rss>