| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- Operations
- API
- Kubernetes
- version-control
- database
- reliability
- NextJS
- HTTP
- Debugging
- 버전관리
- JavaScript
- auth
- PostgreSQL
- react
- Git
- 성능
- observability
- Performance
- aws
- frontend
- DevOps
- backend
- Ops
- architecture
- Infra
- Security
- CSS
- CI
- SRE
- web
- Today
- Total
고민보단 실천을
MongoDB 성능/특성 정리: 인덱스/Working Set/aggregation으로 느린 조회 잡기 본문
MongoDB 성능/특성 정리: 인덱스/Working Set/aggregation으로 느린 조회 잡기
이 글은 MongoDB를 성능/운영 관점에서 길게 정리한 개인 노트입니다. 목표는 "지금 느린 이유"를 증거로 좁히고, 재발을 줄이는 체크리스트를 갖추는 것입니다.
전제: 성능은 DB만으로 결정되지 않습니다. 쿼리 패턴, 데이터 분포, 인덱스/스키마, 애플리케이션 트랜잭션 경계, 인프라(IO/네트워크)가 함께 결정합니다. 그래서 이 글은 '성능 모델(어디서 시간이 쓰이나) -> 설계/쿼리 -> 설정 -> 운영' 순서로 정리합니다.
이 DB를 언제 선택하나
정답은 항상 케이스 바이 케이스지만, 선택을 빠르게 만들기 위해 기준을 먼저 둡니다. 아래에 해당이 많을수록 이 DB를 고려할 만합니다.
- 문서 모델이 자연스럽고 스키마 변화가 잦은 도메인
- 조회 패턴이 명확하고 인덱스로 대부분 커버 가능한 서비스
- 샤딩으로 수평 확장을 염두에 두는 경우(키 설계가 필수)
성능 모델: 어디서 시간이 쓰이나
MongoDB 성능의 핵심은 working set(자주 접근하는 데이터/인덱스)이 메모리에 남아 있느냐입니다. WiredTiger 캐시가 충분하면 낮은 지연을 유지하지만, 캐시에서 밀려 디스크를 왕복하면 지연이 크게 증가합니다.
aggregation pipeline은 stage에 따라 메모리를 많이 쓰거나 디스크로 spill 될 수 있습니다. 운영에서 중요한 건 어떤 쿼리/집계가 스캔을 많이 하는지, 인덱스가 얼마나 앞단에서 데이터를 줄이는지를 explain으로 확인하는 루프입니다.
MongoDB는 조인이 약하므로(가능은 하지만 비용 큼) 데이터 모델링이 곧 성능입니다. 즉, 쿼리 최적화보다 모델링/인덱스가 먼저입니다.
핵심 용어 빠른 사전
성능/장애 분석에서 자주 쓰는 단어는 팀마다 다르게 이해되기 쉽습니다. 용어를 짧게 정의하고, 어떤 지표/증상과 연결되는지 같이 적어둡니다.
| 용어 | 설명 | 실무에서 연결되는 것 |
|---|---|---|
COLLSCAN | 컬렉션 전체 스캔 | 지연 급증/CPU/디스크 IO 상승, 인덱스 없을 때 흔함 |
IXSCAN | 인덱스 스캔 | 스캔량이 줄어 지연 안정화, 다만 반환/정렬 패턴에 따라 추가 비용 |
working set | 자주 접근하는 데이터/인덱스 집합 | 캐시에서 밀리면 갑자기 느려짐 |
WiredTiger cache | 스토리지 엔진 캐시 | 메모리 정책이 전체 지연에 직결 |
spill | 메모리 부족으로 디스크 사용 | aggregation/sort에서 디스크 IO로 지연 증가 |
oplog | 복제 로그(Replica Set) | replication lag, 장애/복구와 연결 |
옵션/핵심 요소(3~6)
자주 부딪히는 핵심 요소를 표로 먼저 고정해 두면, 장애/튜닝 때 팀 커뮤니케이션이 빨라집니다.
| 항목 | 의미 | 언제 쓰는지(실무 상황) |
|---|---|---|
explain("executionStats") | 실행 계획/실행 통계 확인 | 인덱스 사용/스캔량/읽은 문서 수를 확인할 때 |
Index | 조회 패턴을 지원하는 구조 | COLLSCAN을 피하고 지연을 안정화할 때 |
Working Set | 자주 접근하는 데이터/인덱스 집합 | 메모리 압박 시 지연 급증 원인을 설명할 때 |
Aggregation | 집계 파이프라인 | 보고서/대시보드/ETL성 쿼리를 만들 때 |
Sharding Key | 수평 분산의 기준 키 | 확장/핫스팟을 좌우하는 설계 포인트 |
실무 튜닝 포인트(설정/옵션별)
설정에는 정답이 없습니다. 워크로드(읽기/쓰기/동시성/데이터 크기) 기준으로 관측하고 조정합니다. 아래는 실무에서 가장 자주 만지는 레버를 '항목별'로 정리한 것입니다.
wiredTigerCacheSizeGB
의미: WiredTiger 캐시 크기
언제 만지나: working set이 커져 캐시 미스가 늘 때
주의/트레이드오프: OS 캐시/다른 프로세스 메모리와 경쟁
readConcern
의미: 읽기 일관성 수준
언제 만지나: 기능별 일관성/지연 트레이드오프를 설계할 때
주의/트레이드오프: 강한 일관성은 지연/부하 증가 가능
writeConcern
의미: 쓰기 확인 수준
언제 만지나: 내구성 요구를 반영할 때
주의/트레이드오프: 확인 레벨이 높아지면 지연 증가
journalCommitInterval
의미: 저널 커밋 간격
언제 만지나: 내구성/지연을 조정해야 할 때
주의/트레이드오프: 값 변경은 장애 시 유실 가능성에 영향
maxTimeMS
의미: 쿼리 타임아웃
언제 만지나: 폭주하는 쿼리로 시스템이 잠기는 걸 막을 때
주의/트레이드오프: 너무 짧으면 정상 쿼리도 실패
allowDiskUse
의미: aggregation에서 디스크 spill 허용
언제 만지나: 메모리 제한을 넘는 집계가 필요할 때
주의/트레이드오프: 디스크 IO로 지연 증가, 근본 해결(인덱스/필터)과 분리
ttl index
의미: TTL 기반 자동 만료
언제 만지나: 로그/세션 등 보관 기간이 명확한 컬렉션
주의/트레이드오프: TTL 정리는 주기적이라 즉시 삭제가 아님
connection pool
의미: 드라이버 연결 풀
언제 만지나: 연결 폭증/재연결 비용을 줄일 때
주의/트레이드오프: 풀 크기를 무작정 늘리면 서버 리소스를 잠식
쿼리/스키마 설계에서 성능이 갈리는 지점
인덱스 설계는 필터 조건 + 정렬 + 반환 형태를 기준으로 합니다. 정렬이 포함된 쿼리는 인덱스가 정렬까지 커버하지 못하면 in-memory sort가 발생하고, 데이터가 크면 디스크로 spill 될 수 있습니다.
aggregation은 가능한 앞단에서 $match로 문서를 줄이고, 필요한 필드만 남기는($project) 패턴이 기본입니다. 그리고 샤딩을 고려한다면 샤딩 키가 핫스팟을 만들지 않도록 분포를 설계해야 합니다.
// 인덱스 예시
db.orders.createIndex({ userId: 1, createdAt: -1 })
// 실행 통계 확인
db.orders.find({ userId: 42 }).sort({ createdAt: -1 }).limit(50).explain("executionStats")// aggregation 최적화 방향: 앞단에서 줄이고, 필요 필드만
db.events.aggregate([
{ $match: { tenantId: 10, createdAt: { $gte: ISODate("2026-02-01") } } },
{ $project: { createdAt: 1, type: 1 } },
{ $group: { _id: "$type", cnt: { $sum: 1 } } }
], { allowDiskUse: true })진단 명령/쿼리 모음(바로 실행용)
장애 때는 문서 찾을 시간이 없습니다. 아래는 현장에서 자주 쓰는 '첫 5분' 진단 명령/쿼리만 모은 것입니다. 환경/권한에 따라 일부는 제한될 수 있습니다.
// 느린 쿼리(대표 1개) 실행 통계 확인
db.orders.find({ userId: 42 }).sort({ createdAt: -1 }).limit(50).explain("executionStats")
// 현재 작업/락 힌트(운영 영향 가능, 권한 필요)
db.currentOp()
// 서버 상태(캐시/메모리/디스크 등 큰 그림)
db.serverStatus()자주 하는 실수(운영/튜닝)
실수 체크리스트:
- 인덱스 없이 find/aggregation을 늘려 COLLSCAN을 방치한다
- 정렬이 포함된 쿼리에서 인덱스가 정렬까지 커버하는지 확인하지 않는다
- aggregation에서 $match를 뒤에 두거나 불필요한 필드를 끝까지 들고 간다
- working set이 메모리에 안 올라오는데 캐시/메모리 정책을 재평가하지 않는다
- 샤딩을 고려하면서도 샤딩 키 분포(핫스팟)를 검증하지 않는다
- allowDiskUse를 근본 해결 없이 '항상 켜두는' 습관을 만든다
운영/모니터링 체크리스트
운영에서는 캐시/페이지 faults(working set), 상위 쿼리(시간/스캔량), 인덱스 사용률, replication lag(Replica Set), 디스크 IO/큐를 우선으로 봅니다.
갑자기 느려졌다는 이슈는 working set이 캐시에서 밀려난 경우가 흔합니다. 이때는 인덱스/데이터 크기와 캐시/메모리 정책을 같이 재평가해야 합니다.
공통 체크리스트:
- 느린 쿼리 top N을 지속 수집(시간/호출수/총시간)
- 지연(p95/p99)과 오류율을 애플리케이션 지표와 함께 본다
- 캐시/메모리/디스크 IO 중 무엇이 병목인지 먼저 분리한다
- 복제/HA를 쓰면 일관성 요구가 높은 기능을 분리한다
- 튜닝/변경 후에는 동일 조건으로 재측정해 회귀를 막는다
트러블슈팅 루틴(순서 고정)
장애 때는 선택지가 너무 많아서 흔히 길을 잃습니다. 아래 순서를 팀 표준으로 정해두면, 원인 추적이 빨라지고 '감으로 설정만 만지는' 일을 줄일 수 있습니다.
1) 느린 쿼리를 잡는다(로그/프로파일러)
2) explain("executionStats")로 스캔량/인덱스 사용을 본다
3) 정렬/집계가 메모리를 먹는지 확인한다(allowDiskUse 포함)
4) 필요한 인덱스를 추가하거나 쿼리를 리라이트한다
5) working set이 캐시에 올라오도록 데이터/인덱스 크기와 캐시를 재평가한다
6) 샤딩/키 설계 문제면 핫스팟/분포를 확인한다문제 상황(정확히 1개)
상황 -> 집계 대시보드 쿼리가 데이터가 쌓이면서 점점 느려지고 결국 타임아웃이 난다.
원인 -> 초기에는 메모리에서 정렬/그룹이 가능했지만 데이터가 커지며 한계를 넘어 디스크 spill 또는 풀스캔이 발생했다.
해결 -> 앞단 $match/$project로 데이터를 줄이고 인덱스를 추가하며, 필요하면 사전 집계(rollup)나 모델을 변경한다.
예방 팁 -> 집계 쿼리는 성장 후 데이터 규모를 기준으로 리허설하고 스캔량/정렬/메모리 사용을 지표로 운영한다.
참고/출처
정확한 동작/버전별 차이는 공식 문서를 기준으로 확인합니다. 실무에서는 버전 차이가 곧 성능/장애 차이입니다.
'DB' 카테고리의 다른 글
| Oracle 성능/특성 정리: 실행 계획/통계/UNDO와 ORA-01555(snapshot too old) (0) | 2026.03.08 |
|---|---|
| MySQL(InnoDB) 성능/특성 정리: 인덱스/Buffer Pool/잠금으로 성능을 설명하기 (0) | 2026.03.07 |
| MariaDB 성능/특성 정리: MySQL 호환 + 옵티마이저/복제 운영 포인트 (0) | 2026.03.07 |
| Elasticsearch/OpenSearch 성능/특성 정리: shards/heap/refresh/mapping으로 느림을 설명하기 (0) | 2026.03.07 |
| Amazon DynamoDB 성능/특성 정리: 키 설계로 핫 파티션/비용/스로틀링 줄이기 (0) | 2026.03.06 |
