Contents

Mongodb Aggregation 최적화

mongodb aggregation을 사용하다 보면 최적화에 대한 고민을 하지 않을 수 없다.

공식 문서에서는 어떤 방식으로 최적화를 안내하고 있을까?

https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/

실행 엔진에서의 최적화 수행

굳이 사용자가 최적화를 하지 않아도 데이터베이스 레벨에서 최적화를 적재 적소에 수행해준다면 아주 감사한 일이다.

MongoDB의 실행 엔진은 다음 aggregation 쿼리에 대한 최적화를 자동으로 수행해준다.

  1. Projection 연산 최적화 : 데이터베이스가 자동으로 필요 없는 필드를 걸러서 쿼리를 수행하기 때문에, 파이프라인 중간에 필드의 개수를 줄이기 위해 $project 연산을 수행하는 것은 성능 향상에 사실상 거의 도움이 되지 않는다.
  2. 파이프라인 순서 최적화 : aggregation 파이프라인에 $match 단계 다음 $projection, $unset, $addFields, $set과 같은 연산이 온다면 몽고DB는 projection 단계에서 사용되지 않는 필드를 필터 단계에서 걸러버린다.
  3. 파이프라인 병합 최적화 : aggregation의 이전 단계로 특정 파이프라인 단계를 병합할 수 있다면, 실행 엔진이 이를 자동으로 최적화시켜준다. $sort이후 $limit 연산 수행을 하는 예시에서 $limit$sort에 병힙될 수 있는 예시가 있다.
  4. 슬롯 기반 쿼리 실행 엔진 파이프라인 최적화 : 몽고DB는 슬롯 기반의 쿼리 실행 엔진을 사용하여 특별한 조건이 충족되었을 떄 특정 파이프라인 단계들을 수행한다. 대부분의 경우 성능 향상과 CPU, 메모리 사용량을 줄이기 위함이다. $group, $lookup aggregation 들이 이 엔진을 사용한다.

Aggregation 성능 향상 방법

엔진에서 자동으로 수행해주는 최적화 말고, 쿼리를 설계하는 개발자는 어떤 일을 할 수 있을까?

인덱스 사용

먼저 인덱스이다. 일반 findOne, findAll과 같이 aggregation 또한 인덱스를 탈 수 있다.

이상적으로는 인덱스는 스테이지 쿼리를 커버할 수 있다. 커버된 쿼리는 인덱스가 일치하는 모든 다큐먼트를 리턴하기 때문에 높은 성능을 낼 수 있다.

예를 들어 $match, $sort, $group으로 구성된 파이프라인은 모든 스테이지에서 인덱스의 이점을 활용할 수 있다.

  • $match 쿼리에 대한 인덱스는 관련된 데이터를 효과적으로 식별할 수 있다.
  • 정렬 대상인 필드에 대한 인덱스는 $sort 쿼리에 대해 정렬된 순서의 데이터를 리턴할 수 있다.
  • $sort 순서와 일치하는 그룹핑 필드에 대한 인덱스는 $group 스테이지를 실행하기 위한 모든 필드 값을 리턴할 수 있다.

파이프라인이 인덱스를 사용하는지에 대한 여부를 확인하려면, 쿼리 플랜을 확인하고 IXSCAN이나 DISTINCT_SCAN 플랜이 있는지 확인하면 된다.

  • 어떤 경우에는 쿼리 플래너가 DISTINCT_SCAN 인덱스 계획을 사용한다.
  • DISTINCT_SCAN 은 인덱스 키 값에 단일 다큐먼트가 리턴되는 형태이다.
  • 키 값에 다수의 다큐먼트가 존재할 경우 DISTINCT_SCANIXSCAN보다 빠르게 수행된다.
  • 하지만 인덱스 스캔 인자에 따라 DISTINCT_SCANIXSCAN의 수행 시간이 달라질 수 있다.

때문에 aggregation의 초기 스테이지에서 인덱스를 사용하는 것이 중요하다.

파이프라인의 나중 단계에 존재하는, 다른 스테이지로부터 수정되지 않은 컬렉션 데이터를 받는 스테이지들 또한 최적화를 위해 인덱스를 사용할 수 있다. ($lookup, $graphLookup, $unionWith이 포함된다)

다큐먼트 필터

aggregation 연산이 컬렉션 다큐먼트들의 일부분만 필요하다면, 다큐먼트들에 대한 필터링을 먼저 수행하는 것이 좋다.

  • $match, $limit, $skip 스테이지들을 사용해서 파이프라인에 들어오는 다큐먼트들을 제한해라.
  • 가능하다면, $match를 파이프라인의 맨 처음에 두어서 컬렉션 내부의 일치하는 다큐먼트들을 가져올 때 인덱스를 사용할 수 있도록 해라.
  • 파이프라인의 맨 처음 $sort 다음에 오는 $match는 단일 sort 쿼리와 동일하기 때문에 인덱스를 사용할 수 있다.