DATABASE

MongoDB 완전 가이드 - NoSQL 실전

junetapa 2026. 2. 18 12 min read

MongoDB는 가장 널리 쓰이는 NoSQL 데이터베이스다. 도큐먼트 기반 구조로 스키마 없이 유연하게 데이터를 다룰 수 있다. 설치부터 CRUD, 인덱스, Aggregation Pipeline, Mongoose 연동까지 실전 중심으로 정리했다.

MongoDB란? SQL과 NoSQL의 차이

MongoDB는 2009년 출시된 오픈소스 도큐먼트 데이터베이스(Document DB)다. 데이터를 JSON 형식의 BSON(Binary JSON) 도큐먼트로 저장하며, 미리 정해진 스키마 없이도 유연하게 데이터 구조를 변경할 수 있다는 점이 가장 큰 특징다. 관계형 데이터베이스(RDBMS)처럼 테이블과 행이 아닌, 컬렉션(Collection)과 도큐먼트(Document)라는 개념을 사용한다.

📄

유연한 스키마

필드 추가·삭제·변경이 자유로워 빠른 프로토타이핑에 최적

📄

JSON 네이티브

BSON 포맷으로 저장, JavaScript 객체 그대로 저장 가능

수평 확장

샤딩(Sharding)으로 데이터 분산, 대용량 트래픽 처리 가능

Atlas 클라우드

MongoDB 공식 클라우드, 무료 티어 제공, AWS/GCP/Azure 지원

SQL vs NoSQL 핵심 비교

항목 SQL (PostgreSQL, MySQL) NoSQL (MongoDB)
데이터 구조 테이블 (행, 열) 컬렉션 (도큐먼트, JSON)
스키마 고정 (변경 시 마이그레이션) 유연 (필드 자유 추가)
확장 방식 수직 확장 (스펙 업그레이드) 수평 확장 (샤딩 분산)
트랜잭션 완전한 ACID 보장 멀티 도큐먼트 트랜잭션 지원 (v4.0+)
쿼리 언어 SQL (표준화) MQL (MongoDB Query Language)
JOIN 네이티브 JOIN 지원 $lookup (Aggregation)
주요 용도 금융, ERP, 복잡한 관계형 데이터 콘텐츠, 카탈로그, 로그, IoT

언제 MongoDB를 선택하나요?

  • 스키마가 자주 변경되는 초기 개발: MVP 단계에서 데이터 구조가 확정되지 않은 경우 마이그레이션 없이 자유롭게 필드를 추가·수정할 수 있다.
  • 계층형·중첩 데이터: 상품 카탈로그, 블로그 포스트(댓글 포함), 사용자 프로필처럼 관련 데이터를 한 도큐먼트에 임베딩하면 JOIN 없이 빠르게 조회할 수 있다.
  • 대용량 비정형 데이터: 로그, 이벤트 스트림, IoT 센서 데이터처럼 정형화하기 어려운 데이터를 효율적으로 저장한다.
  • 빠른 읽기·쓰기가 필요한 서비스: 게임 리더보드, 실시간 분석 대시보드, 콘텐츠 관리 시스템(CMS)에 적합한다.
실무 조합 팁 대부분의 서비스는 PostgreSQL(트랜잭션 중심 메인 DB) + MongoDB(콘텐츠·로그) + Redis(캐시·세션)를 함께 사용한다. 단일 DB로 모든 것을 해결하려 하지 말고, 각 DB의 강점에 맞춰 역할을 분리하는 것이 좋다.

설치 및 초기 설정 (Docker / 직접 설치)

Docker Compose로 빠르게 시작 (권장)

로컬 개발 환경에서는 Docker를 사용하는 것이 가장 간편한다. 아래 compose 파일을 프로젝트 루트에 저장하자.

docker-compose.ymlyaml
services:
  mongodb:
    image: mongo:7.0
    container_name: my-mongodb
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password
      MONGO_INITDB_DATABASE: mydb
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db

volumes:
  mongodb_data:
Docker 실행 및 mongosh 접속bash
docker-compose up -d

# mongosh 접속
docker exec -it my-mongodb mongosh -u admin -p password

mongosh 기본 명령어

mongosh 기본 명령어js
show dbs           // DB 목록
use mydb           // DB 선택/생성
show collections   // 컬렉션 목록
db.stats()         // DB 통계
직접 설치 방법 macOS는 brew tap mongodb/brew && brew install mongodb-community@7.0, Windows는 공식 다운로드 페이지에서 MSI 인스톨러를 사용하자. 운영 환경(서버)에서는 MongoDB Atlas 또는 패키지 매니저(apt, yum)를 통한 설치를 권장한다.

컬렉션·도큐먼트 개념과 데이터 모델 설계

MongoDB의 컬렉션(Collection)은 SQL의 테이블에 해당하고, 도큐먼트(Document)는 SQL의 행(row)에 해당한다. 도큐먼트는 JSON 형식으로 표현되며, 중첩 객체와 배열을 자유롭게 포함할 수 있다. 각 도큐먼트는 고유한 _id 필드(기본값: ObjectId)를 가집니다.

블로그 포스트 도큐먼트 예시

블로그 포스트 도큐먼트 구조json
{
  "_id": "ObjectId('..')",
  "title": "MongoDB 가이드",
  "author": {
    "id": "user_123",
    "name": "junetapa"
  },
  "tags": ["MongoDB", "NoSQL", "DB"],
  "meta": {
    "views": 1500,
    "likes": 42
  },
  "comments": [],
  "published": true,
  "createdAt": "ISODate('2026-02-19')"
}

임베딩(Embedding) vs 참조(Reference) 선택 가이드

항목 임베딩 (Embedding) 참조 (Reference)
데이터 위치 상위 도큐먼트 내부에 중첩 저장 별도 컬렉션에 저장 후 ID 참조
조회 성능 빠름 (단일 쿼리) $lookup 필요 (추가 쿼리)
데이터 중복 중복 가능 (비정규화) 중복 없음 (정규화)
업데이트 여러 도큐먼트 업데이트 필요 참조 도큐먼트 1회 업데이트
적합한 관계 1:1, 1:N (N이 적고 변경 적은 경우) N:M, 1:N (N이 크거나 자주 변경)
예시 포스트 안에 작성자 이름 포함 포스트에 author_id 저장, users 컬렉션 참조
모델 설계 원칙 — "함께 조회되는 데이터는 함께 저장" MongoDB의 설계 원칙은 SQL의 정규화와 반대다. 실제 애플리케이션의 쿼리 패턴을 먼저 파악한 뒤, 자주 함께 읽히는 데이터는 임베딩하고, 독립적으로 관리되거나 크기가 큰 데이터는 참조 방식을 사용하자.

CRUD 완전 정복 — insertOne, find, updateOne, deleteOne

MongoDB의 CRUD 연산은 SQL의 INSERT, SELECT, UPDATE, DELETE에 대응한다. 모든 연산은 컬렉션 객체(db.컬렉션명)를 통해 수행된다.

Create — 도큐먼트 삽입

insertOne / insertManyjs
// Create
db.posts.insertOne({
  title: "MongoDB 완전 가이드",
  author: { id: "user_1", name: "junetapa" },
  tags: ["MongoDB", "NoSQL"],
  views: 0,
  published: true,
  createdAt: new Date()
});

db.posts.insertMany([
  { title: "첫 번째 포스트", tags: ["intro"], published: true },
  { title: "두 번째 포스트", tags: ["advanced"], published: false }
]);

Read — 도큐먼트 조회

find / 정렬 / 페이징 / projectionjs
// Read
db.posts.find({ published: true })
  .sort({ createdAt: -1 })
  .limit(20)
  .skip(0);

// 특정 필드만 반환 (projection)
db.posts.find(
  { published: true },
  { title: 1, tags: 1, createdAt: 1, _id: 0 }
);

Update — 도큐먼트 수정

updateOne / $set / $inc / $push / upsertjs
// Update
db.posts.updateOne(
  { _id: ObjectId("..") },
  {
    $set: { title: "수정된 제목", updatedAt: new Date() },
    $inc: { "meta.views": 1 }
  }
);

// 배열에 항목 추가
db.posts.updateOne(
  { _id: ObjectId("..") },
  { $push: { tags: "newTag" } }
);

// Upsert
db.users.updateOne(
  { email: "hong@example.com" },
  { $set: { name: "홍길동", updatedAt: new Date() } },
  { upsert: true }
);

Delete — 도큐먼트 삭제

deleteOne / deleteManyjs
// Delete
db.posts.deleteOne({ _id: ObjectId("..") });
db.posts.deleteMany({ published: false, createdAt: { $lt: new Date("2025-01-01") } });

쿼리 연산자 — 조건 검색, 배열, 정규식

MongoDB MQL은 다양한 연산자를 제공한다. SQL의 WHERE 절에 해당하는 조건을 JSON 형식으로 표현한다. 연산자는 달러 기호($)로 시작한다.

비교·논리·배열·정규식·존재 연산자js
// 비교 연산자
db.posts.find({ views: { $gte: 100, $lt: 1000 } });
db.posts.find({ title: { $ne: "삭제됨" } });
db.posts.find({ status: { $in: ["draft", "published"] } });

// 논리 연산자
db.posts.find({
  $and: [
    { published: true },
    { views: { $gte: 50 } }
  ]
});

// 배열 연산자
db.posts.find({ tags: "MongoDB" });           // 배열에 포함
db.posts.find({ tags: { $all: ["DB", "NoSQL"] } }); // 모두 포함
db.posts.find({ "tags.0": "MongoDB" });       // 첫 번째 원소

// 정규식 (대소문자 무관 검색)
db.posts.find({ title: { $regex: /mongodb/i } });

// null/존재 확인
db.posts.find({ deletedAt: null });
db.posts.find({ bio: { $exists: true } });

주요 연산자 빠른 참조

연산자의미SQL 대응
$eq같음= value
$ne다름<> value
$gt / $gte초과 / 이상> / >=
$lt / $lte미만 / 이하< / <=
$in배열 중 하나IN (..)
$nin배열 외 모두NOT IN (..)
$and / $or / $nor논리 AND/OR/NORAND / OR
$exists필드 존재 여부IS NOT NULL
$regex정규식 매칭LIKE / ILIKE
$all배열 모든 요소 포함(배열 전용)

인덱스 설계 — 단일, 복합, 텍스트, TTL

인덱스는 쿼리 성능을 획기적으로 개선한다. 인덱스 없이 MongoDB는 컬렉션 전체를 스캔(Collection Scan)하지만, 인덱스가 있으면 해당 필드만 빠르게 탐색(Index Scan)한다. 단, 인덱스는 쓰기 성능과 저장 공간을 소모하므로 꼭 필요한 쿼리 패턴에만 생성해야 한다.

단일·복합·고유·텍스트·TTL 인덱스js
// 단일 인덱스
db.posts.createIndex({ createdAt: -1 });

// 복합 인덱스 (자주 함께 쓰는 필드)
db.posts.createIndex({ published: 1, createdAt: -1 });

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

// 텍스트 인덱스 (전문 검색)
db.posts.createIndex({ title: "text", content: "text" });
db.posts.find({ $text: { $search: "MongoDB 가이드" } });

// TTL 인덱스 (자동 만료)
// 생성 후 7일 뒤 자동 삭제
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 604800 });

// 인덱스 목록
db.posts.getIndexes();

// 실행 계획 확인 (SQL의 EXPLAIN ANALYZE)
db.posts.find({ published: true }).sort({ createdAt: -1 }).explain("executionStats");

인덱스 설계 원칙

  • ESR 규칙: 복합 인덱스는 Equality(등호) → Sort(정렬) → Range(범위) 순서로 필드를 배치하는 것이 최적다.
  • 선택도(Selectivity): 값의 종류가 많은 필드(email, _id 등)에 인덱스를 생성하면 효과가 큽니다. published(true/false)처럼 카디널리티가 낮은 필드 단독 인덱스는 효과가 적다.
  • 과도한 인덱스 주의: 인덱스가 많을수록 INSERT/UPDATE/DELETE 성능이 저하된다. 실제 쿼리 패턴을 분석 후 필요한 것만 생성하자.
explain()로 인덱스 사용 여부 확인 .explain("executionStats") 결과에서 COLLSCAN이 보이면 인덱스를 타지 않는 것다. IXSCAN이 나와야 인덱스를 사용하는 최적화된 쿼리다. totalDocsExamined 값이 nReturned와 가까울수록 효율적인 쿼리다.

Aggregation Pipeline — $match, $group, $lookup

Aggregation Pipeline은 MongoDB의 가장 강력한 기능 중 하나다. 여러 단계(Stage)를 파이프라인으로 연결해 데이터를 필터링, 그룹화, 조인, 변환하는 복잡한 분석 쿼리를 수행할 수 있다. SQL의 GROUP BY, JOIN, HAVING에 해당하는 작업을 모두 처리한다.

$match + $group + $sort — 그룹 집계

카테고리별 포스트 수와 총 조회수js
db.posts.aggregate([
  { $match: { published: true } },             // 필터
  { $group: {
      _id: { $arrayElemAt: ["$tags", 0] },     // 첫 번째 태그로 그룹
      postCount: { $sum: 1 },
      totalViews: { $sum: "$meta.views" },
      avgViews: { $avg: "$meta.views" }
  }},
  { $sort: { totalViews: -1 } },
  { $limit: 10 }
]);

$lookup — SQL JOIN과 유사한 컬렉션 조인

$lookup + $unwind + $projectjs
db.posts.aggregate([
  { $match: { published: true } },
  { $lookup: {
      from: "users",
      localField: "author.id",
      foreignField: "_id",
      as: "authorInfo"
  }},
  { $unwind: "$authorInfo" },
  { $project: {
      title: 1,
      "authorInfo.name": 1,
      "authorInfo.email": 1,
      views: "$meta.views",
      createdAt: 1
  }},
  { $sort: { createdAt: -1 } },
  { $limit: 20 }
]);

$facet — 여러 집계를 한 번에

$facet으로 월별 통계 + 상위 포스트 동시 조회js
db.posts.aggregate([
  { $match: { published: true } },
  { $facet: {
    "byMonth": [
      { $group: { _id: { $month: "$createdAt" }, count: { $sum: 1 } } },
      { $sort: { "_id": 1 } }
    ],
    "topPosts": [
      { $sort: { "meta.views": -1 } },
      { $limit: 5 },
      { $project: { title: 1, views: "$meta.views" } }
    ]
  }}
]);
주요 Aggregation Stage 요약 $match(필터) → $group(집계) → $sort(정렬) → $limit/$skip(페이징) → $project(필드 선택) → $lookup(JOIN) → $unwind(배열 펼치기) → $addFields(필드 추가) → $facet(다중 집계) 순서로 이해하면 복잡한 파이프라인도 쉽게 구성할 수 있다.

Mongoose로 Node.js 연동 — 스키마, 모델, 쿼리

Mongoose는 Node.js에서 MongoDB를 사용할 때 가장 많이 쓰이는 ODM(Object Document Mapper) 라이브러리다. 스키마 정의, 유효성 검사, 미들웨어, populate(참조 조인) 등 강력한 기능을 제공한다.

설치bash
npm install mongoose

DB 연결 모듈

db.js — 연결 설정js
import mongoose from 'mongoose';

export async function connectDB() {
  await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb');
  console.log('MongoDB 연결 성공');
}

스키마 및 모델 정의

models/Post.js — 스키마·인덱스·미들웨어js
// models/Post.js
const postSchema = new mongoose.Schema({
  title: { type: String, required: true, maxlength: 500 },
  content: { type: String, required: true },
  author: {
    id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
    name: { type: String, required: true }
  },
  tags: [{ type: String }],
  meta: {
    views: { type: Number, default: 0 },
    likes: { type: Number, default: 0 }
  },
  published: { type: Boolean, default: false },
  deletedAt: Date
}, {
  timestamps: true  // createdAt, updatedAt 자동 생성
});

// 인덱스 정의
postSchema.index({ published: 1, createdAt: -1 });
postSchema.index({ tags: 1 });
postSchema.index({ title: 'text', content: 'text' });

// 미들웨어 (소프트 삭제)
postSchema.pre('find', function() {
  this.where({ deletedAt: null });
});

export const Post = mongoose.model('Post', postSchema);

CRUD 사용 예시

생성·조회·populate·업데이트js
// 생성
const post = await Post.create({ title: '제목', content: '내용', author: { id, name } });

// 조회 (페이징)
const posts = await Post.find({ published: true })
  .sort({ createdAt: -1 })
  .limit(20)
  .skip((page - 1) * 20)
  .lean();  // 일반 JS 객체로 반환 (성능 향상)

// populate — 참조 도큐먼트 조인
const post = await Post.findById(id).populate('author.id', 'name email avatar');

// 업데이트
await Post.findByIdAndUpdate(id,
  { $set: { title: '수정 제목' }, $inc: { 'meta.views': 1 } },
  { new: true }  // 업데이트된 도큐먼트 반환
);
.lean() 사용으로 성능 향상 Mongoose는 기본적으로 쿼리 결과를 Mongoose Document 인스턴스(getter, setter, 메서드 포함)로 반환한다. 단순 조회·직렬화만 하는 경우 .lean()을 추가하면 일반 JavaScript 객체로 반환되어 메모리 사용량과 처리 시간을 크게 줄일 수 있다.

Python PyMongo 연동

Python에서 MongoDB를 사용할 때는 공식 드라이버인 PyMongo를 사용한다. 비동기 환경에서는 Motor 라이브러리를 사용할 수 있다.

설치bash
pip install pymongo python-dotenv
PyMongo 기본 CRUD 및 Aggregationpython
from pymongo import MongoClient, DESCENDING
from bson import ObjectId
from datetime import datetime
import os

# 연결
client = MongoClient(os.getenv("MONGODB_URI", "mongodb://localhost:27017/"))
db = client["mydb"]
posts = db["posts"]

# 삽입
result = posts.insert_one({
    "title": "MongoDB Python 가이드",
    "tags": ["Python", "MongoDB"],
    "meta": {"views": 0},
    "published": True,
    "createdAt": datetime.utcnow()
})
print(f"삽입된 ID: {result.inserted_id}")

# 조회 (정렬 + 페이징)
cursor = posts.find(
    {"published": True},
    {"title": 1, "tags": 1, "createdAt": 1}
).sort("createdAt", DESCENDING).limit(20)

for post in cursor:
    print(post["title"])

# 업데이트
posts.update_one(
    {"_id": ObjectId("..")},
    {"$inc": {"meta.views": 1}, "$set": {"updatedAt": datetime.utcnow()}}
)

# Aggregation
pipeline = [
    {"$match": {"published": True}},
    {"$group": {"_id": None, "total": {"$sum": 1}, "avgViews": {"$avg": "$meta.views"}}}
]
result = list(posts.aggregate(pipeline))
비동기 Python — Motor 라이브러리 FastAPI나 asyncio 기반 서비스에서는 PyMongo 대신 pip install motor로 비동기 드라이버를 사용하자. API는 PyMongo와 거의 동일하며 await만 추가하면 된다: result = await collection.find_one({"_id": ObjectId(id)})

운영 팁 — 성능 최적화와 Atlas 클라우드

MongoDB Atlas — 무료 클라우드 시작하기

MongoDB Atlas는 공식 클라우드 데이터베이스 서비스로, 무료 티어(M0, 512MB)를 제공한다. AWS, GCP, Azure 중 원하는 리전을 선택해 배포할 수 있으며, 자동 백업, 모니터링, 보안 설정이 기본 제공된다. 개인 프로젝트나 스타트업의 초기 서비스에 적합한다.

Atlas Connection String 형식bash
# Atlas 연결 문자열 (환경변수로 관리)
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/mydb?retryWrites=true&w=majority

성능 최적화 체크리스트

1

explain()으로 느린 쿼리 찾기

프로파일러(db.setProfilingLevel(1))를 활성화하거나 .explain("executionStats")로 쿼리 플랜을 분석한다. COLLSCAN이 나오면 인덱스 추가를 검토하자.

2

.lean() 사용으로 Mongoose 오버헤드 제거

읽기 전용 조회에 .lean()을 추가하면 Mongoose Document 변환을 건너뛰어 쿼리 성능이 최대 30% 향상된다.

3

projection으로 필요한 필드만 반환

{ title: 1, tags: 1, _id: 0 }처럼 필요한 필드만 지정하면 네트워크 전송량과 메모리 사용량을 줄다. 특히 content처럼 큰 필드는 목록 조회에서 제외하자.

4

배치 작업에 insertMany/bulkWrite 활용

대량 데이터 처리 시 insertOne을 반복 호출하는 대신 insertMany나 bulkWrite를 사용하면 네트워크 왕복 횟수가 줄어 성능이 크게 향상된다.

5

TTL 인덱스로 세션/로그 자동 정리

sessions, logs, temp_data 컬렉션에 TTL 인덱스를 설정하면 만료된 데이터가 자동 삭제되어 별도 정리 스크립트가 필요 없다.

백업 및 복원

mongodump / mongorestorebash
# 백업 (전체 DB)
mongodump --uri="mongodb://localhost:27017" --out=./backup/

# 특정 컬렉션만 백업
mongodump --uri="mongodb://localhost:27017/mydb" --collection=posts --out=./backup/

# 복원
mongorestore --uri="mongodb://localhost:27017" ./backup/
주의 — ObjectId 비교 시 타입 변환 필수 MongoDB의 _id는 ObjectId 타입다. 문자열 "67a1b2c3.."와 ObjectId("67a1b2c3..")는 서로 다릅니다. Node.js에서는 반드시 new ObjectId(idString) 또는 Mongoose에서는 Types.ObjectId(idString)으로 변환 후 쿼리하자. 문자열 그대로 비교하면 항상 결과가 없다.
MongoDB 핵심 정리 MongoDB는 유연한 스키마, BSON 도큐먼트 저장, 수평 확장이 특징인 NoSQL 데이터베이스다. 컬렉션/도큐먼트 구조로 데이터를 관리하며, CRUD 연산부터 강력한 Aggregation Pipeline까지 다양한 쿼리를 지원한다. Node.js에서는 Mongoose ODM으로 스키마 정의, 유효성 검사, 미들웨어를 활용하고, Python에서는 PyMongo로 직관적인 API를 사용할 수 있다. 인덱스 설계(복합 인덱스, TTL), .lean() 활용, projection으로 성능을 최적화하고, MongoDB Atlas를 통해 클라우드 운영까지 쉽게 시작할 수 있다. 스키마 변경이 잦거나 계층형 데이터를 다루는 프로젝트라면 MongoDB가 최선의 선택이다.
MongoDB NoSQL Mongoose Aggregation 인덱스 데이터베이스
junetapa
junetapa
AI 도구를 직접 써보고 솔직한 경험을 공유하는 개발자.
Twitter Facebook URL 복사