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인덱스 스캔)