<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Postgresql on kastori</title><link>http://blog.kastori.dev/tags/postgresql/</link><description>Recent content in Postgresql on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Wed, 03 Jun 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/postgresql/index.xml" rel="self" type="application/rss+xml"/><item><title>[DB 완전 정복 #9] 어떤 DB를 왜 선택하는가 — MySQL, PostgreSQL, Redis, MongoDB 선택 기준</title><link>http://blog.kastori.dev/tech/2026-06-03-db-09-db-selection/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/tech/2026-06-03-db-09-db-selection/</guid><description>&lt;h2 id="왜-이-db를-쓰나요"&gt;&lt;a href="#%ec%99%9c-%ec%9d%b4-db%eb%a5%bc-%ec%93%b0%eb%82%98%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;왜 이 DB를 쓰나요?&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;기술 면접에서 DB 관련 질문은 보통 여기서 끝난다. &amp;ldquo;MySQL 쓰셨다고 하셨는데, 왜 MySQL을 선택하셨나요?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;익숙해서요&amp;quot;는 답이 아니다. &amp;ldquo;팀에서 써왔어서요&amp;quot;도 부족하다. 트레이드오프를 설명할 수 있어야 한다. 이 시리즈 마지막 편에서는 각 DB를 선택하는 근거를 정리한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sql-vs-nosql--먼저-이-구분부터"&gt;&lt;a href="#sql-vs-nosql--%eb%a8%bc%ec%a0%80-%ec%9d%b4-%ea%b5%ac%eb%b6%84%eb%b6%80%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;SQL vs NoSQL — 먼저 이 구분부터
&lt;/h2&gt;&lt;p&gt;NoSQL이 SQL보다 무조건 빠른 게 아니다. &lt;strong&gt;다른 문제를 풀기 위해 만들어진&lt;/strong&gt; 도구다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SQL을 선택할 때:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 데이터 간 관계가 명확하고 JOIN이 자주 필요한 경우
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 트랜잭션(ACID) 보장이 필수인 경우 (결제, 계좌)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 스키마가 안정적이고 자주 바뀌지 않는 경우
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 복잡한 집계·분석 쿼리가 필요한 경우
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NoSQL을 선택할 때:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 스키마가 유동적이거나 비정형 데이터 (로그, 이벤트)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 수평 확장(Sharding)이 필수인 대규모 트래픽
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 단순 키-값 조회 또는 단일 문서 읽기/쓰기 위주
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 최종 일관성(Eventual Consistency)으로 충분한 경우
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="mysql--웹-서비스의-검증된-선택"&gt;&lt;a href="#mysql--%ec%9b%b9-%ec%84%9c%eb%b9%84%ec%8a%a4%ec%9d%98-%ea%b2%80%ec%a6%9d%eb%90%9c-%ec%84%a0%ed%83%9d" class="header-anchor"&gt;&lt;/a&gt;MySQL — 웹 서비스의 검증된 선택
&lt;/h2&gt;&lt;p&gt;MySQL을 선택하는 진짜 이유:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;기술적 근거&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;InnoDB의 MVCC와 행 레벨 락으로 높은 동시성&lt;/li&gt;
&lt;li&gt;Clustered Index로 기본키 조회 최고 성능&lt;/li&gt;
&lt;li&gt;갭 락으로 Phantom Read 방지 (REPEATABLE READ 기본)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;실무적 근거&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Boot + JPA 조합의 레퍼런스 압도적&lt;/li&gt;
&lt;li&gt;AWS RDS, Aurora MySQL 등 클라우드 지원 완벽&lt;/li&gt;
&lt;li&gt;DBA, 운영 인력 시장에서 경험자가 가장 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;언제 MySQL이 부족한가: JSON 데이터를 쿼리하고 인덱스도 필요할 때, 지리 데이터를 다룰 때, AI 벡터 검색이 필요할 때.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="postgresql--기능과-확장성이-필요할-때"&gt;&lt;a href="#postgresql--%ea%b8%b0%eb%8a%a5%ea%b3%bc-%ed%99%95%ec%9e%a5%ec%84%b1%ec%9d%b4-%ed%95%84%ec%9a%94%ed%95%a0-%eb%95%8c" class="header-anchor"&gt;&lt;/a&gt;PostgreSQL — 기능과 확장성이 필요할 때
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;JSONB가 필요하다면 PostgreSQL&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 상품의 동적 속성을 JSON으로 저장하고 쿼리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;#34;color&amp;#34;: &amp;#34;red&amp;#34;, &amp;#34;size&amp;#34;: &amp;#34;L&amp;#34;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MySQL에서 같은 쿼리를 하려면 Generated Column + 인덱스 트릭이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI/ML 연동이 필요하다면 PostgreSQL&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- pgvector: 텍스트 임베딩을 저장하고 유사도 검색
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[0.1, 0.2, ...]&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;LLM 기반 서비스를 만든다면 pgvector를 지원하는 PostgreSQL이 사실상 표준이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지리 데이터라면 PostGIS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위경도 거리 계산, 반경 내 검색, 지도 위에서의 공간 쿼리가 필요하면 PostGIS 확장이 있는 PostgreSQL이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="redis--인메모리의-다양한-역할"&gt;&lt;a href="#redis--%ec%9d%b8%eb%a9%94%eb%aa%a8%eb%a6%ac%ec%9d%98-%eb%8b%a4%ec%96%91%ed%95%9c-%ec%97%ad%ed%95%a0" class="header-anchor"&gt;&lt;/a&gt;Redis — 인메모리의 다양한 역할
&lt;/h2&gt;&lt;p&gt;Redis를 캐시로만 쓰면 절반만 쓰는 것이다.&lt;/p&gt;
&lt;h3 id="캐시"&gt;&lt;a href="#%ec%ba%90%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;캐시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Cacheable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;product&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;#id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;productRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;DB 조회가 많고 데이터 변경이 적은 경우 Redis 캐시로 DB 부하를 줄인다. TTL을 설정해 무효화를 자동화한다.&lt;/p&gt;
&lt;h3 id="세션-저장소"&gt;&lt;a href="#%ec%84%b8%ec%85%98-%ec%a0%80%ec%9e%a5%ec%86%8c" class="header-anchor"&gt;&lt;/a&gt;세션 저장소
&lt;/h3&gt;&lt;p&gt;다중 서버 환경에서 세션을 공유할 때. 서버가 재시작되거나 로드밸런서가 다른 서버로 요청을 보내도 세션이 유지된다.&lt;/p&gt;
&lt;h3 id="분산-락"&gt;&lt;a href="#%eb%b6%84%ec%82%b0-%eb%9d%bd" class="header-anchor"&gt;&lt;/a&gt;분산 락
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 다중 서버 환경에서 재고 차감을 안전하게&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;redissonClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;lock:product:&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;단일 서버에서는 &lt;code&gt;synchronized&lt;/code&gt;로 충분하지만, 서버가 2대 이상이면 Redis 분산 락이 필요하다.&lt;/p&gt;
&lt;h3 id="실시간-랭킹"&gt;&lt;a href="#%ec%8b%a4%ec%8b%9c%ea%b0%84-%eb%9e%ad%ed%82%b9" class="header-anchor"&gt;&lt;/a&gt;실시간 랭킹
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ZADD ranking 2300 &amp;#34;user:2&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ZREVRANGE ranking 0 9 // 상위 10명 — O(log N)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sorted Set의 O(log N) 연산으로 수백만 명 랭킹도 실시간 처리 가능하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mongodb--스키마가-유연해야-할-때"&gt;&lt;a href="#mongodb--%ec%8a%a4%ed%82%a4%eb%a7%88%ea%b0%80-%ec%9c%a0%ec%97%b0%ed%95%b4%ec%95%bc-%ed%95%a0-%eb%95%8c" class="header-anchor"&gt;&lt;/a&gt;MongoDB — 스키마가 유연해야 할 때
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;임베딩이 JOIN을 대체한다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;관계형 DB에서 3개 테이블을 JOIN해야 하는 상품 정보를, MongoDB에서는 단일 문서 조회 1번으로 처리한다. 계층 구조 데이터에서 읽기 성능이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;초기 개발의 스키마 유연성&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;서비스 초기에 데이터 구조가 자주 바뀐다. 관계형 DB에서 컬럼 추가/삭제는 마이그레이션이 필요하다. MongoDB는 문서 구조가 자유로워 마이그레이션 없이 바꿀 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;단, 복잡한 관계와 트랜잭션이 많은 도메인은 MySQL이 맞다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="실무-조합-패턴"&gt;&lt;a href="#%ec%8b%a4%eb%ac%b4-%ec%a1%b0%ed%95%a9-%ed%8c%a8%ed%84%b4" class="header-anchor"&gt;&lt;/a&gt;실무 조합 패턴
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;일반 웹 서비스:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MySQL ← 메인 데이터
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Redis ← 캐시, 세션, 분산 락
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JSON 데이터가 많은 서비스:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL (JSONB) ← 메인 데이터
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Redis ← 캐시, 세션
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;로그·이벤트 처리:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MySQL / PostgreSQL ← 핵심 비즈니스 데이터
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MongoDB ← 로그, 이벤트, 비정형 데이터
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Redis ← 실시간 집계, 캐시
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="cap-이론--왜-nosql은-최종-일관성인가"&gt;&lt;a href="#cap-%ec%9d%b4%eb%a1%a0--%ec%99%9c-nosql%ec%9d%80-%ec%b5%9c%ec%a2%85-%ec%9d%bc%ea%b4%80%ec%84%b1%ec%9d%b8%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;CAP 이론 — 왜 NoSQL은 최종 일관성인가
&lt;/h2&gt;&lt;p&gt;분산 데이터베이스는 세 가지를 동시에 보장할 수 없다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C (Consistency) — 모든 노드가 같은 데이터를 봄
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A (Availability) — 모든 요청이 응답을 받음
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P (Partition Tolerance) — 네트워크 분리 상황에서도 동작
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;네트워크 분리(P)는 분산 시스템에서 항상 발생 가능한 사건이다. P를 포기할 수 없으니, 실제로는 C와 A 중 하나를 선택한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MySQL → CP (일관성 우선, 가용성 희생 가능)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Redis Cluster → AP (가용성 우선, 최종 일관성)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cassandra → AP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MongoDB → 기본 CP, 설정에 따라 AP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Redis가 캐시에 어울리는 이유도 여기 있다. 캐시는 약간 오래된 데이터를 써도 괜찮다(AP). 반면 결제는 항상 최신 데이터가 필요하다(CP, MySQL).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="면접-답변-정리"&gt;&lt;a href="#%eb%a9%b4%ec%a0%91-%eb%8b%b5%eb%b3%80-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;면접 답변 정리
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;&amp;ldquo;어떤 DB를 선택하시겠어요?&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;서비스 특성에 따라 다릅니다. 관계형 데이터와 ACID 트랜잭션이 중요하면 MySQL을, JSON 데이터 쿼리나 지리 기능이 필요하면 PostgreSQL을 선택합니다. 캐시와 세션은 Redis를, 스키마가 유동적이고 계층 구조 데이터가 많으면 MongoDB를 고려합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;MySQL을 쓰는 이유가 뭔가요?&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;Spring + JPA 조합에서 레퍼런스가 가장 많고, InnoDB의 MVCC와 행 레벨 락으로 높은 동시성을 지원하며, AWS RDS와 Aurora MySQL을 통한 운영 편의성도 고려했습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="시리즈를-마치며"&gt;&lt;a href="#%ec%8b%9c%eb%a6%ac%ec%a6%88%eb%a5%bc-%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;시리즈를 마치며
&lt;/h2&gt;&lt;p&gt;DB는 단순히 데이터를 저장하는 도구가 아니다. 각 DB는 특정 문제를 풀기 위해 설계됐고, 그 설계 철학이 성능과 트레이드오프를 결정한다.&lt;/p&gt;
&lt;p&gt;인덱스부터 시작해서 트랜잭션, 쿼리 최적화, 정규화, 커넥션 풀, MySQL vs PostgreSQL, Redis, MongoDB, 그리고 DB 선택 기준까지. 이 시리즈에서 다룬 내용이 면접과 실무에서 근거 있는 선택을 하는 데 도움이 되길 바란다.&lt;/p&gt;</description></item><item><title>[DB 완전 정복 #6] MySQL과 PostgreSQL, 뭐가 다른가 — MVCC 구현부터 JSONB까지</title><link>http://blog.kastori.dev/tech/2026-05-31-db-06-mysql-vs-postgresql/</link><pubDate>Sun, 31 May 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/tech/2026-05-31-db-06-mysql-vs-postgresql/</guid><description>&lt;h2 id="그냥-mysql-쓰면-안-되나요"&gt;&lt;a href="#%ea%b7%b8%eb%83%a5-mysql-%ec%93%b0%eb%a9%b4-%ec%95%88-%eb%90%98%eb%82%98%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;그냥 MySQL 쓰면 안 되나요?&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;사실 대부분의 경우 MySQL로 충분하다. 하지만 &amp;ldquo;왜 MySQL인가요?&amp;ldquo;라는 질문에 &amp;ldquo;익숙해서요&amp;quot;는 좋은 답이 아니다. 두 DB의 내부 구조 차이를 알면, 상황에 따른 선택 근거를 만들 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mvcc-구현-방식--가장-근본적인-차이"&gt;&lt;a href="#mvcc-%ea%b5%ac%ed%98%84-%eb%b0%a9%ec%8b%9d--%ea%b0%80%ec%9e%a5-%ea%b7%bc%eb%b3%b8%ec%a0%81%ec%9d%b8-%ec%b0%a8%ec%9d%b4" class="header-anchor"&gt;&lt;/a&gt;MVCC 구현 방식 — 가장 근본적인 차이
&lt;/h2&gt;&lt;p&gt;앞서 MVCC가 트랜잭션의 일관된 읽기를 보장한다고 배웠다. 그런데 MySQL과 PostgreSQL은 이 MVCC를 다른 방식으로 구현한다.&lt;/p&gt;
&lt;h3 id="mysql-innodb-undo-log-방식"&gt;&lt;a href="#mysql-innodb-undo-log-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;MySQL InnoDB: Undo Log 방식
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[테이블 페이지]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 행 데이터: TX_ID=200, name=&amp;#34;수정됨&amp;#34; ← 항상 최신 버전
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──(포인터)──▶ [Undo Log]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; TX_ID=100, name=&amp;#34;원본&amp;#34; ← 이전 버전
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──▶ [Undo Log]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; TX_ID=50, name=&amp;#34;초기값&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;현재 데이터 페이지에는 &lt;strong&gt;최신 버전만&lt;/strong&gt; 저장한다. 이전 버전은 별도 Undo Log 공간에 체인으로 보관한다. 읽기 시 자신의 스냅샷보다 최신 데이터면 Undo Log에서 이전 버전을 찾아 읽는다.&lt;/p&gt;
&lt;h3 id="postgresql-heap-방식"&gt;&lt;a href="#postgresql-heap-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;PostgreSQL: Heap 방식
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[테이블 파일 (Heap)]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; t_xmin=100, t_xmax=200, name=&amp;#34;원본&amp;#34; ← 이전 버전 (dead tuple)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; t_xmin=200, t_xmax=0, name=&amp;#34;수정됨&amp;#34; ← 현재 버전 (live tuple)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;같은 테이블 파일 안에 &lt;strong&gt;여러 버전이 공존&lt;/strong&gt;한다. 각 행에 생성 트랜잭션(&lt;code&gt;t_xmin&lt;/code&gt;)과 삭제 트랜잭션(&lt;code&gt;t_xmax&lt;/code&gt;)을 기록한다. 읽기 시 내 트랜잭션 기준으로 어떤 버전을 볼지 visibility check로 판단한다.&lt;/p&gt;
&lt;h3 id="운영상-차이"&gt;&lt;a href="#%ec%9a%b4%ec%98%81%ec%83%81-%ec%b0%a8%ec%9d%b4" class="header-anchor"&gt;&lt;/a&gt;운영상 차이
&lt;/h3&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;별도 Undo Log&lt;/td&gt;
 &lt;td&gt;같은 테이블 파일&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;정리 방법&lt;/td&gt;
 &lt;td&gt;Purge 스레드 자동&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;VACUUM&lt;/strong&gt; 필수&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;운영 주의점&lt;/td&gt;
 &lt;td&gt;긴 트랜잭션 (Undo 과다)&lt;/td&gt;
 &lt;td&gt;table bloat, autovacuum 튜닝&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PostgreSQL은 오래된 dead tuple이 쌓이면 &lt;strong&gt;table bloat&lt;/strong&gt; 현상이 발생한다. autovacuum이 자동으로 정리하지만, 대용량 테이블에서는 VACUUM 튜닝이 운영의 핵심이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="인덱스-구조--clustered-vs-heap"&gt;&lt;a href="#%ec%9d%b8%eb%8d%b1%ec%8a%a4-%ea%b5%ac%ec%a1%b0--clustered-vs-heap" class="header-anchor"&gt;&lt;/a&gt;인덱스 구조 — Clustered vs Heap
&lt;/h2&gt;&lt;p&gt;MySQL의 기본키 인덱스는 &lt;strong&gt;Clustered Index&lt;/strong&gt;다. 리프 노드에 실제 행 데이터가 저장되고, 기본키 순서로 물리적으로 정렬된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MySQL Secondary Index 조회:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 인덱스에서 PK 찾기 → PK로 Clustered Index 조회 (2단계)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 모든 인덱스 조회:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 인덱스에서 heap tuple pointer 찾기 → heap에서 행 조회 (1단계)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MySQL의 Secondary Index는 2단계를 거치지만, 기본키 조회는 Clustered Index 덕분에 최고 성능이다. PostgreSQL은 모든 인덱스가 동등한 구조를 가진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="jsonb--postgresql이-mysql을-압도하는-부분"&gt;&lt;a href="#jsonb--postgresql%ec%9d%b4-mysql%ec%9d%84-%ec%95%95%eb%8f%84%ed%95%98%eb%8a%94-%eb%b6%80%eb%b6%84" class="header-anchor"&gt;&lt;/a&gt;JSONB — PostgreSQL이 MySQL을 압도하는 부분
&lt;/h2&gt;&lt;p&gt;반정형 데이터(JSON)를 DB에 저장해야 한다면, PostgreSQL의 JSONB가 압도적으로 유리하다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- MySQL JSON: 텍스트로 저장, 조회마다 파싱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COLUMN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 인덱스를 걸려면 Generated Column 트릭 필요
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COLUMN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta_color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GENERATED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ALWAYS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSON_UNQUOTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSON_EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$.color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta_color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- PostgreSQL JSONB: 바이너리 파싱 후 저장, GIN 인덱스 직접 가능
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COLUMN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 풍부한 연산자
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;#34;color&amp;#34;: &amp;#34;red&amp;#34;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 포함 여부
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 키 존재 여부
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PostgreSQL JSONB는 바이너리로 저장해 파싱 없이 빠르고, GIN 인덱스로 JSON 내부 키에 직접 인덱싱할 수 있다. MySQL은 이 수준의 JSON 쿼리 지원이 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="postgresql의-확장-생태계"&gt;&lt;a href="#postgresql%ec%9d%98-%ed%99%95%ec%9e%a5-%ec%83%9d%ed%83%9c%ea%b3%84" class="header-anchor"&gt;&lt;/a&gt;PostgreSQL의 확장 생태계
&lt;/h2&gt;&lt;p&gt;PostgreSQL은 확장(Extension)으로 기능을 추가할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostGIS → 지리 데이터, 위경도 거리 계산, 공간 인덱스
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pgvector → AI 벡터 임베딩 저장 및 유사도 검색 (LLM 연동)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_trgm → 문자열 유사도 검색 (LIKE &amp;#39;%검색어%&amp;#39; 인덱스)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TimescaleDB → 시계열 데이터 최적화
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;LLM, AI 서비스와 연동하거나 지리 기능이 필요하다면 PostgreSQL이 사실상 유일한 선택이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="라이선스-차이"&gt;&lt;a href="#%eb%9d%bc%ec%9d%b4%ec%84%a0%ec%8a%a4-%ec%b0%a8%ec%9d%b4" class="header-anchor"&gt;&lt;/a&gt;라이선스 차이
&lt;/h2&gt;&lt;p&gt;MySQL은 GPL 라이선스다. 오픈소스 프로젝트에서 MySQL을 수정해 배포하면 소스를 공개해야 한다. MariaDB 포크가 생긴 배경이기도 하다.&lt;/p&gt;
&lt;p&gt;PostgreSQL은 BSD 계열(PostgreSQL License)로 &lt;strong&gt;상업용 포함 완전 자유&lt;/strong&gt;다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="선택-기준-정리"&gt;&lt;a href="#%ec%84%a0%ed%83%9d-%ea%b8%b0%ec%a4%80-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;선택 기준 정리
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MySQL을 선택하는 경우:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - Spring Boot + JPA 조합 (레퍼런스와 생태계 압도적)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 단순 CRUD 위주의 일반 웹 서비스
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 팀에 MySQL 운영 경험이 있는 경우
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - AWS Aurora MySQL 사용 계획
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL을 선택하는 경우:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - JSON 데이터를 쿼리하고 인덱스도 필요한 경우 (JSONB)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 지리 데이터 (PostGIS)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - AI/ML 벡터 검색 (pgvector)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 복잡한 분석 쿼리 위주 서비스
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 오픈소스 라이선스 자유도가 중요한 경우
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="마치며"&gt;&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;마치며
&lt;/h2&gt;&lt;p&gt;MySQL은 웹 서비스의 검증된 선택이고, PostgreSQL은 확장성과 기능 다양성에서 강하다. 어느 쪽이 낫다는 게 아니라, &lt;strong&gt;사용하는 이유를 설명할 수 있느냐&lt;/strong&gt;가 중요하다.&lt;/p&gt;
&lt;p&gt;다음 편에서는 관계형 DB와 전혀 다른 구조로 설계된 Redis의 내부 동작 원리를 다룬다.&lt;/p&gt;</description></item></channel></rss>