Cursus
Bij elke database, inclusief MongoDB, zijn indexen een cruciaal onderdeel van het ontwikkelproces. Zonder indexen zal de databaseprestaties afnemen naarmate je database in omvang groeit. Met geldige indexen blijft de toegangsprestatie optimaal wanneer je data groeit.
In dit artikel bekijken we de verschillende soorten indexen die beschikbaar zijn in MongoDB, een paar tips en tricks voor de beste ervaring en instructies om zeker te weten dat je de indexen die je aanmaakt ook echt benut. Als je MongoDB nog aan het leren bent, raad ik de Introduction to MongoDB-cursus aan.
Waar zijn MongoDB-indexen belangrijk en waarom?
Als we het over indexen hebben, hebben we het over performance. Indexen maken het verschil tussen elk document in een collectie bekijken om te zien of het overeenkomt met onze query, of een specifiekere set documenten die overeenkomt met onze query. Als onze collectie klein is en er niet veel documenten zijn om door te lopen, is de performance in beide gevallen goed. Maar als we 100.000 documenten in een collectie hebben en er 100 aan onze query voldoen, kost het waarschijnlijk wat tijd om alle 100.000 documenten te bekijken om die 100 te vinden, als er geen index is gebaseerd op die 100 documenten.
Er zijn talloze soorten indexen in MongoDB:
- Eén veld: Een index op één specifiek veld binnen het document. De standaardindex op het veld
_idkan worden beschouwd als een enkelveldindex. - Samengesteld: Een index op basis van meerdere velden van een document, waarbij de volgorde van de velden die in de query en in de index worden gebruikt belangrijk is.
- Multikey: Een index die wordt gebruikt op array-velden.
- Wildcard: Een index die velden ondersteunt die tussen documenten net iets anders kunnen heten. Deze index presteert minder goed dan andere indexen.
- Georuimtelijk: Een index om de prestaties te verbeteren bij georuimtelijke queries die breedte- en lengtegraadcoördinaten gebruiken.
- Gehasht: Een index die het best wordt gebruikt met shard keys.
Voor meer informatie over de verschillende indextypen, bekijk de MongoDB-documentatie.
Vaker wel dan niet zul je enkelveld-, samengestelde of multikey-indexen gebruiken, maar uiteindelijk hangt het echt af van het type applicatie, queries en data.
Indexen zijn belangrijk bij elke database-interactie die documenten ophaalt. Dit is niet beperkt tot het ophalen van documenten met een find- of findOne-operatie. Bij het updaten en verwijderen van documenten gebruik je ook een matchcriteria die de database gebruikt om documenten te vinden voordat de update of delete wordt uitgevoerd.
De operators find en findOne zijn niet de enige retrieval-operators die profiteren van een index. Wanneer je de $match-operator gebruikt als de eerste stap van je aggregatiepijplijn, zal een index de boel drastisch versnellen.
MongoDB-indexering: tips en tricks voor succes
Willekeurige indexen aanmaken kan net zo inefficiënt zijn als helemaal geen indexen gebruiken in MongoDB. Denk in plaats daarvan aan een paar zaken die je helpen een veel efficiëntere applicatie te bouwen.
Equality, sort, range (ESR) voor samengestelde indexen
De vuistregel bij het maken van een samengestelde index is bepalen hoe je elk van de velden in je query gaat gebruiken en zorgen dat het de ESR-best practice volgt.
Wat betekent ESR? Laten we het opsplitsen:
- Equality (gelijkheid): Velden die exact gematcht worden, niet losjes. Bijvoorbeeld, matchen op iemands exacte naam in je query is een equality-stap.
- Sort (sorteren): Velden die je gebruikt om oplopend of aflopend te sorteren.
- Range (bereik): Velden waarop je matcht die geen exacte match zijn. Bijvoorbeeld, met de operator
$ltof$gtmatch je documenten binnen een bereik, maar niet op een specifieke exacte waarde.
Om dit te verduidelijken, bekijk de volgende query:
db.employee.find(
{
"department": "Developer Relations",
"salary": {
"$lt": 80000
}
}
).sort(
{
"name": 1
}
);
De bovenstaande query doet een equality-match op de afdeling, een range-match op het salaris en sorteert de resultaten op naam.
Volgens de ESR-regels zou de index er ongeveer zo uitzien:
db.employee.createIndex({
"department": 1,
"name": 1,
"salary": 1
});
We willen de ESR-regels volgen om in-memory sorts te voorkomen en de zoekruimte te verkleinen voor een performante query-uitvoering.
Covered queries
Indexeren in het algemeen zorgt bij een query voor een kleinere subset documenten om door te scannen en verbetert zo de prestaties. Maar als je de prestaties van je applicatie naar een hoger niveau wilt tillen, kijk dan naar covered queries. Bij een covered query haal je alle resultaten direct uit de index, zonder documenten in je collectie te hoeven scannen.
Er zijn een paar haken en ogen die covered queries in jouw applicatie wel of niet mogelijk maken:
- De matchcriteria en sortering moeten exact overeenkomen met waar je index voor is aangemaakt.
- De velden die je in je resultaatset opvraagt, moeten ook overeenkomen met je index.
- Bij sharding moet de shard key onderdeel zijn van je index.
Wat betekent dit concreet?
Neem bijvoorbeeld de volgende index:
db.department.createIndex({
"country": 1,
"salary": 1
});
In het bovenstaande voorbeeld willen we onze documenten indexeren op het veld country en het veld salary. Vervolgens kunnen we de volgende query uitvoeren:
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1
}
);
De bovenstaande query, met projectie, zal onze index gebruiken, maar het is geen covered query. Het veld _id wordt standaard door MongoDB teruggegeven en in dit voorbeeld is het veld _id nooit aan onze index toegevoegd. We kunnen dus óf onze index aanpassen, óf onze query. In het voorbeeld waarbij we de query aanpassen, kan dat zo:
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1,
"_id": 0
}
);
Onthoud: als je een covered query wilt gebruiken, moet de index geldig zijn en moeten de opgevraagde velden in de index zijn opgenomen. Anders vind je alleen documenten die door de index worden beheerd, niet in de index zelf.
Je kunt valideren of een covered query wordt gebruikt door de parameter executionStats te gebruiken in de operator explain die verderop in dit artikel wordt genoemd. Het resultaat totalDocsExamined bepaalt wat er wordt gebruikt. Als de waarde nul is, gebruik je een covered query.
Onnodige indexen verminderen of elimineren
Indexen zijn niet gratis in de context van serverresources. Elke index moet door de server worden onderhouden en dat onderhoud gebruikt CPU, RAM en schijf. Naarmate je meer indexen aanmaakt, creëer je meer overhead, wat kan leiden tot hogere kosten of lagere prestaties bij een ondergedimensioneerde server. Deze performancevermindering kan ook schrijfbewerkingen en deletes raken, niet alleen reads, omdat MongoDB meer resources verbruikt.
Het is een goed idee om alleen indexen te maken voor veel uitgevoerde queries. Heeft die query die eens per maand draait echt een index nodig? Waarschijnlijk niet. Heeft die query die elke 10 seconden draait een index nodig? Zeker weten!
Leer meer over een paar extra best practices voor indexering op de MongoDB-blog.
Werkt je MongoDB-index?
Alleen omdat je een index in MongoDB hebt aangemaakt, betekent dat nog niet dat de query die je uitvoert deze ook daadwerkelijk gebruikt. In MongoDB kunnen we onderzoeken welke index voor een bepaalde query wordt gebruikt.
Neem bijvoorbeeld de volgende index:
db.people.createIndex({
"name": 1,
"is_active": 1
});
In het bovenstaande voorbeeld indexeren we op een veld name en een veld is_active. Nu willen we de volgende query uitvoeren:
db.people.find({
"is_active": true
});
De bovenstaande query draait met of zonder geldige index, en als de dataset klein is, gaat het ongeacht dat snel. Als we echter willen zien of onze index wordt gebruikt, kunnen we de query aanpassen:
db.people.find({
"is_active": true
}).explain();
Deze keer gebruiken we de methode explain om het uitvoeringsplan van de query te krijgen. In het uitvoeringsplan kunnen we het volgende zien:
winningPlan: {
isCached: false,
stage: 'COLLSCAN',
filter: { is_active: { '$eq': true } },
direction: 'forward'
}
Voor deze specifieke query gebruikte MongoDB een "COLLSCAN", wat betekent dat de index niet is gebruikt, ook al bevatte de query een van onze velden. Als we onze query wijzigen naar iets als dit:
db.people.find({
"name": "Nic Raboy",
"is_active": true
}).explain();
Het resultaat van onze explain is veranderd:
winningPlan: {
isCached: false,
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { name: 1, is_active: 1 },
indexName: 'name_1_is_active_1',
isMultiKey: false,
multiKeyPaths: { name: [], is_active: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: {
name: [ '["Nic Raboy", "Nic Raboy"]' ],
is_active: [ '[true, true]' ]
}
}
},
Deze keer zei het uitvoeringsplan dat we een "IXSCAN" gebruikten en het specificeerde de naam van de gebruikte index, die in dit geval de index name_1_is_active_1 was. De naam werd automatisch gedefinieerd toen we de index aanmaakten.
Voor een update-operatie is het proces vergelijkbaar, met als uitzondering waar de functie explain wordt geplaatst.
Een update-operatie kan er als volgt uitzien:
db.people.explain().update(
{
“name”: “Nic Raboy”,
“Is_active”: true
},
{
“$set”: { “is_active”: false }
}
);
Merk op dat in het bovenstaande voorbeeld de explain plaatsvindt vóór de functie update.
Als je de verwachte index in je update-operatie gebruikt, zie je de waarde "IXSCAN" samen met de indexnaam. Meer informatie over update-operaties en het gebruik van indexen daarbij.
Een alternatieve methode om de details van het uitvoeringsplan van je query te vinden, is gebruikmaken van runCommand op de database. Hoewel het minder elegant oogt, is het behoorlijk krachtig en ziet het er ongeveer zo uit:
db.runCommand({
"explain": {
"update": "people",
"updates": [
{
"q": {
"name": "Nic Raboy",
"is_active": true
},
"u": {
"$set": { "is_active": false }
}
}
]
},
"verbosity": "allPlansExecution"
});
Het bovenstaande werkt hetzelfde als de update-operatie met de functie explain.
Als je niet zeker bent over het maken van indexen, of als je merkt dat je queries trager worden uitgevoerd dan verwacht, bekijk dan de uitvoeringsdetails.
Conclusie
Je hebt zojuist enkele best practices rond indexering in MongoDB gezien. Hoewel we niet elk mogelijk indexeringsscenario hebben verkend, kun je dit gebruiken als vertrekpunt om ervoor te zorgen dat je gebruikers de best mogelijke ervaring krijgen bij het werken met je data.
Om een paar dingen te herhalen:
- Volg waar mogelijk de equality, sort, range (ESR)-regel.
- Probeer, als het kan, een covered query te doen.
- Ga niet lukraak indexen creëren, maar bedenk welke queries het meest gebruikt zullen worden en hoeveel data daarin wordt opgehaald.
- Leun op de operator
explainom je indexen en hun prestaties te testen.
De bovenstaande punten zijn geen uitputtende lijst van best practices voor indexering die de prestaties van je applicatie zullen verbeteren. Er zijn ook andere factoren, zoals cardinaliteit.
Voor meer hulp bij indexering, bekijk de MongoDB-documentatie. Als je een betaald niveau binnen MongoDB Atlas gebruikt, overweeg dan enkele van de ingebouwde performance adviser-tools voor index- en queryaanbevelingen.
MongoDB-indexering: veelgestelde vragen
Hoeveel indexen moet ik aanmaken in een MongoDB-collectie?
Dat hangt ervan af. Je zult een balans moeten vinden die voor jou werkt, maar overweeg om alleen te indexeren op queries die vaak worden uitgevoerd in plaats van af en toe.
Ik heb een paar indexen gemaakt, maar mijn queries zijn nog steeds traag. Hoe kan dat?
Weet je zeker dat je queries de indexen gebruiken die je hebt aangemaakt? Gebruik de operator explain of enkele van de adviestools in MongoDB Atlas voor troubleshooting.
Moet bij de ESR-regel de volgorde van indexvelden exact gelijkheid, dan sorteren, dan bereik zijn?
Hoewel MongoDB soms een index buiten volgorde kan gebruiken, is het het meest effectief wanneer de volgorde van de indexsleutels overeenkomt met de sequentie van de query: eerst equality-filters, dan sorteringen en tot slot range.
Wat zijn de verschillende explain-modi?
Standaard draait explain() in de modus "queryPlanner", maar als je andere modi wilt, zijn er ook "executionStats" en "allPlansExecution".

Nic Raboy is een Developer Relations Lead bij MongoDB, waar hij een team van Python-, Java-, C#- en PHP-ontwikkelaars aanstuurt dat geweldige content maakt om developers te helpen MongoDB succesvol in hun projecten op te nemen. Hij heeft ervaring met Golang en JavaScript en schrijft vaak over zijn ontwikkelavonturen.
