본문으로 건너뛰기
김신건의 로그

[DB] MongoDB: 문서 DB, WiredTiger, replica set, aggregation

· 수정 · 📖 약 2분 · 675자/단어 #mongodb #nosql #document #database
MongoDB, BSON, WiredTiger, replica set, sharding, aggregation pipeline, Atlas, oplog, change streams

정의

MongoDBBSON (binary JSON) 문서 기반 NoSQL DB. schema-less 라기보다 flexible schema. 2026 시점 Atlas (managed) 가 대다수.

핵심 개념

RDBMongoDB
DatabaseDatabase
TableCollection
RowDocument (BSON)
ColumnField
IndexIndex (B-tree)
JOIN$lookup (aggregation)
Foreign key없음 (수동 관리)

BSON 형식

Binary JSON = JSON 의 바이너리 직렬화. JSON 보다 빠른 파싱, 타입 정보 내장.

flowchart LR
    BSON["BSON Document"] --> Types
    subgraph Types
        ObjId["ObjectId (12B)<br/>timestamp(4) + random(5) + counter(3)"]
        BoolT["Boolean (1B)"]
        Int32["Int32 (4B)"]
        Int64["Int64 (8B)"]
        Double["Double (8B)"]
        Str["String (UTF-8 + len)"]
        Date["Date (int64 ms)"]
        BinData["BinData (bytes)"]
        Array["Array"]
        EmbDoc["Embedded Document"]
    end
BSON 타입JSON 대응특징
ObjectIdstring12 bytes. 시간 포함 → 정렬 가능
Datestringint64 (ms). 타입 보존
Int32 / Int64number정수 타입 구분
Decimal128number고정소수점 (금융)
BinDatastring (base64)바이너리 직접 저장
// ObjectId 구조
const id = new ObjectId("64a7b3c1d2e3f4a5b6c7d8e9");
id.getTimestamp();  // Date: 2023-07-06T...

문서 모델

{
  "_id": "ObjectId(64a7...)",
  "name": "koa",
  "email": "koa@x.com",
  "addresses": [
    { "type": "home", "city": "Seoul" },
    { "type": "work", "city": "SF" }
  ],
  "tags": ["admin", "pro"],
  "createdAt": "ISODate(2026-06-25T00:00:00Z)"
}

nested + 배열 자유로움. non-join read 가 빠름.

WiredTiger 스토리지 엔진

flowchart TB
    Op["Read / Write"] --> WT["WiredTiger"]
    WT --> Cache["Internal Cache<br/>(기본 ~50% RAM, B-tree pages)"]
    WT --> Journal["Journal (WAL)<br/>128MB 단위 rotate"]
    WT --> Snappy["Block Compressor<br/>(Snappy / Zstd / None)"]
    WT --> DataFiles[("B-tree 데이터 파일")]
    Cache -.->|"eviction"| DataFiles
    Journal -.->|"checkpoint"| DataFiles

내부 체크포인트

sequenceDiagram
    participant App
    participant Cache as WT Cache
    participant Journal as Journal (WAL)
    participant Disk

    App->>Cache: Write
    Cache->>Journal: Journal record (WAL)
    Journal-->>App: COMMIT OK
    Note over Cache,Disk: 60초 또는 2GB 마다 checkpoint
    Cache->>Disk: Dirty pages flush (checkpoint)
    Journal->>Journal: 이전 journal segment 제거
  • Document-level locking (이전 MongoDB < 3.2 의 글로벌 lock 에서 진화).
  • Snappy (기본) / Zstd 압축으로 디스크 사용 50-70% 절감.
  • wiredTigerCacheSizeGB = 기본 min(50% RAM - 1GB, 256MB) 이상.

Replica Set

flowchart LR
    P[("Primary")] -->|oplog| S1[("Secondary 1")]
    P -->|oplog| S2[("Secondary 2")]
    Client -->|write| P
    Client -->|"read (옵션)"| S1
    Client -->|"read (옵션)"| S2
    P & S1 & S2 -->|heartbeat| Election["Election<br/>(primary 장애 시)"]
  • 1 primary + N secondary.
  • oplog (operations log) = capped collection. 변경 연산 순서 보존.
  • 자동 failover: primary 응답 없으면 election → 새 primary.
  • 읽기 분산: readPreference: secondaryPreferred.
// Read preference 예시
db.orders.find({ status: "paid" }).readPref("secondaryPreferred")

// Write concern
db.orders.insertOne(doc, { writeConcern: { w: "majority", j: true } })

Read / Write Concern

Write Concern의미
w: 1primary 기록 후 OK (기본)
w: "majority"과반 secondary 복제 후 OK
j: truejournal (WAL) fsync 후 OK
Read Concern의미
localprimary 의 최신 (기본, 복제 무관)
majority과반이 commit 한 데이터만
linearizable가장 강력, 느림
snapshot트랜잭션 내 일관 뷰

TIP

금융/재고 등 강한 일관성 필요 → w: majority + j: true + readConcern: majority. 느리지만 데이터 손실 없음.

Sharding

flowchart TB
    Client --> Mongos["mongos<br/>(라우터)"]
    Mongos --> Config[("Config Servers<br/>(3-node replica set)")]
    Mongos --> Shard1[("Shard 1")]
    Mongos --> Shard2[("Shard 2")]
    Mongos --> Shard3[("Shard 3")]
    Config -->|chunk map| Mongos
  • shard key 로 분산 (ranged / hashed / zone sharding).
  • chunk 단위 (기본 128MB) 자동 balancing.
  • mongos 는 stateless router. Config server 에서 라우팅 정보 조회.

Aggregation Pipeline

db.orders.aggregate([
  { $match: { status: "paid" } },
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
  }},
  { $unwind: "$user" },
  { $group: { _id: "$user.country", total: { $sum: "$amount" } } },
  { $sort: { total: -1 } },
  { $limit: 10 },
]);
Stage의미
$matchWHERE
$projectSELECT
$groupGROUP BY
$sortORDER BY
$limit / $skipLIMIT / OFFSET
$lookupLEFT JOIN
$unwind배열 분해
$facet다중 sub-pipeline
$bucket범위 그룹화
$setWindowFields윈도우 함수 (5.0+)

TIP

aggregation 순서 최적화: $match$project최대한 일찍. 인덱스 활용 + 처리량 감소.

Index

종류의미
Single field단일
Compound다중
Multikey배열 필드
Textfull-text
2dsphere공간
Hashedsharding 친화
Wildcard임의 필드
TTL자동 만료
db.orders.createIndex({ userId: 1, createdAt: -1 });
db.events.createIndex({ createdAt: 1 }, { expireAfterSeconds: 86400 });

// Explain 로 인덱스 활용 확인
db.orders.find({ userId: "abc" }).explain("executionStats");
// COLLSCAN → IXSCAN 확인

Change Streams

실시간 데이터 변경 스트림. CDC (Change Data Capture) 패턴.

// 컬렉션 변경 감지
const changeStream = db.orders.watch([
  { $match: { "operationType": { $in: ["insert", "update"] } } }
]);

changeStream.on("change", (change) => {
  console.log(change.fullDocument);
  console.log(change.updateDescription.updatedFields);
});
필드의미
operationTypeinsert / update / delete / replace
fullDocument변경 후 전체 문서
updateDescription변경된 필드 목록
resumeToken스트림 재개 위치

Change Streams 는 oplog 기반. Replica Set / Sharded Cluster 에서만 동작.

Transactions (4.0+, 4.2+ sharded)

const session = db.startSession();
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
});
try {
  await coll1.updateOne({ _id: 1 }, { $inc: { x: 1 } }, { session });
  await coll2.insertOne({ ref: 1 }, { session });
  await session.commitTransaction();
} catch (e) {
  await session.abortTransaction();
}

CAUTION

과거 MongoDB = 트랜잭션 없음 이미지가 강했으나, 4.0+ 부터 multi-document ACID. Sharded 트랜잭션은 비용 큼, document 안에 묶을 수 있으면 그게 정통.

쿼리 최적화

flowchart LR
    Query["쿼리"] --> QP["Query Planner"]
    QP --> Plans["후보 실행 계획들"]
    Plans --> Winner["승자 계획 (캐시)"]
    Winner --> Exec["실행"]
    Exec --> Stats["통계 갱신"]
    Stats -.->|"evict 조건"| Plans
// 실행 계획 확인
db.orders.find({ status: "paid", userId: "abc" }).explain("allPlansExecution")

// 인덱스 강제 사용
db.orders.find({ status: "paid" }).hint({ status: 1 })

// 쿼리 통계 (Atlas / mongod)
db.setProfilingLevel(2)  // 모든 쿼리 로깅
db.system.profile.find().sort({ millis: -1 }).limit(5)

흔한 함정

WARNING

  1. _id ObjectId 의 random 분포 = MongoDB 는 _id index 의 단조 증가 (timestamp prefix) 설계. UUID v4 를 _id 로 쓰면 random insert = B-tree 분할 폭증.
  2. $lookup (join) 남용 = NoSQL 답지 않은 비용. 임베드 (embedding) 가 정통 패턴.
  3. transaction 남용 = single document 안의 동작은 항상 atomic. transaction 은 진짜 필요할 때만.
  4. shard key 잘못 선택 = write 분포가 균등하지 않은 키 가 가장 큰 함정. 변경 불가 (6.0+ 재선택 가능).
  5. Change Stream 없이 polling = find({updatedAt: {$gt: ...}}) 루프 = 인덱스 있어도 부하. Change Streams 가 push 방식으로 효율적.
  6. Write Concern w:1 + 재시작 = primary 장애 시 1초 미만 데이터 손실. 중요 데이터는 w: majority.

관련 위키

이 글의 용어 (4개)
[DB] Cassandra: LSM-tree, quorum, eventually consistentdatabase-internals
정의 Apache Cassandra = 마스터리스 (peer-to-peer) 분산 KV 스토어. write 우선, eventually consistent, 수평 무한 확장. 20…
[DB] DynamoDB: PK + SK, single-table design, GSI / LSIdatabase-internals
정의 DynamoDB 는 AWS 의 fully managed key-value + document NoSQL. low-latency, infinite scale, schemale…
[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
[DB] Sharding vs Partitioning: 수평 확장의 두 얼굴database-internals
정의 | | Partitioning | Sharding | |---|---|---| | 범위 | 한 DB 안 | 여러 DB 노드 | | 목적 | 큰 테이블 관리 | 수평 확장 (…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기