Hiljuti rääkisin, kuidas on võimalik kasutada tüüpilisi retsepte PostgreSQL-andmebaasist. Täna räägime sellest, kuidas saab andmete kirjutamist andmebaasi muuta efektiivsemaks, ilma et oleks vaja kasutada mingisuguseid "nuppe" konfiguratsioonis — lihtsalt korraldades andmevooge õigesti.

#1. Секционирование
Artikkel sellest, kuidas ja miks tuleks korraldada on juba olemas, siin räägime aga mõningate lähenemiste rakendamisest meie .
„Aegade alguses...”
Algul, nagu iga MVP puhul, käivitus meie projekt suhteliselt väikese koormuse all — jälgimine toimus vaid kümne tähtsama serveri jaoks, kõik tabelid olid suhteliselt kompaktsed... Kuid aeg läks edasi, jälgitavate hostide arv kasvas ja üritades jälle midagi teha ühega tabelitest, mille suurus on 1,5 TB, mõistsime, et elu nii jätkata on küll võimalik, kuid väga ebamugav.
Ajad olid peaaegu müütilised, toona olid aktuaalsed erinevad PostgreSQL 9.x variandid, seega tuli kogu sektsioonimine teha "käsitsi" — läbi tabelite pärimise ja trigerite dünaamilise suunamisega. TEOSTA.

Saadud lahendus osutus piisavalt universaalseks, et seda saaks rakendada kõigi tabelite jaoks:
- Käivitasime tühi "pealkirja" vanematabeli, millel olid kõik vajalikud indeksid ja triggarid..
- Klient saadab kirje "juurtabelisse", seestpoolt aga läbi suunava triggeri
BEFORE INSERTsaadi kirje „füüsiliselt” vajalikku sektsiooni. Kui sellist sektsiooni ei olnud — püüdsime erandi ja ... - ... abiga moodustati sektor soovitud kuupäeva piiranguga, et andmete väljavõtmise ajal toimuks lugemine ainult seal.
PG10: esimene katse
Kuid pärimise kaudu sektsioonimine ei olnud ajaloos väga kohandatud aktiivse kirjutusvoo või suure arvu alamsektsioonide jaoks. Näiteks võib meenutada, et vajaliku sektsiooni valimise algoritm oli ruudulise keerukusega, mis 100+ sektsiooni puhul töötab, nagu aru saate....
PG10-s optimeeriti seda olukorda oluliselt, rakendades toe . Seetõttu proovisime seda kohe rakendada pärast salvestuse migreerimist, kuid…
Kuidas selgus pärast manuaali uurimist, ei toeta selle versiooni kohandatud sektsioneeritud tabel:
- ei toeta indeksite kirjeldust
- ei toeta sellel triggerite olemasolu
- ei saa olla kellelegi «järeltulijaks»
- ei toeta
INSERT ... ON CONFLICT - ei oska automaatselt sektsiooni luua
Pärast peaga vastu hambaid saamist mõistsime, et ilma rakenduse modifitseerimiseta ei saa ikka hakkama ja lükkasime edasi edasised uuringud kuueks kuuks.
PG10: teine võimalus
Nii hakkasime probleeme järk-järgult lahendama:
- Kuna triggerid ja
ON CONFLICTolid meil siiski vajalikud, koostasime vahendustabeli proxy-tabeli. - Vabastasime end «routimisest» triggerites—ehk siis
TEOSTA. - Tootsime eraldi šabloontabeli kõigi indeksitega, et need isegi proxy-tabelis puuduksid.

Lõpuks, pärast kogu seda teed, saime kohandatud sektsiooniga peamise tabeli lõplikult külge. Uue sektsiooni loomine jäi rakenduse vastutada.
«Laitseme» sõnastikke
Nagu igas analüüsisüsteemis, olid meil ka «faktid» ja «lõike» (sõnastikud). Meie puhul oli näideteks näiteks ühtlastest aeglastest päringutest või päringu enda tekstist.
«Faktid» olid meil päevaste järgi juba ammu sektsioneeritud, seega eemaldasin rahulikult aegunud osad, nad ei seganud meid (logid ju!). Kuid sõnastikega tekkis probleeme…
Ei saa öelda, et neid oleks väga palju olnud, kuid umbes 100 TB «faktide» kohta saime sõnastiku suuruseks 2,5 TB.. Sellisest tabelist ei olnud mugav midagi eemaldada, aeglaselt ja tõhusalt, ning isegi andmete sisestamine muutus järjest aeglasemaks.
Nagu ka sõnastik… igal salvestusel peaks olema täpselt üks kord olemas… ja see on õige, kuid! Keegi ei takista meid omamast erinevat sõnastikku iga päeva kohta! Jah, see toob kaasa teatud ülemäärasuse, kuid võimaldab:
- kirjutada/looda kiiremini väiksema osade suuruse tõttu
- tarbida vähem mälu kompaktsemate indeksitega töötamise tõttu
- hoida vähem andmeid aegunud kiire eemaldamise võimaluse tõttu
Kogu tegevuste komplekti tulemusena CPU koormus vähenes ~30% ja kettakoormus ~50%:

Samas jätkasime andmebaasi kirjutamist täpselt sama, lihtsalt väiksema koormusega.
#2. Эволюция и рефакторинг БД
Nii et, me jõudsime järeldusele, et meil on iga päeva jaoks oma sektsioon andmetega. Tegelikult, CHECK (dt = '2018-10-12'::date) — ja see on sektsioneerimise võti ning tingimus, mille kohaselt kirje satuvad konkreetse sektsiooni.
Kuna kõik meie teenuse aruanded koostatakse konkreetse kuupäeva lõikes, siis olid ka seadmed juba "mitte-sektsioneeritud aegadel" kõik tüüpi (Server, Kuupäev, Plaani mall), (Server, Kuupäev, Plaani sõlm), (Kuupäev, Veaklass, Server),…
Aga nüüd elavad igas sektsioonis oma eksemplarid iga sellise seadme kohta... Ja iga sektsiooni raames kuupäev on konstant… Tundub, et nüüd kirjutame igasse sellesse seadmesse tüüpiliselt konstant ühe väljana, mis tõstab nii selle mahtu kui ka otsimise aega, kuid ei too mingeid tulemusi. Jätame endale ise probleemi, ups…

Optimeerimise suund on ilmselge — lihtsalt eemaldame väljad kuupäeva kõikidest seadmetest sektsioneeritud tabelites. Meie mahtude puhul on kokkuhoid umbes 1TB/nädalas!
Ja nüüd märkame, et see terabait tuleb ka kuidagi salvestada. See tähendab, et me peame nüüd kettalt vähem koormust võtma! Sellel pildil on hästi nähtav saadud efekt läbi viidud puhastusest, millele pühendasime nädala:

#3. «Размазываем» пиковую нагрузку
Üks suur probleem koormatud süsteemides on ülemäärane sünkroonimine mõnede toimingute osas, mis seda ei nõua. Mõnikord "sest ei märganud", mõnikord "niimoodi oli lihtsam", kuid varem või hiljem tuleb sellest vabaneda.
Suurendame eelmist pilti — ja näeme, et meie ketas "tõmbab" koormust kahekordse amplituudiga kõrvuti olevate mõõtmiste vahel, mis "statistiliselt" ei tohiks olla sellise tegevuste arvu juures:

Seda on piisavalt lihtne saavutada. Meie jälgimisel oli juba peaaegu 1000 serverit, igaüks töödeldakse eraldi loogilise vooguna ja iga voog edastab kogunenud teabe andmebaasi teatud perioodilisusega, umbes nii:
setInterval(sendToDB, interval)Probleem peitub just selles, et kõik voogud käivitatakse peaaegu samal ajal, seega nende saatmise hetked langevad peaaegu alati kokku "punkti". Ups nr 2…
Õnneks on seda piisavalt lihtne parandada, lisades "juhuslikku" hajutust ajaliselt:
setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))#4. Кэшируем, что нужно можно
Kolmas traditsiooniline probleem kõrge koormusega — vähem koodi seal, kus see saaksin peaks olema.
Näiteks oleme teinud võimaluse analüüsida plaanide sõlmede lõikes (kõik need Seq Skannimine kasutajate peal), kuid kohe mõelda, et need on massiliselt sarnased — unustasid.
Ei, muidugi ei kirjutata andmebaasi midagi uuesti, see katkestab triggereid INSERT ... ON CONFLICT DO NOTHING. Kuid andmed jõuavad andmebaasi ikka, ning lisaks sellele on üleliigne lugemine konflikti kontrollimiseks tuleb ikkagi teha. Ups №3...
Erinevus andmebaasi saadetud kirjeid arvestades enne/peale vahemälude aktiveerimist — on ilmne:

Ja see — kaasnev koormuse langus salvestis:

Kokkuvõttes
«Terabaidid-päevas» kõlab hirmuäratavalt. Kui teete kõik õigesti, siis on see vaid 2^40 baiti / 86400 sekundit = ~12.5MB/s, mida suutsid isegi lauaarvuti IDE-kettad. 🙂
Aga tõsiselt rääkides, isegi kümnekordse koormuse „kaldumise“ korral päevas, saate rahulikult jääda tänapäevaste SSD-de võimaluste piiresse.

Allikas: habr.com
