PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

A jelentés bemutat néhány olyan megközelítést, amely lehetővé teszi figyelemmel kíséri az SQL-lekérdezések teljesítményét, ha naponta több millió van, és több száz felügyelt PostgreSQL-kiszolgáló létezik.

Milyen technikai megoldások tesznek lehetővé ekkora mennyiségű információ hatékony feldolgozását, és hogyan könnyíti meg ez egy átlagos fejlesztő életét?


Kit érdekel? konkrét problémák elemzése és különféle optimalizálási technikák SQL-lekérdezések és tipikus DBA-problémák megoldása a PostgreSQL-ben – ezt is megteheti cikksorozatot olvasni ebben a témában.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)
A nevem Kirill Borovikov, képviselem Tensor cég. Konkrétan az adatbázisokkal való munkára szakosodtam cégünknél.

Ma elmondom, hogyan optimalizáljuk a lekérdezéseket, amikor nem egyetlen lekérdezés teljesítményét kell „szétszedni”, hanem tömegesen kell megoldani a problémát. Amikor milliónyi kérés van, és meg kell találnia néhányat a megoldás megközelítései ezt a nagy problémát.

Általában a Tensor egymillió ügyfelünk számára az A VLSI a mi alkalmazásunk: vállalati közösségi hálózat, megoldások videokommunikációra, belső és külső dokumentumáramlásra, könyvelési rendszerek könyvelésre és raktárakra,... Vagyis egy ilyen „megakombináció” az integrált ügyvitelhez, amelyben több mint 100 különböző belső projektek.

Annak érdekében, hogy mindegyik normálisan működjön és fejlődjön, országszerte 10 fejlesztési központunk van, amelyekben több is található 1000 fejlesztő.

2008 óta dolgozunk a PostgreSQL-lel, és nagy mennyiségben halmoztunk fel abból, amit feldolgozunk - ügyféladatokat, statisztikai, elemzési, külső információs rendszerekből származó adatokat - több mint 400 TB. Csak körülbelül 250 szerver van termelésben, és összesen körülbelül 1000 adatbázis-kiszolgálót figyelünk.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Az SQL deklaratív nyelv. Nem azt írja le, hogy „hogyan” kellene valaminek működnie, hanem azt, hogy „mit” szeretne elérni. A DBMS jobban tudja, hogyan kell JOIN-t létrehozni – hogyan kell összekötni a táblákat, milyen feltételeket kell szabni, mi megy át az indexen, mi nem...

Egyes DBMS-ek elfogadják a következő tippeket: „Nem, kösse össze ezt a két táblát egy ilyen és olyan sorban”, de a PostgreSQL nem tudja ezt megtenni. Ez a vezető fejlesztők tudatos álláspontja: „Inkább befejezzük a lekérdezésoptimalizálót, mintsem engedjük meg a fejlesztőknek, hogy valamilyen tippet használjanak.”

De annak ellenére, hogy a PostgreSQL nem engedi, hogy a „külső” irányítsa magát, tökéletesen megengedi látni, mi történik benneamikor futtatja a lekérdezést, és hol vannak problémák.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Általánosságban elmondható, hogy általában milyen klasszikus problémákkal találkoznak a fejlesztők [egy DBA-hoz]? „Itt teljesítettük a kérést, és nálunk minden lassan megy, minden lóg, valami történik... Valami baj!”

Az okok szinte mindig ugyanazok:

  • nem hatékony lekérdezési algoritmus
    Fejlesztő: "Most adok neki 10 táblát SQL-ben a JOIN-on keresztül..." - és arra számít, hogy a feltételei csodával határos módon hatékonyan "feloldódnak", és mindent gyorsan megkap. De csodák nem történnek, és minden ilyen változékonyságú rendszer (10 tábla egy FROM-ban) mindig ad valamilyen hibát. [cikk]
  • irreleváns statisztikák
    Ez a pont kifejezetten a PostgreSQL-re vonatkozik, amikor egy nagy adatkészletet „öntöttél” a szerverre, kérelmet küldesz, és az „sexcantitálja” a táblagépedet. Ugyanis tegnap 10 rekord volt benne, ma pedig 10 millió, de a PostgreSQL még nem tud erről, és ezt el kell mondanunk. [cikk]
  • "csatlakoztassa" az erőforrásokat
    Nagy és erősen terhelt adatbázist telepített egy gyenge kiszolgálóra, amely nem rendelkezik elegendő lemezzel, memóriával vagy processzorral. És ez minden... Valahol van egy teljesítményplafon, amely fölé már nem lehet ugrani.
  • blokkolás
    Ez egy nehéz pont, de leginkább a különféle módosító lekérdezésekre vonatkoznak (INSERT, UPDATE, DELETE) - ez egy külön nagy téma.

Tervet készíteni

...És minden másért mi kell egy terv! Látnunk kell, mi történik a szerveren belül.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

A PostgreSQL lekérdezés-végrehajtási terve a lekérdezés-végrehajtási algoritmus fája szöveges megjelenítésben. A tervező elemzése eredményeként pontosan az algoritmus bizonyult a leghatékonyabbnak.

Minden facsomópont egy művelet: adatok lekérése egy táblából vagy indexből, bittérkép készítése, két tábla összekapcsolása, összekapcsolás, metszés vagy kijelölések kizárása. A lekérdezés végrehajtása magában foglalja a fa csomópontjain való átjárást.

A lekérdezési terv beszerzéséhez a legegyszerűbb az utasítás végrehajtása EXPLAIN. Az összes valós attribútum eléréséhez, vagyis egy lekérdezés tényleges végrehajtásához az alapon - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

A rossz rész: amikor futtatod, "itt és most" történik, így csak helyi hibakeresésre alkalmas. Ha veszünk egy erősen terhelt szervert, amely erős adatáramlás alatt áll, és ezt látja: „Ó! Itt van egy lassú végrehajtásXia kérés." Fél órája, egy órája – miközben futott, és megkapta ezt a kérést a naplókból, majd visszahozta a szerverre, a teljes adatkészlete és statisztikái megváltoztak. Futtatja a hibakereséshez – és gyorsan fut! És nem érti, miért, miért ez volt lassan.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Annak érdekében, hogy megértsük, mi történt pontosan abban a pillanatban, amikor a kérést végrehajtották a szerveren, okos emberek írtak auto_explain modul. Szinte az összes leggyakoribb PostgreSQL disztribúcióban megtalálható, és egyszerűen aktiválható a konfigurációs fájlban.

Ha észreveszi, hogy egy kérés tovább fut, mint az Ön által megadott korlát, akkor megteszi „pillanatképet” a kérés tervéről, és összeírja őket a naplóban.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Most úgy tűnik, minden rendben van, odamegyünk a rönkhöz, és meglátjuk... [szöveg lábtörlő]. De nem mondhatunk róla semmit, csak azt, hogy ez egy kiváló terv, mert 11 ms-ig tartott a végrehajtása.

Úgy tűnik, minden rendben van – de semmi sem világos, hogy valójában mi is történt. Az általános időn kívül nem igazán látunk semmit. Mert egy ilyen egyszerű szöveges „bárány” nézése általában nem vizuális.

De még ha nem is nyilvánvaló, még ha kényelmetlen is, vannak alapvetőbb problémák:

  • A csomópont jelzi a teljes részfa erőforrásainak összege alatta. Vagyis nem csak azt lehet megtudni, hogy mennyi időt fordítottak erre az Index Scanre, ha van alatta valamilyen beágyazott feltétel. Dinamikusan meg kell néznünk, hogy vannak-e benne „gyermekek” és feltételes változók, CTE-k – és mindezt ki kell vonnunk „elménkben”.
  • Második pont: a csomóponton feltüntetett idő a egyetlen csomópont végrehajtási ideje. Ha ezt a csomópontot például a táblarekordokon való többszöri cikluson keresztül hajtották végre, akkor a ciklusok száma – ennek a csomópontnak a ciklusai – megnő a tervben. De maga az atomi végrehajtási idő a terv szempontjából változatlan marad. Vagyis annak megértéséhez, hogy összesen mennyi ideig hajtották végre ezt a csomópontot, meg kell szoroznia egy dolgot a másikkal - ismét „a fejében”.

Ilyen helyzetekben értse meg: „Ki a leggyengébb láncszem?” szinte lehetetlen. Ezért maguk a fejlesztők is azt írják a „kézikönyvben”, hogy "A terv megértése művészet, amit tanulni kell, meg kell tapasztalni...".

De 1000 fejlesztőnk van, és ezt a tapasztalatot nem lehet mindegyiküknek átadni. Én, te, ő tudja, de valaki ott már nem tudja. Lehet, hogy tanul, vagy nem, de most dolgoznia kell – és honnan szerezné ezt a tapasztalatot?

Tervezze meg a vizualizációt

Ezért rájöttünk, hogy ezeknek a problémáknak a kezeléséhez szükségünk van a terv jó vizualizációja. [cikk]

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Először „átmentünk a piacon” – nézzük meg az interneten, hogy mi is létezik.

De kiderült, hogy nagyon kevés viszonylag „élő” megoldás van, amely többé-kevésbé fejlődik - szó szerint csak egy: magyarázat.depesz.com írta Hubert Lubaczewski. Amikor a „hírcsatorna” mezőbe beírja a terv szöveges ábrázolását, megjelenik egy táblázat az elemzett adatokkal:

  • a csomópont saját feldolgozási ideje
  • a teljes részfa teljes ideje
  • a statisztikailag elvárható rekordok száma
  • maga a csomóponttest

Ez a szolgáltatás linkarchívum megosztására is képes. Bedobtad a tervedet, és azt mondtad: „Hé, Vasya, itt egy link, valami nincs rendben.”

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

De vannak apró problémák is.

Először is, hatalmas mennyiségű „copy-paste”. Fogsz egy darabot a rönkből, odadugod, és újra és újra.

Másodszor, a nincs elemzés a beolvasott adatok mennyiségéről — ugyanazok a pufferek, mint a kimenet EXPLAIN (ANALYZE, BUFFERS), itt nem látjuk. Egyszerűen nem tudja, hogyan kell szétszedni, megérteni és dolgozni velük. Ha sok adatot olvas, és rájön, hogy esetleg rosszul osztotta ki a lemezt és a memória-gyorsítótárat, ez az információ nagyon fontos.

A harmadik negatív pont a projekt nagyon gyenge fejlődése. A commitok nagyon kicsik, jó, ha félévente egyszer, és a kód Perlben van.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

De ez mind „szöveg”, ezzel valahogy együtt is tudtunk élni, de van, ami nagyon eltántorított minket ettől a szolgáltatástól. Ezek a Common Table Expression (CTE) és a különféle dinamikus csomópontok, például az InitPlan/SubPlan elemzésének hibái.

Ha hisz ennek a képnek, akkor az egyes csomópontok teljes végrehajtási ideje nagyobb, mint a teljes kérés teljes végrehajtási ideje. Ez egyszerű - ennek a CTE-nek a generálási idejét nem vontuk le a CTE Scan csomópontból. Ezért már nem tudjuk a helyes választ arra, hogy maga a CTE-vizsgálat mennyi ideig tartott.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Aztán rájöttünk, hogy ideje megírni a sajátunkat – hurrá! Minden fejlesztő azt mondja: „Most megírjuk a sajátunkat, ez nagyon egyszerű lesz!”

A webszolgáltatásokra jellemző köteget vettünk: egy Node.js + Express alapú magot, Bootstrap-et és D3.js-t használtunk a gyönyörű diagramokhoz. És az elvárásaink teljes mértékben beigazolódtak - 2 hét alatt megkaptuk az első prototípust:

  • egyéni terv elemző
    Ez azt jelenti, hogy mostantól bármilyen tervet elemezni tudunk a PostgreSQL által generált tervekből.
  • a dinamikus csomópontok helyes elemzése - CTE Scan, InitPlan, SubPlan
  • puffereloszlás elemzése - hol a memóriából, hol a helyi gyorsítótárból, hol a lemezről olvassa be az adatlapokat
  • világosságot kapott
    Hogy mindezt ne „ássuk” a naplóba, hanem rögtön a „leggyengébb láncszemet” lássuk a képen.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Valami ilyesmit kaptunk, szintaktikai kiemeléssel. De általában fejlesztőink már nem a terv teljes ábrázolásával dolgoznak, hanem egy rövidebbel. Hiszen már az összes számot elemeltük és dobtuk jobbra-balra, középen pedig csak az első sort hagytuk meg, hogy milyen csomópontról van szó: CTE Scan, CTE generálás vagy valamilyen előjel szerint Seq Scan.

Ezt a rövidített ábrázolást nevezzük terv sablon.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Mi más lenne kényelmes? Kényelmes lenne megnézni, hogy a teljes időnkből melyik csomópont mekkora hányadát szánjuk – és csak „ragasztjuk” oldalra kördiagram.

Rámutatunk a csomópontra, és látjuk – kiderül, hogy a Seq Scan a teljes idő kevesebb mint egynegyedét vette igénybe, a maradék 3/4-et pedig a CTE Scan. Borzalom! Ez egy kis megjegyzés a CTE Scan „tűzsebességéről”, ha aktívan használja őket a lekérdezések során. Nem túl gyorsak – még a rendszeres táblázatszkennelésnél is rosszabbak. [cikk] [cikk]

De általában az ilyen diagramok érdekesebbek, összetettebbek, ha azonnal rámutatunk egy szegmensre, és például azt látjuk, hogy az esetek több mint felében valamilyen Seq Scan „evett”. Sőt, volt benne valami szűrő, sok rekordot eldobtak eszerint... Ezt a képet direkt odadobhatod a fejlesztőnek, és azt mondod: „Vasya, itt neked minden rossz! Találd ki, nézd – valami nincs rendben!”

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Természetesen volt néhány „gereblye” is.

Az első dolog, amivel szembesültünk, a kerekítési probléma volt. A tervben minden egyes csomópont idejét 1 μs pontossággal jelezzük. És amikor a csomóponti ciklusok száma meghaladja például az 1000-et - a PostgreSQL végrehajtása után „pontosságon belül” osztva, akkor a visszaszámításkor a teljes időt kapjuk „valahol 0.95 ms és 1.05 ms között”. Ha a számlálás mikroszekundumokra megy, az rendben van, de amikor már [milli]másodperc, akkor ezt az információt figyelembe kell venni, amikor az erőforrásokat a „ki mennyit fogyasztott” terv csomópontjaihoz „leválaszt”.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

A második, összetettebb pont az erőforrások (azok a pufferek) elosztása a dinamikus csomópontok között. Ez a prototípus első 2 hetébe és további 4 hétbe került nekünk.

Nagyon könnyű ilyen problémát kapni – csinálunk egy CTE-t, és állítólag olvasunk benne valamit. Valójában a PostgreSQL „okos”, és közvetlenül nem olvas semmit. Aztán vesszük róla az első lemezt, és hozzá a százelsőt ugyanattól a CTE-től.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Megnézzük a tervet, és megértjük - furcsa, 3 puffert (adatoldalt) „fogyasztottunk el” a Seq Scanben, még 1-et a CTE Scan-ben, és további 2-t a második CTE Scan-ben. Vagyis ha egyszerűen összeadunk mindent, akkor 6-ot kapunk, de a tabletről csak 3-at olvasunk ki! A CTE Scan nem olvas semmit sehonnan, hanem közvetlenül a folyamatmemóriával működik. Vagyis itt valami egyértelműen nincs rendben!

Tulajdonképpen itt van az a 3 oldal adat, amit a Seq Scan-től kértek, először 1 kérte az 1. CTE Scan-t, majd a 2., és még 2 olvasott fel neki. Vagyis összesen 3 oldal volt olvasott adat, nem 6.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

És ez a kép elvezetett bennünket annak megértéséhez, hogy a terv végrehajtása már nem egy fa, hanem egyszerűen valamiféle aciklikus gráf. És kaptunk egy ilyen diagramot, hogy megértsük, „mi honnan jött először”. Vagyis itt létrehoztunk egy CTE-t a pg_class-ból, és kétszer kértük, és szinte minden időnket az ágon töltöttük, amikor 2. alkalommal kértük. Nyilvánvaló, hogy a 101. bejegyzés elolvasása sokkal drágább, mint az 1. bejegyzés táblagépről történő elolvasása.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Egy darabig kifújtuk a levegőt. Azt mondták: „Most, Neo, tudod a kung-fut! Tapasztalataink most közvetlenül az Ön képernyőjén jelennek meg. Most már használhatod." [cikk]

Naplókonszolidáció

1000 fejlesztőnk megkönnyebbülten felsóhajtott. De megértettük, hogy csak több száz „harci” szerverünk van, és ez a „copy-paste” a fejlesztők részéről egyáltalán nem kényelmes. Rájöttünk, hogy magunknak kell összegyűjtenünk.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Általában van egy szabványos modul, amely képes statisztikákat gyűjteni, de azt is aktiválni kell a konfigurációban - ez modul pg_stat_statements. De nekünk nem jött be.

Először is ugyanazokhoz a lekérdezésekhez rendel hozzá különböző sémákat használva ugyanazon az adatbázison belül különböző lekérdezési azonosítók. Vagyis ha először megteszi SET search_path = '01'; SELECT * FROM user LIMIT 1;majd aztán SET search_path = '02'; és ugyanaz a kérés, akkor ennek a modulnak a statisztikája különböző rekordokat fog tartalmazni, és nem fogok tudni általános statisztikát gyűjteni kifejezetten ezzel a kérési profillal összefüggésben, a sémák figyelembevétele nélkül.

A második pont, ami megakadályozott bennünket abban, hogy ezt használjuk tervek hiánya. Vagyis nincs terv, csak maga a kérés van. Látjuk, mi lassult, de nem értjük, miért. És itt visszatérünk a gyorsan változó adathalmaz problémájához.

És az utolsó pillanat - "tények" hiánya. Vagyis nem lehet megszólítani egy adott lekérdezés-végrehajtási példányt – nincs ilyen, csak összesített statisztikák vannak. Bár lehet ezzel dolgozni, csak nagyon nehéz.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Ezért úgy döntöttünk, hogy harcolunk a copy-paste ellen, és elkezdtünk írni gyűjtő.

A gyűjtő SSH-n keresztül csatlakozik, tanúsítvány segítségével biztonságos kapcsolatot létesít a szerverrel az adatbázissal, és tail -F „ragaszkodik” hozzá a naplófájlban. Tehát ezen az ülésen a teljes naplófájl teljes „tükrét” kapjuk, amelyet a szerver generál. Maga a szerver terhelése minimális, mert ott nem elemezünk semmit, csak tükrözzük a forgalmat.

Mivel a felületet már Node.js-ben kezdtük megírni, folytattuk a gyűjtő írását benne. Ez a technológia pedig igazolta magát, mert nagyon kényelmes a JavaScript használata a gyengén formázott szöveges adatokkal való munkavégzéshez, ami a napló. Maga a Node.js infrastruktúra pedig háttérplatformként lehetővé teszi a hálózati kapcsolatokkal, sőt bármilyen adatfolyammal való egyszerű és kényelmes munkát.

Ennek megfelelően két kapcsolatot „feszítünk ki”: az elsőt, hogy magát a naplót „hallgassuk”, és magunkhoz vigyük, a másodikat pedig azért, hogy rendszeresen megkérdezzük a bázist. „De a napló azt mutatja, hogy a 123-as azonosítójú jel le van tiltva”, de ez nem jelent semmit a fejlesztő számára, és jó lenne megkérdezni az adatbázist: „Mi az az OID = 123?” És ezért rendszeresen megkérdezzük a bázist, amit még nem tudunk magunkról.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

„Csak egy dolgot nem vettél figyelembe, van egy elefántszerű méhfaj!...” Ezt a rendszert akkor kezdtük el fejleszteni, amikor 10 szervert akartunk figyelni. Megértésünk szerint a legkritikusabb, ahol olyan problémák merültek fel, amelyeket nehéz volt kezelni. De az első negyedévben százat kaptunk a monitorozásért – mert működött a rendszer, mindenki akarta, mindenki kényelmesen el volt töltve.

Mindezt össze kell adni, nagy és aktív az adatáramlás. Valójában, amit figyelünk, amivel tudunk foglalkozni, azt használjuk. A PostgreSQL-t adattárolóként is használjuk. És semmivel sem lehet gyorsabban adatokat „önteni”, mint az operátor COPY Még nem.

De az adatok egyszerű „öntése” nem igazán a mi technológiánk. Mert ha körülbelül 50 ezer kérés van másodpercenként száz szerveren, akkor ez napi 100-150 GB naplót generál. Ezért óvatosan kellett „levágni” az alapot.

Először is megtettük napok szerinti felosztás, mert nagyjából senkit nem érdekel a napok közötti összefüggés. Mit változtat azon, hogy mi volt tegnap, ha ma este kiadta az alkalmazás új verzióját – és máris néhány új statisztika.

Másodszor, megtanultuk (kénytelenek voltunk) nagyon-nagyon gyorsan írható a használatával COPY. Vagyis nem csak COPYmert ő gyorsabb annál INSERT, és még gyorsabban.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

A harmadik pont - muszáj volt a triggereket, illetve az idegen kulcsokat elhagyni. Azaz egyáltalán nincs referenciális integritásunk. Mert ha van egy táblád, amelyben van egy pár FK, és az adatbázis szerkezetében azt mondod, hogy „itt van egy naplórekord, amelyre az FK hivatkozik például egy rekordcsoportra”, akkor a beszúráskor a PostgreSQL nem marad más hátra, mint hogyan vegye és tegye őszintén SELECT 1 FROM master_fk1_table WHERE ... azzal az azonosítóval, amelyet beszúrni próbál - csak azért, hogy ellenőrizze, hogy ez a rekord ott van-e, és nem „töri le” ezt az idegen kulcsot a beszúrásával.

A céltábla és indexei egyetlen rekordja helyett azt a további előnyt kapjuk, hogy az összes hivatkozott táblát beolvassuk. De erre egyáltalán nincs szükségünk - az a feladatunk, hogy a lehető legtöbbet és a lehető leggyorsabban rögzítsük a legkisebb terheléssel. Szóval FK - le!

A következő pont az összesítés és a hash. Kezdetben implementáltuk őket az adatbázisba - végül is kényelmes, ha egy rekord megérkezik, azonnal megteheti valamilyen táblagépen "plusz egy" közvetlenül a kioldóban. Nos, ez kényelmes, de ugyanaz a rossz - beszúr egy rekordot, de kénytelen mást olvasni és írni egy másik táblázatból. Sőt, nemcsak olvasol és írsz, hanem minden alkalommal meg is teszed.

Most képzelje el, hogy van egy táblázata, amelyben egyszerűen megszámolja az adott gazdagépen áthaladó kérések számát: +1, +1, +1, ..., +1. És elvileg nincs szüksége erre - ez mind lehetséges összeg a gyűjtő memóriájában és egy mozdulattal elküldi az adatbázisba +10.

Igen, bizonyos problémák esetén a logikai integritásod „szétromolhat”, de ez szinte irreális eset - mert van egy normál szervered, van akkumulátor a vezérlőben, van tranzakciós naplód, naplód a fájlrendszer... Általában nem éri meg. A triggerek/FK futtatásából származó termelékenységvesztés nem éri meg a felmerülő költségeket.

Ugyanez a helyzet a hash-el. Egy bizonyos kérés száll hozzád, abból kiszámolsz egy bizonyos azonosítót az adatbázisban, beírod az adatbázisba, majd elmondod mindenkinek. Minden rendben van addig, amíg a felvételkor nem érkezik hozzád egy másik személy, aki ugyanazt akarja felvenni – és leblokkolsz, és ez már rossz. Ezért, ha néhány azonosító generálását át tudja vinni a kliensre (az adatbázishoz képest), akkor jobb ezt megtenni.

Nekünk pont tökéletes volt a szövegből MD5-öt használni - kérés, terv, sablon,... A gyűjtőoldalon kiszámoljuk, és a kész azonosítót „beöntjük” az adatbázisba. Az MD5 hossza és a napi particionálás lehetővé teszi, hogy ne aggódjunk az esetleges ütközések miatt.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

De ahhoz, hogy mindezt gyorsan rögzíthessük, magát a felvételi eljárást kellett módosítanunk.

Általában hogyan írsz adatokat? Van valami adatkészletünk, több táblára bontjuk, majd MÁSOLÁS - először az elsőbe, majd a másodikba, a harmadikba... Kellemetlen, mert úgy tűnik, három lépésben egy adatfolyamot írunk. szekvenciálisan. Kellemetlen. Meg lehet csinálni gyorsabban? Tud!

Ehhez elég csak ezeket az áramlásokat egymással párhuzamosan bontani. Kiderült, hogy vannak hibáink, kéréseink, sablonjaink, blokkolásaink, ... külön szálakban repülnek – és mindezt párhuzamosan írjuk. Elég ehhez tartson folyamatosan nyitva egy COPY csatornát minden egyes céltáblához.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Vagyis a gyűjtőnél mindig van egy patak, amibe beírhatom a szükséges adatokat. De hogy az adatbázis lássa ezeket az adatokat, és valaki ne ragadjon le az adatok kiírására várva, A COPY-t bizonyos időközönként meg kell szakítani. Számunkra a leghatékonyabb időszak körülbelül 100 ms volt - bezárjuk és azonnal kinyitjuk ugyanahhoz az asztalhoz. És ha néhány csúcs alatt nem elég egy áramlás, akkor egy bizonyos határig összevonjuk.

Ezen túlmenően rájöttünk, hogy egy ilyen terhelési profil esetén rossz az összesítés, amikor a rekordokat kötegekben gyűjtik. A klasszikus gonosz az INSERT ... VALUES és további 1000 rekord. Mert ezen a ponton van egy írási csúcs az adathordozón, és mindenki más, aki megpróbál valamit írni a lemezre, várni fog.

Hogy megszabaduljon az ilyen rendellenességektől, egyszerűen ne összesítsen semmit, egyáltalán ne puffereljen. És ha megtörténik a pufferelés a lemezre (szerencsére a Node.js-ben található Stream API lehetővé teszi, hogy megtudja), halassza el a csatlakozást. Ha olyan eseményt kap, hogy az ismét ingyenes, írjon neki a felhalmozott sorból. És amíg elfoglalt, vedd ki a következő ingyeneset a medencéből, és írj neki.

Az adatrögzítés ezen megközelítésének bevezetése előtt körülbelül 4K írási műveletünk volt, és így 4-szeresére csökkentettük a terhelést. Most újabb hatszorosára nőttek az új felügyelt adatbázisoknak köszönhetően - akár 6 MB/s-ig. Most pedig az elmúlt 100 hónap naplóit körülbelül 3-10 TB-os mennyiségben tároljuk, remélve, hogy mindössze három hónapon belül bármelyik fejlesztő képes lesz megoldani bármilyen problémát.

Megértjük a problémákat

De mindezen adatok összegyűjtése jó, hasznos, releváns, de nem elég – meg kell érteni. Mert ezek több millió különböző terv naponta.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

De a milliók kezelhetetlenek, először „kicsit” kell csinálni. És mindenekelőtt el kell döntenie, hogyan szervezi meg ezt a „kisebb” dolgot.

Három kulcsfontosságú pontot azonosítottunk:

  • aki elküldte ezt a kérést
    Vagyis melyik alkalmazásból „érkezett”: webes felületről, háttérrendszerről, fizetési rendszerről vagy valami másról.
  • ahol ez történt
    Konkrétan melyik szerveren? Mert ha több szerver van egy alkalmazás alatt, és hirtelen az egyik „hülye lesz” (mert „elromlott a lemez”, „kiszivárgott a memória”, valami más probléma), akkor konkrétan a szervert kell megszólítani.
  • mint a probléma így vagy úgy megnyilvánult

Ahhoz, hogy megértsük, „ki” küldte nekünk a kérést, egy szabványos eszközt használunk – beállítunk egy munkamenet-változót: SET application_name = '{bl-host}:{bl-method}'; — elküldjük annak az üzleti logikai gazdagépnek a nevét, amelyről a kérés érkezik, és az azt kezdeményező metódus vagy alkalmazás nevét.

Miután átadtuk a kérés „tulajdonosát”, ki kell adni a naplóba - ehhez konfiguráljuk a változót log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Az érdeklődőknek talán nézd meg a kézikönyvbenmit jelent ez az egész. Kiderül, hogy a naplóban ezt látjuk:

  • idő
  • folyamat- és tranzakcióazonosítók
  • adatbázis név
  • A kérelmet küldő személy IP-címe
  • és a módszer neve

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Aztán rájöttünk, hogy nem túl érdekes egy kérés korrelációját nézni a különböző szerverek között. Nem gyakran fordul elő olyan helyzet, hogy egy-egy alkalmazás itt-ott egyformán elrontja. De még ha ugyanaz is, nézze meg bármelyik szervert.

Szóval itt a vágás "egy szerver - egy nap" minden elemzéshez elégnek bizonyult számunkra.

Az első elemző rész ugyanaz "minta" - a terv bemutatásának rövidített formája, minden számszerű mutatótól megtisztítva. A második vágás az alkalmazás vagy módszer, a harmadik pedig az a konkrét tervcsomópont, amely problémát okozott nekünk.

Amikor konkrét példányokról áttértünk a sablonokra, egyszerre két előnnyel is gazdagodtunk:

  • az elemzéshez szükséges objektumok számának többszörös csökkentése
    A problémát már nem lekérdezések vagy tervek ezrei, hanem több tucat sablon alapján kell elemeznünk.
  • Idővonal
    Vagyis egy bizonyos szakaszon belül összefoglalva a „tényeket” megjelenítheti azok napközbeni megjelenését. És itt megértheti, hogy ha van valamilyen mintája, ami például óránként egyszer történik, de naponta egyszer meg kell történnie, akkor át kell gondolnia, hogy mi történt rosszul - ki okozta és miért, talán itt kell lennie nem kellene. Ez egy másik nem numerikus, tisztán vizuális elemzési módszer.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

A fennmaradó módszerek azokon a mutatókon alapulnak, amelyeket a tervből kinyerünk: hányszor fordult elő ilyen minta, mennyi a teljes és átlagos idő, mennyi adatot olvastak ki a lemezről és mennyit a memóriából...

Mert például a gazdagép analitikai oldalára lép, nézze – valami túl sokat kezd olvasni a lemezen. A szerveren lévő lemez nem tudja kezelni – ki olvas róla?

És tetszőleges oszlop szerint rendezheti, és eldöntheti, hogy most mivel foglalkozik - a processzor vagy a lemez terhelése, vagy a kérések teljes száma... Rendeztük, megnéztük a "legfelső"-ket, javítottuk és kiadta az alkalmazás új verzióját.
[videó előadás]

És azonnal láthatja a különböző alkalmazásokat, amelyek ugyanazzal a sablonnal érkeznek egy hasonló kérésből SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, feldolgozás... És vajon miért olvasná a feldolgozás a felhasználót, ha nem lép kapcsolatba vele.

Ennek ellenkezője az, hogy azonnal látja az alkalmazásból, hogy mit csinál. Például a frontend óránként egyszer ez, ez, ez és ez (az idővonal segít). És rögtön felmerül a kérdés: úgy tűnik, nem a frontend feladata, hogy óránként egyszer csináljon valamit...

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Egy idő után rájöttünk, hogy hiányzik az összesítés statisztika terv csomópontok szerint. A tervekből csak azokat a csomópontokat izoláltuk, amelyek maguknak a tábláknak az adataival csinálnak valamit (index szerint olvassák/írják őket). Valójában csak egy szempont van hozzáadva az előző képhez - hány rekordot hozott nekünk ez a csomópont?, és hányat dobtak el (Szűrővel eltávolított sorok).

Nincs megfelelő indexed a táblán, kérsz hozzá, elrepül az index mellett, beleesik a Seq Scanbe... egy kivételével az összes rekordot kiszűrted. Miért van szükség napi 100 millió szűrt rekordra? Nem érdemes felgöngyölíteni az indexet?

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Az összes tervet csomópontonként elemezve rájöttünk, hogy a tervekben vannak tipikus struktúrák, amelyek nagy valószínűséggel gyanúsnak tűnnek. És jó lenne elmondani a fejlesztőnek: „Barátom, itt először index szerint olvas, majd rendez, majd levágja” - általában egy rekord van.

Valószínűleg mindenki találkozott ezzel a mintával, aki lekérdezést írt: „Adja meg a Vasya utolsó parancsát, annak dátumát.” És ha nincs dátum szerinti indexe, vagy nincs dátum a használt indexben, akkor lépj pontosan ugyanarra a „gereblyére” .

De tudjuk, hogy ez egy „gereblye” – miért ne mondaná el azonnal a fejlesztőnek, mit tegyen. Ennek megfelelően, amikor most nyitunk egy tervet, a fejlesztőnk azonnal lát egy gyönyörű képet a tippekkel, ahol rögtön azt mondják neki: „Hát itt is, ott is vannak problémáid, de ezeket így és úgy oldják meg.”

Emiatt a kezdeti és mostani problémák megoldásához szükséges tapasztalatok mennyisége jelentősen csökkent. Ilyen eszközünk van.

PostgreSQL lekérdezések tömeges optimalizálása. Kirill Borovikov (Tensor)

Forrás: will.com

Hozzászólás