본문 바로가기
기타

mongodb index를 통한 쿼리튜닝

by 초특급하품 2019. 12. 31.

쿼리 튜닝은 흔히 slow query를 찾아, 인덱스 비용을 감수해서 추가할지 판단하는 정도로 진행한다.

다른 엔진과 비슷하게 mongodb에서도 explain을 통해 실제로 인덱스를 추가했을 때 예상한 대로 query plan이 생기는지 쉽게 확인할 수 있다.

 

테스트 데이터를 만들고 인덱스 추가 전/후를 explain을 통해 비교해보자. 먼저 class, grade를 갖는 데이터를 추가한다. 

> db.test.insertMany([
    { class: "A", grade: 1 },
    { class: "A", grade: 2 },
    ...
    { class: "A", grade: 9 },
    { class: "A", grade: 10 }
])
> db.test.insertMany([
    { class: "B", grade: 1 },
    { class: "B", grade: 2 },
    ...
    { class: "B", grade: 9 },
    { class: "B", grade: 10 }
])

 

이 중 class가 A인 doc만 조회하는 쿼리를 explain의 executionStats를 사용해서 query planner가 선정한 정보를 확인한다.

> db.test.find({ class: "A"}).explain("executionStats")
...
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 10,
  "executionTimeMillis" : 0,
  "totalKeysExamined" : 0,
  "totalDocsExamined" : 20,
  "executionStages" : {
    "stage" : "COLLSCAN",
    "filter" : {
      "class" : {
        "$eq" : "A"
      }
    },
    "nReturned" : 10,
    "executionTimeMillisEstimate" : 0,
    "works" : 22,
    "advanced" : 10,
    "needTime" : 11,
    "needYield" : 0,
    "saveState" : 0,
    "restoreState" : 0,
    "isEOF" : 1,
    "direction" : "forward",
    "docsExamined" : 20
  }
}

 

여러 속성들이 있는데 눈여겨 볼만한 것들만 몇 개 정리하면 아래와 같다.

  • totalDocsExamined: 참조된 document 횟수
  • executionStages: 쿼리 수행 상세 정보

executionStages 에는 여러 단계에 나눠 진행되는 쿼리의 상세 정보들이 나와있다. 각 단계에 stage 필드에서 어떤 작업을 했는지 확인할 수 있다.

  • COLLSCAN: 전체 스캔
  • IXSCAN: 인덱스 스캔

전체 20개 doc에서 class: "A"를 찾기 위해 "totalDocsExamined" : 20 20개 모두를 탐색했다는 것을 확인할 수 있다. 상세 정보에는 "stage" : "COLLSCAN"로 스캔 방법이 나와있다.

 

그럼 class에 index를 추가해보고 쿼리 플랜을 확인해보자.

> db.test.createIndex({ class: 1 })
> db.test.find({ class: "A"}).explain("executionStats")
...
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 10,
  "executionTimeMillis" : 0,
  "totalKeysExamined" : 10,
  "totalDocsExamined" : 10,
  "executionStages" : {
    "stage" : "FETCH",
    "nReturned" : 10,
    "docsExamined" : 10,
    "inputStage" : {
      "stage" : "IXSCAN",
      "nReturned" : 10,
      "indexName" : "class_1"
    }
  }
}

 

더 많은 정보들이 있지만 이전 explain과 다른 점 위주로 보기 위해서 삭제했다.
맨 처음 inputStage에서는 "stage" : "IXSCAN"로 인덱스 스캔을 했고, 이때 class_1 이라는 방금 생성한 인덱스를 사용했다. 전체 doc 참조 개수는 "totalDocsExamined" : 10로 필요한 doc만 참조했다는 것을 확인할 수 있다.

 

여기까지는 인덱스를 잘 활용했지만, grade 순으로 정렬하는 쿼리까지 있다면 grade에는 인덱스가 없기 때문에 "stage" : "SORT" 단계가 추가된다.

> db.test.find({ class: "A"}).sort({ grade: 1}).explain("executionStats")
...
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 10,
  "executionTimeMillis" : 0,
  "totalKeysExamined" : 10,
  "totalDocsExamined" : 10,
  "executionStages" : {
    "stage" : "SORT",
    "nReturned" : 10,
    "sortPattern" : {
      "grade" : 1
    },
    "inputStage" : {
      "stage" : "SORT_KEY_GENERATOR",
      "nReturned" : 10,
      "inputStage" : {
        "stage" : "FETCH",
        "nReturned" : 10,
        "docsExamined" : 10,
        "inputStage" : {
          "stage" : "IXSCAN",
          "nReturned" : 10,
          "indexName" : "class_1"
        }
      }
    }
  }
},

 

정렬까지 인덱스를 태우기 위해 class와 grade로 compound index를 추가했다.

> db.test.createIndex({ class:1, grade: 1 })
> db.test.find({ class: "A"}).sort({ grade: 1}).explain("executionStats")
...
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 10,
  "executionTimeMillis" : 0,
  "totalKeysExamined" : 10,
  "totalDocsExamined" : 10,
  "executionStages" : {
    "stage" : "FETCH",
    "nReturned" : 10,
    "docsExamined" : 10,
    "inputStage" : {
      "stage" : "IXSCAN",
      "nReturned" : 10,
      "indexName" : "class_1_grade_1"
    }
  }
},

 

처음 "stage" : "IXSCAN" 인덱스 스캔을 하는 건 똑같지만 "indexName" : "class_1_grade_1" 방금 추가한 compund index를 참조해서 "stage" : "SORT" 단계는 삭제된 쿼리 플랜으로 변경되었다.

 

index는 빠른 검색을 보장해주는 만큼 write시 자료구조를 유지해야 하기 때문에 불필요한 index는 삭제하는 게 답이다. mongodb의 compound index는 index prefix를 지원하기 때문에 처음에 추가했던 class 인덱스는 삭제해도 { class, grade } 인덱스 사용할 수 있다. 이와 같은 경우에는 처음 생성한 class 인덱스를 잊지 말고 삭제를 하자.

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

[bash] history 사용법  (0) 2020.02.11
ssh 터널링  (0) 2020.02.02
[bash] screen 사용 설명서  (0) 2019.12.26
간단한 파일 대칭키 암호화/복호화  (0) 2019.12.24
MySQL DATE INTERVAL 함수 함정  (1) 2019.12.13

댓글