튼튼발자 개발 성장기🏋️

[MongoDB] 인덱싱 #2 본문

기타/MongoDB

[MongoDB] 인덱싱 #2

시뻘건 튼튼발자 2025. 3. 16. 00:09
반응형

explain 출력

MongoDB에서 explain 메서드는 쿼리 실행 계획을 분석하는 데 사용된다.
쿼리를 최적화하고 성능을 향상시키기 위해 반드시 확인해야 할 주요 필드들은 다음과 같다.

 

1. queryPlanner

  • plannerVersion: 실행 계획을 생성한 쿼리 플래너의 버전.
  • namespace: 쿼리가 실행된 컬렉션 정보 (DB명.컬렉션명).
  • indexFilterSet: 특정 인덱스를 강제하는 hint()를 사용했는지 여부.
  • parsedQuery: MongoDB가 내부적으로 해석한 쿼리 조건.
  • winningPlan: 실제로 선택된 실행 계획.
  • rejectedPlans: 평가되었으나 선택되지 않은 실행 계획들.

2. winningPlan (선택된 실행 계획)

  • stage: 실행된 주요 연산 단계 (e.g., COLLSCAN, IXSCAN, FETCH).
  • inputStage: 실행 계획의 하위 단계로, 주어진 쿼리가 어떻게 처리되는지 나타냄.
  • indexName: 사용된 인덱스 이름 (IXSCAN일 경우).
  • direction: 인덱스를 오름차순(forward) 또는 내림차순(backward)으로 탐색하는지 여부.
  • keysExamined: 인덱스에서 검사한 키 개수 (낮을수록 성능이 좋음).
  • docsExamined: 실제로 읽은 문서 개수 (COLLSCAN일 경우 높을 가능성이 큼).
  • filter: 인덱스 후 필터링이 적용된 경우 조건을 표시.

3. executionStats (실행 통계)

  • executionTimeMillis: 쿼리 실행 시간(ms).
  • nReturned: 쿼리 결과로 반환된 문서 개수.
  • totalKeysExamined: 검사한 인덱스 키 개수.
  • totalDocsExamined: 검사한 문서 개수.
  • allPlansExecution: 여러 실행 계획을 비교한 결과 (rejected plan과 함께 분석 가능).

4. serverInfo (서버 정보)

  • host: 쿼리가 실행된 서버의 호스트명.
  • port: 실행된 포트 번호.
  • version: MongoDB 버전.

더 자세한 내용은 [MongoDB 실행계획]을 살펴보자.

 

인덱스를 생성하지 않는 경우

MongoDB에서 인덱스를 생성하지 않으면 전체 컬렉션을 순차적으로 탐색하는 Collection Scan (COLLSCAN)이 수행된다.
이는 쿼리 성능에 큰 영향을 미칠 수 있으며, 특히 대량의 데이터가 저장된 컬렉션에서는 심각한 속도 저하를 초래할 수 있다.

COLLSCAN이 발생하는 경우

  • 컬렉션에 인덱스가 없는 경우.
  • 쿼리가 인덱스를 활용할 수 없는 필드에 조건을 적용하는 경우.
  • 다중 필드 쿼리에서 인덱스를 적절히 활용하지 못하는 경우.

그렇다고해서 꼭 COLLSCAN이 나쁜 것만은 아니다. 어떤 쿼리의 경우에는 인덱스가 도움이 되지 않는 경우가 있다. 인덱스는 컬렉션에서 가져와야하는 부분이 많을 수록 비효율적인데, 인덱스를 하나 사용하려면 두 번의 조회를 해야하기 때문이다. 한 번은 인덱스 항목을 살펴보고, 또 한 번은 도큐먼트를 가리키는 인덱스 포인터를 따라간다. 반면에 컬렉션 스캔을 할 때는 도큐먼트만 살펴보면 된다. 안타깝게도 인덱스를 생성하는 것이 좋을지 안좋을지 판단할 수 있는 메소드나 방법은 없다. 하지만 대략적으로 예측은 할 수 있다. 쿼리가 컬렉션의 30% 이상을 반환하는 경우 인덱스는 종종 쿼리 속도를 높인다. 하지만 이 수치는 2%부터 60%까지 다양하다.

인덱스가 적합한 경우 CALLSCAN이 적합한 경우
큰 컬렉션 작은 컬렉션
큰 도큐먼트 작은 도큐먼트
선택적 쿼리 비선택적 쿼리

인덱스 종류

인덱스를 구축할 때 인덱스 옵션을 지정해 동작 방식을 바꿀 수 있다. 아래 세 가지 인덱스는 가장 일반적인 종류에 대한 간단안 내용이다.

고유 인덱스

MongoDB에서는 고유 인덱스를 사용하여 특정 필드에 대해 중복된 값을 저장하지 못하도록 강제할 수 있다. 이는 데이터 무결성을 보장하는 데 유용하며, 중복을 방지하는 핵심적인 기능이다.

예를 들어, users 컬렉션에서 email 필드는 중복이 없어야 한다고 가정하자.

db.users.createIndex({ email: 1 }, { unique: true })

이제 중복된 이메일을 가진 데이터를 삽입하면 ` E11000 duplicate key error`오류가 발생하면서 고유 인덱스가 중복을 차단하는 것을 볼 수 있다.

> db.users.insertMany([ { name: "Alice", email: "alice@example.com" },
			{ name: "Bob", email: "bob@example.com" },
			{ name: "Charlie", email: "alice@example.com" } ])
E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: "alice@example.com" }

 

parse: true 옵션

고유 인덱스를 만들 때 `sparse: true` 옵션을 추가하면 필드가 없는 문서는 인덱스에 포함되지 않는다. 즉, 필드가 존재하는 경우에만 중복 검사를 수행한다.

db.users.createIndex({ phone: 1 }, { unique: true, sparse: true })

위와 같이 하면 phone 필드가 없는 문서는 삽입할 수 있지만, phone이 존재하면 중복 검사 대상이 된다. 즉, 여러 개의 { name: "Alice" } 문서는 허용되지만 { phone: "010-1234-5678" }이 중복되면 오류가 발생한다.

복합 고유 인덱스

MongoDB에서는 여러 개의 필드를 조합하여 고유 인덱스를 생성할 수 있다. 이러한 복합 고유 인덱스는 특정 필드 조합이 유일해야 할 때 유용하다.

예를 들어, 사용자의 email은 고유해야 하지만, 회사 내에서는 같은 email을 허용한다고 가정하자. 이 경우, email 단독으로 고유 인덱스를 설정하면 같은 회사에서 동일한 이메일을 사용하지 못하는 문제가 발생한다. 이 문제를 해결하려면 companyId와 email을 조합한 복합 고유 인덱스를 만들면 된다.

db.users.createIndex({ companyId: 1, email: 1 }, { unique: true })

이제 같은 companyId 내에서는 중복된 email을 허용하지 않지만, 다른 companyId에서는 같은 email을 사용할 수 있다.

db.> sers.insertMany([ { name: "Alice", companyId: 100, email: "alice@example.com" }, // 허용
			{ name: "Bob", companyId: 200, email: "alice@example.com" }, // 허용
			{ name: "Charlie", companyId: 100, email: "alice@example.com" } // 오류 ])
E11000 duplicate key error collection: mydb.users index: companyId_1_email_1 dup key: { companyId: 100, email: "alice@example.com" }
 

부분 인덱스

MongoDB의 부분 인덱스는 특정 조건을 만족하는 문서에 대해서만 인덱스를 생성하는 기능이다. 이는 인덱스 크기를 줄이고, 불필요한 문서를 제외함으로써 쿼리 성능을 최적화할 수 있다.

일반적인 인덱스는 컬렉션 내 모든 문서에 대해 생성되므로, 대량의 데이터가 있을 경우 인덱스 크기가 커지고 성능이 저하될 수 있다.

예를 들어, 활성화(status: "active")된 사용자만 자주 조회된다면, 굳이 비활성화된(status: "inactive") 사용자까지 인덱스에 포함할 필요가 없다. 이 때 부분 인덱스를 사용하면 "active" 상태의 사용자만 인덱싱할 수 있다.

> db.users.createIndex( { email: 1 }, { unique: true, partialFilterExpression: { status: "active" } } )

> db.users.insertMany([ { name: "Alice", email: "alice@example.com", status: "active" }, // 허용
			{ name: "Bob", email: "bob@example.com", status: "inactive" }, // 허용
			{ name: "Charlie", email: "alice@example.com", status: "inactive" } // 허용) ])
            
> db.users.insertOne({ name: "David", email: "alice@example.com", status: "active" }) // 오류
E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: "alice@example.com" }

 

인덱스 관리

MongoDB에서는 인덱스를 적절히 관리하는 것이 성능 최적화에 매우 중요하다. 인덱스 정보는 모두 system.indexes 컬렉션에 저장되는데, 예약된 컬렉션이기 때문에 도큐먼트를 수정하거나 제거할 수 없다. 이를 통해 불필요한 인덱스를 제거하거나, 현재 사용 중인 인덱스를 모니터링하여 최적의 인덱스 상태를 유지할 수 있다.

현재 존재하는 인덱스 확인

컬렉션에 존재하는 모든 인덱스를 확인하려면 getIndexes()를 사용하면 된다.

> db.users.getIndexes()
[
   {
      "v":2,
      "key":{
         "_id":1
      },
      "name":"_id_"
   },
   {
      "v":2,
      "key":{
         "email":1
      },
      "name":"email_1",
      "unique":true
   },
   {
      "v":2,
      "key":{
         "companyId":1,
         "email":1
      },
      "name":"companyId_1_email_1",
      "unique":true
   }
]
 

 

특정 인덱스 삭제

사용하지 않는 인덱스가 있다면, 삭제하여 저장 공간을 절약할 수 있다. 하지만 삭제된 인덱스는 복구할 수 없으므로 신중하게 삭제해야 한다. 꼭 `getIndexes()`로 실제 사용 여부를 확인한 후 삭제하는 것이 좋다.

db.users.dropIndex("email_1")

참고로 새로운 인덱스를 생성할 때 시간이 오래 걸리고 리소스가 많이 필요하다. MongoDB 4.2 이후 버전에서는 인덱스를 최대한 빨리 구축하기 위해 구축될 때까지 데이터베이스의 모든 읽기와 쓰기를 중단한다. 만약 이게 싫다면 "background" 옵션을 통해 어느정도는 응답하게 할 수는 있다. 이 옵션은 인덱스 구축이 종종 다른 작업에 양보하도록 강제하지만 애플리케이션에는 영향을 주는 것은 변함없다. 심지어 백그라운드 인덱싱은 포그라운드 인덱싱보다 훨씬 느리다. MongoDB 4.2에서는 하이브리드 인덱스 구축이라는 새로운 접근 방식을 도입했다. 인덱스 구축 프로세스의 시작과 끝에만 락을 가지며 프로세스의 나머지 부분은 읽기 및 쓰기 작업을 인터리빙한다.

 

인덱스 크기 확인

인덱스가 차지하는 메모리 크기를 확인하여 불필요한 인덱스를 줄이고 최적화할 수 있다.

예를 들어 email_1 인덱스가 16KB, companyId_1_email_1 인덱스는 24KB 차지한다.

 

> db.users.stats().indexSizes
{ "_id_": 4096, "email_1": 16384, "companyId_1_email_1": 24576 }
 

 

인덱스 재구성

시간이 지나면서 인덱스가 조각화(프래그먼테이션)될 수 있다. 이 때 인덱스를 재구성(compact) 하면 디스크 공간을 절약할 수 있다. 하지만 compact 명령어는 컬렉션이 클수록 실행 시간이 오래 걸릴 수 있으므로 운영 환경에서는 각별한 주의가 필요하다.

db.runCommand({ compact: "users" })
 

 

자동 인덱스 생성 방지

MongoDB는 기본적으로 컬렉션을 만들 때 _id 필드에 자동으로 인덱스를 생성한다. 하지만, 대량의 데이터를 삽입할 때는 인덱스 생성이 성능 저하를 유발할 수 있다. 이 경우, 컬렉션 생성 시 autoIndexId: false 옵션을 사용하면 _id 인덱스 생성을 방지할 수 있다.

> db.createCollection("users", { autoIndexId: false }) // _id 자동 인덱싱 해제
> db.users.createIndex({ _id: 1 }) // _id 자동 인덱싱
 
 

 

 

반응형

'기타 > MongoDB' 카테고리의 다른 글

[MongoDB] 트랜잭션  (0) 2025.03.23
[MongoDB] 특수 인덱스  (0) 2025.03.17
[MongoDB] 인덱싱 #1  (0) 2025.03.15
[MongoDB] 도큐먼트 생성, 갱신, 삭제  (0) 2025.03.06
[MongoDB] MongoDB의 기본  (1) 2025.03.03