ProHoster > Blog > uprava > Pišemo u PostgreSQL na sublight: 1 host, 1 dan, 1TB
Pišemo u PostgreSQL na sublight: 1 host, 1 dan, 1TB
Nedavno sam vam rekao kako, koristeći standardne recepte povećati izvedbu SQL upita za čitanje iz PostgreSQL baze podataka. Danas ćemo govoriti o tome kako snimanje može biti učinkovitije u bazi podataka bez korištenja ikakvih "zaokreta" u konfiguraciji - jednostavnim ispravnim organiziranjem protoka podataka.
U početku, kao i svaki MVP, naš je projekt započeo pod relativno malim opterećenjem - nadgledanje se provodilo samo za deset najkritičnijih poslužitelja, sve su tablice bile relativno kompaktne... Ali kako je vrijeme prolazilo, broj nadziranih hostova postajao je sve veći. , i još jednom smo pokušali nešto učiniti s jednim od tablice veličine 1.5TB, shvatili smo da iako je moguće nastaviti ovako živjeti, bilo je vrlo nezgodno.
Vremena su bila gotovo kao epska vremena, različite verzije PostgreSQL 9.x bile su relevantne, tako da su sva particioniranja morala biti napravljena "ručno" - kroz nasljeđivanje tablice i okidači usmjeravanje s dinamičkim EXECUTE.
Pokazalo se da je dobiveno rješenje dovoljno univerzalno da se može prevesti na sve tablice:
Deklarirana je prazna roditeljska tablica "zaglavlja", koja je sve opisala potrebne indekse i okidače.
Zapis sa stajališta klijenta napravljen je u “root” tablici, te interno korištenjem okidač za usmjeravanjeBEFORE INSERT zapis je "fizički" umetnut u traženi odjeljak. Ako toga još nije bilo, uhvatili smo iznimku i...
… pomoću CREATE TABLE ... (LIKE ... INCLUDING ...) je kreiran na temelju predloška nadređene tablice odjeljak s ograničenjem željenog datumatako da se kod dohvaćanja podataka čitanje vrši samo u njemu.
PG10: prvi pokušaj
Ali particioniranje putem nasljeđivanja povijesno nije bilo prikladno za rad s aktivnim tokom pisanja ili velikim brojem podređenih particija. Na primjer, možete se sjetiti da je algoritam za odabir potrebnog odjeljka imao kvadratna složenost, da radi sa 100+ odjeljaka, sami razumijete kako...
U PG10 ova je situacija uvelike optimizirana implementacijom podrške izvorno particioniranje. Stoga smo ga odmah pokušali primijeniti odmah nakon migracije pohrane, ali...
Kao što se pokazalo nakon kopanja po priručniku, nativno particionirana tablica u ovoj verziji je:
ne podržava indeksne opise
ne podržava okidače na njemu
ne može biti ničiji "potomak"
ne podržavaju INSERT ... ON CONFLICT
ne može automatski generirati odjeljak
Dobivši bolan udarac grabljama u čelo, shvatili smo da je nemoguće bez izmjene aplikacije i odgodili daljnja istraživanja za šest mjeseci.
PG10: druga prilika
Dakle, počeli smo rješavati probleme koji su se pojavili jedan po jedan:
Budući da izaziva i ON CONFLICT Utvrdili smo da nam tu i tamo još trebaju, pa smo napravili međufazu da ih razradimo proxy tablica.
Riješili smo se "usmjeravanja" u okidačima – odnosno iz EXECUTE.
Odvojeno su ga izvadili predloška tablice sa svim indeksimatako da nisu ni prisutni u proxy tablici.
Konačno, nakon svega ovoga, glavni stol smo pregradili nativno. Izrada nove rubrike ipak je prepuštena savjesti aplikacije.
“Piljenje” rječnika
Kao i u svakom analitičkom sustavu, imali smo i mi "činjenice" i "rezovi" (rječnici). U našem slučaju u tom su svojstvu djelovali npr. tijelo predloška slične spore upite ili tekst samog upita.
“Činjenice” su već odavno podijeljene po danima, pa smo mirno brisali zastarjele rubrike i nisu nam smetale (logovi!). Ali bio je problem s rječnicima...
Ne da kažem da ih je bilo puno, ali otprilike 100TB “činjenica” rezultiralo je rječnikom od 2.5TB. Iz takve tablice ne možete jednostavno ništa izbrisati, ne možete je komprimirati u odgovarajućem vremenu, a pisanje u nju postupno je postalo sporije.
Kao u rječniku... u njemu svaka natuknica treba biti predstavljena točno jednom... i to je točno, ali!.. Nitko nam ne brani da imamo poseban rječnik za svaki dan! Da, ovo donosi određenu redundantnost, ali omogućuje:
pisati/čitati brže zbog manje veličine presjeka
troše manje memorije radom s kompaktnijim indeksima
pohraniti manje podataka zbog mogućnosti brzog uklanjanja zastarjelih
Kao rezultat cijelog kompleksa mjera CPU opterećenje smanjeno za ~30%, opterećenje diska za ~50%:
U isto vrijeme, nastavili smo pisati potpuno istu stvar u bazu podataka, samo s manje opterećenja.
#2. Evolucija baze podataka i refaktoriranje
Pa smo pristali na ono što imamo svaki dan ima svoj dio s podacima. Zapravo, CHECK (dt = '2018-10-12'::date) — i tu je ključ particioniranja i uvjet da zapis padne u određeni odjeljak.
Budući da su sva izvješća u našoj usluzi izgrađena u kontekstu određenog datuma, indeksi za njih od "neparticioniranih vremena" bili su svih vrsta (poslužitelj, datum, Predložak plana), (poslužitelj, datum, čvor plana), (datum, klasa pogreške, poslužitelj), ...
Ali sada žive na svakom dijelu svoje kopije svaki takav indeks... I unutar svakog odjeljka datum je konstanta... Ispada da smo sada u svakom takvom indeksu jednostavno unesite konstantu kao jedno od polja, što mu povećava i volumen i vrijeme traženja, ali ne donosi nikakav rezultat. Grablje su ostavili sebi, ups...
Smjer optimizacije je očit – jednostavan uklonite polje datuma iz svih indeksa na pregrađenim stolovima. S obzirom na naše količine, dobitak je otprilike 1TB/tjedan!
Napomenimo sada da je taj terabajt ipak trebalo nekako snimiti. Odnosno i mi disk bi sada trebao manje učitavati! Ova slika jasno pokazuje učinak čišćenja kojem smo posvetili tjedan dana:
#3. “Širenje” vršnog opterećenja
Jedan od velikih problema opterećenih sustava je redundantna sinkronizacija neke operacije koje to ne zahtijevaju. Ponekad "jer nisu primijetili", ponekad "bilo je lakše tako", ali prije ili kasnije toga se morate riješiti.
Povećajmo prethodnu sliku i vidimo da imamo disk “pumpa” pod opterećenjem s dvostrukom amplitudom između susjednih uzoraka, što se jasno “statistički” ne bi smjelo dogoditi s tolikim brojem operacija:
To je prilično lako postići. Već smo započeli praćenje gotovo 1000 poslužitelja, svaku obrađuje zasebna logička nit, a svaka nit resetira akumulirane informacije koje se šalju u bazu podataka određenom učestalošću, otprilike ovako:
setInterval(sendToDB, interval)
Problem ovdje leži upravo u tome što sve niti počinju približno u isto vrijeme, tako da se njihova vremena slanja gotovo uvijek podudaraju "točno". Ups #2...
Srećom, ovo je prilično lako popraviti, dodavanje "slučajnog" zaleta s vremenom:
Treći tradicionalni problem velikog opterećenja je nema predmemorije gdje je on mogao biti.
Na primjer, omogućili smo analizu u smislu čvorova plana (svi ovi Seq Scan on users), ali odmah pomisle da su, uglavnom, isti – zaboravili su.
Ne, naravno, ništa se više ne upisuje u bazu podataka, ovo prekida okidač s INSERT ... ON CONFLICT DO NOTHING. Ali ti podaci ipak dospijevaju u bazu podataka i nepotrebni su čitanje radi provjere sukoba morati učiniti. Ups #3...
Razlika u broju zapisa koji se šalju u bazu podataka prije/nakon što je omogućeno predmemoriranje je očita:
A ovo je popratni pad opterećenja pohrane:
Ukupno
"Terabajt po danu" jednostavno zvuči zastrašujuće. Ako sve učiniš kako treba, onda je ovo pravedno 2^40 bajtova / 86400 sekundi = ~12.5 MB/sda su čak i desktop IDE vijci izdržali. 🙂
Ali ozbiljno, čak i s deseterostrukim "iskrivljenjem" opterećenja tijekom dana, lako možete zadovoljiti mogućnosti modernih SSD-ova.