효과적인 인덱싱 전략을 위해서 가장 중요한 것은 “Selectivity(선택성)“를 높이는 것이다.
즉, 최대한 좁은 범위를 탐색할 수 있도록 인덱스를 만들어야 한다. 그리고 읽기보다 쓰기 작업이 많은 컬렉션에는 인덱스를 복잡하게 설계하지 않아야 한다.
DB의 인덱스는 책의 목차와 비슷
db는 전체 내용을 확인하지 않고, 특정 내용을 가르키는 정렬된 리스트를 확인한다.
몽고DB가 무엇을 하는지 확인 가능
// 100만 유저 생성
> for (i=0; i < 1000000; i++) {
db.User.insertOne(
{
"i": i,
"username": "user" + i,
"created": new Date()
}
);
}
--------------------------------------------------------------------------------
//
> db.User.find({"username":"user101"}).explain("executionStats")
>> { explainVersion: '1',
queryPlanner:
{ namespace: 'practice.User',
indexFilterSet: false,
parsedQuery: { username: { '$eq': 'user101' } },
queryHash: '7D9BB680',
planCacheKey: '7D9BB680',
maxIndexedOrSolutionsReached: false,
maxIndexedAndSolutionsReached: false,
maxScansToExplodeReached: false,
winningPlan:
{ stage: 'COLLSCAN',
filter: { username: { '$eq': 'user101' } },
direction: 'forward' },
rejectedPlans: [] },
executionStats:
{ executionSuccess: true,
nReturned: 1, // return 받은 개수
executionTimeMillis: 8,
totalKeysExamined: 0,
totalDocsExamined: 10558, // 살펴본 document 개수
executionStages:
{ stage: 'COLLSCAN',
filter: { username: { '$eq': 'user101' } },
nReturned: 1,
executionTimeMillisEstimate: 2,
works: 10559,
advanced: 1,
needTime: 10557,
needYield: 0,
saveState: 10,
restoreState: 10,
isEOF: 1,
direction: 'forward',
docsExamined: 10558 } },
command:
{ find: 'User',
filter: { username: 'user101' },
'$db': 'practice' },
serverInfo:
{ host: 'ac-hs55kvl-shard-00-01.o9ycmpz.mongodb.net',
port: 27017,
version: '6.0.10',
gitVersion: '8e4b5670df9b9fe814e57cb5f3f8ee9407237b5a' },
serverParameters:
{ internalQueryFacetBufferSizeBytes: 104857600,
internalQueryFacetMaxOutputDocSizeBytes: 104857600,
internalLookupStageIntermediateDocumentMaxSizeBytes: 16793600,
internalDocumentSourceGroupMaxMemoryBytes: 104857600,
internalQueryMaxBlockingSortMemoryUsageBytes: 33554432,
internalQueryProhibitBlockingMergeOnMongoS: 0,
internalQueryMaxAddToSetBytes: 104857600,
internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600 },
ok: 1,
'$clusterTime':
{ clusterTime: Timestamp({ t: 1695037335, i: 14 }),
signature:
{ hash: Binary(Buffer.from("a21048e472375d56e19d63153edd582cc7313ad1", "hex"), 0),
keyId: 7221939211316756000 } },
operationTime: Timestamp({ t: 1695037335, i: 14 }) }
totalDocsExamined : 모든 doc을 조회 한 이유 - 중복 된 값이 있을 수 있기 때문
인덱스 생성
> db.User.createIndex({"username":1})
>> 'username_1'
인덱스 쿼리
> db.User.find({"username":"user101"}).explain("executionStats")
>> {
explainVersion: '1',
queryPlanner: {
namespace: 'practice.User',
indexFilterSet: false,
parsedQuery: { username: { '$eq': 'user101' } },
queryHash: '7D9BB680',
planCacheKey: '24069050',
maxIndexedOrSolutionsReached: false,
maxIndexedAndSolutionsReached: false,
maxScansToExplodeReached: false,
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { username: 1 },
indexName: 'username_1',
isMultiKey: false,
multiKeyPaths: { username: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { username: [ '["user101", "user101"]' ] }
}
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 0,
totalKeysExamined: 1,
totalDocsExamined: 1, // 1개 조회
executionStages: {
stage: 'FETCH',
nReturned: 1, // 1개 return
executionTimeMillisEstimate: 0, // 0mil초
works: 2,
advanced: 1,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
docsExamined: 1,
alreadyHasObj: 0,
inputStage: {
stage: 'IXSCAN',
nReturned: 1,
executionTimeMillisEstimate: 0,
works: 2,
advanced: 1,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
keyPattern: { username: 1 },
indexName: 'username_1',
isMultiKey: false,
multiKeyPaths: { username: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { username: [ '["user101", "user101"]' ] },
keysExamined: 1,
seeks: 1,
dupsTested: 0,
dupsDropped: 0
}
}
},
command: { find: 'User', filter: { username: 'user101' }, '$db': 'practice' },
serverInfo: {
host: 'ac-hs55kvl-shard-00-01.o9ycmpz.mongodb.net',
port: 27017,
version: '6.0.10',
gitVersion: '8e4b5670df9b9fe814e57cb5f3f8ee9407237b5a'
},
serverParameters: {
internalQueryFacetBufferSizeBytes: 104857600,
internalQueryFacetMaxOutputDocSizeBytes: 104857600,
internalLookupStageIntermediateDocumentMaxSizeBytes: 16793600,
internalDocumentSourceGroupMaxMemoryBytes: 104857600,
internalQueryMaxBlockingSortMemoryUsageBytes: 33554432,
internalQueryProhibitBlockingMergeOnMongoS: 0,
internalQueryMaxAddToSetBytes: 104857600,
internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
},
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1695038170, i: 7 }),
signature: {
hash: Binary(Buffer.from("c6c180ca513554054ad17bdf94d91f2031f89230", "hex"), 0),
keyId: Long("7221939211316756482")
}
},
operationTime: Timestamp({ t: 1695038170, i: 7 })
}
> db.User.find().sort({'created': 1, 'username': 1}) // created 정렬 이후 username 정렬> db.User.createIndex({'age': 1, 'username': 1})
>> 'age_1_username_1'> db.User.find({'age': 21}).sort({"username":-1})
username 이 인덱싱이 되어 있기 때문에 age 21의 마지막 항목부터 순서대로 인덱스 탐색> db.user.find({"age": {"$gte": 21, "$lte": 30}})
age를 사용하여 도큐먼트 반환> db.user.find({"age": {"$gte": 21, "$lte": 30}}).sort({"username": 1})
같은 키를 역순으로 한 인덱스 또한 사용 가능
db.users.craeteIndex(
{"firstname":1},
{
"unique": true,
"partialFileterExpression": {"firstname": {$exists:true}
}
}
)db.{CollectionName}.getIndexes()로 정보 확인 가능db.users.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: '_id_' },
{ v: 2, key: { username: 1 }, name: 'username_1' },
{ v: 2, key: { age: 1, created: 1 }, name: 'age_1_created_1' },
{ v: 2, key: { created: 1, age: 1 }, name: 'created_1_age_1' }
]같은 필드를 가져도 순서에 따라 다른 인덱스가 된다.
v: 1 이 없는 필드는 오래되고 비효율적인 형식으로 저장된 상태이다.
인덱스명은 서버에서 컨트롤 용도로 사용
indexName1_indexDirection1_indexName2_indexDirection2…
db.users.createIndex({'a':1, 'b':1....}, {'name': myIndex})
name을 넣어 인덱스명 지정 가능
db.user.dropIndex(”IndexName”)
mongoDB 4.2 이후 버전에서는 인덱스를 빨리 생성하기 위해, 완료될 때까지 읽기, 쓰기를 중단.
읽기, 쓰기가 작동하게 하려면 생성할 때 background 옵션을 사용 - foreground indexing 보다 느려짐
mongoDB 4.2 시작과 끝에만 락을 가짐, interleaving 으로 작업
속도 : 기존 도큐먼트에 인덱스 넣기 > 인덱싱 후 도큐먼트 넣기