스키마가 자주 바뀌거나 비정형 데이터를 다뤄야 할 때, RDBMS가 오히려 발목을 잡는 순간이 있다. 그런 상황에서 왜 Document DB가 대안이 될까? 이 글에서는 MongoDB의 핵심 개념부터 실무 설계 패턴, 그리고 자주 헷갈리는 포인트까지 전부 정리한다.

MongoDB란

MongoDB는 Document 지향(Document-Oriented) NoSQL 데이터베이스입니다. 2009년에 처음 릴리즈됐고, 현재 가장 널리 쓰이는 NoSQL DB 중 하나예요.

JSON이 아니라 BSON이에요

MongoDB는 데이터를 JSON처럼 생긴 형태로 저장하지만, 실제 내부 저장 포맷은 BSON(Binary JSON) 입니다. 왜 굳이 바이너리로 변환해서 저장할까요?

  • **타입 지원 **: JSON은 문자열, 숫자, 불리언, 배열, 객체, null 밖에 없습니다. BSON은 Date, ObjectId, Decimal128, BinData 같은 타입을 추가로 지원해요.
  • ** 인코딩/디코딩 속도 **: 바이너리라서 파싱이 빠릅니다. JSON은 텍스트니까 매번 파싱해야 하는데, BSON은 길이 정보가 포함돼 있어서 필드를 건너뛸 수 있어요.
  • ** 크기 **: 항상 BSON이 작은 건 아닙니다. 오히려 메타데이터 때문에 약간 더 클 수도 있어요. 하지만 트레이드오프로 속도를 택한 거예요.

"스키마리스"의 진짜 의미

MongoDB를 소개할 때 "스키마가 없다"고 말하는데, 이건 반쪽짜리 설명이에요. 정확하게는 DB 레벨에서 스키마를 강제하지 않는다 는 뜻입니다. 같은 컬렉션에 완전히 다른 구조의 문서를 넣을 수 있다는 거지, 스키마 없이 운영해도 된다는 뜻이 아니에요.

실무에서는 애플리케이션 레벨에서 스키마를 관리 합니다. Mongoose(Node.js)나 Spring Data MongoDB 같은 ODM/ORM이 그 역할을 해요. MongoDB 3.6부터는 $jsonSchema로 DB 레벨 validation도 걸 수 있습니다.

JAVASCRIPT
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["email", "name"],
      properties: {
        email: { bsonType: "string", description: "필수 문자열" },
        name: { bsonType: "string" },
        age: { bsonType: "int", minimum: 0 }
      }
    }
  }
});

RDBMS와 용어 비교

RDBMSMongoDB
DatabaseDatabase
TableCollection
RowDocument
ColumnField
Primary Key_id
JOIN$lookup (or embedding)
IndexIndex

Document 모델 — 임베딩 vs 레퍼런싱

관계형 DB에서는 정규화해서 테이블을 쪼개고 JOIN으로 합치는 게 기본입니다. MongoDB에서는 두 가지 선택지가 있어요.

임베딩 (Embedding)

관련 데이터를 하나의 문서 안에 중첩시키는 방식입니다.

JSON
{
  "_id": ObjectId("..."),
  "name": "홍길동",
  "address": {
    "city": "서울",
    "zipcode": "06234"
  },
  "orders": [
    { "product": "키보드", "price": 89000, "date": "2026-03-10" },
    { "product": "마우스", "price": 45000, "date": "2026-03-12" }
  ]
}

언제 쓰나?

  • 데이터 간 1:1 또는 1:Few 관계일 때
  • 항상 함께 조회되는 데이터일 때
  • 자식 데이터가 부모 없이 단독으로 조회될 일이 없을 때

** 장점 **: 한 번의 read로 모든 데이터를 가져올 수 있습니다. 네트워크 왕복이 줄어들고, atomic write가 보장돼요(단일 문서 내).

** 단점 **: 문서 크기 제한이 16MB입니다. 배열이 무한정 커지면 성능이 떨어지고, 같은 데이터가 여러 문서에 중복 저장될 수 있어요.

레퍼런싱 (Referencing)

다른 문서의 _id를 참조하는 방식입니다. RDBMS의 외래 키와 비슷한 개념이에요.

JSON
// users 컬렉션
{
  "_id": ObjectId("user1"),
  "name": "홍길동"
}

// orders 컬렉션
{
  "_id": ObjectId("order1"),
  "userId": ObjectId("user1"),
  "product": "키보드",
  "price": 89000
}

** 언제 쓰나?**

  • 1:Many에서 Many가 계속 늘어날 때 (댓글, 로그 등)
  • 자식 데이터를 독립적으로 조회해야 할 때
  • 데이터 중복을 피해야 할 때
  • N:M 관계일 때

** 단점 **: 조회 시 $lookup이나 애플리케이션 레벨 JOIN이 필요해서 추가 쿼리가 발생합니다.

판단 기준 정리

기준임베딩레퍼런싱
관계1:1, 1:Few1:Many, N:M
조회 패턴항상 함께독립적으로도 조회
데이터 크기작고 안정적계속 증가
일관성단일 문서 atomic트랜잭션 필요

스키마 설계 패턴

MongoDB에는 공식적으로 정리된 설계 패턴들이 있습니다. 실무에서 자주 쓰이는 것들만 정리해 볼게요.

1:1 관계

그냥 임베딩하면 됩니다. 따로 컬렉션을 나눌 이유가 거의 없어요. 다만 문서가 너무 커지거나 접근 빈도가 크게 다르면 분리를 고려할 수 있습니다.

1:N 관계

N의 크기에 따라 전략이 달라집니다.

  • 1:Few (1~수십 개): 부모 문서에 임베딩. 예: 사용자의 주소 목록.
  • 1:Many (수백~수천 개): 자식 문서에 부모 _id를 저장. 예: 블로그 포스트의 댓글.
  • 1:Squillions (수백만 이상): 자식 문서에 부모 _id를 저장하고, 부모에는 자식 참조를 두지 않습니다. 예: 로그 데이터.

N:M 관계

양쪽에 상대방의 _id 배열을 가지거나, 중간 컬렉션을 만듭니다.

JSON
// 학생
{ "_id": "s1", "name": "김철수", "courseIds": ["c1", "c2"] }

// 수업
{ "_id": "c1", "title": "데이터베이스", "studentIds": ["s1", "s2"] }

배열이 너무 커지면 중간 컬렉션(enrollment)을 따로 두는 게 낫습니다.

Subset Pattern

자주 쓰는 데이터만 부모 문서에 임베딩하고, 전체 데이터는 별도 컬렉션에 두는 패턴입니다.

JSON
// products 컬렉션 — 최근 리뷰 10개만 임베딩
{
  "_id": "p1",
  "name": "무선 키보드",
  "recentReviews": [
    { "user": "김철수", "rating": 5, "text": "좋아요" },
    // ... 최근 10개만
  ]
}

// reviews 컬렉션 — 전체 리뷰 보관
{ "productId": "p1", "user": "김철수", "rating": 5, "text": "좋아요", "createdAt": "..." }

상품 목록 페이지에서는 임베딩된 최근 리뷰만 보여주고, "리뷰 전체보기"를 누르면 reviews 컬렉션에서 가져옵니다. 워킹셋 크기를 줄여서 메모리 효율이 좋아져요.

Bucket Pattern

시계열(time-series) 데이터에 적합한 패턴입니다. 개별 이벤트마다 문서를 만드는 대신, 일정 단위(시간, 일)로 묶어서 하나의 문서에 배열로 저장해요.

JSON
{
  "sensorId": "temp-01",
  "date": "2026-03-15",
  "readings": [
    { "time": "09:00", "value": 22.5 },
    { "time": "09:01", "value": 22.6 },
    // ...
  ],
  "count": 1440,
  "sum": 32400,
  "avg": 22.5
}

문서 수가 줄어들고, 인덱스 크기도 줄어듭니다. IoT 센서 데이터나 주식 시세 같은 곳에서 많이 써요.


CRUD

기본 CRUD 연산을 빠르게 정리해 봅니다.

Create

JAVASCRIPT
// 단건 삽입
db.users.insertOne({ name: "홍길동", age: 30 });

// 다건 삽입 — ordered: false로 하면 하나 실패해도 나머지 계속 삽입
db.users.insertMany([
  { name: "김철수", age: 25 },
  { name: "이영희", age: 28 }
], { ordered: false });

_id를 지정하지 않으면 MongoDB가 ObjectId를 자동 생성합니다.

Read

JAVASCRIPT
// 조건 조회
db.users.find({ age: { $gte: 25 } });

// 프로젝션 — 필요한 필드만
db.users.find({ age: { $gte: 25 } }, { name: 1, age: 1, _id: 0 });

// 정렬 + 페이징
db.users.find().sort({ age: -1 }).skip(10).limit(10);

Update

JAVASCRIPT
// 특정 필드만 수정 — $set
db.users.updateOne(
  { name: "홍길동" },
  { $set: { age: 31 } }
);

// 숫자 증가 — $inc
db.orders.updateOne(
  { _id: "order1" },
  { $inc: { quantity: 1 } }
);

// 배열에 추가 — $push
db.users.updateOne(
  { name: "홍길동" },
  { $push: { tags: "developer" } }
);

// 배열에서 제거 — $pull
db.users.updateOne(
  { name: "홍길동" },
  { $pull: { tags: "intern" } }
);

// 조건에 맞는 문서가 없으면 삽입 — upsert
db.users.updateOne(
  { email: "new@example.com" },
  { $set: { name: "신규유저", age: 20 } },
  { upsert: true }
);

$set 없이 updateOne에 그냥 객체를 넘기면 문서 전체가 교체되니까 주의하세요. 자주 헷갈리는 부분입니다.

Delete

JAVASCRIPT
db.users.deleteOne({ name: "홍길동" });
db.users.deleteMany({ age: { $lt: 18 } });

인덱스

인덱스 없으면 MongoDB도 풀 컬렉션 스캔(COLLSCAN)을 합니다. RDBMS랑 원리는 같아요 - B-Tree 기반이고, 적절한 인덱스가 쿼리 성능을 결정합니다.

Single Field Index

JAVASCRIPT
db.users.createIndex({ email: 1 });  // 1: 오름차순, -1: 내림차순

가장 기본적인 인덱스입니다. 해당 필드로 조회하거나 정렬할 때 사용돼요.

Compound Index

JAVASCRIPT
db.orders.createIndex({ userId: 1, createdAt: -1 });

여러 필드를 조합한 인덱스입니다. ** 필드 순서가 중요 **해요. 위 인덱스는 userId로 먼저 필터링하고 createdAt으로 정렬하는 쿼리에 최적입니다. createdAt만으로 조회하면 이 인덱스를 활용하지 못해요.

이건 ESR 규칙 을 기억하면 됩니다:

  • Equality: 등호 조건 필드가 먼저
  • Sort: 정렬 필드가 다음
  • Range: 범위 조건 필드가 마지막

Multikey Index

배열 필드에 인덱스를 걸면 자동으로 Multikey Index가 됩니다.

JAVASCRIPT
db.products.createIndex({ tags: 1 });
// tags: ["electronics", "sale"] → 각 요소별로 인덱스 엔트리 생성

주의할 점은, compound index에서 ** 배열 필드는 최대 1개 **만 포함할 수 있습니다. 두 개 이상의 배열 필드를 하나의 compound index에 넣으면 에러가 나요.

Text Index

전문 검색을 위한 인덱스입니다. 한국어 지원은 제한적이므로 실무에서는 Elasticsearch를 같이 쓰는 경우가 많아요.

JAVASCRIPT
db.articles.createIndex({ title: "text", body: "text" });
db.articles.find({ $text: { $search: "mongodb 인덱스" } });

TTL Index

일정 시간이 지나면 문서를 자동 삭제하는 인덱스입니다. 세션, 캐시, 로그 데이터에 유용해요.

JAVASCRIPT
db.sessions.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 3600 }  // 1시간 후 자동 삭제
);

TTL 인덱스는 ** 단일 필드에만** 걸 수 있고, 해당 필드의 값이 Date 타입이어야 합니다. MongoDB의 백그라운드 스레드가 60초마다 만료 문서를 체크해서 삭제하기 때문에 정확히 expireAfterSeconds 후에 삭제되는 건 아니에요.

인덱스 확인

JAVASCRIPT
db.users.getIndexes();           // 인덱스 목록
db.users.find({ email: "a@b.com" }).explain("executionStats");  // 실행 계획

explain()winningPlan에서 IXSCAN이 나오면 인덱스를 탔다는 뜻이고, COLLSCAN이면 풀 스캔이라는 뜻입니다.


Aggregation Pipeline

MongoDB의 Aggregation Pipeline은 RDBMS의 GROUP BY, HAVING, JOIN 등을 대체하는 데이터 처리 프레임워크입니다. 스테이지를 파이프라인처럼 연결해서 데이터를 변환해요.

PLAINTEXT
입력 문서 → $match → $group → $sort → $project → 결과

$match — 필터링

SQL의 WHERE에 해당합니다. 파이프라인 초반에 넣어야 인덱스를 활용할 수 있어요.

JAVASCRIPT
{ $match: { status: "active", age: { $gte: 18 } } }

$group — 집계

SQL의 GROUP BY입니다.

JAVASCRIPT
{
  $group: {
    _id: "$category",              // 그룹핑 기준
    totalSales: { $sum: "$price" },
    avgPrice: { $avg: "$price" },
    count: { $sum: 1 }
  }
}

$project — 필드 선택/변환

필요한 필드만 뽑거나 새 필드를 계산해서 추가합니다.

JAVASCRIPT
{
  $project: {
    name: 1,
    priceWithTax: { $multiply: ["$price", 1.1] },
    _id: 0
  }
}

$lookup — JOIN

SQL의 LEFT OUTER JOIN과 비슷합니다. 다른 컬렉션과 연결할 수 있어요.

JAVASCRIPT
{
  $lookup: {
    from: "orders",           // 조인할 컬렉션
    localField: "_id",        // 현재 컬렉션의 필드
    foreignField: "userId",   // 대상 컬렉션의 필드
    as: "userOrders"          // 결과를 담을 배열 필드명
  }
}

결과는 항상 배열로 나옵니다. 매칭되는 문서가 없으면 빈 배열이에요.

$unwind — 배열 풀기

배열을 개별 문서로 풀어헤치는 스테이지입니다. $lookup 결과를 처리하거나 배열 요소별로 집계할 때 써요.

JAVASCRIPT
// 원본: { name: "홍길동", tags: ["A", "B", "C"] }
{ $unwind: "$tags" }
// 결과:
// { name: "홍길동", tags: "A" }
// { name: "홍길동", tags: "B" }
// { name: "홍길동", tags: "C" }

실전 예시 — 카테고리별 매출 TOP 3

JAVASCRIPT
db.orders.aggregate([
  { $match: { status: "completed" } },
  { $group: {
      _id: "$category",
      totalRevenue: { $sum: "$amount" },
      orderCount: { $sum: 1 }
  }},
  { $sort: { totalRevenue: -1 } },
  { $limit: 3 },
  { $project: {
      category: "$_id",
      totalRevenue: 1,
      orderCount: 1,
      _id: 0
  }}
]);

레플리카셋 (Replica Set)

레플리카셋은 MongoDB의 고가용성(HA) 솔루션입니다. 같은 데이터를 여러 노드에 복제해서 장애에 대비해요.

구성

PLAINTEXT
Client → Primary (읽기/쓰기)
              ↓ 복제
         Secondary (읽기 전용)
         Secondary (읽기 전용)
  • Primary: 모든 쓰기 연산을 받는 노드입니다. 하나만 존재해요.
  • Secondary: Primary의 oplog를 복제해서 데이터를 동기화하는 노드입니다. 기본적으로 읽기도 Primary로 가지만, Read Preference를 설정하면 Secondary에서도 읽을 수 있어요.
  • Arbiter: 데이터를 저장하지 않고 투표만 하는 노드입니다. 짝수 멤버일 때 과반수를 만들기 위해 사용해요.

자동 페일오버

Primary가 죽으면 Secondary들이 ** 선거(election)**를 통해 새 Primary를 뽑습니다. 보통 10~12초 안에 완료되고, 그 사이에 쓰기 연산은 불가능해요. 과반수 이상의 노드가 살아있어야 선거가 성립합니다. 그래서 최소 3개 노드를 권장하는 거예요.

Read Preference

읽기 요청을 어디로 보낼지 결정합니다.

모드설명
primaryPrimary에서만 읽기 (기본값, 강한 일관성)
primaryPreferredPrimary 우선, 불가능하면 Secondary
secondarySecondary에서만 읽기
secondaryPreferredSecondary 우선, 불가능하면 Primary
nearest네트워크 지연이 가장 적은 노드

주의할 점은, Secondary에서 읽으면 ** 최신 데이터가 아닐 수 있다 **는 겁니다. 복제 지연(replication lag) 때문이에요. 결제 처리 같은 강한 일관성이 필요한 곳에서는 반드시 primary를 써야 합니다.

Write Concern

쓰기 연산이 "성공"으로 간주되려면 몇 개 노드에 기록돼야 하는지를 지정합니다.

JAVASCRIPT
db.orders.insertOne(
  { product: "키보드", price: 89000 },
  { writeConcern: { w: "majority", j: true } }
);
  • w: 1 — Primary에만 기록되면 성공 (기본값)
  • w: "majority" — 과반수 노드에 기록되면 성공
  • j: true — 저널에 기록될 때까지 대기

w: "majority"를 쓰면 안전하지만 응답 시간이 길어집니다. 트레이드오프예요.


샤딩 (Sharding)

레플리카셋이 고가용성이라면, 샤딩은 ** 수평 확장(horizontal scaling)**입니다. 데이터를 여러 샤드에 분산 저장해요.

구성 요소

PLAINTEXT
Client → mongos (라우터)

    Config Server (메타데이터)

   Shard 1    Shard 2    Shard 3
  (RS)       (RS)       (RS)
  • mongos: 클라이언트 요청을 적절한 샤드로 라우팅하는 프로세스
  • Config Server: 샤드 키 범위, 청크 분배 정보 등 메타데이터 저장
  • Shard: 실제 데이터를 저장하는 레플리카셋

Shard Key 선택

샤드 키는 한번 정하면 변경이 어렵기 때문에 신중하게 골라야 합니다. 좋은 샤드 키의 조건은 다음과 같아요:

  • ** 높은 카디널리티 **: 값의 종류가 다양해야 합니다. boolean 같은 건 최악의 샤드 키예요.
  • ** 균등한 분포 **: 특정 샤드에 데이터가 몰리지 않아야 합니다.
  • ** 쿼리 격리 **: 자주 사용하는 쿼리가 단일 샤드에서 처리될 수 있어야 해요. 모든 샤드에 scatter-gather 쿼리를 날리면 느립니다.

Range Sharding vs Hash Sharding

RangeHash
방식샤드 키 값의 범위로 분배샤드 키의 해시값으로 분배
장점범위 쿼리에 유리데이터 분포가 균등
단점핫스팟 발생 가능 (예: 단조 증가 키)범위 쿼리 시 모든 샤드에 scatter
적합한 경우날짜 범위 조회가 많은 경우랜덤 접근이 많은 경우

_id(ObjectId)를 Range Sharding의 샤드 키로 쓰면 최근 데이터가 항상 마지막 샤드에 몰리는 핫스팟 문제가 생깁니다. 이런 경우 Hash Sharding을 쓰거나 다른 키를 선택해야 해요.

Chunk

청크는 샤딩에서 데이터를 나누는 논리적 단위입니다. 기본 청크 크기는 128MB이고, 청크가 이 크기를 넘으면 자동으로 분할(split)돼요. 밸런서(balancer)가 샤드 간 청크 수가 균등하도록 자동으로 마이그레이션합니다.


MongoDB 4.0+ 트랜잭션

MongoDB 4.0부터 멀티 Document 트랜잭션을 지원합니다. 4.2부터는 샤드 클러스터에서도 사용 가능해요.

사용법

JAVASCRIPT
const session = client.startSession();
session.startTransaction();

try {
  db.accounts.updateOne(
    { _id: "A" }, { $inc: { balance: -10000 } }, { session }
  );
  db.accounts.updateOne(
    { _id: "B" }, { $inc: { balance: 10000 } }, { session }
  );
  session.commitTransaction();
} catch (e) {
  session.abortTransaction();
} finally {
  session.endSession();
}

주의사항

트랜잭션을 쓸 수 있다고 해서 RDBMS처럼 자유롭게 쓰면 안 됩니다.

  • ** 성능 오버헤드 **: 트랜잭션은 lock과 WiredTiger의 snapshot을 사용하므로 단일 문서 연산보다 훨씬 무겁습니다.
  • **60초 제한 **: 기본적으로 트랜잭션은 60초 안에 완료되지 않으면 자동 abort돼요.
  • ** 설계 우선 **: MongoDB 공식 문서에서도 "트랜잭션이 필요하다면 먼저 스키마 설계를 다시 생각해봐라"라고 권고합니다. 임베딩으로 단일 문서 내에서 해결할 수 있으면 그게 훨씬 나아요.
  • ** 레플리카셋 필수 **: 트랜잭션은 standalone 모드에서는 지원되지 않습니다. 최소 레플리카셋 구성이 필요해요.

핵심만 정리하면, MongoDB 트랜잭션은 4.0부터 가능하지만 단일 문서 내 atomic 연산이 기본이고, 멀티 Document 트랜잭션은 스키마 설계로 해결 안 될 때 제한적으로 사용해야 합니다.


WiredTiger 스토리지 엔진

MongoDB 3.2부터 기본 스토리지 엔진이 WiredTiger로 바뀌었습니다. 그 전에는 MMAPv1을 썼는데, WiredTiger가 거의 모든 면에서 나아요.

압축

WiredTiger는 데이터와 인덱스를 압축해서 저장합니다.

  • ** 데이터 **: Snappy 압축 (기본값) 또는 zlib, zstd 사용 가능
  • ** 인덱스 **: prefix 압축

Snappy는 압축률보다 속도를 우선시하고, zlib/zstd는 압축률이 더 높지만 CPU를 더 씁니다. 대부분의 경우 기본 Snappy로 충분해요.

동시성 제어

MMAPv1은 컬렉션 단위로 lock을 잡았는데, WiredTiger는 Document 단위로 lock 을 잡습니다. 이 차이가 엄청나요. 같은 컬렉션에서 서로 다른 문서를 동시에 수정할 수 있으니까 동시성이 크게 향상됩니다.

내부적으로는 MVCC(Multi-Version Concurrency Control) 를 사용해요. 읽기 연산은 특정 시점의 스냅샷을 보기 때문에 쓰기 연산과 충돌하지 않습니다. 이 부분은 PostgreSQL이나 Oracle의 MVCC와 개념이 비슷해요.

체크포인트

WiredTiger는 60초마다 또는 2GB의 저널 데이터가 쌓이면 체크포인트를 생성합니다. 체크포인트는 디스크에 있는 데이터의 일관된 스냅샷이에요. 장애 복구 시 마지막 체크포인트부터 저널을 재생(replay)해서 데이터를 복원합니다.


주의할 점

RDBMS 대신 MongoDB를 선택하는 경우

"언제 MongoDB를 쓰나요?"는 거의 반드시 나오는 질문입니다.

  • **스키마가 자주 변경되는 초기 스타트업 서비스 **: 요구사항이 빠르게 바뀌는데 매번 ALTER TABLE 치기엔 비용이 커요
  • ** 비정형/반정형 데이터 **: 로그, IoT 센서 데이터, 소셜 미디어 피드 같이 구조가 일정하지 않은 데이터
  • ** 높은 쓰기 처리량이 필요한 경우 **: 샤딩을 통한 수평 확장이 RDBMS보다 자연스럽습니다
  • ** 계층적 데이터 **: 카탈로그, 상품 속성처럼 카테고리마다 필드가 다른 경우

반대로 ** 복잡한 JOIN이 많거나, 강한 트랜잭션 보장이 필수인 금융 시스템** 같은 곳에서는 여전히 RDBMS가 적합합니다.

ObjectId 구조

PLAINTEXT
|  4바이트  |  5바이트   |  3바이트  |
| Timestamp | Random    | Counter  |
  • Timestamp (4바이트): Unix epoch 초 단위. ObjectId에서 생성 시각을 추출할 수 있습니다. ObjectId.getTimestamp().
  • Random (5바이트): 프로세스별 랜덤 값. 머신과 프로세스를 구분해요.
  • Counter (3바이트): 같은 초에 같은 프로세스에서 생성된 ObjectId를 구분하는 자동 증가 카운터.

총 12바이트(24자리 hex 문자열)로 이루어져 있고, 대략적으로 시간순 정렬이 가능합니다. 하지만 정확한 정렬이 필요하면 별도의 createdAt 필드를 두는 게 낫습니다.

Change Streams

MongoDB 3.6부터 지원하는 기능으로, 컬렉션이나 데이터베이스의 변경 사항을 실시간으로 구독할 수 있습니다. 내부적으로는 oplog를 tail하는 방식이에요.

JAVASCRIPT
const changeStream = db.orders.watch();

changeStream.on("change", (change) => {
  console.log(change.operationType);  // insert, update, delete, ...
  console.log(change.fullDocument);
});

** 활용 사례:**

  • 실시간 알림 시스템
  • 데이터 동기화 (MongoDB → Elasticsearch 등)
  • 이벤트 드리븐 아키텍처에서 이벤트 소싱

레플리카셋이나 샤드 클러스터에서만 사용 가능합니다. standalone에서는 안 돼요.


파생 개념

  • NoSQL / CAP 정리 — MongoDB는 CP 시스템으로 분류되지만, Write Concern과 Read Preference 설정에 따라 일관성과 가용성의 균형을 조절할 수 있습니다
  • Redis — 캐시 레이어로 MongoDB 앞에 Redis를 두는 구성이 흔합니다. 자주 조회되는 데이터를 Redis에 캐싱하고 MongoDB를 영속 저장소로 사용해요
  • ** 시스템 설계** — 대규모 시스템 설계에서 MongoDB는 카탈로그 서비스, 사용자 프로필, 콘텐츠 관리 같은 유연한 스키마가 필요한 영역에서 선택됩니다
댓글 로딩 중...