Adatok modellezése és particionálása az Azure Cosmos DB-ben való életből vett példa használatával

A KÖVETKEZŐKRE VONATKOZIK: NoSQL

Ez a cikk számos Azure Cosmos DB-fogalomra épül, például az adatmodellezésre, a particionálásra és a kiosztott átviteli sebességre , hogy bemutassa, hogyan lehet kezelni egy valós adattervezési gyakorlatot.

Ha általában relációs adatbázisokkal dolgozik, valószínűleg szokásokat és intuíciókat épített ki az adatmodellek tervezésére. Az Azure Cosmos DB konkrét megkötései, de egyedi erősségei miatt a legtöbb ajánlott eljárás nem fordítható le megfelelően, és az optimálisnál rosszabb megoldásokba húzhatja. A cikk célja, hogy végigvezesse Önt egy valós használati eset Azure Cosmos DB-n való modellezésének teljes folyamatán, az elemmodellezéstől az entitások közös elhelyezésén és a tárolóparticionáláson át.

Töltse le vagy tekintse meg a közösség által létrehozott forráskódot , amely a cikk fogalmait szemlélteti.

Fontos

Egy közösségi közreműködő hozzájárult ehhez a kódmintához, és az Azure Cosmos DB csapata nem támogatja annak karbantartását.

Esetleírás

Ebben a gyakorlatban egy blogplatform tartományát fogjuk figyelembe venni, ahol a felhasználókbejegyzéseket hozhatnak létre. A felhasználók is kedvelhetik és megjegyzéseket fűzhetnek ezekhez a bejegyzésekhez.

Tipp

Néhány szót dőlt betűvel emeltünk ki; ezek a szavak azonosítják, hogy milyen típusú "dolgokat" a modellünknek manipulálnia kell.

További követelmények hozzáadása a specifikációhoz:

  • A címlap megjeleníti a nemrég létrehozott bejegyzések hírcsatornáját,
  • Lekérhetjük egy felhasználó összes bejegyzését, egy bejegyzéshez fűzött összes megjegyzést és egy bejegyzéshez tartozó összes tetszésnyilvánítást,
  • A bejegyzések a szerzők felhasználónevével és a hozzájuk fűzött megjegyzések és kedvelések számával jelennek meg.
  • A megjegyzéseket és a kedveléseket a rendszer az őket létrehozó felhasználók felhasználónevével is visszaadja,
  • Ha listákként jelennek meg, a bejegyzéseknek csak a tartalmuk csonkolt összegzését kell bemutatniuk.

A fő hozzáférési minták azonosítása

Első lépésként a megoldás hozzáférési mintáinak azonosításával némi struktúrát adunk a kezdeti specifikációnak. Az Azure Cosmos DB-hez készült adatmodell tervezésekor fontos tisztában lenni azzal, hogy a modellnek mely kéréseket kell kiszolgálnia, hogy a modell hatékonyan kiszolgálja ezeket a kéréseket.

Az általános folyamat könnyebb követése érdekében ezeket a különböző kéréseket parancsok vagy lekérdezésekként kategorizáljuk, és a CQRS-től némi szókincset kölcsönözünk. A CQRS-ben a parancsok írási kérések (azaz a rendszer frissítésének szándékai), a lekérdezések pedig írásvédett kérések.

Íme a platform által elérhetővé tenni kívánt kérések listája:

  • [C1] Felhasználó létrehozása/szerkesztése
  • [1. negyedév] Felhasználó lekérése
  • [C2] Bejegyzés létrehozása/szerkesztése
  • [2. negyedév] Bejegyzés lekérése
  • [3. negyedév] Felhasználó bejegyzéseinek listázása rövid formában
  • [C3] Megjegyzés létrehozása
  • [4. negyedév] Bejegyzés megjegyzésének listázása
  • [C4] Bejegyzés kedvelés
  • [5. negyedév] Bejegyzés kedvelőinek listázása
  • [6. negyedév] Sorolja fel az x legutóbbi, rövid formátumban létrehozott bejegyzést (hírcsatornát)

Ebben a szakaszban nem gondoltunk az egyes entitások (felhasználó, bejegyzés stb.) részleteire. Ezt a lépést általában az elsők között kell kezelni, amikor egy relációs tárolóra terveznek. Először ezt a lépést kezdjük, mert ki kell találnunk, hogyan fordítják le ezeket az entitásokat táblák, oszlopok, idegen kulcsok stb. szempontjából. Ez sokkal kevésbé aggodalomra ad okot egy olyan dokumentum-adatbázissal kapcsolatban, amely nem kényszeríti ki a sémát íráskor.

A hozzáférési minták kezdettől fogva történő azonosításának fő oka az, hogy a kérések listája lesz a tesztcsomagunk. Minden alkalommal, amikor iteráljuk az adatmodellt, végigmegyünk az egyes kéréseken, és ellenőrizzük annak teljesítményét és méretezhetőségét. Kiszámítjuk az egyes modellekben felhasznált kérelemegységeket, és optimalizáljuk őket. Ezek a modellek az alapértelmezett indexelési szabályzatot használják, és felülbírálhatja adott tulajdonságok indexelésével, ami tovább javíthatja a kérelemegység-használatot és a késést.

V1: Az első verzió

Két tárolóval kezdjük: users és posts.

Felhasználók tárolója

Ez a tároló csak a felhasználói elemeket tárolja:

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

Ezt a tárolót a használatával idparticionáljuk, ami azt jelenti, hogy a tárolón belül minden logikai partíció csak egy elemet tartalmaz.

Bejegyzések tárolója

Ez a tároló entitásokat, például bejegyzéseket, megjegyzéseket és kedveléseket tárol:

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

Ezt a tárolót a használatával postIdparticionáljuk, ami azt jelenti, hogy a tárolón belül minden logikai partíció tartalmaz egy bejegyzést, az adott bejegyzéshez tartozó összes megjegyzést és a bejegyzéshez tartozó összes tetszésnyilvánítást.

Bevezettünk egy type tulajdonságot a tárolóban tárolt elemekben, hogy megkülönböztessük a tároló által üzemeltetett három entitástípust.

Emellett úgy döntöttünk, hogy beágyazás helyett kapcsolódó adatokra hivatkozunk (a fogalmakkal kapcsolatos részletekért tekintse meg ezt a szakaszt ), mert:

  • nincs felső korlátja annak, hogy egy felhasználó hány bejegyzést hozhat létre,
  • a bejegyzések tetszőlegesen hosszúak lehetnek,
  • nincs felső korlátja annak, hogy egy bejegyzés hány megjegyzést és kedvelőt tartalmazhat,
  • azt szeretnénk, hogy hozzá tudjunk adni egy megjegyzést vagy hasonlót egy bejegyzéshez anélkül, hogy frissítenie kellene magát a bejegyzést.

Mennyire teljesít jól a modell?

Itt az ideje, hogy felmérjük az első verzió teljesítményét és méretezhetőségét. A korábban azonosított kérések mindegyikéhez megmérjük a késését, és azt, hogy hány kérelemegységet használ fel. Ezt a mérést egy 100 000 felhasználót tartalmazó, felhasználónként 5–50 bejegyzést tartalmazó, legfeljebb 25 megjegyzést és 100 kedvelést tartalmazó üres adatkészleten végezzük.

[C1] Felhasználó létrehozása/szerkesztése

Ez a kérés egyszerűen implementálható, mivel csak létrehozunk vagy frissítünk egy elemet a users tárolóban. A kérések a partíciókulcsnak köszönhetően szépen oszlanak el az id összes partíción.

Diagram egyetlen elemnek a felhasználói tárolóba való írásáról.

Késés KÉRELEMEGYSÉG-díj Teljesítmény
7 Ms 5.71 RU

[1. negyedév] Felhasználó lekérése

A felhasználó lekéréséhez olvassa be a megfelelő elemet a users tárolóból.

Diagram egyetlen elem lekéréséről a felhasználók tárolójából.

Késés KÉRELEMEGYSÉG-díj Teljesítmény
2 Ms 1 RU

[C2] Bejegyzés létrehozása/szerkesztése

A [C1] fájlhoz hasonlóan csak a posts tárolóba kell írnunk.

Diagram egyetlen bejegyzéselemnek a bejegyzések tárolóba való írásáról.

Késés KÉRELEMEGYSÉG-díj Teljesítmény
9 Ms 8.76 RU

[2. negyedév] Bejegyzés lekérése

Először lekértük a megfelelő dokumentumot a posts tárolóból. De ez nem elég, a specifikációnknak megfelelően a bejegyzés szerzőjének felhasználónevét, a megjegyzések számát és a bejegyzés kedveléseinek számát is összesíteni kell. A felsorolt összesítésekhez további 3 SQL-lekérdezést kell kiadni.

Diagram egy bejegyzés beolvasásáról és további adatok összesítéséről.

A további lekérdezések mindegyike a megfelelő tároló partíciókulcsára szűr, és pontosan ezt szeretnénk maximalizálni a teljesítmény és a méretezhetőség szempontjából. De végül négy műveletet kell végrehajtanunk, hogy egyetlen bejegyzést küldhessünk vissza, így ezt egy következő iterációban javítani fogjuk.

Késés KÉRELEMEGYSÉG-díj Teljesítmény
9 Ms 19.54 RU

[3. negyedév] Felhasználó bejegyzéseinek listázása rövid formában

Először le kell kérnünk a kívánt bejegyzéseket egy SQL-lekérdezéssel, amely lekéri az adott felhasználónak megfelelő bejegyzéseket. De több lekérdezést is ki kell adnunk, hogy összesítsük a szerző felhasználónevét, valamint a megjegyzések és kedvelések számát.

Diagram egy felhasználó összes bejegyzésének lekéréséről és a további adatok összesítéséről.

Ez a megvalósítás számos hátrányt jelent:

  • a megjegyzések és kedvelések számát összesítő lekérdezéseket minden egyes, az első lekérdezés által visszaadott bejegyzéshez ki kell adni,
  • A fő lekérdezés nem szűr a tároló partíciókulcsára posts , ami ki-kivezetéshez és partícióvizsgálathoz vezet a tárolóban.
Késés RU-díj Teljesítmény
130 Ms 619.41 RU

[C3] Megjegyzés létrehozása

A rendszer úgy hoz létre megjegyzést, hogy megírja a megfelelő elemet a posts tárolóban.

Egy megjegyzéselem írásának diagramja a bejegyzéstárolóba.

Késés RU-díj Teljesítmény
7 Ms 8.57 RU

[4. negyedév] Bejegyzés megjegyzésének listázása

Egy lekérdezéssel kezdjük, amely lekéri az adott bejegyzés összes megjegyzését, és ismét össze kell adnunk a felhasználóneveket minden megjegyzéshez.

Diagram egy bejegyzés összes megjegyzésének lekéréséről és a további adatok összesítéséről.

Bár a fő lekérdezés nem szűr a tároló partíciókulcsára, a felhasználónevek összesítése külön-külön bünteti a teljes teljesítményt. Ezt később továbbfejlesztjük.

Késés RU-díj Teljesítmény
23 Ms 27.72 RU

[C4] Bejegyzés kedvelve

A(z) [C3] elemhez hasonlóan a megfelelő elemet is létrehozzuk a posts tárolóban.

Egyetlen (hasonló) elem írásának diagramja a bejegyzéstárolóba.

Késés RU-díj Teljesítmény
6 Ms 7.05 RU

[5. negyedév] Bejegyzések tetszésének listázása

A (4. negyedévhez) hasonlóan lekérdezzük a bejegyzéshez tartozó kedveléseket, majd összesítjük a felhasználóneveket.

Diagram egy bejegyzéshez tartozó összes kedvelés lekéréséről és a további adatok összesítéséről.

Késés RU-díj Teljesítmény
59 Ms 58.92 RU

[6. negyedév] Az x legutóbbi, rövid formában létrehozott bejegyzés listázása (hírcsatorna)

A legutóbbi bejegyzések lekéréséhez lekérdezzük a tárolót csökkenő posts létrehozási dátum szerint rendezve, majd összesítjük a felhasználóneveket és a megjegyzések és kedvelések számát az egyes bejegyzésekhez.

Diagram a legutóbbi bejegyzések lekéréséről és a további adatok összesítéséről.

A kezdeti lekérdezés ismételten nem szűr a tároló partíciókulcsára posts , ami költséges ki-kifúvatáshoz vezet. Ez még rosszabb, mivel nagyobb eredményhalmazt célozunk meg, és egy ORDER BY záradékkal rendezzük az eredményeket, ami drágábbá teszi a kérelemegységeket.

Késés RU-díj Teljesítmény
306 Ms 2063.54 RU

Tükrözés a V1 teljesítményéről

Az előző szakaszban tapasztalt teljesítményproblémákat megvizsgálva két fő problémaosztályt ismerhetünk fel:

  • egyes kérések esetén több lekérdezést kell kiadni a visszaadni kívánt adatok összegyűjtése érdekében,
  • Egyes lekérdezések nem szűrnek az általuk megcélzott tárolók partíciókulcsára, ami olyan kivezetéshez vezet, amely akadályozza a méretezhetőséget.

Az elsővel kezdődően oldjuk meg ezeket a problémákat.

V2: Denormalizálás bevezetése az olvasási lekérdezések optimalizálásához

Bizonyos esetekben azért kell több kérést kiadnunk, mert a kezdeti kérés eredménye nem tartalmazza az összes visszaadni kívánt adatot. Az adatok denormalizálása megoldja ezt a problémát az adatkészletünkben, amikor nem relációs adattárral, például az Azure Cosmos DB-vel dolgozik.

A példában a bejegyzéselemeket úgy módosítjuk, hogy hozzáadjuk a bejegyzés szerzőjének felhasználónevét, a megjegyzések számát és a kedvelések számát:

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

A megjegyzéseket és az elemeket is módosítjuk, hogy hozzáadjuk annak a felhasználónak a felhasználónevét, aki létrehozta őket:

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

Megjegyzés denormalizálása és a hasonlók száma

Azt szeretnénk elérni, hogy minden alkalommal, amikor hozzáadunk egy megjegyzést vagy hasonlót, a vagy a likeCount megfelelő bejegyzésben is növeljükcommentCount. A tároló particionálása posts során postId az új elem (megjegyzés vagy hasonló) és a hozzá tartozó bejegyzés ugyanabban a logikai partícióban található. Ennek eredményeképpen egy tárolt eljárást használhatunk a művelet végrehajtásához.

Megjegyzés létrehozásakor ([C3]) ahelyett, hogy új elemet adnánk hozzá a posts tárolóhoz, a következő tárolt eljárást hívjuk meg a tárolón:

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

Ez a tárolt eljárás a bejegyzés azonosítóját és az új megjegyzés törzsét veszi paraméterként, majd:

  • lekéri a bejegyzést
  • növekményes commentCount
  • lecseréli a bejegyzést
  • hozzáadja az új megjegyzést

Mivel a tárolt eljárások atomi tranzakciókként vannak végrehajtva, a megjegyzések értéke commentCount és tényleges száma mindig szinkronban marad.

Nyilvánvalóan hasonló tárolt eljárást hívunk, amikor új kedveléseket adunk hozzá a növekményhez likeCount.

Felhasználónevek denormalizálása

A felhasználónevek eltérő megközelítést igényelnek, mivel a felhasználók nem csak különböző partíciókban ülnek, hanem egy másik tárolóban is. Amikor denormalizálnunk kell az adatokat a partíciók és tárolók között, használhatjuk a forrástároló változáscsatornáját.

Példánkban a tároló változáscsatornáját használjuk arra, hogy reagáljon, amikor a users felhasználók frissítik a felhasználóneveket. Ha ez történik, propagáljuk a módosítást úgy, hogy meghívunk egy másik tárolt eljárást a posts tárolón:

A felhasználónevek bejegyzéstárolóba való denormalizálásának diagramja.

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

Ez a tárolt eljárás a felhasználó azonosítóját és a felhasználó új felhasználónevét veszi fel paraméterekként, majd:

  • lekéri az összes olyan elemet, amely megfelel a userId (amelyek lehetnek bejegyzések, megjegyzések vagy kedvelések)
  • minden egyes elemhez
    • lecseréli a userUsername
    • lecseréli az elemet

Fontos

Ez a művelet költséges, mert megköveteli, hogy ezt a tárolt eljárást a posts tároló minden partícióján végrehajtsa. Feltételezzük, hogy a felhasználók többsége megfelelő felhasználónevet választ a regisztráció során, és soha nem módosítja, ezért ez a frissítés nagyon ritkán fog futni.

Milyen teljesítménynövekedést jelent a V2?

Beszéljünk a V2 teljesítménynövekedéséről.

[2. negyedév] Bejegyzés lekérése

Most, hogy megtörtént a denormalizálás, csak egyetlen elemet kell lekérnünk a kérés kezeléséhez.

Diagram egyetlen elem lekéréséről a denormalizált bejegyzések tárolójából.

Késés RU-díj Teljesítmény
2 Ms 1 RU

[4. negyedév] Bejegyzés megjegyzésének listázása

Itt ismét megkímélhetjük a további kéréseket, amelyek beolvasták a felhasználóneveket, és egyetlen lekérdezéssel végződnek, amely a partíciókulcsra szűr.

Diagram egy denormalizált bejegyzés összes megjegyzésének lekéréséről.

Késés RU-díj Teljesítmény
4 Ms 7.72 RU

[5. negyedév] Bejegyzések tetszésének listázása

Pontosan ugyanaz a helyzet, amikor felsorolja a kedveléseket.

Diagram egy denormalizált bejegyzés összes kedvelésének lekéréséről.

Késés RU-díj Teljesítmény
4 Ms 8.92 RU

V3: Annak ellenőrzése, hogy az összes kérés méretezhető-e

Még mindig két kérés van, amelyeket még nem optimalizáltunk teljes mértékben a teljesítmény általános fejlesztései során. Ezek a kérések a következők: [Q3] és [Q6]. Ezek olyan lekérdezéseket tartalmazó kérések, amelyek nem szűrnek a megcélzott tárolók partíciókulcsára.

[3. negyedév] Felhasználó bejegyzéseinek listázása rövid formában

Ez a kérés már a V2-ben bevezetett fejlesztésekből is hasznot hoz, ami további lekérdezéseket takarít meg.

Diagram, amely a felhasználó denormalizált bejegyzéseit rövid formában listázó lekérdezést mutatja be.

A fennmaradó lekérdezés azonban továbbra sem szűr a tároló partíciókulcsára posts .

A helyzetre való gondolkodás egyszerű:

  1. Ennek a kérésnek a szűrőre userId kell szűrnie, mert egy adott felhasználó összes bejegyzését le szeretnénk kérni.
  2. Nem működik megfelelően, mert a posts tárolón fut, és nincs userId particionálása.
  3. A nyilvánvalót jelezve a teljesítménnyel kapcsolatos problémát úgy oldjuk meg, hogy végrehajtjuk ezt a kérést egy, a következővel particionált tárolón userId: .
  4. Kiderült, hogy már van ilyen tárolónk: a users tároló!

Ezért bevezetünk egy második szintű denormalizálást úgy, hogy a teljes bejegyzéseket a tárolóba duplikáljuk users . Ezzel hatékonyan megkapjuk a bejegyzéseink másolatát, csak egy másik dimenzió mentén particionálva, így sokkal hatékonyabban lekérhetők az általuk userId.

A users tároló mostantól kétféle elemet tartalmaz:

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

Ebben a példában:

  • Bevezettünk egy type mezőt a felhasználói elemben, amely megkülönbözteti a felhasználókat a bejegyzésektől,
  • Hozzáadtunk egy userId mezőt is a felhasználói elemhez, amely redundáns a id mezővel, de kötelező, mivel a users tároló most már particionált userId (és nem id a korábbiak szerint)

A denormalizálás eléréséhez ismét a változáscsatornát használjuk. Ezúttal a tároló változáscsatornájára posts reagálunk, hogy elküldjük az új vagy frissített bejegyzéseket a users tárolónak. Mivel a bejegyzések listázásához nem szükséges a teljes tartalom visszaadása, a folyamat során csonkíthatjuk őket.

A bejegyzések felhasználók tárolójába való denormalizálásának diagramja.

Most már átirányíthatjuk a lekérdezést a users tárolóhoz, és szűrhetünk a tároló partíciókulcsára.

Diagram egy denormalizált felhasználó összes bejegyzésének lekéréséről.

Késés RU-díj Teljesítmény
4 Ms 6.46 RU

[6. negyedév] Az x legutóbbi, rövid formában létrehozott bejegyzés listázása (hírcsatorna)

Itt is hasonló helyzettel kell foglalkoznunk: még ha a V2-ben bevezetett denormalizálás miatt feleslegesen hagyott lekérdezéseket is megkíméljük, a fennmaradó lekérdezés nem szűr a tároló partíciókulcsára:

Diagram, amely a lekérdezést mutatja, amely felsorolja a rövid formában létrehozott x legutóbbi bejegyzést.

Ugyanezt a megközelítést követve a kérés teljesítményének és méretezhetőségének maximalizálásához csak egy partícióra van szükség. Csak egyetlen partíciót kell elérnünk, mert csak korlátozott számú elemet kell visszaadnunk. Ahhoz, hogy feltöltsük a blogolási platform kezdőlapját, csak le kell szereznünk a 100 legutóbbi bejegyzést, anélkül, hogy a teljes adatkészleten keresztül kellene lapoznunk.

Az utolsó kérés optimalizálása érdekében bevezetünk egy harmadik tárolót a tervünkbe, amely teljes mértékben a kérés kiszolgálására szolgál. Denormalizáljuk a bejegyzéseinket az új feed tárolóban:

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

A type mező particionálja ezt a tárolót, amely mindig post szerepel az elemekben. Ezzel biztosítja, hogy a tároló összes eleme ugyanabban a partícióban üljön.

A denormalizálás eléréséhez csak a korábban bevezetett változáscsatorna-folyamathoz kell csatlakoznunk, hogy elküldjük a bejegyzéseket az új tárolóba. Az egyik fontos dolog, amit szem előtt kell tartanunk, hogy meg kell győződnünk arról, hogy csak a 100 legutóbbi bejegyzést tároljuk; ellenkező esetben a tároló tartalma meghaladhatja a partíció maximális méretét. Ez a korlátozás úgy valósítható meg, hogy minden alkalommal meghív egy eseményindítót , amikor egy dokumentumot hozzáadnak a tárolóhoz:

A bejegyzéseket a hírcsatornatárolóba való denormalizálásának diagramja.

Íme a gyűjtemény csonkítását okozó post-trigger törzse:

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

Az utolsó lépés a lekérdezés átirányítása az új feed tárolóba:

A legutóbbi bejegyzések beolvasásának diagramja.

Késés RU-díj Teljesítmény
9 Ms 16.97 RU

Összegzés

Vessünk egy pillantást a kialakításunk különböző verzióiban bevezetett általános teljesítmény- és méretezhetőségi fejlesztésekre.

1. verzió 2. verzió V3
[C1] 7 ms / 5.71 RU 7 ms / 5.71 RU 7 ms / 5.71 RU
[1. negyedév] 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
[2. negyedév] 9 ms / 19.54 RU 2 ms / 1 RU 2 ms / 1 RU
[3. negyedév] 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
[4. negyedév] 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
[5. negyedév] 59 ms / 58.92 RU 4 ms / 8.92 RU 4 ms / 8.92 RU
[6. negyedév] 306 ms / 2063.54 RU 83 ms / 532.33 RU 9 ms / 16.97 RU

Optimalizáltunk egy írásvédett forgatókönyvet

Észrevehette, hogy az írási kérések (parancsok) rovására összpontosítottuk az olvasási kérések (lekérdezések) teljesítményének javítására irányuló erőfeszítéseinket. Az írási műveletek sok esetben a változáscsatornákon keresztül aktiválják az ezt követő denormalizálást, ami számítási szempontból költségesebb és hosszabb időt tesz lehetővé.

Ezt az olvasási teljesítményre való összpontosítást azzal indokoljuk, hogy a blogolási platform (mint a legtöbb közösségi alkalmazás) írásvédett. Az olvasási terhelés azt jelzi, hogy a kiszolgálandó olvasási kérelmek mennyisége általában nagyságrendekkel magasabb, mint az írási kérelmek száma. Ezért érdemes drágábban végrehajtani az írási kéréseket, hogy az olvasási kérések olcsóbbak és hatékonyabbak legyenek.

Ha a legszélsőségesebb optimalizálást nézzük, a [Q6] 2000-ről 17 kérelemegységre váltott; Ezt úgy értük el, hogy a bejegyzések denormalizálása tételenként körülbelül 10 KÉRELEM költséggel jár. Mivel sokkal több hírcsatorna-kérést szolgálnánk ki, mint a bejegyzések létrehozása vagy frissítése, a denormalizálás költsége elhanyagolható a teljes megtakarítás figyelembevételével.

A denormalizálás növekményesen alkalmazható

A cikkben ismertetett méretezhetőségi fejlesztések közé tartozik az adatok denormalizálása és duplikálása az adatkészletben. Meg kell jegyezni, hogy ezeket az optimalizálásokat nem kell az 1. napon üzembe helyezni. A partíciókulcsokra szűrt lekérdezések nagyobb léptékben jobban teljesítenek, de a partíciók közötti lekérdezések elfogadhatók lehetnek, ha ritkán vagy korlátozott adathalmazon keresztül hívják őket. Ha csak egy prototípust készít, vagy egy kis és ellenőrzött felhasználói bázissal rendelkező terméket indít el, akkor ezeket a fejlesztéseket valószínűleg későbbre is megkímélheti. A legfontosabb tehát a modell teljesítményének monitorozása , hogy eldönthesse, mikor és mikor érdemes behozni őket.

A frissítések más tárolókba való terjesztéséhez használt változáscsatorna folyamatosan tárolja ezeket a frissítéseket. Ez az adatmegőrzés lehetővé teszi, hogy a tároló létrehozása óta minden frissítést lekérjen, és a bootstrap denormalizált nézeteit egyszeri felzárkózási műveletként kérje, még akkor is, ha a rendszer már sok adattal rendelkezik.

Következő lépések

A gyakorlati adatmodellezés és -particionálás bemutatása után érdemes lehet áttekinteni az alábbi cikkeket az általunk tárgyalt fogalmak áttekintéséhez: