Mongodb Aggregation 최적화
mongodb aggregation을 사용하다 보면 최적화에 대한 고민을 하지 않을 수 없다.
공식 문서에서는 어떤 방식으로 최적화를 안내하고 있을까?
https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/
실행 엔진에서의 최적화 수행
굳이 사용자가 최적화를 하지 않아도 데이터베이스 레벨에서 최적화를 적재 적소에 수행해준다면 아주 감사한 일이다.
MongoDB의 실행 엔진은 다음 aggregation 쿼리에 대한 최적화를 자동으로 수행해준다.
- Projection 연산 최적화 : 데이터베이스가 자동으로 필요 없는 필드를 걸러서 쿼리를 수행하기 때문에, 파이프라인 중간에 필드의 개수를 줄이기 위해
$project
연산을 수행하는 것은 성능 향상에 사실상 거의 도움이 되지 않는다. - 파이프라인 순서 최적화 : aggregation 파이프라인에
$match
단계 다음$projection
,$unset
,$addFields
,$set
과 같은 연산이 온다면 몽고DB는 projection 단계에서 사용되지 않는 필드를 필터 단계에서 걸러버린다. - 파이프라인 병합 최적화 : aggregation의 이전 단계로 특정 파이프라인 단계를 병합할 수 있다면, 실행 엔진이 이를 자동으로 최적화시켜준다.
$sort
이후$limit
연산 수행을 하는 예시에서$limit
은$sort
에 병힙될 수 있는 예시가 있다. - 슬롯 기반 쿼리 실행 엔진 파이프라인 최적화 : 몽고DB는 슬롯 기반의 쿼리 실행 엔진을 사용하여 특별한 조건이 충족되었을 떄 특정 파이프라인 단계들을 수행한다. 대부분의 경우 성능 향상과 CPU, 메모리 사용량을 줄이기 위함이다.
$group
,$lookup
aggregation 들이 이 엔진을 사용한다.
Aggregation 성능 향상 방법
엔진에서 자동으로 수행해주는 최적화 말고, 쿼리를 설계하는 개발자는 어떤 일을 할 수 있을까?
인덱스 사용
먼저 인덱스이다. 일반 findOne
, findAll
과 같이 aggregation 또한 인덱스를 탈 수 있다.
이상적으로는 인덱스는 스테이지 쿼리를 커버할 수 있다. 커버된 쿼리는 인덱스가 일치하는 모든 다큐먼트를 리턴하기 때문에 높은 성능을 낼 수 있다.
예를 들어 $match
, $sort
, $group
으로 구성된 파이프라인은 모든 스테이지에서 인덱스의 이점을 활용할 수 있다.
$match
쿼리에 대한 인덱스는 관련된 데이터를 효과적으로 식별할 수 있다.- 정렬 대상인 필드에 대한 인덱스는
$sort
쿼리에 대해 정렬된 순서의 데이터를 리턴할 수 있다. $sort
순서와 일치하는 그룹핑 필드에 대한 인덱스는$group
스테이지를 실행하기 위한 모든 필드 값을 리턴할 수 있다.
파이프라인이 인덱스를 사용하는지에 대한 여부를 확인하려면, 쿼리 플랜을 확인하고 IXSCAN
이나 DISTINCT_SCAN
플랜이 있는지 확인하면 된다.
- 어떤 경우에는 쿼리 플래너가
DISTINCT_SCAN
인덱스 계획을 사용한다. DISTINCT_SCAN
은 인덱스 키 값에 단일 다큐먼트가 리턴되는 형태이다.- 키 값에 다수의 다큐먼트가 존재할 경우
DISTINCT_SCAN
은IXSCAN
보다 빠르게 수행된다. - 하지만 인덱스 스캔 인자에 따라
DISTINCT_SCAN
과IXSCAN
의 수행 시간이 달라질 수 있다.
때문에 aggregation의 초기 스테이지에서 인덱스를 사용하는 것이 중요하다.
파이프라인의 나중 단계에 존재하는, 다른 스테이지로부터 수정되지 않은 컬렉션 데이터를 받는 스테이지들 또한 최적화를 위해 인덱스를 사용할 수 있다. ($lookup
, $graphLookup
, $unionWith
이 포함된다)
다큐먼트 필터
aggregation 연산이 컬렉션 다큐먼트들의 일부분만 필요하다면, 다큐먼트들에 대한 필터링을 먼저 수행하는 것이 좋다.
$match
,$limit
,$skip
스테이지들을 사용해서 파이프라인에 들어오는 다큐먼트들을 제한해라.- 가능하다면,
$match
를 파이프라인의 맨 처음에 두어서 컬렉션 내부의 일치하는 다큐먼트들을 가져올 때 인덱스를 사용할 수 있도록 해라. - 파이프라인의 맨 처음
$sort
다음에 오는$match
는 단일 sort 쿼리와 동일하기 때문에 인덱스를 사용할 수 있다.