MongoDB 인덱스 사용법

MongoDBBeginner
지금 연습하기

소개

이 랩에서는 쿼리 성능을 최적화하기 위해 MongoDB 인덱스를 사용하는 기본 사항을 배우게 됩니다. 인덱스는 컬렉션의 작고 검색하기 쉬운 데이터 부분을 보유하는 특수 데이터 구조로, MongoDB 가 전체 컬렉션을 스캔하는 것보다 훨씬 빠르게 문서를 찾을 수 있도록 합니다.

먼저 인덱스 없이 쿼리 성능을 관찰하는 것부터 시작합니다. 그런 다음 단일 필드 및 복합 인덱스를 생성하고 이러한 인덱스가 쿼리 및 정렬 속도를 극적으로 향상시키는 방법을 확인합니다. 마지막으로 인덱스를 나열하고 제거하여 인덱스를 관리하는 방법을 배우게 됩니다. 이 랩이 끝나면 MongoDB 애플리케이션을 더 효율적으로 만들기 위해 인덱스를 생성하고 사용하는 방법에 대한 실질적인 이해를 갖게 될 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 100%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

인덱스 없이 쿼리하기

인덱스를 생성하기 전에 인덱스 없이 MongoDB 가 어떻게 작동하는지 이해하는 것이 중요합니다. 이 단계에서는 샘플 컬렉션을 설정하고, 쿼리를 실행하고, 전체 컬렉션 스캔의 성능 영향을 확인하기 위해 실행 계획을 분석합니다.

먼저 MongoDB Shell (mongosh) 을 열어 데이터베이스와 상호 작용합니다. 이 명령줄 인터페이스를 통해 MongoDB 인스턴스에 직접 명령을 실행할 수 있습니다.

mongosh

쉘에 들어가면 > 프롬프트가 표시됩니다. indexlab이라는 새 데이터베이스로 전환하고 users 컬렉션에 샘플 문서를 삽입해 보겠습니다. 데이터베이스나 컬렉션이 존재하지 않으면 MongoDB 가 자동으로 생성합니다.

use indexlab
db.users.insertMany([
  { name: "Alice", age: 28, city: "New York" },
  { name: "Bob", age: 35, city: "San Francisco" },
  { name: "Charlie", age: 42, city: "Chicago" },
  { name: "David", age: 25, city: "New York" },
  { name: "Eve", age: 31, city: "San Francisco" }
]);

이제 30 세 이상인 모든 사용자를 찾아보겠습니다. MongoDB 가 이 쿼리를 어떻게 실행하는지 확인하기 위해 .explain("executionStats") 메서드를 사용합니다. 이 메서드는 쿼리의 실행 계획에 대한 자세한 통계를 제공합니다.

db.users.find({ age: { $gt: 30 } }).explain("executionStats");

출력은 쿼리 실행에 대한 자세한 통계를 제공합니다. winningPlanexecutionStats 섹션을 확인하십시오.

예시 출력 (일부 발췌):

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": { "age": { "$gt": 30 } },
      "direction": "forward"
    }
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 3,
    "executionTimeMillis": 0,
    "totalKeysExamined": 0,
    "totalDocsExamined": 5
  }
}

여기서 핵심 정보는 stage: "COLLSCAN"totalDocsExamined: 5입니다.

  • COLLSCAN은 "Collection Scan"을 의미합니다. 이는 MongoDB 가 쿼리와 일치하는 문서를 찾기 위해 컬렉션의 모든 문서를 검사해야 했음을 의미합니다.
  • totalDocsExamined: 5는 컬렉션의 모든 5 개 문서가 스캔되었음을 확인합니다.

작은 컬렉션의 경우 빠르지만, 수백만 개의 문서에 대한 컬렉션 스캔은 매우 느릴 것입니다. 다음 단계에서는 인덱스를 추가하여 이를 수정합니다.

단일 필드 인덱스 생성 및 사용

이제 컬렉션 스캔의 비효율성을 확인했으므로 인덱스를 생성하여 성능을 개선해 보겠습니다. age 필드에 대한 인덱스를 사용하면 MongoDB 가 전체 컬렉션을 스캔하지 않고 관련 문서를 빠르게 찾을 수 있습니다.

이전 단계에서 사용했던 mongosh 쉘에 계속 있어야 합니다.

age 필드에 오름차순으로 인덱스를 생성합니다. 1은 오름차순 인덱스를 지정하고, -1은 내림차순 인덱스를 지정합니다.

db.users.createIndex({ age: 1 });

MongoDB 는 인덱스가 성공적으로 생성되었음을 확인합니다. 이 인덱스의 기본 이름은 age_1입니다.

이제 이전 단계와 동일한 쿼리를 실행하고 실행 계획을 검토합니다.

db.users.find({ age: { $gt: 30 } }).explain("executionStats");

예시 출력 (일부 발췌):

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "IXSCAN",
        "keyPattern": { "age": 1 },
        "indexName": "age_1"
      }
    }
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 3,
    "executionTimeMillis": 0,
    "totalKeysExamined": 3,
    "totalDocsExamined": 3
  }
}

실행 계획의 중요한 변경 사항을 확인하십시오.

  • stage가 이제 IXSCAN으로 변경되었습니다. 이는 "Index Scan"을 의미하며, MongoDB 가 age_1 인덱스를 사용하여 일치하는 문서를 찾았음을 나타냅니다.
  • totalKeysExaminedtotalDocsExamined가 이제 5 가 아닌 3 입니다. MongoDB 는 인덱스를 통해 쿼리와 일치하는 3 개의 문서만 확인하면 되었고 나머지 2 개는 무시했습니다. 이것이 성능 향상의 원인입니다.

복합 인덱스를 사용한 정렬

인덱스는 쿼리 속도 향상뿐만 아니라 효율적인 정렬에도 매우 중요합니다. 인덱스가 없는 필드로 정렬하면 MongoDB 는 메모리 내에서 정렬을 수행해야 하며, 이는 느리고 상당한 RAM 을 소비할 수 있습니다. 여러 필드를 포함하는 **복합 인덱스 (compound index)**는 해당 필드로 필터링하고 정렬하는 쿼리를 최적화할 수 있습니다.

city(오름차순) 및 age(내림차순) 필드에 복합 인덱스를 생성해 보겠습니다. 인덱스 내 필드의 순서는 인덱스가 사용되는 방식에 중요합니다.

db.users.createIndex({ city: 1, age: -1 });

이제 사용자를 도시별로, 그리고 나이별로 정렬하는 쿼리를 실행해 보겠습니다. 인덱스가 정렬에 사용되는지 확인하기 위해 다시 .explain()을 사용합니다.

db.users.find().sort({ city: 1, age: -1 }).explain("executionStats");

예시 출력 (일부 발췌):

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "IXSCAN",
        "keyPattern": { "city": 1, "age": -1 },
        "indexName": "city_1_age_-1"
      }
    }
  }
}

IXSCAN 단계는 MongoDB 가 새로운 city_1_age_-1 인덱스를 사용했음을 보여줍니다. 데이터가 인덱스에 이미 정렬 기준에 따라 정렬되어 있으므로 MongoDB 는 별도의 비용이 많이 드는 메모리 내 정렬 단계를 수행할 필요가 없습니다.

실제 정렬된 결과를 보려면 .explain() 없이 쿼리를 실행합니다.

db.users.find().sort({ city: 1, age: -1 });

출력:

[
  { _id: ObjectId("..."), name: 'Charlie', age: 42, city: 'Chicago' },
  { _id: ObjectId("..."), name: 'Alice', age: 28, city: 'New York' },
  { _id: ObjectId("..."), name: 'David', age: 25, city: 'New York' },
  { _id: ObjectId("..."), name: 'Bob', age: 35, city: 'San Francisco' },
  { _id: ObjectId("..."), name: 'Eve', age: 31, city: 'San Francisco' }
]

문서는 복합 인덱스 정의와 일치하게 먼저 city별로 알파벳순으로, 그리고 각 도시 내에서 age별로 가장 많은 나이부터 적은 나이 순으로 올바르게 정렬됩니다.

인덱스 관리 및 제거

인덱스는 읽기 성능을 향상시키지만 무료는 아닙니다. 인덱스는 저장 공간을 소비하고 쓰기 작업 (삽입, 업데이트, 삭제) 에 약간의 오버헤드를 추가합니다. 따라서 주기적으로 사용되지 않는 인덱스를 검토하고 제거하는 것이 좋습니다.

먼저 getIndexes() 메서드를 사용하여 컬렉션의 모든 인덱스를 나열할 수 있습니다.

db.users.getIndexes();

출력:

[
  { "v": 2, "key": { "_id": 1 }, "name": "_id_" },
  { "v": 2, "key": { "age": 1 }, "name": "age_1" },
  { "v": 2, "key": { "city": 1, "age": -1 }, "name": "city_1_age_-1" }
]

이는 모든 컬렉션에 대해 자동으로 생성되는 _id 필드의 기본 인덱스와 우리가 생성한 두 개의 인덱스를 보여줍니다.

복합 인덱스 city_1_age_-1이 더 이상 필요하지 않다고 가정해 보겠습니다. 인덱스 이름을 인수로 전달하는 dropIndex() 메서드를 사용하여 제거할 수 있습니다.

db.users.dropIndex("city_1_age_-1");

MongoDB 는 삭제 작업 전에 존재했던 인덱스 수를 나타내는 객체를 반환합니다.

{ "nIndexesWas": 3, "ok": 1 }

이제 인덱스를 다시 나열하여 인덱스가 제거되었는지 확인합니다.

db.users.getIndexes();

출력:

[
  { "v": 2, "key": { "_id": 1 }, "name": "_id_" },
  { "v": 2, "key": { "age": 1 }, "name": "age_1" }
]

보시다시피 city_1_age_-1 인덱스가 사라졌습니다. 적절한 인덱스 관리는 건강하고 성능이 뛰어난 데이터베이스를 유지하는 핵심 부분입니다.

MongoDB 쉘을 종료하려면 exit를 입력하거나 Ctrl+D를 누르면 됩니다.

exit;

요약

이 실습에서는 MongoDB 인덱스를 사용하는 필수 기법을 배웠습니다. 인덱스가 없는 쿼리에서 COLLSCAN을 관찰하고 성능 제한 사항을 이해하는 것으로 시작했습니다. 그런 다음 단일 필드 인덱스를 생성하여 쿼리 계획을 훨씬 더 효율적인 IXSCAN으로 변경했습니다.

또한 복합 인덱스를 탐색하고 비용이 많이 드는 메모리 내 정렬을 피하면서 정렬 작업을 최적화하는 데 어떻게 사용할 수 있는지 확인했습니다. 마지막으로 getIndexes()로 인덱스를 나열하고 dropIndex()로 사용되지 않는 인덱스를 제거하여 인덱스를 관리하는 방법을 배웠습니다. 이러한 기술은 MongoDB 로 빠르고 확장 가능한 애플리케이션을 구축하는 데 기본이 됩니다.