ProHoster > Blog > Uprava > V PostgreSQL pišemo na sublight: 1 host, 1 dan, 1TB
V PostgreSQL pišemo na sublight: 1 host, 1 dan, 1TB
Pred kratkim sem vam povedal, kako z uporabo standardnih receptov povečajte zmogljivost bralnih poizvedb SQL iz baze podatkov PostgreSQL. Danes bomo govorili o tem, kako snemanje je lahko bolj učinkovito v podatkovni zbirki brez uporabe kakršnih koli "zasukov" v konfiguraciji - preprosto s pravilno organizacijo tokov podatkov.
Sprva se je naš projekt, tako kot vsak MVP, začel pod dokaj majhno obremenitvijo - nadzor je potekal le za deset najbolj kritičnih strežnikov, vse tabele so bile razmeroma kompaktne ... Toda s časom je število nadzorovanih gostiteljev postajalo vedno večje. , in spet smo poskušali nekaj narediti z enim od mize velikosti 1.5 TB, smo ugotovili, da je bilo tako, čeprav je mogoče še naprej živeti, zelo neprijetno.
Časi so bili skoraj kot epski časi, ustrezne so bile različne različice PostgreSQL 9.x, zato je bilo treba vse particioniranje izvesti "ročno" - prek dedovanje tabel in sprožilci usmerjanje z dinamičnim EXECUTE.
Dobljena rešitev se je izkazala za dovolj univerzalno, da jo je mogoče prevesti v vse tabele:
Navedena je bila prazna nadrejena tabela "glava", ki je vse opisala potrebne indekse in sprožilce.
Zapis z vidika naročnika je bil narejen v “root” tabeli in interno z uporabo sprožilec usmerjanjaBEFORE INSERT zapis je bil "fizično" vstavljen v zahtevano rubriko. Če tega še ni bilo, smo ujeli izjemo in...
… z uporabo CREATE TABLE ... (LIKE ... INCLUDING ...) je bila ustvarjena na podlagi predloge nadrejene tabele rubriko z omejitvijo na želeni datumtako da se ob pridobivanju podatkov branje izvaja samo v njih.
PG10: prvi poskus
Toda particioniranje z dedovanjem v preteklosti ni bilo najbolj primerno za obravnavo aktivnega zapisovalnega toka ali velikega števila podrejenih particij. Na primer, lahko se spomnite, da je imel algoritem za izbiro zahtevanega razdelka kvadratna kompleksnost, da deluje s 100+ razdelki, sami razumete, kako ...
V PG10 je bila ta situacija močno optimizirana z uvedbo podpore izvorno particioniranje. Zato smo ga takoj poskusili uporabiti takoj po selitvi pomnilnika, vendar ...
Kot se je izkazalo po kopanju po priročniku, je izvorno particionirana tabela v tej različici:
ne podpira opisov kazal
ne podpira sprožilcev na njem
ne more biti nikogaršnji "potomec"
ne podpirajo INSERT ... ON CONFLICT
ne more samodejno ustvariti razdelka
Po bolečem udarcu z grabljami v čelo smo ugotovili, da brez spreminjanja aplikacije ne bo šlo, in nadaljnje raziskave odložili za šest mesecev.
PG10: druga priložnost
Tako smo začeli reševati težave, ki so se pojavile eno za drugo:
Ker sproži in ON CONFLICT Ugotovili smo, da jih tu in tam še potrebujemo, zato smo naredili vmesno fazo, da jih obdelamo proxy tabela.
Znebil se "usmerjanja" v sprožilcih – torej od EXECUTE.
Ločeno so ga vzeli ven tabela predloge z vsemi indeksitako da sploh niso prisotni v proxy tabeli.
Končno, po vsem tem, smo glavno mizo razdelili izvorno. Izdelava novega razdelka je še vedno prepuščena vesti aplikacije.
"Žaganje" slovarjev
Kot v vsakem analitičnem sistemu smo imeli tudi pri nas "dejstva" in "rezi" (slovarji). V našem primeru so v tej funkciji delovali npr. telo predloge podobne počasne poizvedbe ali besedilo same poizvedbe.
“Dejstva” so bila že dolgo razdeljena po dnevih, zato smo zastarele rubrike mirno brisali in nas niso motile (dnevniki!). Je bil pa problem s slovarji...
Ne rečem, da jih je bilo veliko, ampak približno 100 TB "dejstev" je povzročilo 2.5 TB slovarja. Iz take tabele ne moreš nič priročno izbrisati, stisniti je ne moreš v ustreznem času, pisanje vanjo pa je postopoma postalo počasnejše.
Kot slovar ... v njem mora biti vsak vnos predstavljen natanko enkrat ... in to je prav, vendar!.. Nihče nam ne preprečuje, da bi imeli za vsak dan poseben slovar! Da, to prinaša določeno redundanco, vendar omogoča:
pisati/brati hitreje zaradi manjše velikosti odseka
porabijo manj pomnilnika z delom s kompaktnejšimi indeksi
shrani manj podatkov zaradi zmožnosti hitre odstranitve zastarelih
Kot rezultat celotnega kompleksa ukrepov Obremenitev procesorja se je zmanjšala za ~30%, obremenitev diska za ~50%:
Hkrati smo v bazo še naprej zapisovali popolnoma isto stvar, le z manjšo obremenitvijo.
#2. Razvoj baze podatkov in preoblikovanje
Tako smo se odločili za to, kar imamo vsak dan ima svoj del s podatki. Pravzaprav, CHECK (dt = '2018-10-12'::date) — in tam je particijski ključ in pogoj, da zapis spada v določen razdelek.
Ker so vsa poročila v naši storitvi zgrajena v kontekstu določenega datuma, so bili indeksi zanje od »neparticioniranih časov« vseh vrst (strežnik, Datum, Predloga načrta), (strežnik, Datum, vozlišče načrta), (Datum, razred napak, strežnik), ...
Zdaj pa živijo na vsakem odseku svoje kopije vsak tak indeks ... In znotraj vsakega razdelka datum je stalnica... Izkazalo se je, da smo zdaj v vsakem takem indeksu preprosto vnesite konstanto kot eno od polj, kar poveča obseg in čas iskanja po njem, vendar ne prinese nobenega rezultata. Grablje so pustili sami sebi, up...
Smer optimizacije je očitna – preprosta odstranite datumsko polje iz vseh indeksov na pregrajenih mizah. Glede na naše količine je dobiček približno 1TB/teden!
Zdaj pa opozorimo, da je bilo treba ta terabajt še nekako posneti. Se pravi tudi mi disk naj bi zdaj manj nalagal! Ta slika jasno prikazuje učinek čiščenja, ki smo mu posvetili en teden:
#3. "Razporeditev" konične obremenitve
Ena od velikih težav obremenjenih sistemov je redundantna sinhronizacija nekatere operacije, ki tega ne zahtevajo. Včasih »ker niso opazili«, včasih »je bilo lažje«, a prej ali slej se je treba znebiti.
Povečajmo prejšnjo sliko in vidimo, da imamo disk »črpa« pod obremenitvijo z dvojno amplitudo med sosednjimi vzorci, kar pa se pri tolikšnem številu operacij očitno »statistično« ne bi smelo zgoditi:
To je zelo enostavno doseči. Monitoring smo že začeli skoraj 1000 strežnikov, vsako obdela ločena logična nit in vsaka nit ponastavi zbrane informacije, ki se pošiljajo v bazo podatkov ob določeni frekvenci, nekako takole:
setInterval(sendToDB, interval)
Težava je pri tem prav v tem, da vse niti se začnejo približno ob istem času, zato se njihovi časi pošiljanja skoraj vedno ujemajo »natančno«. Ups #2 ...
Na srečo je to zelo enostavno popraviti, dodajanje "naključnega" zagona po času:
Tretji tradicionalni problem visoke obremenitve je brez predpomnilnika kje je bi lahko biti.
Omogočili smo na primer analizo glede na vozlišča načrta (vsi ti Seq Scan on users), a takoj mislijo, da so večinoma enaki - pozabili so.
Ne, seveda se spet nič ne zapiše v bazo podatkov, to prekine sprožilec z INSERT ... ON CONFLICT DO NOTHING. Toda ti podatki še vedno dosežejo bazo podatkov in so nepotrebni branje za preverjanje konflikta narediti. Ups #3 ...
Razlika v številu zapisov, poslanih v bazo podatkov pred/po omogočenem predpomnjenju, je očitna:
In to je spremljajoči padec obremenitve pomnilnika:
Skupno
"Terabajt na dan" zveni samo strašljivo. Če narediš vse prav, potem je to samo 2^40 bajtov / 86400 sekund = ~12.5 MB/sda so zdržali celo vijaki IDE namizja. 🙂
Ampak resno, tudi z desetkratnim "nagibom" obremenitve čez dan lahko zlahka dosežete zmogljivosti sodobnih SSD-jev.