Meer informatie over het modelleren en partitioneren van gegevens in Azure Cosmos DB aan de hand van een praktijkvoorbeeld
VAN TOEPASSING OP: NoSQL
Dit artikel bouwt voort op verschillende Concepten van Azure Cosmos DB, zoals gegevensmodellering, partitionering en ingerichte doorvoer om te laten zien hoe u een praktijkoefening voor gegevensontwerp kunt aanpakken.
Als u meestal met relationele databases werkt, hebt u waarschijnlijk gewoonten en intuïtieverheid ontwikkeld over het ontwerpen van een gegevensmodel. Vanwege de specifieke beperkingen, maar ook de unieke sterke punten van Azure Cosmos DB, worden de meeste van deze aanbevolen procedures niet goed vertaald en kunt u naar suboptimale oplossingen slepen. Het doel van dit artikel is om u te begeleiden bij het volledige proces van het modelleren van een praktijkgebruiksscenario in Azure Cosmos DB, van itemmodellering tot colocatie van entiteiten en containerpartitionering.
Download of bekijk een door de community gegenereerde broncode die de concepten uit dit artikel illustreert.
Belangrijk
Een inzender van de community heeft dit codevoorbeeld bijgedragen en het Azure Cosmos DB-team biedt geen ondersteuning voor het onderhoud.
Het scenario
Voor deze oefening gaan we rekening houden met het domein van een blogplatform waar gebruikers berichten kunnen maken. Gebruikers kunnen ook opmerkingen aan deze berichten leuk vinden en toevoegen.
Tip
We hebben enkele woorden cursief gemarkeerd. Deze woorden identificeren het soort 'dingen' dat ons model moet manipuleren.
Meer vereisten toevoegen aan onze specificatie:
- Op een voorpagina wordt een feed met onlangs gemaakte berichten weergegeven,
- We kunnen alle berichten voor een gebruiker, alle opmerkingen voor een bericht en alle vind-ik-leuks voor een bericht ophalen,
- Berichten worden geretourneerd met de gebruikersnaam van hun auteurs en een telling van het aantal opmerkingen en vind-ik-leuks,
- Opmerkingen en vind-ik-leuks worden ook geretourneerd met de gebruikersnaam van de gebruikers die ze hebben gemaakt,
- Wanneer berichten worden weergegeven als lijsten, hoeven berichten alleen een afgekapte samenvatting van hun inhoud weer te geven.
De belangrijkste toegangspatronen identificeren
Om te beginnen geven we een structuur aan onze eerste specificatie door de toegangspatronen van onze oplossing te identificeren. Bij het ontwerpen van een gegevensmodel voor Azure Cosmos DB is het belangrijk om te begrijpen welke aanvragen het model moet dienen om ervoor te zorgen dat het model deze aanvragen efficiënt verwerkt.
Om het algehele proces gemakkelijker te volgen, categoriseren we deze verschillende aanvragen als opdrachten of query's, waardoor een deel van de woordenlijst van CQRS wordt geleend. In CQRS zijn opdrachten schrijfaanvragen (dat wil gezegd, intenties om het systeem bij te werken) en query's zijn alleen-lezen aanvragen.
Hier volgt de lijst met aanvragen die door ons platform worden weergegeven:
- [C1] Een gebruiker maken/bewerken
- [Q1] Een gebruiker ophalen
- [C2] Een bericht maken/bewerken
- [Q2] Een bericht ophalen
- [Q3] Berichten van een gebruiker in korte vorm weergeven
- [C3] Een opmerking maken
- [Q4] Opmerkingen van een bericht weergeven
- [C4] Een bericht leuk vinden
- [Q5] Vind-ik-leuks van een bericht weergeven
- [Q6] De x meest recente berichten weergeven die zijn gemaakt in korte vorm (feed)
In deze fase hebben we niet nagedacht over de details van wat elke entiteit (gebruiker, post enzovoort) bevat. Deze stap is meestal een van de eerste die moeten worden aangepakt bij het ontwerpen op basis van een relationeel archief. We beginnen met deze stap eerst omdat we moeten achterhalen hoe deze entiteiten zich vertalen in termen van tabellen, kolommen, refererende sleutels, enzovoort. Het is veel minder een probleem met een documentdatabase die geen schema bij schrijven afdwingt.
De belangrijkste reden waarom het belangrijk is om onze toegangspatronen vanaf het begin te identificeren, is omdat deze lijst met aanvragen onze testsuite wordt. Telkens wanneer we het gegevensmodel herhalen, doorlopen we elk van de aanvragen en controleren we de prestaties en schaalbaarheid. We berekenen de verbruikte aanvraageenheden in elk model en optimaliseren ze. Al deze modellen gebruiken het standaardindexeringsbeleid en u kunt dit overschrijven door specifieke eigenschappen te indexeren, waardoor het RU-verbruik en de latentie verder kunnen worden verbeterd.
V1: Een eerste versie
We beginnen met twee containers: users
en posts
.
Gebruikerscontainer
In deze container worden alleen gebruikersitems opgeslagen:
{
"id": "<user-id>",
"username": "<username>"
}
We partitioneren deze container op id
, wat betekent dat elke logische partitie binnen die container slechts één item bevat.
Berichtencontainer
Deze container host entiteiten zoals berichten, opmerkingen en vind-ik-leuks:
{
"id": "<post-id>",
"type": "post",
"postId": "<post-id>",
"userId": "<post-author-id>",
"title": "<post-title>",
"content": "<post-content>",
"creationDate": "<post-creation-date>"
}
{
"id": "<comment-id>",
"type": "comment",
"postId": "<post-id>",
"userId": "<comment-author-id>",
"content": "<comment-content>",
"creationDate": "<comment-creation-date>"
}
{
"id": "<like-id>",
"type": "like",
"postId": "<post-id>",
"userId": "<liker-id>",
"creationDate": "<like-creation-date>"
}
We partitioneren deze container op postId
, wat betekent dat elke logische partitie in die container één post bevat, alle opmerkingen voor dat bericht en alle vind-ik-leuks voor dat bericht.
We hebben een type
eigenschap geïntroduceerd in de items die zijn opgeslagen in deze container om onderscheid te maken tussen de drie typen entiteiten die door deze container worden gehost.
We hebben er ook voor gekozen om te verwijzen naar gerelateerde gegevens in plaats van deze in te sluiten (raadpleeg deze sectie voor meer informatie over deze concepten), omdat:
- er is geen bovengrens voor het aantal berichten dat een gebruiker kan maken,
- berichten kunnen willekeurig lang zijn,
- er is geen bovengrens voor het aantal opmerkingen en vind ik leuk dat een bericht kan hebben,
- we willen een opmerking of een like aan een bericht kunnen toevoegen zonder het bericht zelf bij te werken.
Hoe goed presteert ons model?
Het is nu tijd om de prestaties en schaalbaarheid van onze eerste versie te beoordelen. Voor elk van de eerder geïdentificeerde aanvragen meten we de latentie en het aantal aanvraageenheden dat wordt verbruikt. Deze meting wordt uitgevoerd op basis van een dummygegevensset met 100.000 gebruikers met 5 tot 50 berichten per gebruiker, tot 25 opmerkingen en 100 vind-ik-leuks per bericht.
[C1] Een gebruiker maken/bewerken
Deze aanvraag is eenvoudig te implementeren omdat we alleen een item in de users
container maken of bijwerken. De aanvragen worden mooi verdeeld over alle partities dankzij de id
partitiesleutel.
Latentie | RU-kosten | Prestaties |
---|---|---|
7 mevrouw |
5.71 RU |
✅ |
[Q1] Een gebruiker ophalen
Het ophalen van een gebruiker wordt uitgevoerd door het bijbehorende item uit de users
container te lezen.
Latentie | RU-kosten | Prestaties |
---|---|---|
2 mevrouw |
1 RU |
✅ |
[C2] Een bericht maken/bewerken
Net als bij [C1] moeten we alleen naar de posts
container schrijven.
Latentie | RU-kosten | Prestaties |
---|---|---|
9 mevrouw |
8.76 RU |
✅ |
[Q2] Een bericht ophalen
We beginnen met het ophalen van het bijbehorende document uit de posts
container. Maar dat is niet voldoende, volgens onze specificatie moeten we ook de gebruikersnaam van de auteur van het bericht aggregeren, het aantal opmerkingen en het aantal vind-ik-leuks voor het bericht. Voor de vermelde aggregaties moeten nog drie SQL-query's worden uitgegeven.
Elk van de meer query's filters op de partitiesleutel van de respectieve container, wat precies is wat we willen maximaliseren prestaties en schaalbaarheid. Maar uiteindelijk moeten we vier bewerkingen uitvoeren om één post te retourneren, dus we verbeteren dat in een volgende iteratie.
Latentie | RU-kosten | Prestaties |
---|---|---|
9 mevrouw |
19.54 RU |
⚠ |
[Q3] Berichten van een gebruiker in korte vorm weergeven
Eerst moeten we de gewenste berichten ophalen met een SQL-query waarmee de berichten worden opgehaald die overeenkomen met die specifieke gebruiker. Maar we moeten ook meer query's uitgeven om de gebruikersnaam van de auteur en het aantal opmerkingen en vind-ik-leuks samen te voegen.
Deze implementatie biedt veel nadelen:
- de query's die het aantal opmerkingen aggregeren en likes moeten worden uitgegeven voor elk bericht dat door de eerste query wordt geretourneerd,
- de hoofdquery filtert niet op de partitiesleutel van de
posts
container, wat leidt tot een uitwaaier en een partitiescan in de container.
Latentie | RU-kosten | Prestaties |
---|---|---|
130 mevrouw |
619.41 RU |
⚠ |
[C3] Een opmerking maken
Er wordt een opmerking gemaakt door het bijbehorende item in de posts
container te schrijven.
Latentie | RU-kosten | Prestaties |
---|---|---|
7 mevrouw |
8.57 RU |
✅ |
[Q4] Opmerkingen van een bericht weergeven
We beginnen met een query waarmee alle opmerkingen voor dat bericht worden opgehaald en opnieuw moeten we ook gebruikersnamen afzonderlijk aggregeren voor elke opmerking.
Hoewel de hoofdquery wel filtert op de partitiesleutel van de container, worden de gebruikersnamen afzonderlijk gestraft voor de algehele prestaties. Dat verbeteren we later.
Latentie | RU-kosten | Prestaties |
---|---|---|
23 mevrouw |
27.72 RU |
⚠ |
[C4] Een bericht leuk vinden
Net als [C3] maken we het bijbehorende item in de posts
container.
Latentie | RU-kosten | Prestaties |
---|---|---|
6 mevrouw |
7.05 RU |
✅ |
[Q5] Vind-ik-leuks van een bericht weergeven
Net als bij [Q4] voeren we een query uit op de vind-ik-leuks voor dat bericht en voegen we vervolgens hun gebruikersnamen samen.
Latentie | RU-kosten | Prestaties |
---|---|---|
59 mevrouw |
58.92 RU |
⚠ |
[Q6] De x meest recente berichten weergeven die zijn gemaakt in korte vorm (feed)
We halen de meest recente berichten op door een query uit te voeren op de posts
container, gesorteerd op aflopende aanmaakdatum, en vervolgens gebruikersnamen en tellingen van opmerkingen en vind-ik-leuks voor elk van de berichten samen te voegen.
Nogmaals, onze eerste query filtert niet op de partitiesleutel van de posts
container, waardoor een kostbare fan-out wordt geactiveerd. Deze is nog erger omdat we een grotere resultatenset richten en de resultaten sorteren met een ORDER BY
component, waardoor het duurder wordt in termen van aanvraageenheden.
Latentie | RU-kosten | Prestaties |
---|---|---|
306 mevrouw |
2063.54 RU |
⚠ |
Nadenken over de prestaties van V1
Als we kijken naar de prestatieproblemen die we in de vorige sectie hebben ondervonden, kunnen we twee hoofdklassen van problemen identificeren:
- voor sommige aanvragen moeten meerdere query's worden uitgegeven om alle gegevens te verzamelen die we moeten retourneren.
- Sommige query's filteren niet op de partitiesleutel van de containers die ze targeten, wat leidt tot een fan-out die onze schaalbaarheid belemmert.
Laten we elk van deze problemen oplossen, beginnend met de eerste.
V2: Inleiding tot denormalisatie om leesquery's te optimaliseren
De reden waarom we in sommige gevallen meer aanvragen moeten uitgeven, is omdat de resultaten van de eerste aanvraag niet alle gegevens bevatten die we moeten retourneren. Het denormaliseren van gegevens lost dit soort problemen op in onze gegevensset wanneer u werkt met een niet-relationeel gegevensarchief zoals Azure Cosmos DB.
In ons voorbeeld wijzigen we postitems om de gebruikersnaam van de auteur van het bericht toe te voegen, het aantal opmerkingen en het aantal vind-ik-leuks:
{
"id": "<post-id>",
"type": "post",
"postId": "<post-id>",
"userId": "<post-author-id>",
"userUsername": "<post-author-username>",
"title": "<post-title>",
"content": "<post-content>",
"commentCount": <count-of-comments>,
"likeCount": <count-of-likes>,
"creationDate": "<post-creation-date>"
}
We wijzigen ook opmerkingen en like-items om de gebruikersnaam toe te voegen van de gebruiker die deze heeft gemaakt:
{
"id": "<comment-id>",
"type": "comment",
"postId": "<post-id>",
"userId": "<comment-author-id>",
"userUsername": "<comment-author-username>",
"content": "<comment-content>",
"creationDate": "<comment-creation-date>"
}
{
"id": "<like-id>",
"type": "like",
"postId": "<post-id>",
"userId": "<liker-id>",
"userUsername": "<liker-username>",
"creationDate": "<like-creation-date>"
}
Denormalisatie van opmerkingen en like-tellingen
Wat we willen bereiken, is dat telkens wanneer we een opmerking of een dergelijke toevoegen, we ook de commentCount
of de likeCount
in het bijbehorende bericht verhogen. Als postId
de container wordt gepartitioneert posts
, bevinden het nieuwe item (opmerking of vind ik leuk) en het bijbehorende bericht zich in dezelfde logische partitie. Als gevolg hiervan kunnen we een opgeslagen procedure gebruiken om die bewerking uit te voeren.
Wanneer u een opmerking maakt ([C3]), wordt in plaats van alleen een nieuw item toe te voegen in de posts
container, de volgende opgeslagen procedure voor die container aangeroepen:
function createComment(postId, comment) {
var collection = getContext().getCollection();
collection.readDocument(
`${collection.getAltLink()}/docs/${postId}`,
function (err, post) {
if (err) throw err;
post.commentCount++;
collection.replaceDocument(
post._self,
post,
function (err) {
if (err) throw err;
comment.postId = postId;
collection.createDocument(
collection.getSelfLink(),
comment
);
}
);
})
}
Deze opgeslagen procedure neemt de id van het bericht en de hoofdtekst van de nieuwe opmerking als parameters, en vervolgens:
- haalt het bericht op
- wordt de
commentCount
- vervangt het bericht
- voegt de nieuwe opmerking toe
Aangezien opgeslagen procedures worden uitgevoerd als atomische transacties, blijft de waarde van commentCount
en het werkelijke aantal opmerkingen altijd gesynchroniseerd.
We noemen uiteraard een vergelijkbare opgeslagen procedure bij het toevoegen van nieuwe likes om het likeCount
te verhogen.
Gebruikersnamen denormaliseren
Gebruikersnamen vereisen een andere benadering omdat gebruikers niet alleen in verschillende partities, maar in een andere container zitten. Wanneer we gegevens moeten denormaliseren tussen partities en containers, kunnen we de wijzigingenfeed van de broncontainer gebruiken.
In ons voorbeeld gebruiken we de wijzigingenfeed van de users
container om te reageren wanneer gebruikers hun gebruikersnamen bijwerken. Als dat gebeurt, wordt de wijziging doorgegeven door een andere opgeslagen procedure op de posts
container aan te roepen:
function updateUsernames(userId, username) {
var collection = getContext().getCollection();
collection.queryDocuments(
collection.getSelfLink(),
`SELECT * FROM p WHERE p.userId = '${userId}'`,
function (err, results) {
if (err) throw err;
for (var i in results) {
var doc = results[i];
doc.userUsername = username;
collection.upsertDocument(
collection.getSelfLink(),
doc);
}
});
}
Deze opgeslagen procedure gebruikt de id van de gebruiker en de nieuwe gebruikersnaam van de gebruiker als parameters. Vervolgens:
- haalt alle items op die overeenkomen met de
userId
items (die berichten, opmerkingen of vind-ik-leuks kunnen zijn) - voor elk van deze items
- vervangt de
userUsername
- vervangt het item
- vervangt de
Belangrijk
Deze bewerking is kostbaar omdat deze opgeslagen procedure moet worden uitgevoerd op elke partitie van de posts
container. We gaan ervan uit dat de meeste gebruikers tijdens de registratie een geschikte gebruikersnaam kiezen en deze nooit wijzigen, dus deze update wordt zelden uitgevoerd.
Wat zijn de prestatieverbeteringen van V2?
Laten we het hebben over enkele prestatieverbeteringen van V2.
[Q2] Een bericht ophalen
Nu onze denormalisatie is ingesteld, hoeven we slechts één item op te halen om die aanvraag af te handelen.
Latentie | RU-kosten | Prestaties |
---|---|---|
2 mevrouw |
1 RU |
✅ |
[Q4] Opmerkingen van een bericht weergeven
Ook hier kunnen we de extra aanvragen die de gebruikersnamen hebben opgehaald, besparen en uiteindelijk één query gebruiken die op de partitiesleutel filtert.
Latentie | RU-kosten | Prestaties |
---|---|---|
4 mevrouw |
7.72 RU |
✅ |
[Q5] Vind-ik-leuks van een bericht weergeven
Exact dezelfde situatie bij het weergeven van vind-ik-leuks.
Latentie | RU-kosten | Prestaties |
---|---|---|
4 mevrouw |
8.92 RU |
✅ |
V3: Ervoor zorgen dat alle aanvragen schaalbaar zijn
Er zijn nog steeds twee aanvragen die we niet volledig hebben geoptimaliseerd bij het bekijken van onze algehele prestatieverbeteringen. Deze aanvragen zijn [Q3] en [Q6]. Dit zijn de aanvragen die betrekking hebben op query's die niet filteren op de partitiesleutel van de containers waarop ze zijn gericht.
[Q3] Berichten van een gebruiker in korte vorm weergeven
Deze aanvraag profiteert al van de verbeteringen die zijn geïntroduceerd in V2, waardoor meer query's worden bespaard.
De resterende query wordt echter nog steeds niet gefilterd op de partitiesleutel van de posts
container.
De manier om na te denken over deze situatie is eenvoudig:
- Deze aanvraag moet worden gefilterd op de
userId
pagina omdat we alle berichten voor een bepaalde gebruiker willen ophalen. - Het werkt niet goed omdat deze wordt uitgevoerd op de
posts
container, die geen partitionering heeftuserId
. - Wat duidelijk is, zouden we ons prestatieprobleem oplossen door deze aanvraag uit te voeren op een container die is gepartitioneerd met
userId
. - Het blijkt dat we al een dergelijke container hebben: de
users
container!
We introduceren dus een tweede niveau van denormalisatie door hele berichten te dupliceren naar de users
container. Door dat te doen, krijgen we effectief een kopie van onze berichten, alleen gepartitioneerd langs een andere dimensie, waardoor ze veel efficiënter kunnen worden opgehaald door hun userId
.
De users
container bevat nu twee soorten items:
{
"id": "<user-id>",
"type": "user",
"userId": "<user-id>",
"username": "<username>"
}
{
"id": "<post-id>",
"type": "post",
"postId": "<post-id>",
"userId": "<post-author-id>",
"userUsername": "<post-author-username>",
"title": "<post-title>",
"content": "<post-content>",
"commentCount": <count-of-comments>,
"likeCount": <count-of-likes>,
"creationDate": "<post-creation-date>"
}
In dit voorbeeld:
- We hebben een
type
veld in het gebruikersitem geïntroduceerd om gebruikers te onderscheiden van berichten, - We hebben ook een
userId
veld toegevoegd in het gebruikersitem, dat overbodig is met hetid
veld, maar is vereist omdat deusers
container nu is gepartitioneerd metuserId
(en nietid
zoals eerder)
Om die denormalisatie te bereiken, gebruiken we opnieuw de wijzigingenfeed. Deze keer reageren we op de wijzigingenfeed van de posts
container om nieuwe of bijgewerkte post naar de users
container te verzenden. En omdat het weergeven van berichten niet nodig is om hun volledige inhoud te retourneren, kunnen we ze in het proces afkappen.
We kunnen onze query nu routeren naar de users
container en filteren op de partitiesleutel van de container.
Latentie | RU-kosten | Prestaties |
---|---|---|
4 mevrouw |
6.46 RU |
✅ |
[Q6] De x meest recente berichten weergeven die zijn gemaakt in korte vorm (feed)
We moeten hier een vergelijkbare situatie ondervinden: zelfs nadat de meer query's onnodig zijn gelaten door de denormalisatie die in V2 is geïntroduceerd, filtert de resterende query niet op de partitiesleutel van de container:
Voor het maximaliseren van de prestaties en schaalbaarheid van deze aanvraag moet slechts één partitie worden bereikt door dezelfde benadering te volgen. Het is mogelijk om slechts één partitie te bereiken, omdat we slechts een beperkt aantal items hoeven te retourneren. Om de startpagina van ons blogplatform te kunnen vullen, moeten we alleen de 100 meest recente berichten ophalen, zonder dat u de hele gegevensset hoeft te pagineren.
Om deze laatste aanvraag te optimaliseren, introduceren we een derde container in ons ontwerp, volledig gewijd aan het leveren van deze aanvraag. We denormaliseren onze berichten naar die nieuwe feed
container:
{
"id": "<post-id>",
"type": "post",
"postId": "<post-id>",
"userId": "<post-author-id>",
"userUsername": "<post-author-username>",
"title": "<post-title>",
"content": "<post-content>",
"commentCount": <count-of-comments>,
"likeCount": <count-of-likes>,
"creationDate": "<post-creation-date>"
}
Het type
veld partitioneert deze container, die zich altijd post
in onze items bevindt. Dit zorgt ervoor dat alle items in deze container zich in dezelfde partitie bevinden.
Om de denormalisatie te bereiken, moeten we alleen de pijplijn van de wijzigingenfeed koppelen die we eerder hebben geïntroduceerd om de berichten naar die nieuwe container te verzenden. Belangrijk om rekening mee te houden is dat we ervoor moeten zorgen dat we alleen de 100 meest recente berichten opslaan; anders kan de inhoud van de container groter worden dan de maximale grootte van een partitie. Deze beperking kan worden geïmplementeerd door een post-trigger aan te roepen telkens wanneer een document wordt toegevoegd in de container:
Hier volgt de hoofdtekst van de posttrigger waarmee de verzameling wordt afgekapt:
function truncateFeed() {
const maxDocs = 100;
var context = getContext();
var collection = context.getCollection();
collection.queryDocuments(
collection.getSelfLink(),
"SELECT VALUE COUNT(1) FROM f",
function (err, results) {
if (err) throw err;
processCountResults(results);
});
function processCountResults(results) {
// + 1 because the query didn't count the newly inserted doc
if ((results[0] + 1) > maxDocs) {
var docsToRemove = results[0] + 1 - maxDocs;
collection.queryDocuments(
collection.getSelfLink(),
`SELECT TOP ${docsToRemove} * FROM f ORDER BY f.creationDate`,
function (err, results) {
if (err) throw err;
processDocsToRemove(results, 0);
});
}
}
function processDocsToRemove(results, index) {
var doc = results[index];
if (doc) {
collection.deleteDocument(
doc._self,
function (err) {
if (err) throw err;
processDocsToRemove(results, index + 1);
});
}
}
}
De laatste stap is het omleiden van onze query naar onze nieuwe feed
container:
Latentie | RU-kosten | Prestaties |
---|---|---|
9 mevrouw |
16.97 RU |
✅ |
Conclusie
Laten we eens kijken naar de algemene prestatie- en schaalbaarheidsverbeteringen die we hebben geïntroduceerd in de verschillende versies van ons ontwerp.
V1 | V2 | V3 | |
---|---|---|---|
[C1] | 7 ms / 5.71 RU |
7 ms / 5.71 RU |
7 ms / 5.71 RU |
[Q1] | 2 ms / 1 RU |
2 ms / 1 RU |
2 ms / 1 RU |
[C2] | 9 ms / 8.76 RU |
9 ms / 8.76 RU |
9 ms / 8.76 RU |
[Q2] | 9 ms / 19.54 RU |
2 ms / 1 RU |
2 ms / 1 RU |
[Q3] | 130 ms / 619.41 RU |
28 ms / 201.54 RU |
4 ms / 6.46 RU |
[C3] | 7 ms / 8.57 RU |
7 ms / 15.27 RU |
7 ms / 15.27 RU |
[Q4] | 23 ms / 27.72 RU |
4 ms / 7.72 RU |
4 ms / 7.72 RU |
[C4] | 6 ms / 7.05 RU |
7 ms / 14.67 RU |
7 ms / 14.67 RU |
[Q5] | 59 ms / 58.92 RU |
4 ms / 8.92 RU |
4 ms / 8.92 RU |
[Q6] | 306 ms / 2063.54 RU |
83 ms / 532.33 RU |
9 ms / 16.97 RU |
We hebben een scenario met veel leesbewerkingen geoptimaliseerd
Mogelijk hebt u gemerkt dat we onze inspanningen hebben geconcentreerd om de prestaties van leesaanvragen (query's) te verbeteren ten koste van schrijfaanvragen (opdrachten). In veel gevallen activeren schrijfbewerkingen nu volgende denormalisatie via wijzigingenfeeds, waardoor ze rekenkundig duurder en langer zijn om te worden gerealiseerd.
We rechtvaardigen deze focus op leesprestaties door het feit dat een blogplatform (zoals de meeste sociale apps) leesintensief is. Een werkbelasting met veel leesbewerkingen geeft aan dat de hoeveelheid leesaanvragen die het moet verwerken, meestal een hogere orde heeft dan het aantal schrijfaanvragen. Het is dus logisch om schrijfaanvragen duurder te maken om leesaanvragen goedkoper en beter te laten presteren.
Als we kijken naar de meest extreme optimalisatie die we hebben gedaan, ging [Q6] van 2000+ RU's naar slechts 17 RU's; we hebben dat bereikt door het denormaliseren van berichten ten koste van ongeveer 10 RU's per item. Omdat we veel meer feedaanvragen zouden leveren dan het maken of bijwerken van berichten, is de kosten van deze denormalisatie te verwaarlozen gezien de totale besparingen.
Denormalisatie kan incrementeel worden toegepast
De schaalbaarheidsverbeteringen die we in dit artikel hebben verkend, omvatten denormalisatie en duplicatie van gegevens in de gegevensset. Er moet worden opgemerkt dat deze optimalisaties niet op dag 1 hoeven te worden uitgevoerd. Query's die filteren op partitiesleutels, presteren beter op schaal, maar query's tussen partities kunnen acceptabel zijn als ze zelden of tegen een beperkte gegevensset worden aangeroepen. Als u alleen een prototype bouwt of een product start met een klein en gecontroleerd gebruikersbestand, kunt u deze verbeteringen waarschijnlijk later besparen. Wat belangrijk is, is om de prestaties van uw model te bewaken , zodat u kunt bepalen of en wanneer het tijd is om ze binnen te halen.
De wijzigingenfeed die we gebruiken om updates naar andere containers te distribueren, slaan al deze updates permanent op. Deze persistentie maakt het mogelijk om alle updates aan te vragen sinds het maken van de container en bootstrap gedenormaliseerde weergaven als een eenmalige inhaalbewerking, zelfs als uw systeem al veel gegevens heeft.
Volgende stappen
Na deze inleiding tot praktische gegevensmodellering en partitionering, kunt u de volgende artikelen controleren om de concepten te bekijken die we hebben behandeld: