Databáze messengeru (č. 1): navrhujeme rámec databáze

Jak lze převést obchodní požadavky do konkrétních datových struktur na příkladu navrhování databáze "od nuly" pro messenger.

Databáze messengeru (č. 1): navrhujeme rámec databáze
Naše databáze nebude tak rozsáhlá a distribuovaná, jak u VKontakte nebo Badoo, ale "aby to bylo", a přitom dobře — funkčně, rychle a vešlo se na jeden server PostgreSQL — aby bylo možné nasadit samostatnou instance služby někde bokem, například.

Proto se nebudeme dotýkat otázek shardingu, replikace a geograficky distribuovaných systémů, ale zaměříme se na schématická řešení uvnitř DB.

Krok 1: Trochu obchodní specifiky

Naši zprávu budeme navrhovat nikoli abstraktně, ale integrovat ji do prostředí korporátní sociální sítě. To znamená, že lidé u nás ne "jen tak si píší", ale komunikují mezi sebou v kontextu řešení určitých obchodních úkolů.

Jaké obchodní úkoly existují?.. Podíváme se na příkladu Vasila — vedoucího oddělení vývoje.

  • "Nikole, k této úloze je patch potřeba už dnes!"
    To znamená, že diskuse může probíhat v kontextu nějakého dokumentu.
  • "Kolja, jdeme večer na Dota?"
    To znamená, že i u jednoho páru účastníků může komunikace probíhat na různá témata.
  • "Petře, Nikolaje, podívejte se v příloze na ceník nového serveru."
    Tak, jedno zpráva může mít více adresátů. Přičemž zpráva může obsahovat připojené soubory.
  • "Semyone, podívej se také."
    A měla by být možnost do již existující konverzace pozvat nového účastníka.

Zatím se zastavíme na tomto seznamu "očekávaných" potřeb.

Bez porozumění aplikační specifiky úkolu a jejím omezením je prakticky nemožné navrhnout efektivní schéma DB pro její řešení.

Krok 2: Minimální logické schéma

Schématicky to zatím vypadá velmi podobně jako emailová komunikace — tradiční nástroj pro podnikání. Ano, "algoritmicky" jsou si mnohé obchodní úkoly podobné, proto budou i nástroje pro jejich řešení strukturálně podobné.

Pojďme zafixovat už získané logické schéma vztahů entit. Pro zjednodušení pochopení naší modely využijeme nejjednodušší variantu zobrazení ER-modelu bez zbytečných UML nebo IDEF notací:

Databáze messengeru (č. 1): navrhujeme rámec databáze

V našem příkladu jsou postava, dokument a binární "tělo" souboru "vnější" entity, které existují i bez naší služby. Proto je budeme dále vnímat jako odkazy "někam" podle UUID.

Kreslete schémata co nejjednodušeji — většina těch, kterým je budete ukazovat, nejsou experti na čtení UML/IDEF. Ale — kreslete určitě.

Krok 3: Návrh struktury tabulek

O názvech tabulek a políK "ruským" názvům polí a tabulek lze přistupovat různě, ale to už je otázka vkusu. Protože u nás v "Tenzoře" není žádný vývojář-cizinec, a PostgreSQL nám umožňuje dávat názvy i hieroglyfy, pokud jsou uzavřeny v uvozovkách, tak dáváme přednost jednoznačným názvům objektů, abychom předešli nejasnostem.
Vzhledem k tomu, že zprávy píše víc lidí najednou, někteří mohou dokonce činit tak v offline režimu, je nejjednodušší varianta — používat UUID jako identifikátory nejen pro vnější entity, ale i pro všechny objekty uvnitř naší služby. A můžete je generovat i na straně klienta — to nám pomůže uchovat odesílání zpráv během krátkodobé nedostupnosti DB, a pravděpodobnost kolize je extrémně nízká.

Návrh struktury tabulek v naší databázi přijme tento tvar:
Tabulky : RU

CREATE TABLE "Téma"(
  "Téma"
    uuid
      PRIMARY KEY
, "Dokument"
    uuid
, "Název"
    text
);

CREATE TABLE "Zpráva"(
  "Zpráva"
    uuid
      PRIMARY KEY
, "Téma"
    uuid
, "Autor"
    uuid
, "DatumČas"
    timestamp
, "Text"
    text
);

CREATE TABLE "Adresát"(
  "Zpráva"
    uuid
, "Postava"
    uuid
, PRIMARY KEY("Zpráva", "Postava")
);

CREATE TABLE "Soubor"(
  "Soubor"
    uuid
      PRIMARY KEY
, "Zpráva"
    uuid
, "BLOB"
    uuid
, "Název"
    text
);

Tabulky : EN

CREATE TABLE theme(
  theme
    uuid
      PRIMARY KEY
, document
    uuid
, title
    text
);

CREATE TABLE message(
  message
    uuid
      PRIMARY KEY
, theme
    uuid
, author
    uuid
, dt
    timestamp
, body
    text
);

CREATE TABLE message_addressee(
  message
    uuid
, person
    uuid
, PRIMARY KEY(message, person)
);

CREATE TABLE message_file(
  file
    uuid
      PRIMARY KEY
, message
    uuid
, content
    uuid
, filename
    text
);

Nejdůležitější při popisu formátu je začít "rozkrývat" graf vazeb od tabulek, které se navzájem neodkazují samy na nikoho.

Krok 4: Zjistit nejasné potřeby

Všechno, navrhli jsme databázi, do které lze skvěle psát a nějak číst. číst.

Pojďme se vžít do role uživatele naší služby – co bychom s její pomocí chtěli dělat?

  • Poslední zprávy
    To je chronologicky seřazené podle různých kritérií rejstřík "mých" zpráv. Kde jsem jedním z adresátů, kde jsem autorem, kde mi bylo napsáno, ale já jsem neodpověděl, kde mi neodpověděli, …
  • Ú účastníci konverzace
    Kdo se vůbec účastní tohoto dlouhého chatu?

Naše struktura umožňuje vyřešit oba tyto úkoly "v zásadě", ale rychle – ne. Problém spočívá v tom, že pro třídění v rámci prvního úkolu není možné vytvořit index, který by vyhovoval každému z účastníků (a bude nutné extrahovat všechny záznamy), a pro vyřešení druhého úkolu je nezbytné extrahovat všechny zprávy o tématu.

Nepředpokládané uživatelské úkoly mohou výrazně ovlivnit výkon.

Krok 5: Rozumná denormalizace

Obě naše problémy pomohou vyřešit dodatečné tabulky, do kterých budeme duplicovat část dat, potřebných pro vytváření na nich vhodných pro naše úkoly indexů.
Databáze messengeru (č. 1): navrhujeme rámec databáze

Tabulky : RU

CREATE TABLE "RegistryZpráv"(
  "Vlastník"
    uuid
, "TypRegistru"
    smallint
, "DatumČas"
    timestamp
, "Zpráva"
    uuid
, PRIMARY KEY("Vlastník", "TypRegistru", "Zpráva")
);
CREATE INDEX ON "RegistryZpráv"("Vlastník", "TypRegistru", "DatumČas" DESC);

CREATE TABLE "ÚčastníkTématu"(
  "Téma"
    uuid
, "Osoba"
    uuid
, PRIMARY KEY("Téma", "Osoba")
);

Tabulky : EN

CREATE TABLE message_registry(
  owner
    uuid
, registry
    smallint
, dt
    timestamp
, message
    uuid
, PRIMARY KEY(owner, registry, message)
);
CREATE INDEX ON message_registry(owner, registry, dt DESC);

CREATE TABLE theme_participant(
  theme
    uuid
, person
    uuid
, PRIMARY KEY(theme, person)
);

Zde jsme použili dva typické přístupy, které se uplatňují při vytváření pomocných tabulek:

  • Násobení záznamů
    Vytváříme z jedné základní zprávy hned několik následných záznamů v různých typech rejstříků pro různé vlastníky – jak pro odesílatele, tak pro příjemce. Každý z rejstříků nyní leží na indexu – vždyť typicky chceme vidět jen první stránku.
  • Unikalizace záznamů
    Při každém odeslání zprávy v rámci konkrétního tématu stačí zkontrolovat, zda již takový záznam existuje. Pokud ne – přidáme jej do našeho „slovníku“.

V další části článku se budeme zabývat implementací partitioningu do struktury naší databáze.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster