MongoDB 고급 가이드 | 인덱싱·Aggregation·샤딩·복제·성능 최적화

MongoDB 고급 가이드 | 인덱싱·Aggregation·샤딩·복제·성능 최적화

이 글의 핵심

MongoDB의 고급 기능을 실전 예제로 완벽 정리합니다. 인덱싱 전략, Aggregation Pipeline, 샤딩, 복제, 트랜잭션, 성능 최적화까지 실무에 바로 적용할 수 있는 가이드입니다.

실무 경험 공유: 일 10억 건의 로그를 처리하는 MongoDB 클러스터를 운영하면서, 샤딩으로 쿼리 속도를 5배 향상시키고 인덱싱으로 디스크 사용량을 60% 절감한 경험을 공유합니다.

들어가며: “MongoDB가 느려요”

실무 문제 시나리오

시나리오 1: 쿼리가 10초 걸려요
1억 건 컬렉션에서 쿼리가 느립니다. 인덱스로 0.1초로 단축합니다.

시나리오 2: 데이터가 너무 많아요
단일 서버로 감당 안 됩니다. 샤딩으로 분산합니다.

시나리오 3: 복잡한 집계가 필요해요
여러 단계의 데이터 변환이 필요합니다. Aggregation Pipeline으로 해결합니다.


1. MongoDB란?

핵심 특징

MongoDB는 문서 지향 NoSQL 데이터베이스입니다.

주요 장점:

  • 유연한 스키마: JSON 형태 문서
  • 수평 확장: 샤딩 지원
  • 강력한 쿼리: Aggregation Pipeline
  • 고가용성: 복제 세트
  • 빠른 성능: 인메모리 처리

2. 인덱싱 전략

단일 필드 인덱스

// 인덱스 생성
db.users.createIndex({ email: 1 });

// 복합 인덱스
db.orders.createIndex({ userId: 1, createdAt: -1 });

// 유니크 인덱스
db.users.createIndex({ email: 1 }, { unique: true });

// 부분 인덱스
db.users.createIndex(
  { email: 1 },
  { partialFilterExpression: { isActive: true } }
);

텍스트 인덱스

// 전문 검색 인덱스
db.articles.createIndex({ title: 'text', content: 'text' });

// 검색
db.articles.find({ $text: { $search: 'mongodb tutorial' } });

지리 공간 인덱스

// 2dsphere 인덱스
db.places.createIndex({ location: '2dsphere' });

// 근처 검색
db.places.find({
  location: {
    $near: {
      $geometry: {
        type: 'Point',
        coordinates: [127.0276, 37.4979],  // 강남역
      },
      $maxDistance: 1000,  // 1km
    },
  },
});

3. Aggregation Pipeline

기본 사용

db.orders.aggregate([
  // 1. 필터링
  { $match: { status: 'completed' } },
  
  // 2. 그룹화
  {
    $group: {
      _id: '$userId',
      totalAmount: { $sum: '$amount' },
      orderCount: { $sum: 1 },
    },
  },
  
  // 3. 정렬
  { $sort: { totalAmount: -1 } },
  
  // 4. 제한
  { $limit: 10 },
]);

복잡한 집계

// 사용자별 월별 매출
db.orders.aggregate([
  {
    $match: {
      createdAt: {
        $gte: new Date('2026-01-01'),
        $lt: new Date('2027-01-01'),
      },
    },
  },
  {
    $group: {
      _id: {
        userId: '$userId',
        year: { $year: '$createdAt' },
        month: { $month: '$createdAt' },
      },
      revenue: { $sum: '$amount' },
      orders: { $sum: 1 },
    },
  },
  {
    $lookup: {
      from: 'users',
      localField: '_id.userId',
      foreignField: '_id',
      as: 'user',
    },
  },
  { $unwind: '$user' },
  {
    $project: {
      _id: 0,
      userName: '$user.name',
      year: '$_id.year',
      month: '$_id.month',
      revenue: 1,
      orders: 1,
    },
  },
  { $sort: { revenue: -1 } },
]);

4. 샤딩

샤드 키 선택

// 좋은 샤드 키: 고른 분산
sh.shardCollection('mydb.users', { userId: 'hashed' });

// 나쁜 샤드 키: 불균형 분산
sh.shardCollection('mydb.orders', { createdAt: 1 });  // 최근 데이터에 몰림

샤딩 설정

# Config Server 시작
mongod --configsvr --replSet configReplSet --port 27019

# Shard Server 시작
mongod --shardsvr --replSet shard1 --port 27018

# Mongos (라우터) 시작
mongos --configdb configReplSet/localhost:27019 --port 27017

5. 복제 세트

복제 세트 구성

// Primary 서버에서
rs.initiate({
  _id: 'rs0',
  members: [
    { _id: 0, host: 'localhost:27017' },
    { _id: 1, host: 'localhost:27018' },
    { _id: 2, host: 'localhost:27019' },
  ],
});

// 상태 확인
rs.status();

Read Preference

// Node.js 드라이버
const client = new MongoClient(uri, {
  readPreference: 'secondaryPreferred',  // Secondary에서 읽기 우선
});

6. 트랜잭션

단일 문서 트랜잭션

const session = client.startSession();

try {
  await session.withTransaction(async () => {
    await db.collection('accounts').updateOne(
      { _id: fromAccount },
      { $inc: { balance: -100 } },
      { session }
    );

    await db.collection('accounts').updateOne(
      { _id: toAccount },
      { $inc: { balance: 100 } },
      { session }
    );
  });
} finally {
  await session.endSession();
}

7. 성능 최적화

쿼리 최적화

// ❌ 느린 쿼리
db.users.find({ email: { $regex: /.*@example.com/ } });

// ✅ 빠른 쿼리 (인덱스 사용)
db.users.find({ email: '[email protected]' });

// Explain으로 확인
db.users.find({ email: '[email protected]' }).explain('executionStats');

Projection (필요한 필드만)

// ❌ 모든 필드 조회
db.users.find({});

// ✅ 필요한 필드만
db.users.find({}, { name: 1, email: 1, _id: 0 });

Covered Query (인덱스만 사용)

// 인덱스 생성
db.users.createIndex({ email: 1, name: 1 });

// Covered Query (디스크 접근 없음)
db.users.find(
  { email: '[email protected]' },
  { email: 1, name: 1, _id: 0 }
);

8. 실전 예제: 로그 분석 시스템

// 로그 컬렉션
db.logs.insertMany([
  {
    userId: 1,
    action: 'login',
    ip: '192.168.1.1',
    timestamp: new Date('2026-04-01T10:00:00Z'),
  },
  {
    userId: 1,
    action: 'view_product',
    productId: 123,
    timestamp: new Date('2026-04-01T10:05:00Z'),
  },
  {
    userId: 2,
    action: 'purchase',
    amount: 99.99,
    timestamp: new Date('2026-04-01T10:10:00Z'),
  },
]);

// 인덱스 생성
db.logs.createIndex({ userId: 1, timestamp: -1 });
db.logs.createIndex({ action: 1 });

// 사용자별 일일 활동 분석
db.logs.aggregate([
  {
    $match: {
      timestamp: {
        $gte: new Date('2026-04-01'),
        $lt: new Date('2026-04-02'),
      },
    },
  },
  {
    $group: {
      _id: {
        userId: '$userId',
        action: '$action',
      },
      count: { $sum: 1 },
    },
  },
  {
    $group: {
      _id: '$_id.userId',
      actions: {
        $push: {
          action: '$_id.action',
          count: '$count',
        },
      },
      totalActions: { $sum: '$count' },
    },
  },
  { $sort: { totalActions: -1 } },
  { $limit: 10 },
]);

정리 및 체크리스트

핵심 요약

  • MongoDB: 문서 지향 NoSQL 데이터베이스
  • 인덱싱: B-Tree, 텍스트, 지리 공간 인덱스
  • Aggregation: 강력한 데이터 집계 파이프라인
  • 샤딩: 수평 확장
  • 복제: 고가용성
  • 트랜잭션: ACID 보장

프로덕션 체크리스트

  • 적절한 인덱스 생성
  • Aggregation Pipeline 최적화
  • 샤딩 전략 수립 (필요 시)
  • 복제 세트 구성
  • 백업 자동화
  • 모니터링 설정

같이 보면 좋은 글

  • PostgreSQL 고급 가이드
  • Redis 고급 가이드
  • Prisma 완벽 가이드

이 글에서 다루는 키워드

MongoDB, NoSQL, Database, Indexing, Aggregation, Sharding, Performance

자주 묻는 질문 (FAQ)

Q. MongoDB vs PostgreSQL, 어떤 게 나은가요?

A. MongoDB는 유연한 스키마와 수평 확장에 유리합니다. PostgreSQL은 복잡한 관계와 트랜잭션에 강합니다. 요구사항에 따라 선택하세요.

Q. 트랜잭션을 사용할 수 있나요?

A. 네, MongoDB 4.0부터 다중 문서 트랜잭션을 지원합니다. 복제 세트나 샤드 클러스터에서 사용 가능합니다.

Q. 인덱스를 많이 만들면 성능이 나빠지나요?

A. 네, 쓰기 성능이 저하됩니다. 자주 조회하는 필드에만 인덱스를 만드세요.

Q. 샤딩은 언제 사용하나요?

A. 단일 서버로 감당할 수 없을 때 (수억 건 이상, 수 TB 이상) 사용합니다. 초기에는 복제 세트로 충분합니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3