Sdílet prostřednictvím


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 řešit cvičení návrhu dat z reálného světa.

Pokud obvykle pracujete s relačními databázemi, pravděpodobně jste vytvořili návyky a intuitivně, jak navrhnout datový model. Vzhledem ke konkrétním omezením, ale také jedinečným výhodám služby Azure Cosmos DB se většina těchto osvědčených postupů dobře nepřekládá a může vás přetáhnout do neoptimálních řešení. Cílem tohoto článku je provést celý proces modelování skutečného případu použití ve službě Azure Cosmos DB od modelování položek po kolokaci entit a dělení kontejnerů.

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

Důležité

Přispěvatel komunity přispěl touto ukázkou kódu a tým Služby Azure Cosmos DB nepodporuje údržbu.

Scénář

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

Tip

Zvýraznili jsme některá slova kurzívou. Tato slova identifikují druh "věcí", se kterým bude náš model muset manipulovat.

Přidání dalších požadavků do naší specifikace:

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

Identifikace hlavních vzorů přístupu

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

Abychom si usnadnili sledování celého procesu, kategorizujeme tyto různé požadavky jako příkazy nebo dotazy a výpůjčky některých slovníků z CQRS. V CQRS jsou příkazy požadavky na zápis (tj. záměry aktualizovat systém) 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
  • [Q1] Načtení uživatele
  • [C2] Vytvoření nebo úprava příspěvku
  • [Q2] Načtení příspěvku
  • [Q3] Výpis příspěvků uživatele ve krátkém formátu
  • [C3] Vytvoření komentáře
  • [Q4] Výpis komentářů k příspěvku
  • [C4] Líbí se příspěvku
  • [Q5] Zobrazení seznamu lajků příspěvku
  • [Q6] Zobrazení seznamu x nejnovějších příspěvků vytvořených v krátkém formátu (informační kanál)

V této fázi jsme nepřemýšleli o podrobnostech o tom, co každá entita (uživatel, příspěvek atd.) obsahuje. Tento krok je obvykle mezi prvními, které je potřeba řešit při návrhu proti relačnímu úložišti. Nejprve začneme tímto krokem, protože musíme zjistit, jak se tyto entity překládají z hlediska tabulek, sloupců, cizích klíčů atd. Je to mnohem méně starostí s dokumentovou databází, která nevynucuje žádné schéma při zápisu.

Hlavním důvodem, proč je důležité identifikovat vzory přístupu od začátku, je to proto, že tento seznam požadavků bude naší testovací sadou. Pokaždé, když iterujeme datový model, procházíme jednotlivé požadavky a kontrolujeme jeho výkon a škálovatelnost. Vypočítáme jednotky žádostí 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í, které můžou dále zlepšit spotřebu a latenci RU.

V1: První verze

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

Kontejner Uživatelé

Tento kontejner ukládá pouze položky uživatele:

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

Tento kontejner rozdělíme na oddíly id, což znamená, že každý logický oddíl v rámci tohoto kontejneru obsahuje pouze jednu položku.

Příspěvky kontejneru

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 tomto kontejneru obsahuje jeden příspěvek, všechny komentáře pro tento příspěvek a všechny lajky pro tento příspěvek.

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

Rozhodli jsme se také místo vložení odkazovat na související data (podrobnosti o těchto konceptech najdete v této části ):

  • 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 počtu komentářů a lajků, které by příspěvek mohl mít,
  • Chceme být schopni přidat komentář nebo lajk do příspěvku, aniž bychom museli aktualizovat samotný příspěvek.

Jak dobře náš model funguje?

Teď je čas posoudit výkon a škálovatelnost naší první verze. U každého dříve zjištěného požadavku měříme jeho latenci a počet jednotek žádostí, které spotřebuje. Toto měření se provádí proti 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

Tento požadavek je jednoduchý pro implementaci při pouhém vytvoření nebo aktualizaci položky v kontejneru users . Požadavky se pěkně rozprostírají mezi všechny oddíly díky klíči oddílu id .

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

Latence Poplatek za RU Výkon
7 milisekunda 5.71 RU

[Q1] Načtení uživatele

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

Diagram načítání jedné položky z kontejneru uživatelů

Latence Poplatek za RU Výkon
2 milisekunda 1 RU

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

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

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

Latence Poplatek za RU Výkon
9 milisekunda 8.76 RU

[Q2] Načtení příspěvku

Začneme načtením odpovídajícího dokumentu z kontejneru posts . To ale nestačí, jak je uvedeno v naší specifikaci, 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čítání 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 maximalizovat výkon a škálovatelnost. Ale nakonec musíme 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 milisekunda 19.54 RU

[Q3] Výpis příspěvků uživatele ve krátkém formátu

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é vydávat další dotazy k agregaci uživatelského jména autora a počtu komentářů a lajků.

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

Tato implementace představuje mnoho nevýhod:

  • dotazy agregující počty komentářů a lajky musí být vydány pro každý příspěvek vrácený prvním dotazem,
  • Hlavní dotaz nefiltruje klíč posts oddílu kontejneru, což vede ke kontrole ventilátoru a prohledávání oddílů napříč kontejnerem.
Latence Poplatek za RU Výkon
130 milisekunda 619.41 RU

[C3] Vytvoření komentáře

Komentář se vytvoří napsáním odpovídající položky v kontejneru posts .

Diagram psaní jedné položky komentáře do kontejneru příspěvků

Latence Poplatek za RU Výkon
7 milisekunda 8.57 RU

[Q4] Výpis komentářů k příspěvku

Začneme dotazem, který načte všechny komentáře pro daný příspěvek a znovu, musíme také agregovat uživatelská jména pro každý komentář samostatně.

Diagram načítání všech komentářů pro příspěvek 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 milisekunda 27.72 RU

[C4] Líbí se příspěvku

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

Diagram zápisu jedné položky (například) do kontejneru příspěvků

Latence Poplatek za RU Výkon
6 milisekunda 7.05 RU

[Q5] Zobrazení seznamu lajků příspěvku

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

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

Latence Poplatek za RU Výkon
59 milisekunda 58.92 RU

[Q6] Zobrazení seznamu x nejnovějších příspěvků vytvořených v krátkém formátu (informační kanál)

Nejnovější příspěvky načteme dotazováním kontejneru posts seřazeného sestupným datem vytvoření, agregací uživatelských jmen a počty komentářů a lajků pro každý příspěvek.

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

Náš počáteční dotaz znovu nefiltruje klíč posts oddílu kontejneru, který aktivuje nákladný ventilátor. Tato sada výsledků 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ž z hlediska jednotek žádostí zdražuje.

Latence Poplatek za RU Výkon
306 milisekunda 2063.54 RU

Reflektování výkonu V1

Při pohledu na problémy s výkonem, kterým jsme čelí v předchozí části, můžeme identifikovat dvě hlavní třídy problémů:

  • některé požadavky vyžadují vydání 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í škálovatelnosti.

Pojďme všechny tyto problémy vyřešit tak, že začneme prvním.

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

Důvodem, proč musíme v některých případech vydávat další žádosti, 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 v naší sadě 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>"
}

Také upravíme komentář a lajkujeme položky a přidáme uživatelské jméno uživatele, který ho 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 komentáře a lajkování počtů

Chceme toho dosáhnout tak, že pokaždé, když přidáme komentář nebo něco podobného, také zvýšíme commentCount nebo v odpovídajícím příspěvku likeCount . Když postId kontejner rozdělíte posts , nová položka (komentář nebo lajk) a odpovídající příspěvek se nachází ve stejném logickém oddílu. V důsledku toho můžeme k provedení této operace použít uloženou proceduru.

Při vytváření komentáře ([C3]) místo pouhého přidání nové položky v posts kontejneru volá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 ID příspěvku a textu nového komentáře jako parametry a pak:

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

Při provádění uložených procedur jako atomických transakcí zůstává hodnota commentCount a skutečný počet komentářů vždy synchronizovaný.

Samozřejmě voláme podobnou uloženou proceduru při přidávání nových lajků k přírůstku likeCount.

Denormalizace uživatelských jmen

Uživatelská jména vyžadují jiný přístup, protože uživatelé se nacházejí nejen 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žijeme kanál změn kontejneru users k reakci vždy, když uživatelé aktualizují svoje 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 příspěvků

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 odpovídající userId (které můžou 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é jsou zvýšení výkonu V2?

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

[Q2] Načtení příspěvku

Teď, když je naše denormalizace na místě, musíme načíst pouze jednu položku pro zpracování této žádosti.

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

Latence Poplatek za RU Výkon
2 milisekunda 1 RU

[Q4] Výpis komentářů k příspěvku

Opět můžeme ušetřit další požadavky, které načítá uživatelská jména, a nakonec jediným dotazem, který filtruje klíč oddílu.

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

Latence Poplatek za RU Výkon
4 milisekunda 7.72 RU

[Q5] Zobrazení seznamu lajků příspěvku

Stejná situace při výpisu lajků.

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

Latence Poplatek za RU Výkon
4 milisekunda 8.92 RU

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

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

[Q3] Výpis příspěvků uživatele ve krátkém formátu

Tato žádost již přináší výhody vylepšení představených ve verzi 2, která šetří více dotazů.

Diagram znázorňující dotaz, který zobrazí seznam denormalizovaných příspěvků uživatele v krátké podobě

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

Způsob, jak se zamyslet nad touto situací, je jednoduchá:

  1. Tento požadavek musí filtrovat podle userId toho, že chceme načíst všechny příspěvky pro konkrétního uživatele.
  2. Nefunguje dobře, protože se provádí proti kontejneru posts , který nemá userId dělení.
  3. Když uvedeme zřejmé, vyřešili bychom problém s výkonem provedením tohoto požadavku na kontejner rozdělený na oddíly userId.
  4. Ukázalo se, že už takový kontejner máme: users kontejner!

Proto zavádíme druhou úroveň denormalizace duplikováním celých příspěvků do kontejneru users . Díky tomu efektivně získáme kopii našich příspěvků, pouze rozdělené podle jiné dimenze, což jim umožní zefektivnit načítání podle jejich 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:

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

Abychom toho denormalizace dosáhli, znovu použijeme informační kanál změn. Tentokrát reagujeme na kanál změn kontejneru posts a odešleme do kontejneru users jakýkoli nový nebo aktualizovaný příspěvek. 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 klíč oddílu kontejneru.

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

Latence Poplatek za RU Výkon
4 milisekunda 6.46 RU

[Q6] Zobrazení seznamu x nejnovějších příspěvků vytvořených v krátkém formátu (informační kanál)

Musíme se zde vypořádat s podobnou situací: i po uvolnění dalších dotazů nepotřebných denormalizací zavedeným ve V2 se zbývající dotaz nefiltruje na klíč oddílu kontejneru:

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

Při dosažení stejného přístupu vyžaduje maximalizace výkonu a škálovatelnosti tohoto požadavku, 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ší blogovací platformy, stačí získat 100 nejnovějších příspěvků, aniž by bylo nutné stránkovat celou datovou sadu.

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

{
    "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 sedět ve stejném oddílu.

Abychom dosáhli denormalizace, stačí připojit kanál kanálu změn, který jsme dříve zavedli k odeslání příspěvků do tohoto nového kontejneru. Je důležité mít na paměti, že musíme zajistit, že ukládáme pouze 100 nejnovějších příspěvků; jinak se obsah kontejneru může zvětšit nad maximální velikost oddílu. Toto omezení je možné implementovat volá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 text post-triggeru, 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 na nový feed kontejner:

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

Latence Poplatek za RU Výkon
9 milisekunda 16.97 RU

Závěr

Pojďme se podívat na celkové vylepšení 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
[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

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 žádostí o čtení (dotazů) na úkor požadavků na zápis (příkazy). V mnoha případech teď operace zápisu aktivují následnou denormalizaci prostřednictvím kanálů změn, což z nich dělá výpočetně nákladnější a delší materializaci.

Odůvodňujeme toto zaměření na výkon čtení tím, že platforma pro blogování (podobně jako většina sociálních aplikací) je pro čtení silná. Ú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 žádosti o zápis byly dražší, aby bylo možné žádosti o čtení levnější a lépe fungovat.

Pokud se podíváme na nejvýkonnější optimalizaci, kterou jsme provedli, [Q6] přešla z 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. Protože bychom sloužili mnohem více žádostí o kanál než vytváření nebo aktualizace příspěvků, náklady na tuto denormalizaci jsou zanedbatelné vzhledem k celkové úsporě.

Denormalizace se dá použít přírůstkově.

Vylepšení škálovatelnosti, která jsme prozkoumali v tomto článku, zahrnují denormalizaci a duplikování 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í ve velkém lépe, ale dotazy napříč oddíly můžou být přijatelné, pokud se volají zřídka nebo v omezené sadě dat. Pokud vytváříte prototyp nebo spouštíte produkt s malou a řízenou uživatelskou základnou, můžete tato vylepšení pravděpodobně ušetřit pro pozdější použití. Co je pak důležité, je monitorovat výkon modelu, abyste se mohli rozhodnout, jestli a kdy je čas je přenést.

Kanál změn, který používáme k distribuci aktualizací do jiných kontejnerů, uchovává všechny tyto aktualizace trvale. Tato trvalost umožňuje požadovat všechny aktualizace od vytvoření kontejneru a denormalizovaných zobrazení bootstrap jako jednorázově dohoněnou operaci i v případě, že váš systém již obsahuje mnoho dat.

Další kroky

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