MongoDB에서 느린 쿼리 진단 및 해결: 실용적인 가이드

MongoDB에서 느린 쿼리를 진단하고 해결하는 기술을 마스터하세요. 이 실용적인 가이드는 데이터베이스 프로파일러를 사용하여 병목 현상을 식별하고 강력한 `explain()` 메서드를 활용하여 실행 계획을 분석하는 방법을 알려줍니다. 성능을 최적화하고 NoSQL 데이터베이스가 최고의 효율로 작동하도록 보장하기 위해 ESR 규칙 및 커버링 인덱스 생성과 같은 필수 인덱싱 전략을 학습합니다.

43 조회수

MongoDB에서 느린 쿼리 진단 및 해결: 실용 가이드

MongoDB는 유연성과 확장성으로 잘 알려져 있어 최신 애플리케이션을 위한 최고의 선택지입니다. 그러나 데이터 볼륨이 증가하거나 애플리케이션 패턴이 변경되면 쿼리가 느려져 사용자 경험과 애플리케이션 응답성에 영향을 미칠 수 있습니다. 느린 쿼리는 MongoDB 배포 관리에 있어 가장 흔한 운영상의 장애물 중 하나입니다.

이 가이드는 비효율적인 쿼리로 인해 발생하는 성능 병목 현상을 식별, 분석 및 해결하기 위한 구조화된 접근 방식을 제공합니다. explain()과 같은 내장 MongoDB 도구를 활용하고 최적의 성능 달성을 위한 적절한 인덱싱의 중요한 역할을 자세히 살펴보겠습니다.

쿼리가 느려지는 이유 이해하기

진단으로 넘어가기 전에, MongoDB에서 느린 쿼리 실행의 일반적인 원인을 이해하는 것이 중요합니다.

  1. 누락되거나 비효율적인 인덱스: 가장 빈번한 원인입니다. 인덱스가 없으면 MongoDB는 필요한 데이터를 빠르게 찾는 대신 컬렉션 스캔(모든 문서를 검사)을 수행해야 합니다.
  2. 쿼리 복잡성: 애그리게이션 단계, 대규모 정렬 또는 컬렉션 간 조회(lookup)가 필요한 작업은 최적화되지 않은 경우 본질적으로 느릴 수 있습니다.
  3. 데이터 볼륨: 인덱스가 지정된 쿼리라도 데이터 세트가 방대하고 쿼리가 필터링 전에 여전히 수백만 개의 문서를 처리해야 하는 경우 느려질 수 있습니다.
  4. 하드웨어 제약: RAM 부족(광범위한 디스크 스와핑 초래) 또는 느린 디스크 I/O는 모든 작업에 걸쳐 성능을 저하시킬 수 있습니다.

1단계: 프로파일링을 사용하여 느린 쿼리 식별

해결의 첫 단계는 식별입니다. MongoDB의 데이터베이스 프로파일러는 데이터베이스 작업의 실행 시간을 기록하여 어떤 쿼리가 문제를 일으키는지 정확히 찾아낼 수 있도록 합니다.

프로파일러 활성화 및 구성

프로파일러는 다른 수준에서 작동합니다. 레벨 0은 프로파일링을 비활성화합니다. 레벨 1은 모든 쓰기 작업을 프로파일링합니다. 레벨 2는 모든 작업을 프로파일링합니다.

느린 쿼리를 분석하기 위해 일반적으로 특정 임계값(예: 100밀리초)을 초과하는 작업을 캡처하도록 프로파일러를 설정합니다.

// 프로파일링하려는 데이터베이스로 전환
use myDatabase

// 50ms(50000마이크로초)보다 오래 걸리는 작업을 캡처하도록 프로파일러 레벨을 2로 설정
// 참고: 임계값은 마이크로초 단위로 지정됩니다.
db.setProfilingLevel(2, { slowms: 50 })

프로파일러 결과 검토

기록된 느린 작업은 system.profile 컬렉션에 저장됩니다. 이 컬렉션을 쿼리하여 최근의 느린 쿼리를 확인할 수 있습니다.

// 50ms보다 오래 걸리는 작업 찾기
db.system.profile.find({ ns: "myDatabase.myCollection", millis: { $gt: 50 } }).sort({ ts: -1 }).limit(10).pretty()

모범 사례: 레벨 2에서 프로파일링을 지속적으로 모니터링하면 system.profile 컬렉션에 상당한 쓰기 부하가 발생할 수 있습니다. 진단을 위해 프로파일링 레벨을 일시적으로 설정하거나 성능 조언자(Performance Advisor)를 활용하는 프로덕션 모니터링 도구를 사용하십시오.

2단계: explain()을 사용하여 쿼리 실행 분석

느린 쿼리가 식별되면 explain() 메서드는 가장 강력한 진단 도구입니다. 이는 쿼리를 어떻게 처리하는지에 대한 세부 실행 계획을 반환합니다.

explain('executionStats') 사용하기

executionStats 상세 수준은 실제 실행 시간 및 리소스 사용량을 포함하여 가장 포괄적인 출력을 제공합니다.

users 컬렉션을 대상으로 하는 이 느린 쿼리를 고려해 보겠습니다.

db.users.find({ status: "active", city: "New York" }).sort({ registrationDate: -1 }).explain('executionStats')

출력 해석

explain() 출력에서 검사해야 할 주요 필드는 다음과 같습니다.

필드 설명 느림의 지표
winningPlan.stage 쿼리 최적화 도구가 선택한 최종 실행 방법. COLLSCAN(컬렉션 스캔)을 찾으십시오.
executionStats.nReturned 작업에서 반환된 문서 수. 예상되는 결과가 적을 때 높은 수치는 필터링이 초기에 제대로 이루어지지 않았음을 나타내는 경우가 많습니다.
executionStats.totalKeysExamined 확인된 인덱스 키 수. 인덱스가 효과적으로 사용된 경우 일반적으로 nReturned와 가까워야 합니다.
executionStats.totalDocsExamined 실제로 디스크/메모리에서 검색된 문서 수. 높은 수치는 인덱스가 충분히 선택적이지 않았음을 시사합니다.
executionStats.executionTimeMillis 실행에 소요된 총 시간. 실제 지연 시간과 비교하십시오.

빨간색 깃발: COLLSCAN

winningPlan.stageCOLLSCAN을 표시하면 MongoDB가 전체 컬렉션을 스캔한 것입니다. 이는 적절한 인덱스가 없거나 무시되었음을 나타내는 주요 지표입니다.

3단계: 인덱스 전략 구현

COLLSCAN을 해결하려면 일반적으로 쿼리 패턴에 맞게 인덱스를 생성하거나 조정해야 합니다.

복합 인덱스 생성

여러 필드(예: 등식 일치, 범위 필터 또는 정렬)를 포함하는 쿼리의 경우 복합 인덱스가 종종 필요합니다. MongoDB는 ESR 규칙(등식, 정렬, 범위)을 사용하여 복합 인덱스 내 필드의 최적 순서를 결정합니다.

예제 시나리오:
쿼리: db.orders.find({ status: "PENDING", customerId: 123 }).sort({ orderDate: -1 })

ESR에 따라 인덱스는 다음 구조를 따라야 합니다.

  1. 등식 술어 (status, customerId)
  2. 정렬 술어 (orderDate)

인덱스 생성:

db.orders.createIndex( { status: 1, customerId: 1, orderDate: -1 } )

이 인덱스를 통해 MongoDB는 상태와 고객 ID로 빠르게 필터링한 다음 orderDate를 기준으로 이미 정렬된 결과를 효율적으로 검색할 수 있습니다.

정렬 작업 처리

explain()이 많은 문서를 메모리에 로드해야 했던 SORT 단계를 표시하는 경우(높은 docsExamined 및 잠재적인 메모리 의존성으로 표시됨), 이는 MongoDB가 정렬 요구 사항을 충족하기 위해 인덱스를 사용할 수 없었음을 의미합니다.

경고: MongoDB는 인메모리 정렬에 대해 기본 메모리 제한(일반적으로 100MB)을 부과합니다. 정렬 작업이 이 한도를 초과하면 실패하거나 디스크 기반 정렬을 강제 실행하여 매우 느려집니다.

.sort() 절에 사용된 필드가 적절한 복합 인덱스의 후행 요소로 포함되어 있는지 확인하십시오.

4단계: 고급 최적화 기술

인덱싱만으로 느림 현상이 해결되지 않으면 다음 고급 단계를 고려하십시오.

프로젝션 최적화

애플리케이션에 엄격하게 필요한 필드만 반환하도록 프로젝션(.select() 또는 .find()의 두 번째 인수)을 사용하십시오. 이는 네트워크 지연 시간과 MongoDB가 처리하고 전송해야 하는 데이터 양을 줄여줍니다.

// name, email, _id 필드만 반환
db.users.find({ city: "Boston" }, { name: 1, email: 1, _id: 1 })

커버링 인덱스

커버링 인덱스는 궁극적인 성능 목표입니다. 이는 쿼리(필터, 프로젝션 및 정렬에서)에 필요한 모든 필드가 인덱스 자체 내에 존재할 때 발생합니다. 이 경우 MongoDB는 실제 문서를 가져올 필요가 없습니다(COLLSCAN이 방지되고 totalDocsExamined가 0이거나 매우 낮습니다).

explain() 출력에서 커버링 인덱스는 해당 단계가 IXSCAN을 표시하고 totalDocsExamined가 0이 되도록 합니다.

하드웨어 및 구성 검토

인덱스가 있어도 프로파일러가 높은 totalKeysExamined를 표시하는 경우, 문제는 I/O 제약일 수 있습니다. 작업 세트가 RAM에 맞는지 확인하여 자주 쿼리되는 데이터에 대한 디스크 액세스를 최소화하십시오. 부하가 많은 상태에서 성능이 계속 저하되는 경우 메모리 매핑 및 저널링과 관련된 mongod 구성 설정을 검토하십시오.

요약 및 다음 단계

느린 MongoDB 쿼리를 진단하는 것은 반복적인 프로세스입니다. 프로파일링하여 위반자를 찾고, 설명(Explain)하여 느린 이유를 이해하고, 인덱싱하여 근본적인 실행 계획을 수정하십시오. 특히 효과적인 복합 인덱스 및 커버링 인덱스에 중점을 둔 이러한 기술을 체계적으로 적용하면 MongoDB 배포의 상태와 응답성을 크게 개선할 수 있습니다.

실행 가능한 체크리스트:

  1. 느린 쿼리 캡처를 위해 프로파일러를 일시적으로 활성화합니다(slowms).
  2. explain('executionStats')를 사용하여 문제가 있는 쿼리를 실행합니다.
  3. COLLSCAN 또는 높은 totalDocsExamined를 확인합니다.
  4. ESR 규칙에 따라 필터와 정렬을 커버하도록 복합 인덱스를 생성하거나 수정합니다.
  5. explain() 명령을 다시 실행하여 개선 사항을 확인합니다.