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 Azure Cosmos DB-concepten, zoals gegevensmodellering, partitionering en ingerichte doorvoer , om te laten zien hoe u een praktijkoefening voor het ontwerpen van gegevens kunt uitvoeren.

Als u meestal met relationele databases werkt, hebt u waarschijnlijk gewoonten en intuïties ontwikkeld voor het ontwerpen van een gegevensmodel. Vanwege de specifieke beperkingen, maar ook de unieke sterke punten van Azure Cosmos DB, vertalen de meeste van deze best practices zich niet goed en kunnen ze 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 communitybijdrager heeft dit codevoorbeeld bijgedragen en het Azure Cosmos DB-team biedt geen ondersteuning voor het onderhoud ervan.

Het scenario

Voor deze oefening gaan we kijken naar het domein van een blogplatform waar gebruikersberichten kunnen maken. Gebruikers kunnen ook opmerkingenleuk vinden en toevoegen aan deze berichten.

Tip

We hebben enkele woorden cursief gemarkeerd; deze woorden identificeren het soort 'dingen' dat ons model moet manipuleren.

Meer vereisten aan onze specificatie toevoegen:

  • Op een voorpagina wordt een feed met onlangs gemaakte berichten weergegeven.
  • We kunnen alle berichten voor een gebruiker ophalen, alle opmerkingen voor een bericht en alle vind-ik-leuks voor een bericht,
  • Berichten worden geretourneerd met de gebruikersnaam van hun auteurs en een telling van het aantal opmerkingen en vind-ik-leuks die ze hebben,
  • 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 ze alleen een afgekapte samenvatting van hun inhoud weer te geven.

De belangrijkste toegangspatronen identificeren

Om te beginnen geven we structuur aan onze initiële 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 ons model moet uitvoeren om ervoor te zorgen dat het model deze aanvragen efficiënt verwerkt.

Om het algemene proces gemakkelijker te volgen, categoriseren we deze verschillende aanvragen als opdrachten of query's, waardoor we wat woorden uit CQRS halen. In CQRS zijn opdrachten schrijfaanvragen (dat wil gezegd, intenties om het systeem bij te werken) en query's zijn alleen-lezenaanvragen.

Dit is de lijst met aanvragen die ons platform beschikbaar maakt:

  • [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] De opmerkingen van een bericht weergeven
  • [C4] Een bericht leuk vinden
  • [Q5] De vind-ik-leuks van een bericht weergeven
  • [Q6] De x meest recente berichten weergeven die in korte vorm zijn gemaakt (feed)

In dit stadium hebben we nog niet nagedacht over de details van wat elke entiteit (gebruiker, post, enzovoort) bevat. Deze stap is meestal een van de eerste stappen die moeten worden uitgevoerd bij het ontwerpen op basis van een relationele winkel. We beginnen eerst met deze stap omdat we moeten uitzoeken hoe deze entiteiten worden vertaald in termen van tabellen, kolommen, refererende sleutels, enzovoort. Het is veel minder een probleem met een documentdatabase die geen schema afdwingt tijdens het schrijven.

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 ons gegevensmodel herhalen, doorlopen we elk van de aanvragen en controleren we de prestaties en schaalbaarheid. We berekenen de aanvraageenheden die in elk model worden verbruikt en optimaliseren deze. Al deze modellen maken gebruik van 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 met id, wat betekent dat elke logische partitie binnen die container slechts één item bevat.

Berichtencontainer

Deze container fungeert als host voor 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 met postId, wat betekent dat elke logische partitie in die container één bericht 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 in deze container zijn opgeslagen 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-leuks dat een bericht kan hebben,
  • we willen een opmerking of een like aan een bericht kunnen toevoegen zonder dat we het bericht zelf hoeven 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 en maximaal 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 zijn goed verdeeld over alle partities dankzij de id partitiesleutel.

Diagram van het schrijven van één item naar de container van de gebruikers.

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.

Diagram van het ophalen van één item uit de container van de gebruikers.

Latentie RU-kosten Prestaties
2 Mevrouw 1 RU

[C2] Een bericht maken/bewerken

Net als bij [C1] hoeven we alleen maar naar de posts container te schrijven.

Diagram van het schrijven van één postitem naar de berichtencontainer.

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 genoeg, volgens onze specificatie moeten we ook de gebruikersnaam van de auteur van het bericht, het aantal opmerkingen en het aantal vind-ik-leuks voor het bericht aggregeren. Voor de vermelde aggregaties moeten nog 3 SQL-query's worden uitgegeven.

Diagram van het ophalen van een bericht en het aggregeren van aanvullende gegevens.

Elk van de meer query's filtert op de partitiesleutel van de respectieve container, wat precies is wat we willen om de prestaties en schaalbaarheid te maximaliseren. Maar uiteindelijk moeten we vier bewerkingen uitvoeren om één bericht te retourneren, dus dat verbeteren we 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 die de berichten ophaalt die overeenkomen met die specifieke gebruiker. Maar we moeten ook meer query's uitvoeren om de gebruikersnaam van de auteur en het aantal opmerkingen en vind-ik-leuks samen te voegen.

Diagram van het ophalen van alle berichten voor een gebruiker en het aggregeren van hun aanvullende gegevens.

Deze implementatie heeft veel nadelen:

  • de query's die het aantal opmerkingen en vind-ik-leuks samenvoegen, 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 fan-out en een partitiescan in de container.
Latentie RU-kosten Prestaties
130 Mevrouw 619.41 RU

[C3] Een opmerking maken

Een opmerking wordt gemaakt door het bijbehorende item in de posts container te schrijven.

Diagram van het schrijven van één opmerkingsitem naar de berichtencontainer.

Latentie RU-kosten Prestaties
7 Mevrouw 8.57 RU

[Q4] De opmerkingen van een bericht weergeven

We beginnen met een query die alle opmerkingen voor dat bericht ophaalt en opnieuw moeten we gebruikersnamen afzonderlijk aggregeren voor elke opmerking.

Diagram van het ophalen van alle opmerkingen voor een bericht en het aggregeren van de bijbehorende aanvullende gegevens.

Hoewel de hoofdquery wel filtert op de partitiesleutel van de container, worden de algehele prestaties bestraft door de gebruikersnamen afzonderlijk te aggregeren. 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.

Diagram van het schrijven van één item (zoals) naar de berichtencontainer.

Latentie RU-kosten Prestaties
6 Mevrouw 7.05 RU

[Q5] Vind-ik-leuks van een bericht weergeven

Net als [Q4] voeren we een query uit op de vind-ik-leuks voor dat bericht en voegen we vervolgens hun gebruikersnamen samen.

Diagram van het ophalen van alle vind-ik-leuks voor een bericht en het aggregeren van hun aanvullende gegevens.

Latentie RU-kosten Prestaties
59 Mevrouw 58.92 RU

[Q6] De x meest recente berichten weergeven die in korte vorm zijn gemaakt (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 aantallen opmerkingen en vind-ik-leuks voor elk van de berichten samen te voegen.

Diagram van het ophalen van de meest recente berichten en het samenvoegen van hun aanvullende gegevens.

Nogmaals, onze eerste query filtert niet op de partitiesleutel van de posts container, wat een dure uitwaaiering veroorzaakt. Dit is nog erger omdat we ons richten op een grotere resultatenset 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 nodig hebben om te retourneren,
  • sommige query's filteren niet op de partitiesleutel van de containers waarop ze zijn gericht, wat leidt tot een uitwaaiering die onze schaalbaarheid belemmert.

Laten we elk van deze problemen oplossen, te beginnen 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, het aantal opmerkingen en het aantal vind-ik-leuks toe te voegen:

{
    "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 items met opmerkingen en vind-ik-leuks om de gebruikersnaam toe te voegen van de gebruiker die ze 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>"
}

Het aantal opmerkingen en vind-ik-leuks denormaliseren

Wat we willen bereiken, is dat telkens wanneer we een opmerking of een like toevoegen, we ook de commentCount of de likeCount in het bijbehorende bericht verhogen. Als postId partities onze posts container, het nieuwe item (opmerking of like) en de bijbehorende post zich in dezelfde logische partitie bevinden. Als gevolg hiervan kunnen we een opgeslagen procedure gebruiken om die bewerking uit te voeren.

Wanneer u een opmerking ([C3]) maakt, wordt in plaats van alleen een nieuw item toe te voegen aan 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
  • hiermee wordt de commentCount
  • vervangt de post
  • voegt de nieuwe opmerking toe

Omdat opgeslagen procedures worden uitgevoerd als atomische transacties, blijven de waarde van commentCount en het werkelijke aantal opmerkingen altijd gesynchroniseerd.

We roepen uiteraard een vergelijkbare opgeslagen procedure aan bij het toevoegen van nieuwe likes om de likeCountte verhogen.

Gebruikersnamen denormaliseren

Gebruikersnamen vereisen een andere benadering omdat gebruikers zich niet alleen in verschillende partities bevinden, maar ook in een andere container. Wanneer we gegevens in partities en containers moeten denormaliseren, 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:

Diagram van het denormaliseren van gebruikersnamen in de berichtencontainer.

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 en vervolgens:

  • haalt alle items op die overeenkomen met de userId (dit kunnen berichten, opmerkingen of vind-ik-leuks zijn)
  • voor elk van deze items
    • vervangt de userUsername
    • vervangt het item

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 zullen wijzigen, dus deze update wordt zeer zelden uitgevoerd.

Wat zijn de prestatieverbeteringen van V2?

Laten we het eens 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.

Diagram van het ophalen van één item uit de gedenormaliseerde berichtencontainer.

Latentie RU-kosten Prestaties
2 Mevrouw 1 RU

[Q4] De opmerkingen van een bericht weergeven

Ook hier kunnen we de extra aanvragen besparen die de gebruikersnamen hebben opgehaald en eindigen met één query die filtert op de partitiesleutel.

Diagram van het ophalen van alle opmerkingen voor een gedenormaliseerd bericht.

Latentie RU-kosten Prestaties
4 Mevrouw 7.72 RU

[Q5] Vind-ik-leuks van een bericht weergeven

Exact dezelfde situatie bij het vermelden van de vind-ik-leuks.

Diagram van het ophalen van alle vind-ik-leuks voor een gedenormaliseerd bericht.

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 met betrekking tot 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.

Diagram met de query om de gedenormaliseerde berichten van een gebruiker in korte vorm weer te geven.

Maar de resterende query filtert nog steeds niet op de partitiesleutel van de posts container.

De manier om over deze situatie na te denken is eenvoudig:

  1. Deze aanvraag moet filteren op de userId omdat we alle berichten voor een bepaalde gebruiker willen ophalen.
  2. Het werkt niet goed omdat deze wordt uitgevoerd op basis van de posts container, die geen userId partitionering heeft.
  3. Het voor de hand liggende is dat we ons prestatieprobleem oplossen door deze aanvraag uit te voeren voor een container die is gepartitioneerd met userId.
  4. Het blijkt dat we zo'n container al hebben: de users container!

Daarom introduceren we een tweede niveau van denormalisatie door volledige berichten naar de users container te dupliceren. 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 geldt het volgende:

  • We hebben een type veld in het gebruikersitem geïntroduceerd om gebruikers te onderscheiden van berichten.
  • We hebben ook een userId veld toegevoegd aan het gebruikersitem, dat overbodig is met het id veld, maar vereist is omdat de users container nu is gepartitioneerd met userId (en niet id zoals voorheen)

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 de volledige inhoud hoeft te retourneren, kunnen we ze in het proces afkappen.

Diagram van het denormaliseren van berichten in de container van de gebruikers.

We kunnen de query nu routeren naar de users container, door te filteren op de partitiesleutel van de container.

Diagram van het ophalen van alle berichten voor een gedenormaliseerde gebruiker.

Latentie RU-kosten Prestaties
4 Mevrouw 6.46 RU

[Q6] De x meest recente berichten weergeven die in korte vorm zijn gemaakt (feed)

We hebben hier te maken met een vergelijkbare situatie: zelfs na het besparen van de meer query's die onnodig zijn door de denormalisatie die in V2 is geïntroduceerd, filtert de resterende query niet op de partitiesleutel van de container:

Diagram met de query voor het weergeven van de x meest recente berichten die in korte vorm zijn gemaakt.

Voor het maximaliseren van de prestaties en schaalbaarheid van deze aanvraag hoeft slechts één partitie te worden bereikt. Het is denkbaar om slechts één partitie te raken, omdat we slechts een beperkt aantal items hoeven te retourneren. Om de startpagina van ons blogplatform te vullen, hoeven we alleen de 100 meest recente berichten te krijgen, zonder dat we de hele gegevensset hoeven te pagineren.

Om deze laatste aanvraag te optimaliseren, introduceren we dus een derde container in ons ontwerp, volledig gewijd aan 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. Als u dit doet, zorgt u ervoor dat alle items in deze container zich in dezelfde partitie bevinden.

Om de denormalisatie te bereiken, hoeven we alleen maar de pijplijn van de wijzigingenfeed aan te haken die we eerder hebben geïntroduceerd om de posten naar die nieuwe container te verzenden. Een belangrijk ding om in gedachten 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 in de container wordt toegevoegd:

Diagram van het denormaliseren van posten in de feedcontainer.

Dit is de hoofdtekst van de posttrigger die de verzameling afkapt:

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 bestaat uit het omleiden van de query naar de nieuwe feed container:

Diagram van het ophalen van de meest recente berichten.

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

Het is u misschien opgevallen 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 de daaropvolgende denormalisatie via wijzigingsfeeds, waardoor ze rekenkundig duurder zijn en langer zijn te materialiseren.

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 het aantal leesaanvragen dat moet worden verwerkt, meestal groter is dan het aantal schrijfaanvragen. Het is dus zinvol om schrijfaanvragen duurder te maken om uit te voeren, zodat leesaanvragen goedkoper en beter presteren.

Als we kijken naar de meest extreme optimalisatie die we hebben gedaan, is [Q6] gegaan van 2000 + RU's naar slechts 17 RU's; We hebben dit bereikt door berichten te denormaliseren tegen een prijs van ongeveer 10 RU's per item. Omdat we veel meer feedaanvragen zouden verwerken dan het maken of bijwerken van berichten, zijn de kosten van deze denormalisatie verwaarloosbaar gezien de algehele besparingen.

Denormalisatie kan incrementeel worden toegepast

De schaalbaarheidsverbeteringen die we in dit artikel hebben verkend, hebben betrekking op denormalisatie en duplicatie van gegevens in de gegevensset. Er moet worden opgemerkt dat deze optimalisaties niet op dag 1 hoeven te worden ingesteld. Query's die op partitiesleutels filteren, presteren beter op schaal, maar query's tussen partities kunnen acceptabel zijn als ze zelden worden aangeroepen of op basis van een beperkte gegevensset. Als u slechts een prototype bouwt of een product met een klein en beheerd gebruikersbestand start, kunt u deze verbeteringen waarschijnlijk voor later besparen. Het is vervolgens belangrijk om de prestaties van uw model te bewaken , zodat u kunt bepalen of en wanneer het tijd is om ze binnen te halen.

In de wijzigingenfeed die we gebruiken om updates naar andere containers te distribueren, worden al deze updates permanent opgeslagen. Deze persistentie maakt het mogelijk om alle updates aan te vragen sinds het maken van de container en bootstrap gedenormaliseerde weergaven op 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 bekijken om de concepten te bekijken die we hebben behandeld: