Modelování a dělení dat ve službě Azure Cosmos DB s využitím příkladu z reálného světa

PLATÍ PRO: NoSQL

Tento článek vychází z několika konceptů služby Azure Cosmos DB, jako je modelování dat, dělení na oddíly a zřízená propustnost , a ukazuje, jak se vypořádat s reálným cvičením návrhu dat.

Pokud obvykle pracujete s relačními databázemi, pravděpodobně jste si na návrhu datového modelu vytvořili návyky a intuitivně. Vzhledem ke specifickým omezením, ale také jedinečným stránkám služby Azure Cosmos DB se většina těchto osvědčených postupů nepřekládá dobře a můžou vás přetáhnout k neoptimálním řešením. Cílem tohoto článku je provést vás celým procesem modelování reálného případu použití ve službě Azure Cosmos DB, od modelování položek až po společné umístění entit a dělení kontejnerů.

Stáhněte si nebo zobrazte zdrojový kód vygenerovaný komunitou , který ilustruje koncepty z tohoto článku.

Důležité

Přispěvatel komunity přispěl tímto vzorovým kódem a tým Azure Cosmos DB nepodporuje jeho údržbu.

Scénář

V tomto cvičení budeme uvažovat o doméně platformy pro blogování, kde uživatelé můžou vytvářet příspěvky. Uživatelé také můžou k těmto příspěvkům přidávat komentáře a lajky.

Tip

Zdůraznili jsme některá slova kurzívou; tato slova identifikují druh "věcí", s nimiž bude náš model muset manipulovat.

Přidání dalších požadavků k naší specifikaci:

  • Na úvodní stránce se zobrazí informační kanál naposledy vytvořených příspěvků.
  • Můžeme načíst všechny příspěvky pro uživatele, všechny komentáře k příspěvku a všechny lajky pro příspěvek,
  • Příspěvky se vrací s uživatelským jménem autorů a počtem komentářů a lajků, které mají.
  • Komentáře a lajky se také vrací s uživatelským jménem uživatelů, kteří je vytvořili.
  • Když se zobrazí jako seznamy, stačí, když příspěvky zobrazí zkrácený souhrn jejich obsahu.

Identifikace hlavních vzorů přístupu

Pro začátek dáme naší počáteční specifikaci určitou strukturu tím, že identifikujeme vzory přístupu k našemu řešení. Při návrhu datového modelu pro službu Azure Cosmos DB je důležité pochopit, které požadavky musí náš model sloužit, aby se zajistilo, že model tyto požadavky obsluhuje efektivně.

Abychom usnadnili sledování celého procesu, kategorizujeme tyto různé požadavky jako příkazy nebo dotazy a vypůjčíme si slovník z CQRS. V CQRS jsou příkazy požadavky na zápis (tj. záměry aktualizace systému) a dotazy jsou požadavky jen pro čtení.

Tady je seznam požadavků, které naše platforma zveřejňuje:

  • [C1] Vytvoření nebo úprava uživatele
  • [Otázka 1] Načtení uživatele
  • [C2] Vytvoření/úprava příspěvku
  • [Otázka 2] Načtení příspěvku
  • [Otázka 3] Zobrazení krátkých příspěvků uživatele
  • [C3] Vytvoření komentáře
  • [Otázka 4] Zobrazení komentářů k příspěvku
  • [C4] Lajk příspěvku
  • [Otázka 5] Zobrazení seznamu to se mi líbí příspěvku
  • [Otázka 6] Seznam x nejnovějších příspěvků vytvořených v krátké podobě (informační kanál)

V této fázi jsme nepřemýšleli nad podrobnostmi o tom, co jednotlivé entity (uživatel, příspěvek atd.) obsahují. Tento krok je obvykle mezi prvními, které je potřeba provést při návrhu proti relačnímu úložišti. Tento krok začneme nejprve, protože musíme zjistit, jak se tyto entity překládají z hlediska tabulek, sloupců, cizích klíčů atd. Mnohem méně se to týká databáze dokumentů, která při zápisu nevynucuje žádné schéma.

Hlavním důvodem, proč je důležité identifikovat vzory přístupu od začátku, je to, že tento seznam požadavků bude naší testovací sadou. Při každé iteraci datového modelu procházíme jednotlivé požadavky a kontrolujeme jeho výkon a škálovatelnost. Vypočítáme jednotky požadavků spotřebované v každém modelu a optimalizujeme je. Všechny tyto modely používají výchozí zásady indexování a můžete je přepsat indexováním konkrétních vlastností, což může dále zlepšit spotřebu a latenci RU.

V1: První verze

Začneme se dvěma kontejnery: users a posts.

Kontejner Users

Tento kontejner ukládá pouze položky uživatelů:

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

Tento kontejner rozdělíme na oddíly, idcož znamená, že každý logický oddíl v tomto kontejneru obsahuje pouze jednu položku.

Kontejner Posts

Tento kontejner hostuje entity, jako jsou příspěvky, komentáře a lajky:

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

Tento kontejner rozdělíme podle postId, což znamená, že každý logický oddíl v rámci tohoto kontejneru obsahuje jeden příspěvek, všechny komentáře k danému příspěvku a všechny lajky pro tento příspěvek.

Zavedli jsme vlastnost v položkách uložených v tomto kontejneru, abychom rozlišili type tři typy entit, které tento kontejner hostuje.

Také jsme se rozhodli odkazovat na související data místo jejich vkládání (podrobnosti o těchto konceptech najdete v této části ), protože:

  • neexistuje žádný horní limit počtu příspěvků, které může uživatel vytvořit.
  • příspěvky mohou být libovolně dlouhé,
  • neexistuje žádný horní limit pro to, kolik komentářů a lajků může příspěvek mít,
  • Chceme mít možnost přidat k příspěvku komentář nebo lajk, aniž byste museli aktualizovat samotný příspěvek.

Jak dobře funguje náš model?

Teď je čas posoudit výkon a škálovatelnost naší první verze. U každého dříve identifikovaného požadavku měříme jeho latenci a počet jednotek žádostí, které spotřebují. Toto měření se provádí vůči fiktivní datové sadě obsahující 100 000 uživatelů s 5 až 50 příspěvky na uživatele a až 25 komentářů a 100 lajků na příspěvek.

[C1] Vytvoření nebo úprava uživatele

Implementace tohoto požadavku je jednoduchá, protože pouze vytvoříme nebo aktualizujeme položku v kontejneru users . Požadavky jsou díky klíči oddílu pěkně rozložené mezi všechny oddíly id .

Diagram zápisu jedné položky do kontejneru uživatelů

Latence Poplatek za RU Výkon
7 Paní 5.71 RU

[Otázka 1] Načtení uživatele

Načtení uživatele se provádí načtením odpovídající položky z kontejneru users .

Diagram načtení jedné položky z kontejneru uživatelů

Latence Poplatek za RU Výkon
2 Paní 1 RU

[C2] Vytvoření/úprava příspěvku

Podobně jako u [C1] stačí zapisovat do kontejneru posts .

Diagram zápisu jedné položky příspěvku do kontejneru posts

Latence Poplatek za RU Výkon
9 Paní 8.76 RU

[Otázka 2] Načtení příspěvku

Začneme načtením odpovídajícího dokumentu z kontejneru posts . To ale nestačí, protože podle naší specifikace musíme také agregovat uživatelské jméno autora příspěvku, počty komentářů a počty lajků pro příspěvek. Uvedené agregace vyžadují vydání dalších 3 dotazů SQL.

Diagram načtení příspěvku a agregace dalších dat

Každý z dalších dotazů filtruje klíč oddílu příslušného kontejneru, což je přesně to, co chceme dosáhnout maximálního výkonu a škálovatelnosti. Nakonec ale budeme muset provést čtyři operace, abychom vrátili jeden příspěvek, takže to v další iteraci vylepšíme.

Latence Poplatek za RU Výkon
9 Paní 19.54 RU

[Otázka 3] Zobrazení krátkých příspěvků uživatele

Nejprve musíme načíst požadované příspěvky pomocí dotazu SQL, který načte příspěvky odpovídající danému uživateli. Musíme ale také vydat další dotazy, abychom agregovali uživatelské jméno autora a počty komentářů a lajků.

Diagram načtení všech příspěvků pro uživatele a agregace jeho dalších dat

Tato implementace má řadu nevýhod:

  • dotazy agregující počty komentářů a lajků musí být vystaveny pro každý příspěvek vrácený prvním dotazem,
  • hlavní dotaz nefiltruje klíč oddílu kontejneru posts , což vede k fan-outu a prohledávání oddílu v kontejneru.
Latence Poplatek za RU Výkon
130 Paní 619.41 RU

[C3] Vytvoření komentáře

Komentář se vytvoří zápisem odpovídající položky do kontejneru posts .

Diagram zápisu jedné položky komentáře do kontejneru posts

Latence Poplatek za RU Výkon
7 Paní 8.57 RU

[Otázka 4] Zobrazení komentářů k příspěvku

Začneme dotazem, který načte všechny komentáře k danému příspěvku, a znovu potřebujeme agregovat uživatelská jména zvlášť pro každý komentář.

Diagram načtení všech komentářů k příspěvku a agregace jejich dalších dat

I když hlavní dotaz filtruje klíč oddílu kontejneru, agregace uživatelských jmen samostatně penalizuje celkový výkon. Později to vylepšíme.

Latence Poplatek za RU Výkon
23 Paní 27.72 RU

[C4] Lajk příspěvku

Stejně jako [C3] vytvoříme odpovídající položku v kontejneru posts .

Diagram zápisu jedné položky (like) do kontejneru posts

Latence Poplatek za RU Výkon
6 Paní 7.05 RU

[Otázka 5] Zobrazení seznamu to se mi líbí příspěvku

Stejně jako [Q4] se dotazujeme na lajky pro daný příspěvek a pak agregujeme jejich uživatelská jména.

Diagram načtení všech lajků pro příspěvek a agregace jejich dalších dat

Latence Poplatek za RU Výkon
59 Paní 58.92 RU

[Otázka 6] Seznam x nejnovějších příspěvků vytvořených v krátké podobě (informační kanál)

Nejnovější příspěvky načítáme dotazem posts na kontejner seřazený podle sestupného data vytvoření a agregujeme uživatelská jména a počty komentářů a lajků pro každý z příspěvků.

Diagram načítání nejnovějších příspěvků a agregace jejich dalších dat

Náš počáteční dotaz opět nefiltruje klíč oddílu kontejneru posts , což aktivuje nákladné rozdmýchaní. Tato možnost je ještě horší, protože cílíme na větší sadu výsledků a výsledky seřadíme pomocí ORDER BY klauzule, což je nákladnější z hlediska jednotek žádostí.

Latence Poplatek za RU Výkon
306 Paní 2063.54 RU

Odraz na výkonu V1

Když se podíváme na problémy s výkonem, se kterými jsme se setkali v předchozí části, můžeme identifikovat dvě hlavní třídy problémů:

  • Některé požadavky vyžadují, aby bylo vydáno více dotazů, aby bylo možné shromáždit všechna data, která potřebujeme vrátit,
  • Některé dotazy nefiltrují klíč oddílu kontejnerů, na které cílí, což vede k rozšíření, které brání naší škálovatelnosti.

Pojďme vyřešit každý z těchto problémů, počínaje prvním z nich.

V2: Představujeme denormalizaci pro optimalizaci dotazů na čtení

Důvodem, proč v některých případech musíme vydávat více požadavků, je to, že výsledky počátečního požadavku neobsahují všechna data, která potřebujeme vrátit. Denormalizace dat řeší tento druh problému napříč naší sadou dat při práci s nerelačním úložištěm dat, jako je Azure Cosmos DB.

V našem příkladu upravíme položky příspěvku tak, aby se přidalo uživatelské jméno autora příspěvku, počet komentářů a počet lajků:

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

Upravíme také komentáře a lajky tak, aby se přidalo uživatelské jméno uživatele, který je vytvořil:

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

Denormalizace počtu komentářů a lajků

Chceme dosáhnout toho, že pokaždé, když přidáme komentář nebo lajk, také v příslušném příspěvku commentCountlikeCount navýšíme hodnotu nebo . Jako postId oddíly kontejneru posts se nová položka (komentář nebo lajk) a její odpovídající příspěvek nacházejí ve stejném logickém oddílu. V důsledku toho můžeme k provedení této operace použít uloženou proceduru .

Když vytvoříte komentář ([C3]), místo přidání nové položky do kontejneru posts zavoláme následující uloženou proceduru v tomto kontejneru:

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

Tato uložená procedura převezme JAKO parametry ID příspěvku a text nového komentáře a pak:

  • načte příspěvek.
  • zvýší hodnotu commentCount
  • nahrazuje příspěvek.
  • přidá nový komentář.

Vzhledem k tomu, že se uložené procedury provádějí jako atomické transakce, hodnota commentCount a skutečný počet komentářů se vždy synchronizují.

Podobnou uloženou proceduru samozřejmě nazýváme při přidávání nových lajků pro zvýšení likeCount.

Denormalizace uživatelských jmen

Uživatelská jména vyžadují jiný přístup, protože uživatelé nejsou pouze v různých oddílech, ale v jiném kontejneru. Když musíme denormalizovat data napříč oddíly a kontejnery, můžeme použít kanál změn zdrojového kontejneru.

V našem příkladu používáme kanál změn kontejneru users k tomu, abychom reagovali pokaždé, když uživatelé aktualizují svá uživatelská jména. Když k tomu dojde, rozšíříme změnu voláním jiné uložené procedury v kontejneru posts :

Diagram denormalizace uživatelských jmen do kontejneru posts

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

Tato uložená procedura přebírá ID uživatele a nové uživatelské jméno uživatele jako parametry a pak:

  • Načte všechny položky, které odpovídají userId (můžou to být příspěvky, komentáře nebo lajky).
  • pro každou z těchto položek
    • nahrazuje userUsername
    • nahradí položku.

Důležité

Tato operace je nákladná, protože vyžaduje, aby se tato uložená procedura spustila v každém oddílu kontejneru posts . Předpokládáme, že většina uživatelů zvolí během registrace vhodné uživatelské jméno a nikdy ho nezmění, takže tato aktualizace se spustí velmi zřídka.

Jaké je zvýšení výkonu verze V2?

Pojďme si promluvit o některých nárůstech výkonu V2.

[Otázka 2] Načtení příspěvku

Teď, když je naše denormalizace na místě, stačí načíst jednu položku, která tento požadavek zpracuje.

Diagram načtení jedné položky z kontejneru denormalizovaných příspěvků

Latence Poplatek za RU Výkon
2 Paní 1 RU

[Otázka 4] Zobrazení komentářů k příspěvku

Tady opět můžeme ušetřit dodatečné požadavky, které načítá uživatelská jména, a skončíme jediným dotazem, který filtruje klíč oddílu.

Diagram načtení všech komentářů pro denormalizovaný příspěvek

Latence Poplatek za RU Výkon
4 Paní 7.72 RU

[Otázka 5] Zobrazení seznamu to se mi líbí příspěvku

Úplně stejná situace při výpisu lajků.

Diagram načtení všech lajků pro denormalizovaný příspěvek

Latence Poplatek za RU Výkon
4 Paní 8.92 RU

V3: Zajištění škálovatelnosti všech požadavků

Stále existují dva požadavky, které jsme při pohledu na celková vylepšení výkonu úplně neoptimalizovali. Jedná se o žádosti [Q3] a [Q6]. Jedná se o požadavky zahrnující dotazy, které nefiltrují klíč oddílu kontejnerů, na které cílí.

[Otázka 3] Zobrazení krátkých příspěvků uživatele

Tento požadavek už těží z vylepšení zavedených ve verzi 2, která šetří více dotazů.

Diagram znázorňující dotaz na krátký výpis denormalizovaných příspěvků uživatele

Zbývající dotaz ale stále nefiltruje klíč oddílu kontejneru posts .

Způsob, jak o této situaci přemýšlet, je jednoduchý:

  1. Tento požadavek musí filtrovat userId podle , protože chceme načíst všechny příspěvky pro konkrétního uživatele.
  2. Nefunguje dobře, protože se spouští v kontejneru posts , který ho nerozděluje userId .
  3. Je zřejmé, že problém s výkonem bychom vyřešili spuštěním tohoto požadavku na kontejner rozdělený do oddílů pomocí userId.
  4. Ukazuje se, že takový kontejner už máme: users kontejner!

Proto zavádíme druhou úroveň denormalizace duplikováním celých příspěvků do kontejneru users . Tímto způsobem získáme kopii našich příspěvků, pouze rozdělených podle jiné dimenze, což z nich zefektivňuje načítání pomocí .userId

Kontejner users teď obsahuje dva druhy položek:

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

V tomto příkladu:

  • Do položky uživatele jsme zavedli type pole, které uživatele odliší od příspěvků.
  • Do položky uživatele jsme také přidali userId pole, které je s polem id redundantní, ale vyžaduje se, protože users kontejner je teď rozdělený userId na oddíly (a ne id jako dříve).

K dosažení této denormalizace znovu použijeme kanál změn. Tentokrát reagujeme na kanál změn kontejneru, abychom do kontejneru posts odeslali jakýkoli nový nebo aktualizovaný příspěvek users . A protože výpis příspěvků nevyžaduje vrácení celého obsahu, můžeme je v procesu zkrátit.

Diagram denormalizace příspěvků do kontejneru uživatelů

Teď můžeme směrovat dotaz do kontejneru users a filtrovat podle klíče oddílu kontejneru.

Diagram načítání všech příspěvků pro denormalizovaného uživatele

Latence Poplatek za RU Výkon
4 Paní 6.46 RU

[Otázka 6] Seznam x nejnovějších příspěvků vytvořených v krátké podobě (informační kanál)

V tomto případě se musíme vypořádat s podobnou situací: i když se v důsledku denormalizace ve verzi 2 šetří další dotazy, které zůstaly nepotřebné, zbývající dotaz nefiltruje klíč oddílu kontejneru:

Diagram znázorňující dotaz na seznam x nejnovějších příspěvků vytvořených v krátké podobě

Při použití stejného přístupu k maximalizaci výkonu a škálovatelnosti tohoto požadavku je nutné, aby dosáhl pouze jednoho oddílu. Dosažení pouze jednoho oddílu je možné, protože musíme vrátit pouze omezený počet položek. Abychom mohli naplnit domovskou stránku naší platformy pro blogování, stačí získat 100 nejnovějších příspěvků, aniž bychom museli stránkovat celou datovou sadu.

Abychom optimalizovali tento poslední požadavek, zavádíme do našeho návrhu třetí kontejner, který je zcela vyhrazený pro obsluhu tohoto požadavku. Denormalizujeme naše příspěvky do nového feed kontejneru:

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

Pole type rozdělí tento kontejner, který je vždy post v našich položkách. Tím zajistíte, že všechny položky v tomto kontejneru budou ve stejném oddílu.

Abychom dosáhli denormalizace, stačí se připojit k kanálu kanálu změn, který jsme dříve zavedli k odesílání příspěvků do tohoto nového kontejneru. Jedna důležitá věc, kterou je třeba mít na paměti, je, že musíme zajistit, abychom uložili pouze 100 nejnovějších příspěvků; v opačném případě se obsah kontejneru může zvětšit nad rámec maximální velikosti oddílu. Toto omezení se dá implementovat zavoláním triggeru po každém přidání dokumentu do kontejneru:

Diagram denormalizace příspěvků do kontejneru informačního kanálu

Tady je tělo triggeru post, který zkrátí kolekci:

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

Posledním krokem je přesměrování dotazu do nového feed kontejneru:

Diagram načítání nejnovějších příspěvků

Latence Poplatek za RU Výkon
9 Paní 16.97 RU

Závěr

Pojďme se podívat na vylepšení celkového výkonu a škálovatelnosti, která jsme zavedli v různých verzích našeho návrhu.

V1 V2 V3
[C1] 7 ms / 5.71 RU 7 ms / 5.71 RU 7 ms / 5.71 RU
[Otázka 1] 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
[Otázka 2] 9 ms / 19.54 RU 2 ms / 1 RU 2 ms / 1 RU
[Otázka 3] 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
[Otázka 4] 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
[Otázka 5] 59 ms / 58.92 RU 4 ms / 8.92 RU 4 ms / 8.92 RU
[Otázka 6] 306 ms / 2063.54 RU 83 ms / 532.33 RU 9 ms / 16.97 RU

Optimalizovali jsme scénář náročný na čtení.

Možná jste si všimli, že jsme se zaměřili na zlepšení výkonu požadavků na čtení (dotazů) na úkor žádostí o zápis (příkazů). V mnoha případech teď operace zápisu aktivují následnou denormalizaci prostřednictvím kanálů změn, díky čemuž jsou výpočetně nákladnější a jsou delší na materializaci.

Toto zaměření na výkon při čtení zdůvodňujeme tím, že platforma pro blogování (stejně jako většina sociálních aplikací) je náročné na čtení. Úloha náročná na čtení značí, že množství požadavků na čtení, které musí obsloužit, je obvykle řádově vyšší než počet žádostí o zápis. Proto je vhodné, aby provádění požadavků na zápis bylo nákladnější, aby žádosti o čtení byly levnější a výkonnější.

Když se podíváme na nejextrémnější optimalizaci, kterou jsme provedli, [Q6] přešlo z více než 2000 RU na pouhých 17 RU; dosáhli jsme toho denormalizací příspěvků za cenu přibližně 10 RU na položku. Vzhledem k tomu, že bychom obsloužili mnohem více žádostí o informační kanál než vytváření nebo aktualizace příspěvků, jsou náklady na tuto denormalizaci vzhledem k celkovým úsporám zanedbatelné.

Denormalizace může být použita přírůstkově

Vylepšení škálovatelnosti, která jsme prozkoumali v tomto článku, zahrnují denormalizaci a duplikaci dat v datové sadě. Je třeba poznamenat, že tyto optimalizace nemusí být zavedeny v den 1. Dotazy, které filtrují klíče oddílů, fungují lépe ve velkém měřítku, ale dotazy napříč oddíly můžou být přijatelné, pokud se volají zřídka nebo s omezenou datovou sadou. Pokud právě vytváříte prototyp nebo spouštíte produkt s malou a řízenou uživatelskou základnou, pravděpodobně můžete tato vylepšení ušetřit na později. Důležité je pak monitorovat výkon modelu, abyste se mohli rozhodnout, jestli a kdy je čas ho zavést.

Kanál změn, který používáme k distribuci aktualizací do jiných kontejnerů, ukládá všechny tyto aktualizace trvale. Tato trvalost umožňuje vyžádat si všechny aktualizace od vytvoření kontejneru a zobrazení denormalizovaného spuštění jako jednorázovou operaci dohánění, i když váš systém už obsahuje mnoho dat.

Další kroky

Po tomto úvodu do praktického modelování dat a dělení můžete zkontrolovat koncepty, které jsme probrali v následujících článcích: