<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aggregation on kastori</title><link>http://blog.kastori.dev/tags/aggregation/</link><description>Recent content in Aggregation on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Tue, 02 Jun 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/aggregation/index.xml" rel="self" type="application/rss+xml"/><item><title>[DB 완전 정복 #8] MongoDB는 왜 임베딩을 권장하나 — 문서 모델과 WiredTiger</title><link>http://blog.kastori.dev/tech/2026-06-02-db-08-mongodb-internals/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/tech/2026-06-02-db-08-mongodb-internals/</guid><description>&lt;h2 id="mongodb는-그냥-json-저장하는-db-아닌가요"&gt;&lt;a href="#mongodb%eb%8a%94-%ea%b7%b8%eb%83%a5-json-%ec%a0%80%ec%9e%a5%ed%95%98%eb%8a%94-db-%ec%95%84%eb%8b%8c%ea%b0%80%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;MongoDB는 그냥 JSON 저장하는 DB 아닌가요?&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;MongoDB를 처음 접하면 이런 인식이 생기기 쉽다. 실제로 저장 형태가 JSON처럼 보이기 때문이다. 하지만 MongoDB가 진짜로 해결하려는 문제는 &amp;ldquo;JSON 저장&amp;quot;이 아니라 &lt;strong&gt;관계형 모델이 맞지 않는 데이터 구조&lt;/strong&gt;다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="문서-모델--계층-구조를-그대로-저장한다"&gt;&lt;a href="#%eb%ac%b8%ec%84%9c-%eb%aa%a8%eb%8d%b8--%ea%b3%84%ec%b8%b5-%ea%b5%ac%ec%a1%b0%eb%a5%bc-%ea%b7%b8%eb%8c%80%eb%a1%9c-%ec%a0%80%ec%9e%a5%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;문서 모델 — 계층 구조를 그대로 저장한다
&lt;/h2&gt;&lt;p&gt;관계형 DB는 데이터를 정규화해서 여러 테이블에 나눠 저장한다. 조회 시 JOIN으로 합친다. MongoDB는 반대로, &lt;strong&gt;관련 데이터를 하나의 문서 안에 중첩해서&lt;/strong&gt; 저장한다.&lt;/p&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;관계형 DB:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; products (id, name, price)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; product_options (id, product_id, size, color, stock)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; product_images (id, product_id, url, order)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 조회 시 3개 테이블 JOIN
&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;MongoDB:
&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; _id: ObjectId(&amp;#34;...&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; name: &amp;#34;운동화&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; price: 89000,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; options: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; { size: &amp;#34;260&amp;#34;, color: &amp;#34;black&amp;#34;, stock: 10 },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; { size: &amp;#34;270&amp;#34;, color: &amp;#34;white&amp;#34;, stock: 5 }
&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; images: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; { url: &amp;#34;https://...&amp;#34;, order: 1 },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; { url: &amp;#34;https://...&amp;#34;, order: 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; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 단일 문서 조회 1번
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;JOIN 없이 한 번의 조회로 모든 데이터를 가져온다. 그리고 컬렉션 내 문서마다 구조가 달라도 된다 — 어떤 상품은 &lt;code&gt;options&lt;/code&gt; 필드가 없어도 괜찮다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="임베딩-vs-참조--mongodb-설계의-핵심-결정"&gt;&lt;a href="#%ec%9e%84%eb%b2%a0%eb%94%a9-vs-%ec%b0%b8%ec%a1%b0--mongodb-%ec%84%a4%ea%b3%84%ec%9d%98-%ed%95%b5%ec%8b%ac-%ea%b2%b0%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;임베딩 vs 참조 — MongoDB 설계의 핵심 결정
&lt;/h2&gt;&lt;p&gt;MongoDB 설계에서 가장 중요한 질문은 &amp;ldquo;관련 데이터를 한 문서 안에 넣을까(임베딩), 별도 컬렉션으로 분리할까(참조)?&amp;ldquo;다.&lt;/p&gt;
&lt;h3 id="임베딩--함께-조회되는-데이터"&gt;&lt;a href="#%ec%9e%84%eb%b2%a0%eb%94%a9--%ed%95%a8%ea%bb%98-%ec%a1%b0%ed%9a%8c%eb%90%98%eb%8a%94-%eb%8d%b0%ec%9d%b4%ed%84%b0" 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-javascript" data-lang="javascript"&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="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;order1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;orderNo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ORD-001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;홍길동&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;노트북&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;마우스&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;totalAmount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1560000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;항상 함께 조회하는 데이터&lt;/li&gt;
&lt;li&gt;1:1 또는 1:소수 관계&lt;/li&gt;
&lt;li&gt;임베딩된 데이터가 독립적으로 존재할 필요가 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;주문 생성 당시의 상품 정보를 함께 저장하는 것은 의미가 있다. 나중에 상품 가격이 바뀌어도 주문 당시 가격이 보존된다.&lt;/p&gt;
&lt;h3 id="참조--무제한-증가하거나-독립-조회가-필요한-경우"&gt;&lt;a href="#%ec%b0%b8%ec%a1%b0--%eb%ac%b4%ec%a0%9c%ed%95%9c-%ec%a6%9d%ea%b0%80%ed%95%98%ea%b1%b0%eb%82%98-%eb%8f%85%eb%a6%bd-%ec%a1%b0%ed%9a%8c%ea%b0%80-%ed%95%84%ec%9a%94%ed%95%9c-%ea%b2%bd%ec%9a%b0" 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-javascript" data-lang="javascript"&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="c1"&gt;// posts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;post1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;글 제목&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;...&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&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 class="c1"&gt;// comments
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;c1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;post1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;댓글1&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;c2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;post1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;댓글2&amp;#34;&lt;/span&gt; &lt;span class="p"&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;// ... 댓글이 수천 개가 되어도 posts 문서 크기는 그대로
&lt;/span&gt;&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;배열이 무제한 증가할 수 있는 경우 (댓글, 로그)&lt;/li&gt;
&lt;li&gt;양쪽 데이터를 독립적으로 수정하는 경우&lt;/li&gt;
&lt;li&gt;N:M 관계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;문서 크기 한도는 16MB다. 배열에 데이터를 계속 추가하면 결국 이 한도에 부딪힌다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="wiredtiger--mongodb의-스토리지-엔진"&gt;&lt;a href="#wiredtiger--mongodb%ec%9d%98-%ec%8a%a4%ed%86%a0%eb%a6%ac%ec%a7%80-%ec%97%94%ec%a7%84" class="header-anchor"&gt;&lt;/a&gt;WiredTiger — MongoDB의 스토리지 엔진
&lt;/h2&gt;&lt;p&gt;MongoDB 3.2부터 기본 스토리지 엔진이다.&lt;/p&gt;
&lt;h3 id="문서-레벨-락"&gt;&lt;a href="#%eb%ac%b8%ec%84%9c-%eb%a0%88%eb%b2%a8-%eb%9d%bd" class="header-anchor"&gt;&lt;/a&gt;문서 레벨 락
&lt;/h3&gt;&lt;p&gt;구 엔진(MMAPv1)은 컬렉션 전체에 락을 걸었다. 같은 컬렉션 안의 다른 문서도 동시 쓰기가 불가능했다.&lt;/p&gt;
&lt;p&gt;WiredTiger는 &lt;strong&gt;문서 레벨 락&lt;/strong&gt;을 지원한다. 같은 컬렉션의 다른 문서에 동시에 쓸 수 있다. MVCC도 지원해서 읽기와 쓰기가 서로를 방해하지 않는다.&lt;/p&gt;
&lt;h3 id="압축"&gt;&lt;a href="#%ec%95%95%ec%b6%95" class="header-anchor"&gt;&lt;/a&gt;압축
&lt;/h3&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;기본 압축: snappy (빠름, 적당한 압축률)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;높은 압축: zlib 또는 zstd (더 강한 압축, CPU 비용 있음)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ 실제로 디스크 사용량 50~80% 감소
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="wal-journal"&gt;&lt;a href="#wal-journal" class="header-anchor"&gt;&lt;/a&gt;WAL (Journal)
&lt;/h3&gt;&lt;p&gt;쓰기 전에 Journal(WAL)에 먼저 기록한다. 서버가 갑자기 꺼져도 Journal로 복구할 수 있다. 기본적으로 100ms마다 또는 128KB마다 flush된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="집계-파이프라인--sql의-group-by를-대체하는-방법"&gt;&lt;a href="#%ec%a7%91%ea%b3%84-%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b8--sql%ec%9d%98-group-by%eb%a5%bc-%eb%8c%80%ec%b2%b4%ed%95%98%eb%8a%94-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;집계 파이프라인 — SQL의 GROUP BY를 대체하는 방법
&lt;/h2&gt;&lt;p&gt;MongoDB에서 집계 쿼리는 &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-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SQL: SELECT status, COUNT(*), SUM(amount)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// FROM orders WHERE created_at &amp;gt;= &amp;#39;2026-01-01&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// GROUP BY status HAVING COUNT(*) &amp;gt; 10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ORDER BY SUM(amount) DESC
&lt;/span&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 class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$match&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$gte&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2026-01-01&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;$status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$sum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;totalAmount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$sum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;$amount&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$match&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$gt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$sort&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;totalAmount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&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;$match&lt;/code&gt;를 파이프라인 앞에 배치해서 처리 문서 수를 먼저 줄여야 한다. 뒤에 두면 전체 문서를 집계한 다음 필터링하는 낭비가 생긴다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="트랜잭션"&gt;&lt;a href="#%ed%8a%b8%eb%9e%9c%ec%9e%ad%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;트랜잭션
&lt;/h2&gt;&lt;p&gt;MongoDB는 &lt;strong&gt;단일 문서 내 업데이트는 항상 원자적&lt;/strong&gt;이다. 여러 필드를 한 번에 수정해도 일부만 반영되는 일이 없다.&lt;/p&gt;
&lt;p&gt;멀티 문서 트랜잭션은 4.0부터 지원되지만, 관계형 DB 트랜잭션보다 비용이 높다. MongoDB 설계 철학은 &lt;strong&gt;트랜잭션이 필요 없도록 임베딩으로 설계&lt;/strong&gt;하는 것이다. 트랜잭션은 임베딩으로 해결이 안 되는 경우에만 쓴다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="언제-mongodb를-선택하나"&gt;&lt;a href="#%ec%96%b8%ec%a0%9c-mongodb%eb%a5%bc-%ec%84%a0%ed%83%9d%ed%95%98%eb%82%98" class="header-anchor"&gt;&lt;/a&gt;언제 MongoDB를 선택하나
&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;✅ 이런 경우 MongoDB
&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; - 지리 데이터 (2dsphere 인덱스 내장)
&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;❌ 이런 경우 MongoDB 비적합
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 복잡한 JOIN이 많은 경우 ($lookup은 있지만 SQL 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;/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;MongoDB의 강점은 &amp;ldquo;스키마 없이 저장한다&amp;quot;가 아니라 &lt;strong&gt;계층 구조 데이터를 JOIN 없이 한 번에 읽는다&lt;/strong&gt;는 것이다. 임베딩 vs 참조를 올바르게 선택하는 것이 MongoDB 성능의 핵심이다.&lt;/p&gt;
&lt;p&gt;마지막 편에서는 이번 시리즈를 정리하며 각 DB를 어떤 기준으로 선택하는지 종합한다.&lt;/p&gt;</description></item></channel></rss>