MongoDB 완벽 가이드: NoSQL 문서 데이터베이스

MongoDB 완벽 가이드: NoSQL 문서 데이터베이스

이 글의 핵심

MongoDB는 유연한 스키마와 수평 확장이 가능한 NoSQL 문서 데이터베이스입니다. JSON 형태의 BSON 문서로 데이터를 저장하며, 강력한 쿼리, 집계 파이프라인, 샤딩으로 대규모 애플리케이션에 적합합니다.

MongoDB란?

MongoDB는 문서 지향(Document-Oriented) NoSQL 데이터베이스로, JSON과 유사한 BSON(Binary JSON) 형식으로 데이터를 저장합니다. 2009년 출시 이후 가장 인기있는 NoSQL 데이터베이스로 자리잡았습니다.

핵심 특징

  1. 문서 기반

    • JSON 형태 저장
    • 유연한 스키마
    • 중첩 구조 지원
  2. 확장성

    • 수평 확장 (샤딩)
    • 레플리카셋
    • 자동 페일오버
  3. 강력한 쿼리

    • 풍부한 쿼리 연산자
    • 집계 파이프라인
    • 텍스트 검색
  4. 고성능

    • 인덱싱
    • 메모리 맵 파일
    • WiredTiger 스토리지 엔진

SQL vs NoSQL 비교

항목SQL (MySQL)NoSQL (MongoDB)
데이터 모델테이블 (행/열)문서 (JSON)
스키마고정유연
확장수직 (Scale-up)수평 (Scale-out)
JOIN강력제한적 (Lookup)
트랜잭션완벽 지원4.0부터 지원
사용 사례금융, 재고 관리소셜 미디어, IoT
학습 곡선중간낮음

설치

MongoDB 설치

# macOS (Homebrew)
brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-community

# Ubuntu
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl start mongod

# Docker
docker run -d -p 27017:27017 --name mongodb mongo:7

Node.js 드라이버 설치

# MongoDB 드라이버
npm install mongodb

# Mongoose ODM (권장)
npm install mongoose

기본 CRUD

연결

// MongoDB 드라이버
import { MongoClient } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('myapp');
const users = db.collection('users');

// Mongoose
import mongoose from 'mongoose';

await mongoose.connect('mongodb://localhost:27017/myapp');

Create (삽입)

// 단일 문서
const result = await users.insertOne({
  name: '홍길동',
  email: 'hong@example.com',
  age: 25,
  tags: ['developer', 'nodejs']
});

console.log(result.insertedId);

// 여러 문서
const result = await users.insertMany([
  { name: '김철수', email: 'kim@example.com' },
  { name: '이영희', email: 'lee@example.com' }
]);

console.log(result.insertedIds);

Read (조회)

// 모든 문서
const allUsers = await users.find().toArray();

// 조건 조회
const result = await users.find({ age: { $gte: 20 } }).toArray();

// 단일 문서
const user = await users.findOne({ email: 'hong@example.com' });

// Projection (필드 선택)
const users = await users.find(
  { age: { $gte: 20 } },
  { projection: { name: 1, email: 1, _id: 0 } }
).toArray();

// 정렬
const users = await users.find().sort({ age: -1 }).toArray();

// 페이지네이션
const page = 1;
const limit = 10;
const users = await users.find()
  .skip((page - 1) * limit)
  .limit(limit)
  .toArray();

Update (수정)

// 단일 문서
await users.updateOne(
  { email: 'hong@example.com' },
  { $set: { age: 26 } }
);

// 여러 문서
await users.updateMany(
  { age: { $lt: 20 } },
  { $set: { category: 'minor' } }
);

// Upsert (없으면 생성)
await users.updateOne(
  { email: 'new@example.com' },
  { $set: { name: '신규' } },
  { upsert: true }
);

// 증가/감소
await users.updateOne(
  { _id: userId },
  { $inc: { views: 1 } }
);

// 배열 추가
await users.updateOne(
  { _id: userId },
  { $push: { tags: 'react' } }
);

Delete (삭제)

// 단일 문서
await users.deleteOne({ email: 'hong@example.com' });

// 여러 문서
await users.deleteMany({ age: { $lt: 18 } });

Mongoose ODM

스키마 정의

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
    minlength: 2,
    maxlength: 50
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
  },
  age: {
    type: Number,
    min: 0,
    max: 150
  },
  tags: [String],
  profile: {
    bio: String,
    avatar: String,
    social: {
      twitter: String,
      github: String
    }
  },
  isActive: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true  // createdAt, updatedAt 자동 생성
});

// 인덱스
userSchema.index({ email: 1 });
userSchema.index({ name: 'text' });

// 가상 필드
userSchema.virtual('fullName').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

// 메서드
userSchema.methods.comparePassword = async function(password) {
  return bcrypt.compare(password, this.password);
};

// 스태틱 메서드
userSchema.statics.findByEmail = function(email) {
  return this.findOne({ email });
};

const User = mongoose.model('User', userSchema);

Mongoose CRUD

// Create
const user = new User({
  name: '홍길동',
  email: 'hong@example.com'
});
await user.save();

// 또는
const user = await User.create({
  name: '홍길동',
  email: 'hong@example.com'
});

// Read
const users = await User.find({ age: { $gte: 20 } });
const user = await User.findById(userId);
const user = await User.findOne({ email: 'hong@example.com' });

// Update
const user = await User.findByIdAndUpdate(
  userId,
  { age: 26 },
  { new: true }  // 업데이트된 문서 반환
);

// Delete
await User.findByIdAndDelete(userId);

집계 파이프라인

// 사용자 연령대별 통계
const stats = await users.aggregate([
  // 필터링
  { $match: { isActive: true } },
  
  // 그룹화
  {
    $group: {
      _id: {
        $floor: { $divide: ['$age', 10] }
      },
      count: { $sum: 1 },
      avgAge: { $avg: '$age' }
    }
  },
  
  // 정렬
  { $sort: { _id: 1 } },
  
  // 프로젝션
  {
    $project: {
      ageGroup: { $multiply: ['$_id', 10] },
      count: 1,
      avgAge: { $round: ['$avgAge', 1] }
    }
  }
]);

인덱싱

// 단일 필드 인덱스
await users.createIndex({ email: 1 });

// 복합 인덱스
await users.createIndex({ lastName: 1, firstName: 1 });

// 텍스트 인덱스
await users.createIndex({ bio: 'text', tags: 'text' });

// 지리 공간 인덱스
await locations.createIndex({ location: '2dsphere' });

// 인덱스 확인
const indexes = await users.indexes();

MongoDB는 유연하고 확장 가능한 NoSQL 데이터베이스입니다. 문서 기반 모델과 강력한 쿼리로 현대적인 애플리케이션 개발에 적합합니다.