Dela via


Så här modellerar och partitionerar du data i Azure Cosmos DB med ett verkligt exempel

GÄLLER FÖR: NoSQL

Den här artikeln bygger på flera Azure Cosmos DB-begrepp som datamodellering, partitionering och etablerat dataflöde för att visa hur du hanterar en verklig datadesignövning.

Om du vanligtvis arbetar med relationsdatabaser har du förmodligen skapat vanor och intuitioner för hur du utformar en datamodell. På grund av de specifika begränsningarna, men även de unika styrkorna i Azure Cosmos DB, översätter de flesta av dessa metodtips inte bra och kan dra dig till suboptimala lösningar. Målet med den här artikeln är att vägleda dig genom hela processen med att modellera ett verkligt användningsfall i Azure Cosmos DB, från objektmodellering till entitetssamlokalisering och containerpartitionering.

Ladda ned eller visa en communitygenererad källkod som illustrerar begreppen i den här artikeln.

Viktigt!

En communitydeltagare bidrog med det här kodexemplet och Azure Cosmos DB-teamet stöder inte underhåll.

Scenariot

I den här övningen ska vi överväga domänen för en bloggplattform där användare kan skapa inlägg. Användare kan också gilla och lägga till kommentarer i dessa inlägg.

Dricks

Vi har markerat några ord i kursiv stil; dessa ord identifierar den typ av "saker" som vår modell måste manipulera.

Lägga till fler krav i vår specifikation:

  • En förstasida visar ett flöde med nyligen skapade inlägg,
  • Vi kan hämta alla inlägg för en användare, alla kommentarer för ett inlägg och alla gillar för ett inlägg,
  • Inlägg returneras med användarnamnet för sina författare och antalet kommentarer och likes de har,
  • Kommentarer och gilla-markeringar returneras också med användarnamnet för de användare som har skapat dem.
  • När inlägg visas som listor behöver de bara presentera en trunkerad sammanfattning av innehållet.

Identifiera de viktigaste åtkomstmönstren

Till att börja med ger vi en viss struktur till vår ursprungliga specifikation genom att identifiera vår lösnings åtkomstmönster. När du utformar en datamodell för Azure Cosmos DB är det viktigt att förstå vilka begäranden som vår modell måste hantera för att säkerställa att modellen hanterar dessa begäranden effektivt.

För att göra den övergripande processen enklare att följa kategoriserar vi dessa olika begäranden som antingen kommandon eller frågor och lånar lite vokabulär från CQRS. I CQRS är kommandon skrivbegäranden (dvs. avsikter att uppdatera systemet) och frågor är skrivskyddade begäranden.

Här är listan över begäranden som vår plattform exponerar:

  • [C1] Skapa/redigera en användare
  • [Q1] Hämta en användare
  • [C2] Skapa/redigera ett inlägg
  • [Q2] Hämta ett inlägg
  • [Q3] Visa en lista över en användares inlägg i kort format
  • [C3] Skapa en kommentar
  • [Q4] Lista ett inläggs kommentarer
  • [C4] Gilla ett inlägg
  • [Q5] Visa en lista över gilla-markeringar för ett inlägg
  • [Q6] Visa en lista över de x senaste inläggen som skapats i kort form (feed)

I det här skedet har vi inte tänkt på detaljerna i vad varje entitet (användare, inlägg osv.) innehåller. Det här steget är vanligtvis bland de första som ska hanteras när du utformar mot ett relationsarkiv. Vi börjar med det här steget först eftersom vi måste ta reda på hur dessa entiteter översätts i termer av tabeller, kolumner, sekundärnycklar osv. Det är mycket mindre problem med en dokumentdatabas som inte tillämpar något schema vid skrivning.

Den främsta anledningen till att det är viktigt att identifiera våra åtkomstmönster från början är att den här listan med begäranden kommer att vara vår testsvit. Varje gång vi itererar över vår datamodell går vi igenom var och en av begäranden och kontrollerar dess prestanda och skalbarhet. Vi beräknar de enheter för begärande som förbrukas i varje modell och optimerar dem. Alla dessa modeller använder standardindexeringsprincipen och du kan åsidosätta den genom att indexera specifika egenskaper, vilket ytterligare kan förbättra RU-förbrukningen och svarstiden.

V1: En första version

Vi börjar med två containrar: users och posts.

Användarcontainer

Den här containern lagrar endast användarobjekt:

{
    "id": "<user-id>",
    "username": "<username>"
}

Vi partitionera den här containern med id, vilket innebär att varje logisk partition i containern endast innehåller ett objekt.

Inläggscontainer

Den här containern är värd för entiteter som inlägg, kommentarer och gilla-markeringar:

{
    "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>"
}

Vi partitionera den här containern med postId, vilket innebär att varje logisk partition i containern innehåller ett inlägg, alla kommentarer för det inlägget och alla likes för det inlägget.

Vi har introducerat en type egenskap i objekten som lagras i den här containern för att skilja mellan de tre typer av entiteter som den här containern är värd för.

Vi har också valt att referera till relaterade data i stället för att bädda in dem (se det här avsnittet för mer information om dessa begrepp) eftersom:

  • det finns ingen övre gräns för hur många inlägg en användare kan skapa,
  • kan vara godtyckligt långa,
  • det finns ingen övre gräns för hur många kommentarer och gillar ett inlägg kan ha,
  • Vi vill kunna lägga till en kommentar eller liknande till ett inlägg utan att behöva uppdatera själva inlägget.

Hur bra presterar vår modell?

Nu är det dags att utvärdera prestanda och skalbarhet för vår första version. För var och en av de begäranden som tidigare identifierats mäter vi dess svarstid och hur många enheter för begäranden den förbrukar. Den här mätningen görs mot en dummydatauppsättning som innehåller 100 000 användare med 5 till 50 inlägg per användare och upp till 25 kommentarer och 100 gilla-markeringar per inlägg.

[C1] Skapa/redigera en användare

Den här begäran är enkel att implementera eftersom vi bara skapar eller uppdaterar ett objekt i containern users . Begäranden sprids fint över alla partitioner tack vare partitionsnyckeln id .

Diagram över hur du skriver ett enskilt objekt till användarnas container.

Svarstider RU-avgift Prestanda
7 Ms 5.71 RU

[Q1] Hämta en användare

Hämtning av en användare görs genom att läsa motsvarande objekt från containern users .

Diagram över hur du hämtar ett enskilt objekt från användarnas container.

Svarstider RU-avgift Prestanda
2 Ms 1 RU

[C2] Skapa/redigera ett inlägg

På samma sätt som [C1] måste vi bara skriva till containern posts .

Diagram över hur du skriver ett postobjekt till postcontainern.

Svarstider RU-avgift Prestanda
9 Ms 8.76 RU

[Q2] Hämta ett inlägg

Vi börjar med att hämta motsvarande dokument från containern posts . Men det räcker inte, enligt vår specifikation måste vi också aggregera användarnamnet för inläggets författare, antalet kommentarer och antalet likes för inlägget. Aggregeringarna i listan kräver att ytterligare tre SQL-frågor utfärdas.

Diagram över hur du hämtar ett inlägg och aggregerar ytterligare data.

Var och en av de fler frågorna filtrerar på partitionsnyckeln för sin respektive container, vilket är exakt vad vi vill maximera prestanda och skalbarhet. Men så småningom måste vi utföra fyra åtgärder för att returnera ett enda inlägg, så vi kommer att förbättra det i en nästa iteration.

Svarstider RU-avgift Prestanda
9 Ms 19.54 RU

[Q3] Visa en lista över en användares inlägg i kort format

Först måste vi hämta önskade inlägg med en SQL-fråga som hämtar inlägg som motsvarar den specifika användaren. Men vi måste också utfärda fler frågor för att aggregera författarens användarnamn och antalet kommentarer och gilla-markeringar.

Diagram över hur du hämtar alla inlägg för en användare och aggregerar deras ytterligare data.

Den här implementeringen visar på många nackdelar:

  • frågorna som aggregerar antalet kommentarer och gilla-markeringar måste utfärdas för varje inlägg som returneras av den första frågan.
  • Huvudfrågan filtrerar inte på partitionsnyckeln i containern posts , vilket leder till en utloggning och en partitionsgenomsökning i containern.
Svarstider RU-avgift Prestanda
130 Ms 619.41 RU

[C3] Skapa en kommentar

En kommentar skapas genom att motsvarande objekt skrivs i containern posts .

Diagram över hur du skriver ett enskilt kommentarsobjekt till inläggscontainern.

Svarstider RU-avgift Prestanda
7 Ms 8.57 RU

[Q4] Lista ett inläggs kommentarer

Vi börjar med en fråga som hämtar alla kommentarer för det inlägget och återigen måste vi även aggregera användarnamn separat för varje kommentar.

Diagram över hur du hämtar alla kommentarer för ett inlägg och aggregerar deras ytterligare data.

Även om huvudfrågan filtrerar på containerns partitionsnyckel, straffar aggregering av användarnamnen separat den övergripande prestandan. Vi förbättrar det senare.

Svarstider RU-avgift Prestanda
23 Ms 27.72 RU

[C4] Gilla ett inlägg

Precis som [C3] skapar vi motsvarande objekt i containern posts .

Diagram över hur du skriver ett enskilt (liknande) objekt till postcontainern.

Svarstider RU-avgift Prestanda
6 Ms 7.05 RU

[Q5] Visa en lista över gilla-markeringar för ett inlägg

Precis som [Q4] frågar vi likes för det inlägget och aggregerar sedan deras användarnamn.

Diagram över hur du hämtar alla likes för ett inlägg och aggregerar deras ytterligare data.

Svarstider RU-avgift Prestanda
59 Ms 58.92 RU

[Q6] Visa en lista över de x senaste inläggen som skapats i kort form (feed)

Vi hämtar de senaste inläggen genom att fråga containern posts sorterat efter fallande skapandedatum och sedan aggregera användarnamn och antal kommentarer och gilla-markeringar för var och en av inläggen.

Diagram över hur du hämtar de senaste inläggen och aggregerar deras ytterligare data.

Återigen filtrerar vår första fråga inte på partitionsnyckeln för containern posts , vilket utlöser en kostsam utlösning. Den här är ännu värre eftersom vi riktar in oss på en större resultatuppsättning och sorterar resultatet med en ORDER BY sats, vilket gör det dyrare när det gäller enheter för begäran.

Svarstider RU-avgift Prestanda
306 Ms 2063.54 RU

Reflektera över prestanda för V1

Om vi tittar på prestandaproblemen i föregående avsnitt kan vi identifiera två huvudklasser av problem:

  • vissa begäranden kräver att flera frågor utfärdas för att samla in alla data som vi behöver returnera,
  • vissa frågor filtrerar inte på partitionsnyckeln för de containrar som de riktar in sig på, vilket leder till en utskalning som hindrar vår skalbarhet.

Låt oss lösa vart och ett av dessa problem, från och med det första.

V2: Introduktion till avnormalisering för att optimera läsfrågor

Anledningen till att vi måste utfärda fler begäranden i vissa fall är att resultatet av den första begäran inte innehåller alla data som vi behöver returnera. Att avnormalisera data löser den här typen av problem i vår datauppsättning när du arbetar med ett icke-relationsdatalager som Azure Cosmos DB.

I vårt exempel ändrar vi inläggsobjekt för att lägga till användarnamnet för inläggets författare, antalet kommentarer och antalet gilla-markeringar:

{
    "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>"
}

Vi ändrar även kommentarer och liknande objekt för att lägga till användarnamnet för användaren som har skapat dem:

{
    "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>"
}

Avnormalisera kommentarer och liknande antal

Vad vi vill uppnå är att varje gång vi lägger till en kommentar eller liknande ökar commentCount vi också eller likeCount i motsvarande inlägg. När postId containern partitioneras posts finns det nya objektet (kommentar eller liknande) och motsvarande inlägg i samma logiska partition. Därför kan vi använda en lagrad procedur för att utföra den åtgärden.

När du skapar en kommentar ([C3]), i stället för att bara lägga till ett nytt objekt i containern posts anropar vi följande lagrade procedur i containern:

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
          );
        }
      );
    })
}

Den här lagrade proceduren tar ID:t för inlägget och brödtexten för den nya kommentaren som parametrar och sedan:

  • hämtar inlägget
  • ökar commentCount
  • ersätter inlägget
  • lägger till den nya kommentaren

När lagrade procedurer körs som atomiska transaktioner förblir värdet commentCount för och det faktiska antalet kommentarer alltid synkroniserade.

Vi anropar uppenbarligen en liknande lagrad procedur när du lägger till nya likes för att öka likeCount.

Avnormalisera användarnamn

Användarnamn kräver en annan metod eftersom användarna inte bara finns i olika partitioner, utan i en annan container. När vi måste avnormalisera data mellan partitioner och containrar kan vi använda källcontainerns ändringsflöde.

I vårt exempel använder vi containerns ändringsflöde users för att reagera när användarna uppdaterar sina användarnamn. När det händer sprider vi ändringen genom att anropa en annan lagrad procedur i containern posts :

Diagram över avnormalisering av användarnamn i postcontainern.

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);
      }
    });
}

Den här lagrade proceduren tar användarens ID och användarens nya användarnamn som parametrar. Sedan:

  • hämtar alla objekt som matchar userId (som kan vara inlägg, kommentarer eller gilla-markeringar)
  • för vart och ett av dessa objekt
    • ersätter userUsername
    • ersätter objektet

Viktigt!

Den här åtgärden är kostsam eftersom den kräver att den här lagrade proceduren körs på varje partition i containern posts . Vi antar att de flesta användare väljer ett lämpligt användarnamn under registreringen och aldrig ändrar det, så den här uppdateringen körs mycket sällan.

Vilka prestandavinster har V2?

Låt oss prata om några av prestandavinsterna för V2.

[Q2] Hämta ett inlägg

Nu när vår avnormalisering är på plats behöver vi bara hämta ett enda objekt för att hantera begäran.

Diagram över hur du hämtar ett enskilt objekt från containern avnormaliserade inlägg.

Svarstider RU-avgift Prestanda
2 Ms 1 RU

[Q4] Lista ett inläggs kommentarer

Återigen kan vi spara de extra begäranden som hämtade användarnamnen och sluta med en enda fråga som filtrerar på partitionsnyckeln.

Diagram över hur du hämtar alla kommentarer för ett avnormaliserat inlägg.

Svarstider RU-avgift Prestanda
4 Ms 7.72 RU

[Q5] Visa en lista över gilla-markeringar för ett inlägg

Exakt samma situation när du listar gilla-markeringar.

Diagram över hur du hämtar alla likes för ett avnormaliserat inlägg.

Svarstider RU-avgift Prestanda
4 Ms 8.92 RU

V3: Kontrollera att alla begäranden är skalbara

Det finns fortfarande två begäranden som vi inte har optimerat fullt ut när vi tittar på våra övergripande prestandaförbättringar. Dessa begäranden är [Q3] och [Q6]. Det är begäranden som rör frågor som inte filtrerar på partitionsnyckeln för de containrar som de riktar in sig på.

[Q3] Visa en lista över en användares inlägg i kort format

Den här begäran drar redan nytta av de förbättringar som introducerades i V2, vilket sparar fler frågor.

Diagram som visar frågan för att lista en användares avnormaliserade inlägg i kort form.

Men den återstående frågan filtrerar fortfarande inte på containerns posts partitionsnyckel.

Sättet att tänka på den här situationen är enkelt:

  1. Den här begäran måste filtrera på userId eftersom vi vill hämta alla inlägg för en viss användare.
  2. Det fungerar inte bra eftersom det körs mot containern posts , som inte har userId partitionering.
  3. Om vi anger det uppenbara skulle vi lösa vårt prestandaproblem genom att köra den här begäran mot en container som partitionerats med userId.
  4. Det visar sig att vi redan har en sådan container: containern users !

Så vi introducerar en andra nivå av avnormalisering genom att duplicera hela inlägg till containern users . Genom att göra det får vi effektivt en kopia av våra inlägg, endast partitionerade längs en annan dimension, vilket gör dem mycket mer effektiva att hämta med sina userId.

Containern users innehåller nu två typer av objekt:

{
    "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>"
}

I det här exemplet:

  • Vi har introducerat ett type fält i användarobjektet för att skilja användare från inlägg,
  • Vi har också lagt till ett userId fält i användarobjektet, som är redundant med id fältet men krävs eftersom containern users nu är partitionerad med userId (och inte id som tidigare)

För att uppnå den avnormaliseringen använder vi återigen ändringsflödet. Den här gången reagerar vi på containerns posts ändringsflöde för att skicka nya eller uppdaterade inlägg till containern users . Och eftersom listning av inlägg inte kräver att deras fullständiga innehåll returneras kan vi trunkera dem i processen.

Diagram över avnormalisering av inlägg i användarnas container.

Nu kan vi dirigera frågan till containern users och filtrera containerns partitionsnyckel.

Diagram över hämtning av alla inlägg för en avnormaliserad användare.

Svarstider RU-avgift Prestanda
4 Ms 6.46 RU

[Q6] Visa en lista över de x senaste inläggen som skapats i kort form (feed)

Vi måste hantera en liknande situation här: även efter att du har sparat de fler frågor som inte behövs av den avnormalisering som introducerades i V2 filtrerar inte den återstående frågan på containerns partitionsnyckel:

Diagram som visar frågan för att lista de x senaste inläggen som skapats i kort form.

Med samma metod kräver en maximering av den här begärans prestanda och skalbarhet att den bara når en partition. Det är bara att träffa en enda partition eftersom vi bara behöver returnera ett begränsat antal objekt. För att fylla vår bloggplattforms startsida behöver vi bara få de 100 senaste inläggen, utan att behöva sidnumrera genom hela datamängden.

För att optimera den senaste begäran introducerar vi därför en tredje container i vår design, helt dedikerad till att hantera den här begäran. Vi avnormaliserar våra inlägg till den nya feed containern:

{
    "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>"
}

Fältet type partitioner den här containern, som alltid post finns i våra objekt. Detta säkerställer att alla objekt i den här containern finns i samma partition.

För att uppnå avnormaliseringen behöver vi bara haka på den ändringsflödespipeline som vi tidigare har introducerat för att skicka inläggen till den nya containern. En viktig sak att tänka på är att vi måste se till att vi bara lagrar de 100 senaste inläggen; Annars kan innehållet i containern växa utöver den maximala storleken på en partition. Den här begränsningen kan implementeras genom att anropa en efterutlösare varje gång ett dokument läggs till i containern:

Diagram över avnormalisering av inlägg i feedcontainern.

Här är brödtexten i postutlösaren som trunkerar samlingen:

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);
        });
    }
  }
}

Det sista steget är att omdirigera frågan till vår nya feed container:

Diagram över hur du hämtar de senaste inläggen.

Svarstider RU-avgift Prestanda
9 Ms 16.97 RU

Slutsats

Låt oss ta en titt på de övergripande prestanda- och skalbarhetsförbättringar som vi har introducerat i de olika versionerna av vår design.

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

Vi har optimerat ett läsintensivt scenario

Du kanske har märkt att vi har koncentrerat våra ansträngningar på att förbättra prestandan för läsbegäranden (frågor) på bekostnad av skrivbegäranden (kommandon). I många fall utlöser skrivåtgärder nu efterföljande avnormalisering via ändringsflöden, vilket gör dem mer beräkningsmässigt dyra och längre att materialisera.

Vi motiverar detta fokus på läsprestanda genom att en bloggplattform (som de flesta sociala appar) är läsintensiv. En läsintensiv arbetsbelastning anger att mängden läsbegäranden som den måste hantera vanligtvis är större än antalet skrivbegäranden. Därför är det klokt att göra skrivbegäranden dyrare att köra för att låta läsbegäranden vara billigare och bättre presterande.

Om vi tittar på den mest extrema optimeringen vi har gjort gick [Q6] från 2000+ RU:er till bara 17 RU:er. Vi har uppnått det genom att avnormalisera inlägg till en kostnad av cirka 10 RU:er per objekt. Eftersom vi skulle hantera mycket fler feedbegäranden än att skapa eller uppdatera inlägg, är kostnaden för den här avnormaliseringen försumbar med tanke på de totala besparingarna.

Avormalisering kan tillämpas stegvis

De skalbarhetsförbättringar som vi har utforskat i den här artikeln omfattar avnormalisering och duplicering av data i datauppsättningen. Observera att dessa optimeringar inte behöver införas dag 1. Frågor som filtrerar på partitionsnycklar fungerar bättre i stor skala, men frågor mellan partitioner kan accepteras om de anropas sällan eller mot en begränsad datauppsättning. Om du bara skapar en prototyp eller lanserar en produkt med en liten och kontrollerad användarbas kan du förmodligen spara dessa förbättringar till senare. Det viktiga är sedan att övervaka modellens prestanda så att du kan avgöra om och när det är dags att ta in dem.

Ändringsflödet som vi använder för att distribuera uppdateringar till andra containrar lagrar alla dessa uppdateringar beständigt. Den här beständigheten gör det möjligt att begära alla uppdateringar sedan containern och bootstrap-denormaliserade vyer skapades som en engångsåtgärd, även om systemet redan har många data.

Nästa steg

Efter den här introduktionen till praktisk datamodellering och partitionering kanske du vill kontrollera följande artiklar för att granska de begrepp som vi har gått igenom: