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, musí příspěvky prezentovat jenom 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 CQRTS 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, id
což 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í 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
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
.
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
.
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
.
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čí, 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.
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ů.
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
.
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ář.
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
.
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.
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ů.
Náš počáteční dotaz opět nefiltruje klíč oddílu kontejneru posts
, což aktivuje nákladné rozdmýchaní. To 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 dražší 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č musíme v některých případech 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 v naší datové sadě 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 commentCount
likeCount
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
:
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.
- nahrazuje
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.
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.
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ů.
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ů.
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ý:
- Tento požadavek musí filtrovat
userId
podle , protože chceme načíst všechny příspěvky pro konkrétního uživatele. - Nefunguje dobře, protože se spouští v kontejneru
posts
, který ho nerozdělujeuserId
. - 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
. - 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 polemid
redundantní, ale vyžaduje se, protožeusers
kontejner je teď rozdělenýuserId
na oddíly (a neid
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.
Teď můžeme směrovat dotaz do kontejneru users
a filtrovat podle klíče oddílu kontejneru.
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 zbytečné, zbývající dotaz nefiltruje klíč oddílu kontejneru:
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:
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:
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 |
[Q5] | 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 žádostí o čtení (dotazů) na úkor žádostí o zápis (příkazů). V mnoha případech nyní operace zápisu aktivují následnou denormalizaci prostřednictvím kanálů změn, což z nich činí výpočetně nákladnější a delší materializaci.
Toto zaměření na výkon čtení odůvodňujeme tím, že platforma pro blogování (jako většina sociálních aplikací) je náročné na čtení. Úloha náročná na čtení značí, že množství žádostí o čtení, které musí obsloužit, je obvykle řádově vyšší než počet žádostí o zápis. Proto je vhodné, aby provádění žádostí o zápis bylo nákladnější, aby byly žádosti o čtení levnější a výkonnější.
Když se podíváme na nejextrémnější optimalizaci, kterou jsme udělali, [Q6] přešla 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ů, náklady na tuto denormalizaci jsou vzhledem k celkovým úsporám zanedbatelné.
Denormalizaci lze použít přírůstkově.
Vylepšení škálovatelnosti, která jsme prozkoumali v tomto článku, zahrnují denormalizaci a duplikaci dat napříč datovou sadou. 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 s omezenou sadou dat. 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 sledovat 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 požadovat všechny aktualizace od vytvoření kontejneru a zobrazení denormalizovaného spouštění jako jednorázovou operaci doháněcí, i když váš systém již 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: