인덱스
관계형 데이터베이스처럼 인덱스로 지정한 필드 키를 정렬된 상태로 저장하는 Object입니다.
기본적으로 자주 조회되는 필드는 인덱스로 지정하는 것이 좋으나 인덱스를 많이 지정할수록 데이터를 입력할 때마다 후처리로 정렬을 진행해야 하므로 Write 성능이 떨어진다는 단점이 있습니다.
정리하자면 인덱스를 지정하면 Read 성능은 올라가지만 Write 성능이 떨어지는 trade-off 관계이므로 인덱스를 시스템 요구사항에 맞게 적절히 설정할 필요가 있습니다.
MongoDB에서 제공하는 인덱스
MongoDB의 장점 중 하나는 아래 나열한 것처럼 다양한 인덱스를 제공한다는 것입니다.
- Single Field Index: 단일 필드에 부여하는 인덱스
- Compound Index: 여러 필드에 부여하는 인덱스
- Multikey Index: 배열 필드에 부여하는 인덱스
- Geospatial Index: 지역 좌표에 쓰이는 인덱스
- Wildcard Index: 유연한 schema에서 필드명이 정확하지 않을 때 쓰이는 인덱스
- Text Index: 검색 기능에 쓰이는 인덱스
- Hashed Index: Sharded Cluster에 쓰이는 인덱스
- TTL Index: 강력한 기능 중 하나로 Time To Live 부여하여 지정된 시간 이후 인덱스에 해당하는 데이터 삭제됨
- Unique Index: 중복 데이터 방지하는 인덱스
- Partial & Sparse Index: 컬렉션 내 해당 필드가 속한 document에만 부여하는 인덱스
- Case Insensitive Index: 대소문자 구분 안 하는 인덱스
- Hidden Index: 인덱스를 추가할 때 운영 중인 서비스에 지장을 줄 수 있으므로 인덱스를 hidden 속성으로 추가해서 테스트를 진행한 뒤 성능이 괜찮다고 판단되면 hidden 속성을 풀어서 적용시킴
Compound Index
여러 필드를 갖는 Compound Index의 경우 인덱스를 부여하는 순서가 중요합니다.
이때 적용되는 규칙이 E-S-R Rule이라고 하는데 MongoDB 뿐만 아니라 B-Tree Index를 사용하는 RDBMS에도 사용되는 규칙입니다.
E-S-R Rule에서 인덱스를 부여하는 순서는 이름 그래도 E, S, R 순서이며 항상 이 공식이 성립하는 것은 아니지만 통상적으로 성립되기 때문에 해당 원칙을 적용할 것을 추천합니다.
- E: Equality (동등 조건)
- S: Sort (정렬)
- R: Range (범위)
예를 들자면, cars 컬렉션에서 Ford가 생산한 차들 중 가격이 $15,000 이상인 차들을 모델명 기준으로 정렬되도록 조회를 한다면 아래와 같이 쿼리를 호출할 것입니다.
db.cars.find(
{
manufacturer: 'Ford',
cost: { $gte: 15000 }
} ).sort( { model: 1 } )
이럴 경우 E-S-R Rule을 따라 { manufacturer: 1, model: 1, cost: 1 } 순서로 Compund Index를 부여합니다.
Multikey Index
앞서 설명했다시피 Multikey Index는 배열 필드에 부여하는 인덱스입니다.
예를 들어, 아래와 같이 컬렉션이 구성이 되어 있고 multikey index를 { "addr.zip": 1 }으로 부여했다고 가정하겠습니다.
{
userid: "jaimemin",
addr: [
{zip: 10036},
{zip: 94301}
]
},
{
userid: "gudetama",
addr: [
{zip: 10011},
{zip: 94311},
{zip: 30000}
]
}
document는 2개뿐이지만 첫 번째 document는 addr 배열 크기가 2, 두 번째 document 내 addr 배열 크기가 3이기 때문에 인덱스는 총 5개가 부여된 상태입니다.
또한, addr 배열에 {zip: 20000}을 추가할 경우 인덱스는 6개가 부여된 상태가 됩니다.
이처럼 Multikey Index는 배열 내 필드에 인덱스를 부여할 수 있지만 인덱스가 적용된 entry를 추가 및 삭제하는 작업에 드는 비용이 document를 저장하는 비용보다 크므로 배열 크기가 작을 때 사용하는 것을 추천합니다.
만약 배열 크기가 예를 들어 천개가 넘어가면 아래와 같이 여러 가지 방면을 고려해야 합니다.
- 배열 크기를 줄이고 압축
- 쿼리 수정
- 모델링 통해 배열 필드 제거
이외 Index
앞서 설명한 두 인덱스를 제외한 Index들은 아래 공식 문서를 참고하시길 바랍니다.
https://www.mongodb.com/docs/manual/indexes/
Index 생성 시 주의사항
인덱스 생성 작업은 웬만하면 background에서
MongoDB 4.2 버전 이전까지는 인덱스 생성 시 background 옵션을 별도로 부여하지 않을 경우 foreground에서 처리가 되기 때문에 인덱스가 생성되는 동안 DB 단위의 lock이 걸려 읽기/쓰기가 제한됩니다.
다행히 4.2 버전부터는 background에서 생성되는 것이 디폴트가 되었기 때문에 걱정할 필요 없지만 서비스가 4.2 버전보다 낮은 버전을 사용할 경우 상용 서비스에서는 인덱스 부여할 때 주의를 해야 합니다.
버전 < 4.2: db.collection.createIndex({a: 1}, {background: true})
버전 >= 4.2: db.collection.createIndex({a: 1})
또한 인덱스를 생성하는 구문에 대한 검사가 취약하기 때문에 4.2 이전 버전에서는 아래와 같이 background 옵션을 엉뚱한 인자에 부여할 경우 예상과 달리 foreground에서 인덱스를 생성할 수 있는 문제가 발생할 수 있습니다.
db.collection.createIndex(
{"deleteDate": 1},
{expireAfterSeconds: 1296000}, // 원래는 여기에 background: true를 부여해야 한다.
{background: true}
)
db.collection.createIndex(
{"deleteDate": 1},
{expireAfterSeconds: 1296000, background: true} // 정상 케이스
)
Rolling 인덱스
MongoDB 4.4 버전 이전까지는 Replica Set에서 내부적으로 Primary 멤버에 인덱스를 생성하고 Secondary 멤버에서 복제하는 방식을 채택했습니다.
이는 인덱스 생성으로 인해서 발생하는 성능 저하를 줄이기 위해 선택된 방식으로 멤버 하나씩 접속해서 롤링 형태로 인덱스를 생성합니다.
이렇게 보면 합리적인 방식이라고 생각이 들지만 사실 아래와 같은 단점들 때문에 4.4 버전부터는 모든 멤버에 일괄적으로 인덱스를 부여합니다.
- Unique Index의 경우 컬렉션에 대해서 쓰기가 없다는 것을 보장하고 인덱스를 생성해야 하기 때문에 번거로움
- Index 생성하는 동안 쓰기 lock이 걸리기 때문에 Index 생성 시간이 Oplog Window Hour보다 작아야 함
- oplog window hour보다 index drop이 오래 걸릴 경우 initial sync를 진행하는 데 걸리는 시간이 엄청 오래 걸릴 수 있기 때문에 충분한 시간을 부여해야 함
Resumable Index Build
MongoDB 5.0 버전부터는 인덱스 생성 중에 "정상적으로 프로세스가 종료"될 경우 다시 기동 되었을 때 기존의 progress에 이어서 인덱스가 생성됩니다.
하지만, 서비스 운영 중 발생하는 프로세스 종료는 대부분 비정상적으로 종료가 되기 때문에 처음부터 인덱스를 생성하는 경우가 대부분입니다.
참고
백엔드 개발자를 위한 한 번에 끝내는 대용량 데이터 & 트래픽 처리 초격자 패키지 Online - 비즈니스 요구사항에 유연한 MongoDB
'DB > MongoDB' 카테고리의 다른 글
[MongoDB] 스키마 모델링 기법 (0) | 2023.12.08 |
---|---|
[MongoDB] Read/Write 제어 (0) | 2023.11.29 |
[MongoDB] MongoDB 개요 (0) | 2023.11.26 |