遅い理由はたいていインデックスだ
한국어 원문으로 표시합니다.
한 줄 요약
인덱스가 없으면 문서 DB 도 전부 훑는다. COLLSCAN 이라고 적힌다. RDB 의
Seq Scan 과 정확히 같은 것이고, 대책도 같다.
왜 필요한가 — 느린 이유는 대개 하나다
"MongoDB 가 느리다" 는 신고의 대부분은 데이터베이스가 아니라 인덱스가 없는 조회다. 문서가 만 건일 때는 아무도 모른다. 백만 건이 되면 같은 코드가 갑자기 느려지고, 그때 사람들은 데이터베이스를 의심한다.
확인하는 방법은 RDB 와 같다. 계획을 물어본다.
db.items.find({ name: "빨간 배낭" }).explain()
계획 안에서 볼 단어는 둘뿐이다. COLLSCAN 이면 전부 훑은 것이고, IXSCAN
이면 인덱스를 탄 것이다.
숫자를 함께 본다
단계 이름만으로는 절반이다. explain('executionStats') 를 쓰면 실제로
몇 건을 훑었는지가 나온다.
| 값 | 뜻 |
|---|---|
nReturned |
실제로 돌려준 문서 수 |
totalDocsExamined |
그것을 찾으려고 열어 본 문서 수 |
이 둘의 비율이 인덱스의 성적표다. 한 건을 돌려주려고 백만 건을 열었다면 인덱스가 없거나 잘못 걸린 것이다. 반대로 둘이 비슷하면 잘 걸린 것이다.
복합 인덱스는 순서가 전부다
인덱스를 만들기로 했다면 다음 질문은 어떤 순서로 묶느냐다. 문서 DB 의 복합 인덱스도 값으로 정렬된 구조라 앞쪽 필드부터 쓸 수 있고, 그 규칙이 무엇을 쓸 수 있고 무엇을 못 쓰는지를 정한다.
{ status: 1, createdAt: -1 } 로 만든 인덱스는 이렇게 쓰인다.
status만으로 찾기 — 쓸 수 있다.status로 찾고createdAt으로 정렬 — 쓸 수 있다. 정렬이 공짜가 된다.createdAt만으로 찾기 — 못 쓴다. 앞쪽이 비어 있기 때문이다.
그래서 실무의 순서 규칙이 나온다. 같음으로 거르는 필드를 앞에, 범위로 거르거나 정렬에 쓰는 필드를 뒤에 둔다. 범위 조건이 걸린 필드 뒤의 필드는 탐색에 쓰이지 못하고 걸러 내기에만 쓰인다.
정렬이 인덱스를 타지 못하면 조용히 비싸진다. 문서 DB 는 메모리 안에서 정렬하는데 그 양에 상한이 있어서, 넘으면 오류로 실패하거나 임시 파일을 쓴다. 결과가 적어도 정렬 전의 후보가 많으면 걸린다는 점이 특히 사람을 놀라게 한다. 계획에서 정렬 단계가 따로 보이면 그 순서를 인덱스로 옮길 수 있는지 본다.
배열 필드에 거는 인덱스는 성격이 다르다. 배열 원소마다 항목이 하나씩 생기므로 문서 하나가 인덱스에 여러 번 들어간다. 원소가 수백 개인 배열에 인덱스를 걸면 그만큼 커지고 쓰기도 느려진다. 그리고 배열 필드를 둘 이상 한 인덱스에 묶을 수 없다. 조합의 수가 곱해져 폭발하기 때문이다.
마지막으로 필요한 값이 전부 인덱스 안에 있으면 문서를 아예 열지 않는다.
앞에서 본 totalDocsExamined 가 0 이 되는 경우가 이것이고, 목록 화면처럼
몇 개 필드만 보여 주는 조회에서 특히 값이 크다.
현장에서
인덱스를 더 만드는 것이 늘 답은 아니다. 인덱스는 쓰기를 느리게 하고 공간을 먹는다. 문서를 하나 넣을 때마다 모든 인덱스가 갱신된다.
그래서 순서가 있다. 먼저 계획을 읽어 무엇이 느린지 확인하고, 그다음 그 조회 하나를 위한 인덱스를 만든다. "혹시 몰라서" 만든 인덱스는 쓰기 비용만 남기고 아무 조회도 빠르게 하지 않는 경우가 많다.
이 순서를 지키는지가 SQL 최적화 경험을 묻는 면접에서 갈리는 지점이다.