리포트 쿼리 하나가 aggregation 안에서 7초 걸려서 튜닝한 기록. MongoDB 3.4 기준.
컬렉션: events, 5천만 doc. 하루치 집계해서 이벤트 타입별 count를 내는 쿼리.
db.events.aggregate([
{ $match: { createdAt: { $gte: ISODate("2017-03-20"), $lt: ISODate("2017-03-21") } } },
{ $group: { _id: "$type", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]);
// 7.2 초
Explain 보니 $match가 인덱스 안 탐. 인덱스는 {createdAt: 1, userId: 1} 이었는데 쿼리에서 userId 없어서 prefix만 탄다고 하더라. 실제로는 index만 스캔해도 빠를텐데 옵티마이저가 전체 읽고 있었음.
조치:
1. {createdAt: 1, type: 1} 인덱스 추가. type이 group key라 covering 가능.
2. 3.4부터 추가된 $project + $expr 기능 검토했는데 이 케이스엔 불필요. 그냥 인덱스로 해결.
3. $match를 맨 앞에 두는건 기본중 기본. 원 쿼리도 맞음.
db.events.aggregate([
{ $match: { createdAt: { $gte: ISODate("2017-03-20"), $lt: ISODate("2017-03-21") } } },
{ $group: { _id: "$type", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
], { hint: { createdAt: 1, type: 1 } });
// 0.9 초
인덱스 커버 + hint 명시. 대략 8배 향상.
추가 팁:
allowDiskUse: true옵션. 메모리 100MB 넘는 aggregation은 이거 없으면 실패. 리포트 쿼리엔 필수- 3.4의
$facet: 한 번 순회로 여러 집계 결과를 배열로 받음. 대시보드 같은데 유용 $lookup(join)은 쓸만하긴 한데 크면 역시 느림. denormalize가 Mongo 철학에 맞음
MongoDB는 인덱스 설계가 실무 성능의 90%. RDBMS랑 똑같이 쿼리 패턴 보고 인덱스 짜야 함. 문서 DB라고 "아무거나 넣으면 된다"가 아님. 당연하지만 매번 상기해야 되는 포인트.