Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

Nedávno jsem vám řekl, jak na to, pomocí standardních receptů zvýšit výkon čtecích dotazů SQL z databáze PostgreSQL. Dnes si povíme jak nahrávání lze provádět efektivněji v databázi bez použití jakýchkoliv „zvratů“ v konfiguraci – jednoduše správným uspořádáním datových toků.

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

#1. Dělení sekcí

Článek o tom, jak a proč se vyplatí organizovat aplikované dělení „teoreticky“ již bylo, zde budeme hovořit o praxi uplatňování některých přístupů v rámci našeho monitorovací služba pro stovky PostgreSQL serverů.

"Věci minulých dnů..."

Zpočátku, jako každý MVP, náš projekt začínal v poměrně malé zátěži - monitorování probíhalo pouze pro deset nejkritičtějších serverů, všechny tabulky byly relativně kompaktní... Postupem času však počet monitorovaných hostitelů stále více přibýval. , a ještě jednou jsme se pokusili něco udělat s jedním z stoly o velikosti 1.5 TB, uvědomili jsme si, že i když je možné takto žít dál, je to velmi nepohodlné.

Časy byly téměř jako epické časy, různé verze PostgreSQL 9.x byly relevantní, takže veškeré dělení bylo třeba provádět „ručně“ – prostřednictvím dědičnost tabulky a spouštěče směrování s dynamickým EXECUTE.

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB
Výsledné řešení se ukázalo jako dostatečně univerzální, že jej bylo možné přeložit do všech tabulek:

  • Byla deklarována prázdná nadřazená tabulka „záhlaví“, která vše popisovala potřebné indexy a spouštěče.
  • Záznam z pohledu klienta byl proveden v „kořenové“ tabulce a interně pomocí směrovací spoušť BEFORE INSERT záznam byl „fyzicky“ vložen do požadované sekce. Pokud nic takového ještě nebylo, chytili jsme výjimku a...
  • … používáním CREATE TABLE ... (LIKE ... INCLUDING ...) byl vytvořen na základě šablony nadřazené tabulky sekce s omezením na požadovaný termíntakže při načítání dat se provádí čtení pouze v nich.

PG10: první pokus

Ale dělení pomocí dědičnosti nebylo historicky vhodné pro řešení aktivního zapisovacího proudu nebo velkého počtu podřízených oddílů. Například si můžete vzpomenout, že algoritmus pro výběr požadované sekce měl kvadratická složitost, že to funguje se 100+ sekcemi, sám chápeš jak...

V PG10 byla tato situace výrazně optimalizována implementací podpory nativní dělení. Proto jsme jej ihned po migraci úložiště zkusili aplikovat, ale...

Jak se ukázalo po prostudování manuálu, nativně rozdělená tabulka v této verzi je:

  • nepodporuje popisy indexů
  • nepodporuje spouštěče na něm
  • nemůže být něčí „potomek“
  • nepodporují INSERT ... ON CONFLICT
  • nemůže generovat sekci automaticky

Když jsme dostali bolestivou ránu hráběmi do čela, uvědomili jsme si, že bez úpravy aplikace se to neobejde, a další výzkum jsme o šest měsíců odložili.

PG10: druhá šance

Začali jsme tedy řešit problémy, které se objevily jeden po druhém:

  1. Protože spouští a ON CONFLICT Zjistili jsme, že je tu a tam ještě potřebujeme, a tak jsme si udělali mezistupeň, abychom je vypracovali proxy tabulka.
  2. Zbavil jsem se "routingu" v triggerech - tedy od EXECUTE.
  3. Vytáhli to zvlášť šablonová tabulka se všemi indexytakže nejsou ani přítomny v proxy tabulce.

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB
Nakonec jsme po tom všem nativně rozdělili hlavní tabulku. Vytvoření nové sekce je stále ponecháno na svědomí aplikace.

"Pila" slovníky

Jako v každém analytickém systému jsme také měli "fakta" a "střihy" (slovníky). V našem případě v této funkci jednali např. tělo šablony podobné pomalé dotazy nebo samotný text dotazu.

„Fakta“ byla již dlouho rozdělena podle dne, takže jsme v klidu smazali zastaralé sekce a nevadily nám (logy!). Ale byl problém se slovníky...

Neříkám, že jich bylo hodně, ale přibližně 100 TB „faktů“ vedlo k 2.5 TB slovníku. Z takové tabulky nelze pohodlně nic smazat, nelze ji zkomprimovat v přiměřeném čase a zápis do ní se postupně zpomaloval.

Jako ve slovníku... v něm by měl být každý záznam uveden právě jednou... a to je správně, ale!... Nikdo nám nebrání mít samostatný slovník pro každý den! Ano, přináší to určitou redundanci, ale umožňuje:

  • rychlejší zápis/čtení kvůli menší velikosti sekce
  • spotřebovávají méně paměti pomocí práce s kompaktnějšími indexy
  • ukládat méně dat díky schopnosti rychle odstranit zastaralé

Výsledkem celého komplexu opatření Zatížení procesoru sníženo o ~30%, zatížení disku o ~50%:

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB
Zároveň jsme pokračovali v zápisu úplně stejného do databáze, akorát s menší zátěží.

#2. Evoluce a refaktoring databáze

Takže jsme se shodli na tom, co máme každý den má svůj oddíl s daty. Vlastně, CHECK (dt = '2018-10-12'::date) — a existuje rozdělovací klíč a podmínka pro to, aby záznam spadal do určité sekce.

Vzhledem k tomu, že všechny přehledy v naší službě jsou sestaveny v kontextu konkrétního data, indexy pro ně od „dob bez rozdělení“ byly všech typů (server, datum, šablona plánu), (server, datum, uzel plánu), (datum, třída chyb, server), ...

Ale teď žijí na každém úseku vaše kopie každý takový index... A v rámci každé sekce datum je konstantní... Ukazuje se, že nyní jsme v každém takovém indexu stačí zadat konstantu jako jedno z polí, které zvyšuje jak svůj objem, tak i dobu jeho hledání, ale nepřináší žádný výsledek. Nechali si hrábě pro sebe, ups...

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB
Směr optimalizace je zřejmý – jednoduchý odeberte pole data ze všech indexů na dělených tabulkách. Vzhledem k našim objemům je zisk asi 1 TB/týden!

Nyní poznamenejme, že tento terabajt bylo ještě třeba nějak zaznamenat. Tedy my také disk by se nyní měl zatěžovat méně! Tento obrázek jasně ukazuje efekt získaný čištěním, kterému jsme věnovali týden:

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

#3. „Rozložení“ špičkové zátěže

Jedním z velkých problémů zatížených systémů je redundantní synchronizace některé operace, které to nevyžadují. Někdy „protože si toho nevšimli“, někdy „tak to bylo jednodušší“, ale dříve nebo později se toho musíte zbavit.

Přiblížíme si předchozí obrázek a uvidíme, že máme disk „pumpy“ pod zátěží s dvojnásobnou amplitudou mezi sousedními vzorky, což by se jasně „statisticky“ nemělo stát při takovém počtu operací:

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

Toho lze dosáhnout poměrně snadno. Již jsme zahájili monitoring téměř 1000 serverů, každé je zpracováno samostatným logickým vláknem a každé vlákno resetuje nashromážděné informace, které mají být odesílány do databáze s určitou frekvencí, asi takto:

setInterval(sendToDB, interval)

Problém zde spočívá právě v tom, že všechna vlákna začínají přibližně ve stejnou dobu, takže jejich časy odeslání se téměř vždy shodují „do puntíku“. Jejda #2...

Naštěstí je to docela snadné opravit, přidání „náhodného“ rozběhu časem:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. Ukládáme do mezipaměti, co potřebujeme

Třetí tradiční problém s vysokou zátěží je žádná mezipaměť kde je mohl být.

Například jsme umožnili analyzovat z hlediska uzlů plánu (všechny tyto Seq Scan on users), ale hned si pomyslí, že jsou z větší části stejní - zapomněli.

Ne, samozřejmě, do databáze se znovu nic nezapisuje, tím se přeruší spoušť INSERT ... ON CONFLICT DO NOTHING. Tato data se však stále dostávají do databáze a jsou nepotřebná čtení pro kontrolu konfliktu muset udělat. Jejda #3...

Rozdíl v počtu záznamů odeslaných do databáze před/po povolení ukládání do mezipaměti je zřejmý:

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

A toto je doprovodný pokles zatížení úložiště:

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

Celkem

„Terabajt za den“ zní děsivě. Pokud děláte vše správně, pak je to správné 2^40 bajtů / 86400 12.5 sekund = ~XNUMX MB/sže i šrouby IDE stolního počítače držely. 🙂

Ale vážně, i při desetinásobném „zkosení“ zátěže během dne se snadno setkáte se schopnostmi moderních SSD.

Píšeme v PostgreSQL na sublight: 1 hostitel, 1 den, 1 TB

Zdroj: www.habr.com

Přidat komentář