느린 MongoDB 집계 파이프라인을 프로파일링하고 최적화하는 방법

느린 집계 파이프라인을 진단하는 방법을 학습하여 MongoDB 성능을 마스터하세요. 이 가이드는 MongoDB 프로파일러와 `.explain('executionStats')` 메서드를 활성화하고 사용하여 복잡한 단계 내의 병목 현상을 정확히 찾아내는 방법을 자세히 설명합니다. `$match` 및 `$sort`에 대한 최적의 인덱싱과 `$lookup`의 효율적인 사용에 중점을 두어 데이터 변환 속도를 획기적으로 높이는 실행 가능한 튜닝 전략을 알아보세요.

37 조회수

느린 MongoDB 애그리게이션 파이프라인 프로파일링 및 최적화 방법

MongoDB의 애그리게이션 프레임워크는 데이터베이스 내에서 정교한 데이터 변환, 그룹화 및 분석을 수행하는 강력한 도구입니다. 하지만 여러 단계(stages), 대규모 데이터 세트 또는 비효율적인 연산자를 포함하는 복잡한 파이프라인은 심각한 성능 병목 현상을 유발할 수 있습니다. 쿼리가 느려질 때, 시간이 어디에 소요되는지 이해하는 것은 최적화에 결정적인 요소입니다. 이 가이드는 MongoDB의 내장된 프로파일링 도구를 사용하여 애그리게이션 단계 내의 속도 저하 지점을 정확히 찾아내는 방법과 최대 효율성을 위해 튜닝할 수 있는 실행 가능한 단계를 자세히 설명합니다.

프로파일링은 성능 튜닝의 핵심입니다. 데이터베이스 프로파일러를 활성화함으로써 느린 작업에 대한 실행 통계를 캡처할 수 있으며, 모호한 성능 문제를 인덱싱 또는 쿼리 재작성을 통해 해결할 수 있는 구체적이고 측정 가능한 문제로 전환할 수 있습니다.

MongoDB 프로파일러 이해

MongoDB 프로파일러는 데이터베이스 작업의 실행 세부 정보를 기록합니다. 여기에는 find, update, delete 작업과, 이 가이드에서 가장 중요한 aggregate 명령이 포함됩니다. 프로파일러는 작업에 걸린 시간, 소비한 리소스, 그리고 대기 시간에 가장 많이 기여한 단계를 기록합니다.

프로파일링 수준 활성화 및 구성

프로파일링을 시작하기 전에, 프로파일러가 활성화되어 있고 필요한 데이터를 캡처하는 수준으로 설정되어 있는지 확인해야 합니다. 프로파일링 수준은 0(비활성화)부터 2(모든 작업 로깅)까지 설정할 수 있습니다.

수준 설명
0 프로파일러가 비활성화됩니다.
1 slowOpThresholdMs 설정보다 오래 걸리는 작업을 로깅합니다.
2 데이터베이스에 대해 실행된 모든 작업을 로깅합니다.

프로파일러 수준을 설정하려면 db.setProfilingLevel() 명령을 사용합니다. 과도한 디스크 I/O를 방지하기 위해 성능 테스트 중에는 일반적으로 수준 1 또는 2를 일시적으로 사용하는 것이 좋습니다.

예시: 프로파일러를 수준 1로 설정 (100ms보다 느린 작업 로깅)

// 데이터베이스에 연결: use myDatabase
db.setProfilingLevel(1, { slowOpThresholdMs: 100 })

// 설정 확인
db.getProfilingStatus()

모범 사례: 모든 작업을 로깅하면 쓰기 성능에 심각한 영향을 미칠 수 있으므로, 프로덕션 시스템에서 프로파일러를 수준 2로 무기한 두어서는 안 됩니다.

프로파일링된 애그리게이션 데이터 보기

프로파일링된 작업은 프로파일링하는 데이터베이스 내의 system.profile 컬렉션에 저장됩니다. 이 컬렉션을 쿼리하여 최근 느린 애그리게이션을 찾을 수 있습니다.

느린 애그리게이션 쿼리를 찾으려면, op 필드가 'aggregate'이고 실행 시간(millis)이 설정한 임계값을 초과하는 결과를 필터링합니다.

// 지난 한 시간 동안 느렸던 모든 애그리게이션 작업 찾기
db.system.profile.find(
  {
    op: 'aggregate',
    millis: { $gt: 100 } // 100ms보다 느린 작업
  }
).sort({ ts: -1 }).limit(5).pretty()

애그리게이션 파이프라인 실행 세부 정보 분석

프로파일러의 출력은 매우 중요합니다. 느린 애그리게이션 문서를 검토할 때, 특히 결과 내의 planSummary와 더 중요하게는 stages 배열을 찾아보십시오.

.explain('executionStats') 상세 출력 활용

프로파일러는 과거 데이터를 캡처하지만, .explain('executionStats')를 사용하여 애그리게이션을 실행하면 현재 데이터 세트에서 MongoDB가 파이프라인을 어떻게 실행했는지에 대한 실시간의 세부적인 정보를 제공하며, 단계별 타이밍도 포함합니다.

Explain 사용 예시:

db.collection('sales').aggregate([
  { $match: { status: 'A' } },
  { $group: { _id: '$customerId', total: { $sum: '$amount' } } }
]).explain('executionStats');

출력에서 stages 배열은 파이프라인의 각 연산자를 자세히 설명합니다. 각 단계에 대해 다음을 확인하십시오.

  • executionTimeMillis: 해당 특정 단계를 실행하는 데 소요된 시간.
  • nReturned: 다음 단계로 전달된 문서의 수.
  • totalKeysExamined / totalDocsExamined: I/O 비용을 나타내는 지표.

executionTimeMillis가 매우 높거나, 반환하는 문서보다 훨씬 더 많은 문서를 검사하는(totalDocsExamined) 단계가 주요 최적화 대상입니다.

느린 애그리게이션 단계 최적화를 위한 전략

프로파일링을 통해 병목 현상 단계(예: $match, $lookup, 또는 정렬 단계)가 확인되면, 대상에 맞춘 최적화 기술을 적용할 수 있습니다.

1. 초기 필터링 최적화 ($match)

가능하다면 $match 단계는 항상 파이프라인의 첫 번째 단계여야 합니다. 조기에 필터링하면 이후의 리소스 집약적인 단계(예: $group 또는 $lookup)가 처리해야 하는 문서의 수가 줄어듭니다.

인덱싱의 역할:
초기 $match 단계가 느리다면, 필터에 사용된 필드에 인덱스가 누락되었을 가능성이 매우 높습니다. $match에 사용되는 필드를 인덱스가 커버하도록 확인하십시오.

$match 단계에 인덱스가 없는 필드가 포함되면, 해당 단계는 전체 컬렉션 스캔을 수행할 수 있으며, 이는 Explain 출력에서 높은 totalDocsExamined로 명시적으로 표시됩니다.

2. $lookup (조인) 효율적으로 활용하기

$lookup 단계는 종종 가장 느린 구성 요소입니다. 이는 다른 컬렉션에 대해 사실상 안티 조인(anti-join)을 수행합니다.

  • 외래 키 인덱싱: 외래(조회된) 컬렉션에서 조인하는 필드에 인덱스가 있는지 확인하십시오. 이는 내부 조회 프로세스의 속도를 크게 높여줍니다.
  • 조회 전 필터링: 가능하면 $lookup 이전에 $match 단계를 적용하여 필요한 문서에 대해서만 조인이 수행되도록 하십시오.

3. 비용이 많이 드는 정렬 ($sort) 처리

문서를 정렬하는 것은 특히 대규모 결과 세트에서 계산 비용이 많이 듭니다. MongoDB는 인덱스 접두사가 쿼리 필터와 일치하고 정렬 순서가 인덱스 정의와 일치하는 경우에만 정렬에 인덱스를 사용할 수 있습니다.

$sort의 주요 최적화:
$sort 단계의 비용이 많이 드는 것으로 보이면, 필터와 필요한 정렬 순서에 일치하는 커버드 인덱스(covered index)를 생성해 보십시오. 예를 들어, { status: 1 }로 필터링한 다음 { date: -1 }로 정렬하는 경우, { status: 1, date: -1 } 인덱스는 MongoDB가 비용이 많이 드는 인메모리 정렬 없이도 필요한 순서로 문서를 검색할 수 있도록 합니다.

4. $project를 사용하여 데이터 이동 최소화

$project 단계를 전략적으로 사용하여 파이프라인 아래로 전달되는 데이터의 양을 줄이십시오. 후반 단계에서 몇 가지 필드만 필요한 경우, 파이프라인 초기에 $project를 사용하여 불필요한 필드와 임베디드 문서를 버리십시오. 문서 크기가 작아지면 파이프라인 단계 간에 이동하는 데이터가 줄어들고 잠재적으로 메모리 활용도가 향상됩니다.

5. 인덱스를 사용할 수 없는 비용이 많이 드는 단계 피하기

$unwind와 같은 단계는 많은 새로운 문서를 생성하여 처리 오버헤드를 급격히 증가시킬 수 있습니다. 때로는 필요하지만, $unwind에 대한 입력이 가능한 한 작도록 보장하십시오. 마찬가지로, 인덱스 지원 없이 계산이나 복잡한 표현식에 의존하는 등 데이터 세트를 완전히 재평가하도록 강제하는 단계는 최소화해야 합니다.

요약 및 다음 단계

MongoDB 애그리게이션 파이프라인을 프로파일링하고 최적화하려면 체계적이고 증거 기반의 접근 방식이 필요합니다. 내장 프로파일러(db.setProfilingLevel)를 활용하고 상세 실행 통계(.explain('executionStats'))를 실행함으로써, 복잡한 성능 문제를 해결 가능한 단계로 바꿀 수 있습니다.

최적화 워크플로우:

  1. 프로파일링 활성화: 수준 1을 설정하고 slowOpThresholdMs를 정의합니다.
  2. 쿼리 실행: 느린 애그리게이션 파이프라인을 실행합니다.
  3. 프로파일링된 데이터 분석: 가장 많은 시간을 소비하는 특정 단계를 식별합니다.
  4. 세부 설명: 문제가 되는 파이프라인에 대해 .explain('executionStats')를 사용합니다.
  5. 튜닝: 필요한 인덱스를 생성하고, 단계를 재정렬하며(필터링을 먼저), 비용이 많이 드는 연산자에 전달되는 데이터를 단순화합니다.

지속적인 모니터링은 새로 추가된 기능이나 증가된 데이터 볼륨으로 인해 해결했던 성능 문제가 다시 발생하지 않도록 보장합니다.