Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

La raporto prezentas kelkajn alirojn kiuj permesas monitoru la agadon de SQL-demandoj kiam estas milionoj da ili tage, kaj estas centoj da monitoritaj serviloj PostgreSQL.

Kiuj teknikaj solvoj permesas al ni efike prilabori tian informon, kaj kiel ĉi tio faciligas la vivon de ordinara programisto?


Kiu interesiĝas? analizo de specifaj problemoj kaj diversaj optimumigaj teknikoj SQL-demandoj kaj solvado de tipaj DBA-problemoj en PostgreSQL - vi ankaŭ povas legi serion da artikoloj pri ĉi tiu temo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)
Mia nomo estas Kirill Borovikov, mi reprezentas Tensorfirmao. Specife, mi specialiĝas pri laboro kun datumbazoj en nia kompanio.

Hodiaŭ mi rakontos al vi kiel ni optimumigas demandojn, kiam vi ne bezonas "dispreni" la agadon de ununura demando, sed solvi la problemon amase. Kiam estas milionoj da petoj, kaj vi bezonas trovi iujn aliroj al solvo ĉi tiu granda problemo.

Ĝenerale, Tensor por miliono da niaj klientoj estas VLSI estas nia aplikaĵo: kompania socia reto, solvoj por videokomunikado, por interna kaj ekstera dokumentfluo, kontadaj sistemoj por kontado kaj magazenoj,... Tio estas tia "mega-kombino" por integra komerca administrado, en kiu estas pli ol 100 malsamaj; internaj projektoj.

Por certigi, ke ili ĉiuj funkcias kaj evoluas normale, ni havas 10 evolucentrojn tra la lando, kun pliaj en ili 1000 programistoj.

Ni laboras kun PostgreSQL ekde 2008 kaj amasigis grandan kvanton de tio, kion ni prilaboras - klientdatenoj, statistikaj, analizaj, datumoj de eksteraj informsistemoj - pli ol 400TB. Estas ĉirkaŭ 250 serviloj en produktado sole, kaj entute estas ĉirkaŭ 1000 datumbazaj serviloj, kiujn ni kontrolas.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

SQL estas deklara lingvo. Vi priskribas ne "kiel" io devus funkcii, sed "kion" vi volas atingi. La DBMS scias pli bone kiel fari JOIN - kiel konekti viajn tabelojn, kiajn kondiĉojn trudi, kio trairos la indekso, kio ne...

Iuj DBMS-oj akceptas sugestojn: "Ne, konektu ĉi tiujn du tabelojn en tia aŭ tia vico," sed PostgreSQL ne povas fari tion. Ĉi tiu estas la konscia pozicio de gvidaj programistoj: "Ni preferus fini la demand-optimumigilon ol permesi al programistoj uzi ian sugestojn."

Sed, malgraŭ la fakto, ke PostgreSQL ne permesas la "eksteron" regi sin, ĝi perfekte permesas vidu, kio okazas en likiam vi plenumas vian demandon, kaj kie ĝi havas problemojn.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ĝenerale, kun kiaj klasikaj problemoj programisto [al DBA] kutime venas? “Ĉi tie ni plenumis la peton, kaj ĉio estas malrapida ĉe ni, ĉio pendas, io okazas... Ia problemo!”

La kialoj estas preskaŭ ĉiam la samaj:

  • malefika demanda algoritmo
    Ellaboranto: "Nun mi donas al li 10 tabelojn en SQL per JOIN..." - kaj atendas, ke liaj kondiĉoj mirakle estos efike "malligitaj" kaj li ricevos ĉion rapide. Sed mirakloj ne okazas, kaj ĉiu sistemo kun tia ŝanĝebleco (10 tabeloj en unu FROM) ĉiam donas ian eraron. [artikolo]
  • senrilataj statistikoj
    Ĉi tiu punkto estas tre grava specife por PostgreSQL, kiam vi "verŝis" grandan datumaron sur la servilon, faras peton, kaj ĝi "sexcanits" vian tablojdon. Ĉar hieraŭ estis 10 rekordoj en ĝi, kaj hodiaŭ estas 10 milionoj, sed PostgreSQL ankoraŭ ne konscias pri tio, kaj ni devas rakonti ĝin pri tio. [artikolo]
  • "ŝtopilo" sur rimedoj
    Vi instalis grandan kaj tre ŝarĝitan datumbazon sur malforta servilo, kiu ne havas sufiĉe da disko, memoro aŭ procesoro-efikeco. Kaj jen ĉio... Ie estas prezenta plafono super kiu vi ne plu povas salti.
  • blokado
    Ĉi tio estas malfacila punkto, sed ili plej gravas por diversaj modifdemandoj (INSERTI, ĜISDATIGI, FORIGI) - ĉi tio estas aparta granda temo.

Ricevi planon

...Kaj por ĉio alia ni bezonas planon! Ni devas vidi kio okazas ene de la servilo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Demanda ekzekutplano por PostgreSQL estas arbo de la demanda ekzekutalgoritmo en teksta reprezentado. Ĝuste la algoritmo, kiel rezulto de analizo de la planisto, estis trovita esti la plej efika.

Ĉiu arba nodo estas operacio: preni datumojn de tabelo aŭ indekso, konstrui bitmapon, kunigi du tabelojn, kunigi, intersekci aŭ ekskludi elektojn. Efektivigi demandon implicas marŝi tra la nodoj de ĉi tiu arbo.

Por akiri la demandplanon, la plej facila maniero estas ekzekuti la deklaron EXPLAIN. Por akiri ĉiujn realajn atributojn, tio estas, efektive efektivigi demandon sur la bazo - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

La malbona parto: kiam oni rulas ĝin, ĝi okazas "ĉi tie kaj nun", do ĝi taŭgas nur por loka senararigado. Se vi prenas tre ŝarĝitan servilon kiu estas sub forta fluo de datumoj ŝanĝoj, kaj vi vidas: "Ho! Ĉi tie ni havas malrapidan ekzekutonsya peto." Antaŭ duonhoro, unu horo - dum vi kuris kaj ricevis ĉi tiun peton de la protokoloj, revenigante ĝin al la servilo, via tuta datumaro kaj statistiko ŝanĝiĝis. Vi rulu ĝin por sencimigi - kaj ĝi funkcias rapide! Kaj vi ne povas kompreni kial, kial estis malrapide.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Por kompreni, kio okazis ĝuste en la momento, kiam la peto estis plenumita sur la servilo, inteligentaj homoj skribis modulo auto_klarigas. Ĝi ĉeestas en preskaŭ ĉiuj plej oftaj PostgreSQL-distribuoj, kaj simple povas esti aktivigita en la agorda dosiero.

Se ĝi rimarkas, ke iu peto funkcias pli longe ol la limo, kiun vi diris al ĝi, ĝi faras "momentfoto" de la plano de ĉi tiu peto kaj skribas ilin kune en la protokolo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ĉio ŝajnas esti bona nun, ni iras al la ŝtipo kaj vidas tie... [teksta piedtuko]. Sed ni ne povas diri ion ajn pri ĝi, krom la fakto, ke ĝi estas bonega plano ĉar necesis 11ms por efektivigi.

Ĉio ŝajnas esti bona – sed nenio estas klara, kio efektive okazis. Krom la ĝenerala tempo, ni vere vidas nenion. Ĉar rigardi tian "ŝafidon" de simpla teksto ĝenerale ne estas vida.

Sed eĉ se ĝi ne estas evidenta, eĉ se ĝi estas maloportuna, estas pli fundamentaj problemoj:

  • La nodo indikas sumo de rimedoj de la tuta subarbo sub li. Tio estas, vi ne povas simple ekscii kiom da tempo estis elspezita por ĉi tiu speciala Indeksa Skanado se estas ia nestita kondiĉo sub ĝi. Ni devas dinamike rigardi por vidi ĉu estas "infanoj" kaj kondiĉaj variabloj, CTE-oj ene - kaj subtrahi ĉion ĉi "en niaj mensoj".
  • Dua punkto: la tempo kiu estas indikita sur la nodo estas sola noda ekzekuttempo. Se ĉi tiu nodo estis ekzekutita kiel rezulto de, ekzemple, buklo tra tabloregistroj plurfoje, tiam la nombro da bukloj - cikloj de ĉi tiu nodo - pliiĝas en la plano. Sed la atoma ekzekuttempo mem restas la sama laŭ plano. Tio estas, por kompreni kiom longe ĉi tiu nodo estis farita entute, vi devas multobligi unu aferon per alia - denove, "en via kapo".

En tiaj situacioj, komprenu "Kiu estas la plej malforta ligilo?" preskaŭ neebla. Sekve, eĉ la programistoj mem skribas en la "manlibro" tion "Kompreni planon estas arto, kiun oni devas lerni, sperti...".

Sed ni havas 1000 programistojn, kaj vi ne povas transdoni ĉi tiun sperton al ĉiu el ili. Mi, vi, li scias, sed iu tie ne plu scias. Eble li lernos, aŭ eble ne, sed li bezonas labori nun – kaj kie li akirus ĉi tiun sperton?

Plana bildigo

Tial ni rimarkis, ke por trakti ĉi tiujn problemojn, ni bezonas bona bildigo de la plano. [artikolo]

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ni unue iris "tra la merkato" - ni rigardu en la Interreto por vidi kio eĉ ekzistas.

Sed montriĝis, ke estas tre malmultaj relative "vivaj" solvoj, kiuj pli-malpli evoluas - laŭvorte, nur unu: klarigi.depesz.com de Hubert Lubaczewski. Kiam vi enigas la kampon "nutraĵo" tekstan reprezenton de la plano, ĝi montras al vi tabelon kun la analizitaj datumoj:

  • la propra pretigtempo de nodo
  • tuta tempo por la tuta subarbo
  • nombro da rekordoj kiuj estis prenitaj kiuj estis statistike atenditaj
  • la noda korpo mem

Ĉi tiu servo ankaŭ havas la kapablon dividi arkivon de ligiloj. Vi enĵetis vian planon tien kaj diris: "He, Vasja, jen ligo, tie estas io malĝusta."

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Sed estas ankaŭ malgrandaj problemoj.

Unue, grandega kvanto da "kopi-alglui". Vi prenas pecon de la ŝtipo, enŝovas ĝin tien, kaj denove, kaj denove.

Due neniu analizo de la kvanto de datumoj legitaj — la samaj bufroj kiuj eligas EXPLAIN (ANALYZE, BUFFERS), ni ne vidas ĝin ĉi tie. Li simple ne scias kiel malmunti ilin, kompreni ilin kaj labori kun ili. Kiam vi legas multajn datumojn kaj rimarkas, ke vi eble misasignas la diskon kaj memorkaŝmemoron, ĉi tiu informo estas tre grava.

La tria negativa punkto estas la tre malforta evoluo de ĉi tiu projekto. La kommitaĵoj estas tre malgrandaj, estas bone se unufoje ĉiun ses monatojn, kaj la kodo estas en Perl.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Sed ĉi tio estas ĉio "kantoteksto", ni povus iel vivi kun ĉi tio, sed estas unu afero, kiu ege deturnis nin de ĉi tiu servo. Ĉi tiuj estas eraroj en la analizo de Common Table Expression (CTE) kaj diversaj dinamikaj nodoj kiel InitPlan/SubPlan.

Se vi kredas ĉi tiun bildon, tiam la tuta ekzekuttempo de ĉiu individua nodo estas pli granda ol la tuta ekzekuttempo de la tuta peto. Estas simple - la generaciotempo de ĉi tiu CTE ne estis subtrahita de la CTE Scan-nodo. Tial ni ne plu scias la ĝustan respondon al kiom longe la CTE-skanado mem daŭris.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Tiam ni konstatis, ke estas tempo por skribi nian propran — hur! Ĉiu programisto diras: "Nun ni skribos nian propran, ĝi estos tre facila!"

Ni prenis stakon tipan por retservoj: kerno bazita sur Node.js + Express, uzis Bootstrap kaj D3.js por belaj diagramoj. Kaj niaj atendoj estis plene pravigitaj - ni ricevis la unuan prototipon en 2 semajnoj:

  • laŭmenda plano analizilo
    Tio estas, nun ni povas analizi ajnan planon el tiuj generitaj de PostgreSQL.
  • ĝusta analizo de dinamikaj nodoj - CTE-Skanado, InitPlan, SubPlan
  • analizo de bufroj-distribuo - kie datumpaĝoj estas legitaj el memoro, kie el la loka kaŝmemoro, kie el disko
  • akiris klarecon
    Por ne "fosi" ĉion ĉi en la protokolo, sed por vidi la "plej malfortan ligilon" tuj en la bildo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ni ricevis ion tian, kun sintaksa reliefigo inkluzivita. Sed kutime niaj programistoj ne plu laboras kun kompleta reprezentado de la plano, sed kun pli mallonga. Ja ni jam analizis ĉiujn nombrojn kaj ĵetis ilin maldekstren kaj dekstren, kaj meze ni lasis nur la unuan linion, kia nodo ĝi estas: CTE Scan, CTE-generacio aŭ Seq Scan laŭ iu signo.

Ĉi tiu estas la mallongigita reprezentado, kiun ni nomas plano ŝablono.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Kio alia estus oportuna? Estus oportune vidi, kian parton de nia tuta tempo estas asignita al kiu nodo - kaj simple "gluu ĝin" flanke. torta diagramo.

Ni montras al la nodo kaj vidu - rezultas, ke Seq Scan prenis malpli ol kvaronon de la tuta tempo, kaj la restanta 3/4 estis prenita de CTE Scan. Teruro! Ĉi tio estas eta noto pri la "fajra rapideco" de CTE Scan se vi aktive uzas ilin en viaj demandoj. Ili ne estas tre rapidaj - ili estas malsuperaj eĉ al regula tabloskanado. [artikolo] [artikolo]

Sed kutime tiaj diagramoj estas pli interesaj, pli kompleksaj, kiam ni tuj montras al segmento kaj vidas, ekzemple, ke pli ol duono de la tempo iu Seq Scan "manĝis". Krome, estis ia Filtrilo interne, multaj rekordoj estis forĵetitaj laŭ ĝi... Vi povas rekte ĵeti ĉi tiun bildon al la programisto kaj diri: "Vasja, ĉio estas malbona ĉi tie por vi! Eltrovu ĝin, rigardu - io estas malĝusta!"

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Kompreneble, estis iuj "rastiloj" implikitaj.

La unua afero, kiun ni renkontis, estis la rondigoproblemo. La tempo de ĉiu individua nodo en la plano estas indikita kun precizeco de 1 μs. Kaj kiam la nombro da nodaj cikloj superas, ekzemple, 1000 - post ekzekuto PostgreSQL dividita "en precizeco", tiam kalkulante reen ni ricevas la tutan tempon "ie inter 0.95ms kaj 1.05ms". Kiam la kalkulo iras al mikrosekundoj, tio estas en ordo, sed kiam ĝi estas jam [mili] sekundoj, vi devas konsideri ĉi tiun informon kiam "malligas" rimedojn al la nodoj de la plano "kiu konsumis kiom".

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

La dua punkto, pli kompleksa, estas la distribuado de resursoj (tiuj bufroj) inter dinamikaj nodoj. Ĉi tio kostis al ni la unuajn 2 semajnojn de la prototipo kaj pliajn 4 semajnojn.

Estas sufiĉe facile akiri tian problemon - ni faras CTE kaj supozeble legas ion en ĝi. Fakte, PostgreSQL estas "inteligenta" kaj ne legos ion rekte tie. Tiam ni prenas la unuan rekordon el ĝi, kaj al ĝi la cent unuan el la sama CTE.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ni rigardas la planon kaj komprenas - estas strange, ni havas 3 bufrojn (datumpaĝoj) "konsumitaj" en Seq Scan, 1 pli en CTE Scan, kaj 2 pli en la dua CTE Scan. Tio estas, se ni simple resumas ĉion, ni ricevos 6, sed el la tabuleto ni legas nur 3! CTE Scan ne legas ion ajn de ie ajn, sed funkcias rekte kun la procezmemoro. Tio estas, io klare misas ĉi tie!

Fakte, rezultas, ke jen ĉiuj tiuj 3 paĝoj da datumoj, kiuj estis petitaj de Seq Scan, unue 1 petis la 1-an CTE-Skanadon, kaj poste la 2-a, kaj 2 pliaj estis legitaj al li. Tio estas, entute de 3 paĝoj estis legitaj datumoj, ne 6.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Kaj ĉi tiu bildo kondukis nin al la kompreno, ke la plenumo de plano ne plu estas arbo, sed simple ia acikla grafeo. Kaj ni ricevis diagramon tian, por ke ni komprenu "kio venis de kie unue." Tio estas, ĉi tie ni kreis CTE de pg_class, kaj petis ĝin dufoje, kaj preskaŭ nia tuta tempo pasigis sur la branĉo kiam ni petis ĝin la 2-an fojon. Estas klare, ke legado de la 101-a enskribo estas multe pli multekosta ol nur legi la 1-an enskribon de la tablojdo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ni elspiris iom da tempo. Ili diris: “Nun, Neo, vi scias kungfuon! Nun nia sperto estas ĝuste sur via ekrano. Nun vi povas uzi ĝin." [artikolo]

Log firmiĝo

Niaj 1000 programistoj spiris trankvile. Sed ni komprenis, ke ni havas nur centojn da "batalaj" serviloj, kaj ĉio ĉi tiu "kopi-alglui" fare de la programistoj tute ne estas oportuna. Ni konstatis, ke ni mem devas kolekti ĝin.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Ĝenerale, ekzistas norma modulo kiu povas kolekti statistikojn, tamen, ĝi ankaŭ devas esti aktivigita en la agordo - ĉi tio pg_stat_statements modulo. Sed li ne konvenis al ni.

Unue, ĝi asignas al la samaj demandoj uzante malsamajn skemojn ene de la sama datumbazo malsamaj QueryIds. Tio estas, se vi unue faras SET search_path = '01'; SELECT * FROM user LIMIT 1;kaj tiam SET search_path = '02'; kaj la sama peto, tiam la statistikoj de ĉi tiu modulo havos malsamajn registrojn, kaj mi ne povos kolekti ĝeneralajn statistikojn specife en la kunteksto de ĉi tiu peta profilo, sen konsideri la skemojn.

La dua punkto, kiu malhelpis nin uzi ĝin, estas manko de planoj. Tio estas, ne ekzistas plano, ekzistas nur la peto mem. Ni vidas, kio malrapidiĝis, sed ni ne komprenas kial. Kaj ĉi tie ni revenas al la problemo de rapide ŝanĝiĝanta datumaro.

Kaj la lasta momento - manko de "faktoj". Tio estas, vi ne povas trakti specifan okazon de demanda ekzekuto - ekzistas neniu, ekzistas nur agregitaj statistikoj. Kvankam eblas labori kun ĉi tio, ĝi estas nur tre malfacila.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Tial, ni decidis batali kopi-alglui kaj komencis verki kolektanto.

La kolektanto konektas per SSH, establas sekuran ligon al la servilo kun la datumbazo uzante atestilon, kaj tail -F "alkroĉiĝas" al ĝi en la protokoldosiero. Do en ĉi tiu kunsido ni ricevas kompletan "spegulon" de la tuta protokolo-dosiero, kiun la servilo generas. La ŝarĝo sur la servilo mem estas minimuma, ĉar ni analizas nenion tie, ni nur spegulas la trafikon.

Ĉar ni jam komencis verki la interfacon en Node.js, ni daŭre skribis la kolektanton en ĝi. Kaj ĉi tiu teknologio pravigis sin, ĉar estas tre oportune uzi JavaScript por labori kun malforte formatitaj tekstaj datumoj, kio estas la protokolo. Kaj la infrastrukturo Node.js mem kiel backend-platformo ebligas al vi facile kaj oportune labori kun retaj konektoj, kaj efektive kun ajnaj datumfluoj.

Sekve, ni "streĉas" du ligojn: la unua por "aŭskulti" la ŝtipo mem kaj preni ĝin al ni mem, kaj la dua por periode demandi la bazon. "Sed la protokolo montras, ke la signo kun oid 123 estas blokita," sed ĉi tio signifas nenion por la programisto, kaj estus bone demandi la datumbazon, "Kio estas OID = 123 ĉiuokaze?" Kaj do ni periode demandas al la bazo tion, kion ni ankoraŭ ne scias pri ni mem.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

"Estas nur unu afero, kiun vi ne konsideris, ekzistas specio de elefantsimilaj abeloj!..." Ni komencis evoluigi ĉi tiun sistemon kiam ni volis monitori 10 servilojn. La plej kritika en nia kompreno, kie aperis iuj problemoj, kiuj estis malfacile trakteblaj. Sed dum la unua trimonato ni ricevis cent por monitorado – ĉar la sistemo funkciis, ĉiuj volis ĝin, ĉiuj estis komfortaj.

Ĉio ĉi devas esti aldonita, la datumfluo estas granda kaj aktiva. Fakte, kion ni kontrolas, kion ni povas trakti, estas tio, kion ni uzas. Ni ankaŭ uzas PostgreSQL kiel konservadon de datumoj. Kaj nenio estas pli rapida por "verŝi" datumojn en ĝin ol la funkciigisto COPY Ankoraŭ ne.

Sed simple "verŝi" datumojn ne vere estas nia teknologio. Ĉar se vi havas proksimume 50k petojn je sekundo sur cent serviloj, tiam ĉi tio generos 100-150GB da protokoloj tage. Tial ni devis zorge "tranĉi" la bazon.

Unue, ni faris dispartigo tage, ĉar, ĝenerale, neniu interesiĝas pri la korelacio inter tagoj. Kia diferenco faras tion, kion vi havis hieraŭ, se ĉi-nokte vi lanĉis novan version de la aplikaĵo - kaj jam kelkajn novajn statistikojn.

Due, ni lernis (estis devigitaj) tre, tre rapide skribi uzante COPY. Tio estas, ne nur COPYĉar li estas pli rapida ol INSERT, kaj eĉ pli rapide.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

La tria punkto - mi devis forlasi ellasilon, respektive, kaj fremdaj ŝlosiloj. Tio estas, ni tute ne havas referencan integrecon. Ĉar se vi havas tabelon, kiu havas paron da FK-oj, kaj vi diras en la datumbaza strukturo, ke "ĉi tie estas protokolo, kiu estas referencita de FK, ekzemple al grupo de registroj", tiam kiam vi enmetas ĝin, PostgreSQL. restas nenio krom kiel preni ĝin kaj fari ĝin honeste SELECT 1 FROM master_fk1_table WHERE ... kun la identigilo, kiun vi provas enmeti - nur por kontroli, ke ĉi tiu rekordo ĉeestas tie, ke vi ne "rompu" ĉi tiun Fremdan Ŝlosilon per via enmeto.

Anstataŭ unu rekordo al la celtabelo kaj ĝiaj indeksoj, ni ricevas la plian avantaĝon legi de ĉiuj tabeloj al kiuj ĝi rilatas. Sed ni tute ne bezonas ĉi tion - nia tasko estas registri kiel eble plej multe kaj kiel eble plej rapide kun la plej malgranda ŝarĝo. Do FK - malsupren!

La sekva punkto estas agregado kaj haŝado. Komence, ni efektivigis ilin en la datumbazo - finfine, estas oportune tuj, kiam rekordo alvenas, fari ĝin en ia tablojdo. "plus unu" ĝuste en la ellasilo. Nu, ĝi estas oportuna, sed la sama malbona afero - vi enmetas unu registron, sed estas devigita legi kaj skribi ion alian el alia tabelo. Cetere, vi ne nur legas kaj skribas, vi ankaŭ faras ĝin ĉiufoje.

Nun imagu, ke vi havas tabelon en kiu vi simple kalkulas la nombron da petoj, kiuj pasis tra specifa gastiganto: +1, +1, +1, ..., +1. Kaj vi, principe, ne bezonas ĉi tion - ĉio eblas sumo en memoro sur la kolektanto kaj sendu al la datumbazo unufoje +10.

Jes, en kazo de iuj problemoj, via logika integreco povas "disfali", sed ĉi tio estas preskaŭ nereala kazo - ĉar vi havas normalan servilon, ĝi havas kuirilaron en la regilo, vi havas transakcian protokolon, protokolon sur la. dosiersistemo... Ĝenerale, ĝi ne indas. La perdo de produktiveco, kiun vi ricevas pro funkciado de ellasiloj/FK, ne valoras la elspezon, kiun vi faras.

Estas same kun hashing. Certa peto flugas al vi, vi kalkulas certan identigilon el ĝi en la datumbazo, skribas ĝin al la datumbazo kaj poste rakontas ĝin al ĉiuj. Ĉio estas en ordo ĝis kiam, en la momento de la registrado, venas al vi dua persono, kiu volas registri la samon - kaj vi estas blokita, kaj ĉi tio jam estas malbona. Tial, se vi povas transdoni la generacion de iuj identigiloj al la kliento (rilate al la datumbazo), estas pli bone fari tion.

Estis nur perfekta por ni uzi MD5 el la teksto - peto, plano, ŝablono,... Ni kalkulas ĝin ĉe la kolektanto, kaj "verŝas" la pretan identigilon en la datumbazon. La longeco de MD5 kaj ĉiutaga dispartigo permesas al ni ne zorgi pri eblaj kolizioj.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Sed por registri ĉion ĉi rapide, ni bezonis modifi la registran proceduron mem.

Kiel vi kutime skribas datumojn? Ni havas ian datuman aron, ni dividas ĝin en plurajn tabelojn, kaj poste KOPIU ĝin - unue en la unuan, poste en la duan, en la trian... Estas maloportune, ĉar ni ŝajnas skribi unu datumfluon en tri paŝoj. sinsekve. Malagrabla. Ĉu ĝi povas esti farita pli rapide? Povas!

Por fari tion, sufiĉas nur malkomponi ĉi tiujn fluojn paralele unu kun la alia. Montriĝas, ke ni havas erarojn, petojn, ŝablonojn, blokadon, ... flugantajn en apartaj fadenoj – kaj ni skribas ĉion paralele. Sufiĉe por ĉi tio tenu COPY-kanalon konstante malfermita por ĉiu individua celtabelo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Tio estas, ĉe la kolektanto ĉiam estas rivereto, en kiun mi povas skribi la datumojn kiujn mi bezonas. Sed por ke la datumbazo vidu ĉi tiujn datumojn, kaj iu ne blokiĝu atendante ke ĉi tiuj datumoj estos skribitaj, COPY devas esti interrompita je certaj intervaloj. Por ni, la plej efika periodo estis ĉirkaŭ 100ms - ni fermas ĝin kaj tuj malfermas ĝin denove al la sama tablo. Kaj se ni ne havas sufiĉe da unu fluo dum kelkaj pintoj, tiam ni faras kunigon ĝis certa limo.

Aldone, ni eksciis, ke por tia ŝarĝprofilo, ajna agregado, kiam rekordoj estas kolektitaj en aroj, estas malbona. Klasika malbono estas INSERT ... VALUES kaj pliaj 1000 rekordoj. Ĉar ĉe tiu punkto vi havas skribpinton en la amaskomunikilaro, kaj ĉiuj aliaj provas skribi ion al la disko atendos.

Por forigi tiajn anomaliojn, simple ne kunigu ion ajn, tute ne bufrigu. Kaj se bufrado al disko okazas (feliĉe, la Stream API en Node.js permesas vin ekscii) - prokrastu ĉi tiun konekton. Kiam vi ricevas eventon, ke ĝi denove estas senpaga, skribu al ĝi el la amasigita vico. Kaj dum ĝi estas okupata, prenu la sekvan senpagan el la naĝejo kaj skribu al ĝi.

Antaŭ enkonduki ĉi tiun aliron al datumregistrado, ni havis proksimume 4K skribajn operaciojn, kaj tiamaniere ni reduktis la ŝarĝon je 4 fojojn. Nun ili kreskis ankoraŭ 6 fojojn pro novaj monitoritaj datumbazoj - ĝis 100MB/s. Kaj nun ni stokas protokolojn dum la lastaj 3 monatoj en volumo de ĉirkaŭ 10-15TB, esperante, ke en nur tri monatoj ĉiu programisto povos solvi ajnan problemon.

Ni komprenas la problemojn

Sed simple kolekti ĉiujn ĉi datumojn estas bona, utila, grava, sed ne sufiĉa - ĝi devas esti komprenita. Ĉar ĉi tiuj estas milionoj da malsamaj planoj ĉiutage.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Sed milionoj estas neregeblaj, ni unue devas fari "pli malgrandan". Kaj, antaŭ ĉio, vi devas decidi kiel vi organizos ĉi tiun "pli malgrandan" aferon.

Ni identigis tri ĉefajn punktojn:

  • kiu sendis ĉi tiun peton
    Tio estas, de kia aplikaĵo ĝi "alvenis": retinterfaco, backend, pagsistemo aŭ io alia.
  • kie ĝi okazis
    Sur kiu specifa servilo? Ĉar se vi havas plurajn servilojn sub unu aplikaĵo, kaj subite unu "stultas" (ĉar la "disko estas putra", "memoro likita", ia alia problemo), tiam vi devas specife trakti la servilon.
  • kiom la problemo manifestiĝis laŭ unu aŭ alia maniero

Por kompreni "kiu" sendis al ni peton, ni uzas norman ilon - fiksante sean variablon: SET application_name = '{bl-host}:{bl-method}'; — ni sendas la nomon de la komerca logika gastiganto de kiu venas la peto, kaj la nomon de la metodo aŭ aplikaĵo kiu iniciatis ĝin.

Post kiam ni pasis la "posedanton" de la peto, ĝi devas esti eligita al la protokolo - por tio ni agordas la variablon log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Por interesatoj, eble rigardu en la manlibrokion ĉio signifas. Rezultas, ke ni vidas en la protokolo:

  • время
  • procezaj kaj transakciaj identigiloj
  • datumbaza nomo
  • IP de la persono kiu sendis ĉi tiun peton
  • kaj metodonomo

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Tiam ni rimarkis, ke ne estas tre interese rigardi la korelacion por unu peto inter malsamaj serviloj. Ne ofte vi havas situacion kie unu aplikaĵo ŝraŭbas egale tie kaj tie. Sed eĉ se ĝi estas la sama, rigardu iun el ĉi tiuj serviloj.

Do jen la tranĉo "unu servilo - unu tagon" ĝi montriĝis sufiĉa por ni por ajna analizo.

La unua analiza sekcio estas la sama "specimeno" - mallongigita formo de prezento de la plano, malplenigita de ĉiuj nombraj indikiloj. La dua tranĉo estas la aplikaĵo aŭ metodo, kaj la tria tranĉo estas la specifa plannodo, kiu kaŭzis al ni problemojn.

Kiam ni transiris de specifaj okazoj al ŝablonoj, ni ricevis du avantaĝojn samtempe:

  • multobla redukto de la nombro da objektoj por analizo
    Ni devas analizi la problemon ne plu per miloj da demandoj aŭ planoj, sed per dekoj da ŝablonoj.
  • templinio
    Tio estas, resumante la "faktojn" ene de certa sekcio, vi povas montri ilian aspekton dum la tago. Kaj ĉi tie vi povas kompreni, ke se vi havas ian ŝablonon kiu okazas, ekzemple, unufoje hore, sed ĝi devus okazi unufoje tage, vi devus pensi pri kio misfunkciis - kiu kaŭzis ĝin kaj kial, eble ĝi devus esti ĉi tie. ne devus. Ĉi tio estas alia ne-nombra, pure vida, metodo de analizo.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

La ceteraj metodoj baziĝas sur la indikiloj, kiujn ni ĉerpas el la plano: kiom da fojoj tia ŝablono okazis, la tuta kaj meza tempo, kiom da datumoj estis legitaj de la disko, kaj kiom da memoro...

Ĉar, ekzemple, vi venas al la analizpaĝo por la gastiganto, rigardu - io komencas legi tro multe sur la disko. La disko sur la servilo ne povas pritrakti ĝin - kiu legas el ĝi?

Kaj vi povas ordigi laŭ iu ajn kolumno kaj decidi, pri kio vi traktos nun - la ŝarĝo sur la procesoro aŭ la disko, aŭ la totala nombro da petoj... Ni ordigis ĝin, rigardis la "suprajn", riparis ĝin kaj lanĉis novan version de la aplikaĵo.
[videoprelego]

Kaj tuj vi povas vidi malsamajn aplikojn, kiuj venas kun la sama ŝablono de peto kiel SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, processing... Kaj vi scivolas kial prilaborado legus la uzanton se li ne interagas kun li.

La kontraŭa maniero estas tuj vidi de la aplikaĵo kion ĝi faras. Ekzemple, la fasado estas ĉi tio, ĉi tiu, ĉi tiu, kaj ĉi tiu unufoje hore (la templinio helpas). Kaj tuj aperas la demando: ŝajnas, ke ne estas la tasko de la fasado fari ion unufoje en horo...

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Post iom da tempo, ni rimarkis, ke mankas al ni agregitaj statistiko per plannodoj. Ni izolis de la planoj nur tiujn nodojn, kiuj faras ion per la datumoj de la tabeloj mem (legi/skribi ilin per indekso aŭ ne). Fakte, nur unu aspekto estas aldonita relative al la antaŭa bildo - kiom da registroj alportis al ni ĉi tiu nodo?, kaj kiom multaj estis forĵetitaj (Vicoj Forigitaj de Filtrilo).

Vi ne havas taŭgan indekson sur la telero, vi petas ĝin, ĝi preterflugas la indekson, falas en Seq Scan... vi filtris ĉiujn rekordojn krom unu. Kial vi bezonas 100M filtritajn rekordojn tage? Ĉu ne estas pli bone kunigi la indekson?

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

Analizinte ĉiujn planojn nodo post nodo, ni rimarkis, ke estas iuj tipaj strukturoj en la planoj, kiuj tre verŝajne aspektas suspektindaj. Kaj estus bone diri al la programisto: "Amiko, ĉi tie vi unue legas per indekso, poste ordigu, kaj poste fortranĉas" - kiel regulo, estas unu rekordo.

Ĉiuj, kiuj skribis demandojn, verŝajne renkontis ĉi tiun ŝablonon: "Donu al mi la lastan mendon por Vasja, ĝian daton." Kaj se vi ne havas indekson laŭ dato, aŭ ne estas dato en la indekso, kiun vi uzis, tiam vi faros surpaŝu ĝuste la saman "raskilon".

Sed ni scias, ke ĉi tio estas "raketo" - do kial ne tuj diri al la programisto, kion li devas fari. Sekve, kiam nun malfermas planon, nia programisto tuj vidas belan bildon kun konsiletoj, kie ili tuj diras al li: "Vi havas problemojn ĉi tie kaj tie, sed ili estas solvitaj tiel kaj tiel."

Kiel rezulto, la kvanto da sperto, kiun oni bezonis por solvi problemojn komence kaj nun, grave malpliiĝis. Ĉi tiu estas la speco de ilo, kiun ni havas.

Pogranda optimumigo de PostgreSQL-demandoj. Kirill Borovikov (tensoro)

fonto: www.habr.com

Aldoni komenton