08. 인덱싱과 쿼리 최적화
주요 내용
- 인덱싱의 기본 개념과 이론
- 인덱스 관리를 위한 실용적인 조언
- 복잡한 쿼리에 대한 복합 인덱스 사용
- 쿼리 최적화
- 모든 MongoDB 인덱싱 옵션
#
인덱싱의 기본 개념과 이론#
인덱스- 책갈피로 설명 (없으면 full scan 해봐야 함)
- 인덱스를 설정한 키로 검색하면 도큐먼트 가져오는 속도 증가
- 한 쿼리에 하나의 인덱스를 사용
#
복합키 인덱스(compound-key index)- 두가지 이상의 키를 동시에 사용하는 인덱스
- 복합 인덱스에 사용되는
키 순서
가 중요! (용도에따라 결정) - ingredient-name 복합 인덱스가 있다면 ingrendient 단일 인덱스는 필요 없음
- 복합인덱스로 ingredient 단일 인덱스 기능도 가능하기 때문
a-b 복합 인덱스가 있따면 a 단일 인덱스는 필요없고, b 단일 인덱스는 필요하다
- 복합키로 조회 성능을 향상 시켜야하는 경우에만 사용
#
인덱스 사용 시 유의사항- 인덱스는 유지비용이 들어감
- 삽입, 삭제, 인덱스 키 업데이트, 도큐먼트 재배치 마다 인덱스를 수정해야함
- 인덱스 사용은 읽기 위주의 기능/서비스에 적합
- 인덱스가 있더라도 읽기 쿼리를 빠르게 처리하지 못할 가능성이 존재
- 인덱스와 작업 중 데이터를 램에서 다 처리하지 못할 때 발생 (페이지 폴트 + 스래싱)
#
B-트리- 인덱스는 B-트리로 구성됨
- 특징
- 정확한 일치, 범위 조건, 정렬, 프리픽스 일치, 인덱스 전용 쿼리 등 효율적으로 해당 쿼리들을 처리해 주는 구조
- 항시 balanced 상태 유지
#
인덱스 타입- 고유 인덱스 (unique index)
- unique 키
db.users.createIndex({username: 1}, {unique: true})
- 희소 인덱스 (sparse index)
- 3.2 부터 partial index 를 sparse index 상위 세트로 제공하니 이를 활용하자
- 인덱스의 키가 널이 아닌 값만 존재
- 기본적으로 밀집(dense) 인덱스
- 밀집 엔덱스는 null 값이 여러개인 필드의 인덱스에선 사용 불가 (null 한개 이후로 생성이 안됨)
- 그래서 null이 포함된 unique 인덱스, 즉
고유 희소 인덱스
를 만들때 사용
- 고유 희소 인덱스
db.products.createIndex({sku: 1}, {unique: true, sparse: true})
- 익명 리뷰와 user_id가 null이 많이 존재할 때도 사용
db.reviews.createIndex({user_id: 1}, {sparse: true, unique: false})
- 대신 null에 대한 검색은 포기
- 다중키 인덱스 (multikey index)
- 인덱스 키의 여러개 엔트리가 동일한 도큐먼트를 가리킨다
tags: ["tools", "gardening", "soil"]
로 예를 들면,tools
을 가지는 모든 다큐먼트는,tools
인덱스 키로 검색 가능하다- 즉 한 인덱스 키가 여러개의 다큐먼트를 가리킬 수 있고,
- 여러 인덱스 키가 하나의 다큐먼트를 가리킬 수 있다
- 단일 키보다 다중 키 인덱스가 생성, 업데이트, 삭제 비용이 크다
- 해시 인덱스
- 키에 순서가 없이 해시로 동작하는 인덱스
db.recipes.createIndex({recipe_name: 'hashed})
- 제안 사항
- 범위 쿼리 지원 X
- 다중키 해시 인덱스 지원 X
- 부동 소수점 값은 해시 전 정수로 변환됨
- 인덱스의 지역성이 골고루 분포되므로
샤딩
에 유리 - 기본키(ObjectId, OID)가 해시 인덱스를 사용
- 지리공간적 인덱스 (geospatial index)
- 위도, 경도값에 따라 도큐먼트를 특정 위치에 가까이 배치하는 인덱스
#
인덱스 관리를 위한 실용적인 조언#
인덱스 명령어- 인덱스 조회:
db.users.getIndexes()
- 인덱스 생성:
db.users.createIndex({zip: 1})
- 인덱스 삭제:
db.users.dropIndex("zip_1")
(인자로 조회한|지정한 인덱스 이름을 넣는다)
#
인덱스 구축- 인덱스를 운영되고 있는 컬렉션 위에 만드는 이유
- 데이터 이전 시, 이전 완료 후 인덱스 구축 (이상적으로 균형잡히고 압축된 인덱스를 만들 수 있음)
- 새로운 쿼리 최적화
- 인덱스 구축은 데이터 마이그레이션과 같이 취급 (오래 걸림)
- 인덱스 구축 단계
- 인덱스할 값 정렬
- 정렬된 값 인덱스로 삽입
- 인덱스 구축 진척상황 확인:
db.currentOp()
#
백그라운드 인덱싱- 실제 서비스 되고 있는 DB 에 대한 엑세스를 중지시킬 수 없을때 사용
- 쓰기는 락이 걸리지만 조회는 가능함 (성능저하는 그래도 발생)
db.values.createIndex({open: 1, close: 1}, {background: true})
#
오프라인 인덱싱- 복제노드를 오프라인으로 바꾸고 인덱스 구축
- 마스터로부터 해당 인덱스를 업데이트 받음
- 업데이트 완료 후, 해당 노드를 프라이머리 노드로 바꿈
- 세컨더리 노드의 인덱스 업데이트를 같은 방식으로 진행
#
백업- 백업에 인덱스를 포함해야 한다면 MongoDB 데이터 파일 자체를 백업해야함
#
defragmenting조각 모음
- 업데이트 삭제가 빈번하다면 인덱스가 단편화 됨
- 이때
db.values.reIndex()
를 통해 인덱스 재구축 가능 - 마찬가지 락 주의
#
쿼리 최적화#
느린 쿼리 탐지
- bson 파일을 이용한 mongorestore
mongorestore -d stocks -c values <bsonFileName> -u '' -p '' --authenticationDatabase=<dbName>
- 프로파일링 세팅
db.setProfilingLevel(2)
: level 2는 제일 상세한 수준system.profile
에 capped 컬랙션 형태로 저장 (128kb)- 각 쿼리의 explain 결과물들이 저장
#
느린 쿼리 분석- 쿼리의 마지막에
explain()
붙여 확인 가능 db.values.find({}).sort({close: -1}).limit(1).explain()
db.values.find({}).sort({close: -1}).limit(1).explain('executionStats')
- 리턴된 문서 수 (n) 과 쿼리 시 살펴본 문서 (nscanned) 의 수가 거의 근접한지 확인
- 인덱스 스캔되는지 확인 (
COLLSCAN
풀 스캔,IXSCAN
인덱스 스캔)