MongoDB의 성능 분석, 쿼리 최적화, 서버 튜닝, 모니터링을 정리합니다.
쿼리 성능 분석
explain()
// 실행 계획 확인
db.users.find({ dept: "개발팀", salary: { $gte: 5000000 } }).explain("executionStats")
핵심 지표
| 지표 |
의미 |
목표 |
| totalDocsExamined |
검사한 문서 수 |
nReturned에 가까울수록 좋음 |
| totalKeysExamined |
검사한 인덱스 키 수 |
nReturned에 가까울수록 좋음 |
| executionTimeMillis |
실행 시간 |
작을수록 좋음 |
| stage: COLLSCAN |
전체 스캔 |
인덱스 추가 필요 |
| stage: IXSCAN |
인덱스 사용 |
좋음 |
비효율 쿼리 판별
// 비효율: 10000개 검사해서 10개 반환
// totalDocsExamined: 10000, nReturned: 10 → 인덱스 필요
// 효율: 10개 검사해서 10개 반환
// totalDocsExamined: 10, nReturned: 10 → 최적
쿼리 최적화 기법
1. 적절한 인덱스 생성
// ❌ COLLSCAN
db.orders.find({ user_id: "u123", status: "pending" }).sort({ created_at: -1 })
// ✅ 복합 인덱스 (ESR 규칙)
db.orders.createIndex({ user_id: 1, created_at: -1, status: 1 })
2. 프로젝션으로 필요한 필드만 조회
// ❌ 전체 문서 반환
db.users.find({ dept: "개발팀" })
// ✅ 필요한 필드만
db.users.find({ dept: "개발팀" }, { name: 1, email: 1, _id: 0 })
3. 커버링 인덱스 활용
// 인덱스에 포함된 필드만 조회하면 문서 접근 불필요
db.users.createIndex({ dept: 1, name: 1, salary: 1 })
db.users.find({ dept: "개발팀" }, { name: 1, salary: 1, _id: 0 })
// explain → stage: PROJECTION_COVERED (최고 성능)
4. $in 대신 여러 쿼리 또는 인덱스 활용
// 큰 $in 배열은 성능 저하 가능
db.users.find({ _id: { $in: [/* 수천 개 ID */] } })
// 배치 처리 권장
5. 대량 쓰기 최적화
// ❌ 개별 삽입
for (let i = 0; i < 10000; i++) {
db.logs.insertOne({ msg: `log ${i}` })
}
// ✅ 벌크 삽입
const bulk = db.logs.initializeUnorderedBulkOp()
for (let i = 0; i < 10000; i++) {
bulk.insert({ msg: `log ${i}` })
}
bulk.execute()
// ✅ insertMany
db.logs.insertMany(docs, { ordered: false })
서버 설정 튜닝
WiredTiger 캐시 (mongod.conf)
storage:
wiredTiger:
engineConfig:
# 캐시 크기 (기본: RAM의 50% - 1GB, 또는 256MB 중 큰 값)
cacheSizeGB: 4
연결 설정
net:
maxIncomingConnections: 65536
프로파일러 (슬로우 쿼리 기록)
// 프로파일러 활성화 (100ms 이상 기록)
db.setProfilingLevel(1, { slowms: 100 })
// 프로파일러 상태 확인
db.getProfilingStatus()
// 슬로우 쿼리 조회
db.system.profile.find().sort({ ts: -1 }).limit(10)
// 프로파일러 비활성화
db.setProfilingLevel(0)
모니터링
서버 상태
db.serverStatus() // 전체 서버 상태
db.serverStatus().connections // 연결 정보
db.serverStatus().opcounters // 명령 카운터
db.serverStatus().wiredTiger.cache // 캐시 상태
mongostat / mongotop
# 실시간 서버 통계 (1초 간격)
mongostat --uri="mongodb://admin:pass@localhost:27017"
# 컬렉션별 읽기/쓰기 시간
mongotop --uri="mongodb://admin:pass@localhost:27017"
핵심 모니터링 지표
| 지표 |
확인 방법 |
정상 범위 |
| 연결 수 |
db.serverStatus().connections |
maxConnections 이하 |
| 캐시 히트율 |
WiredTiger cache stats |
95% 이상 |
| 큐 대기 |
db.serverStatus().globalLock |
0에 가까울수록 |
| Oplog 크기 |
rs.printReplicationInfo() |
24시간 이상 보존 |
| 디스크 사용량 |
db.stats() |
여유 공간 20% 이상 |
| 슬로우 쿼리 |
system.profile |
최소화 |
컬렉션/DB 크기 확인
// 데이터베이스 크기
db.stats()
// 컬렉션 크기
db.users.stats()
// 사람이 읽기 쉬운 형태
db.users.stats({ scale: 1024 * 1024 }) // MB 단위
성능 체크리스트
| 단계 |
점검 항목 |
확인 방법 |
| 1 |
프로파일러로 슬로우 쿼리 식별 |
db.setProfilingLevel(1) |
| 2 |
explain으로 실행 계획 분석 |
COLLSCAN 여부 확인 |
| 3 |
인덱스 추가/최적화 |
ESR 규칙 적용 |
| 4 |
미사용 인덱스 제거 |
$indexStats |
| 5 |
스키마 설계 검토 |
Embedding vs Referencing |
| 6 |
WiredTiger 캐시 확인 |
히트율 95% 이상 |
| 7 |
연결 풀 설정 |
애플리케이션 커넥션 풀 |
| 8 |
Write Concern 적정성 |
용도에 맞는 수준 |
| 9 |
컬렉션 크기 확인 |
대용량 컬렉션 샤딩 검토 |
| 10 |
정기적 compact |
단편화 해소 |
// 컬렉션 단편화 해소 (운영 주의: 락 발생)
db.runCommand({ compact: "users" })
관련된 글 (mongodb > lecture-mongodb)