쿼리 튜닝은 흔히 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 |
댓글