Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

UzpÅ«Å”anās ietekme uz tabulām un indeksiem ir plaÅ”i zināma un ir sastopama ne tikai Postgres. Ir veidi, kā ar to rÄ«koties, piemēram, VACUUM FULL vai CLUSTER, taču tie darbÄ«bas laikā bloķē galdus, un tāpēc tos ne vienmēr var izmantot.

Rakstā bÅ«s neliela teorija par to, kā rodas uzpÅ«Å”anās, kā ar to cÄ«nÄ«ties, par atliktajiem ierobežojumiem un problēmām, ko tie rada paplaÅ”inājuma pg_repack lietoÅ”anā.

Å is raksts ir uzrakstÄ«ts, pamatojoties uz mana runa PgConf.Russia 2020. gadā.

Kāpēc rodas vēdera uzpÅ«Å”anās?

Postgres pamatā ir vairāku versiju modelis (MVCC). Tās bÅ«tÄ«ba ir tāda, ka katrai tabulas rindai var bÅ«t vairākas versijas, savukārt transakcijām ir redzama ne vairāk kā viena no Ŕīm versijām, taču ne vienmēr ir viena un tā pati. Tas ļauj vairākiem darÄ«jumiem darboties vienlaikus un praktiski neietekmē viens otru.

AcÄ«mredzot visas Ŕīs versijas ir jāsaglabā. Postgres strādā ar atmiņu pa lappusei, un lapa ir minimālais datu apjoms, ko var nolasÄ«t no diska vai ierakstÄ«t. ApskatÄ«sim nelielu piemēru, lai saprastu, kā tas notiek.

Pieņemsim, ka mums ir tabula, kurai esam pievienojuÅ”i vairākus ierakstus. Faila pirmajā lapā, kurā glabājas tabula, ir parādÄ«juÅ”ies jauni dati. Å Ä«s ir rindu reāllaika versijas, kas ir pieejamas citām transakcijām pēc saistÄ«bu izpildes (vienkārŔības labad mēs pieņemsim, ka izolācijas lÄ«menis ir Read Committed).

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Pēc tam mēs atjauninājām vienu no ierakstiem, tādējādi atzÄ«mējot veco versiju kā vairs neatbilstoÅ”u.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Soli pa solim, atjauninot un dzÄ“Å”ot rindu versijas, mēs nonācām pie lapas, kurā aptuveni puse datu ir ā€œatkritumiā€. Å ie dati nav redzami nevienam darÄ«jumam.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Postgres ir mehānisms VAKUUMS, kas notÄ«ra novecojuŔās versijas un nodroÅ”ina vietu jauniem datiem. Bet, ja tas nav pietiekami agresÄ«vi konfigurēts vai ir aizņemts ar darbu citās tabulās, tad paliek ā€œatkritumu datiā€, un mums ir jāizmanto papildu lapas jauniem datiem.

Tātad mÅ«su piemērā tabula kādā brÄ«dÄ« sastāvēs no četrām lapām, bet tikai puse no tās saturēs reāllaika datus. Rezultātā, piekļūstot tabulai, mēs nolasÄ«sim daudz vairāk datu nekā nepiecieÅ”ams.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Pat ja VACUUM tagad izdzÄ“Å” visas neatbilstoŔās rindu versijas, situācija dramatiski neuzlabosies. Mums bÅ«s brÄ«va vieta lapās vai pat veselas lapas jaunām rindām, taču mēs joprojām nolasÄ«sim vairāk datu nekā nepiecieÅ”ams.
Starp citu, ja faila beigās bÅ«tu pilnÄ«gi tukÅ”a lapa (otrā mÅ«su piemērā), tad VACUUM varētu to apgriezt. Bet tagad viņa ir pa vidu, tāpēc ar viņu neko nevar darÄ«t.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Kad Ŕādu tukÅ”u vai ļoti retu lapu skaits kļūst liels, ko sauc par uzpÅ«Å”anos, tas sāk ietekmēt veiktspēju.

Viss iepriekÅ” aprakstÄ«tais ir uzpÅ«Å”anās raÅ”anās mehānika tabulās. Indeksos tas notiek apmēram tāpat.

Vai man ir vēdera uzpÅ«Å”anās?

Ir vairāki veidi, kā noteikt, vai jums ir vēdera uzpÅ«Å”anās. Pirmā ideja ir izmantot iekŔējo Postgres statistiku, kas satur aptuvenu informāciju par rindu skaitu tabulās, "dzÄ«vo" rindu skaitu utt. Internetā varat atrast daudz gatavu skriptu variācijas. Mēs ņēmām par pamatu skripts no PostgreSQL Experts, kas var novērtēt bloat tabulas kopā ar grauzdÄ“Å”anas un uzpÅ«Å”anās btree indeksiem. MÅ«su pieredze liecina, ka tā kļūda ir 10-20%.

Vēl viens veids ir izmantot paplaÅ”inājumu pgstattuple, kas ļauj ieskatÄ«ties lapās un iegÅ«t gan aptuveno, gan precÄ«zu uzpÅ«Å”anās vērtÄ«bu. Bet otrajā gadÄ«jumā jums bÅ«s jāskenē visa tabula.

Mēs uzskatām, ka neliela uzpÅ«Å”anās vērtÄ«ba, lÄ«dz 20%, ir pieņemama. To var uzskatÄ«t par pildfaktora analogu tabulas Šø indeksi. Pie 50% un vairāk var sākties veiktspējas problēmas.

Veidi, kā cÄ«nÄ«ties ar vēdera uzpÅ«Å”anos

Postgres ir vairāki veidi, kā tikt galā ar vēdera uzpÅ«Å”anos, taču tie ne vienmēr ir piemēroti ikvienam.

Konfigurējiet AUTOVACUUM, lai nenotiktu uzpÅ«Å”anās. Vai precÄ«zāk, noturēt to sev pieņemamā lÄ«menÄ«. Å Ä·iet, ka tas ir "kapteiņa" padoms, taču patiesÄ«bā to ne vienmēr ir viegli sasniegt. Piemēram, jums ir aktÄ«va izstrāde ar regulārām izmaiņām datu shēmā vai notiek kāda veida datu migrācija. Rezultātā jÅ«su slodzes profils var bieži mainÄ«ties un parasti atŔķiras atkarÄ«bā no tabulas. Tas nozÄ«mē, ka jums ir pastāvÄ«gi jāstrādā nedaudz uz priekÅ”u un jāpielāgo AUTOVACUUM katra galda mainÄ«gajam profilam. Bet acÄ«mredzot tas nav viegli izdarāms.

Vēl viens izplatÄ«ts iemesls, kāpēc AUTOVACUUM nevar sekot lÄ«dzi tabulām, ir tas, ka ir ilgstoÅ”i veikti darÄ«jumi, kas neļauj tai tÄ«rÄ«t Å”iem darÄ«jumiem pieejamos datus. ArÄ« Å”eit ieteikums ir acÄ«mredzams - atbrÄ«vojieties no ā€œkarājoÅ”iemā€ darÄ«jumiem un samaziniet aktÄ«vo darÄ«jumu laiku. Bet, ja jÅ«su lietojumprogrammas slodze ir OLAP un OLTP hibrÄ«ds, jums vienlaikus var bÅ«t daudz biežu atjauninājumu un Ä«su vaicājumu, kā arÄ« ilgtermiņa darbÄ«bas, piemēram, atskaites veidoÅ”ana. Šādā situācijā ir vērts padomāt par slodzes sadalÄ«Å”anu pa dažādām bāzēm, kas ļaus precÄ«zāk noregulēt katru no tām.

Vēl viens piemērs - pat ja profils ir viendabÄ«gs, bet datu bāze ir ļoti noslogota, tad pat agresÄ«vākais AUTOVACUUM var netikt galā, un rodas uzpÅ«Å”anās. MērogoÅ”ana (vertikāla vai horizontāla) ir vienÄ«gais risinājums.

Ko darīt situācijā, kad esi uzstādījis AUTOVAKUUMU, bet uzpūŔanās turpina augt.

Komanda VAKUUMS PILNS pārbÅ«vē tabulu un indeksu saturu un atstāj tajos tikai atbilstoÅ”os datus. Lai novērstu uzpÅ«Å”anos, tas darbojas lieliski, taču tā izpildes laikā tiek fiksēts ekskluzÄ«vs bloķētājs uz galda (AccessExclusiveLock), kas neļaus izpildÄ«t vaicājumus Å”ajā tabulā, pat atlasa. Ja varat atļauties pārtraukt pakalpojumu vai tā daļu uz kādu laiku (no desmitiem minÅ«Å”u lÄ«dz vairākām stundām atkarÄ«bā no datu bāzes lieluma un aparatÅ«ras), tad Ŕī iespēja ir vislabākā. Diemžēl plānotās apkopes laikā mums nav laika palaist VACUUM FULL, tāpēc Ŕī metode mums nav piemērota.

Komanda CLUSTER PārbÅ«vē tabulu saturu tāpat kā VACUUM FULL, bet ļauj norādÄ«t indeksu, pēc kura dati tiks fiziski pasÅ«tÄ«ti diskā (bet turpmāk jaunām rindām secÄ«ba netiek garantēta). Noteiktās situācijās tā ir laba optimizācija vairākiem vaicājumiem ā€” nolasot vairākus ierakstus pēc indeksa. Komandas trÅ«kums ir tāds pats kā VACUUM FULL - tā darbÄ«bas laikā bloķē galdu.

Komanda REINDEKSS lÄ«dzÄ«gi kā iepriekŔējie divi, bet pārbÅ«vē konkrētu indeksu vai visus tabulas indeksus. Slēdzenes ir nedaudz vājākas: ShareLock tabulā (novērÅ” modifikācijas, bet ļauj atlasÄ«t) un AccessExclusiveLock indeksam, kas tiek pārbÅ«vēts (bloķē vaicājumus, izmantojot Å”o indeksu). Tomēr Postgres 12. versijā parādÄ«jās parametrs LÄŖDZEKÄ»I, kas ļauj atjaunot indeksu, nebloķējot vienlaicÄ«gu ierakstu pievienoÅ”anu, modificÄ“Å”anu vai dzÄ“Å”anu.

IepriekŔējās Postgres versijās jÅ«s varat sasniegt rezultātu, kas lÄ«dzÄ«gs REINDEX VIENLAIDÄŖGI izmantojot VIENLAIDÄŖGI IZVEIDOT INDEKSU. Tas ļauj izveidot indeksu bez stingras bloÄ·Ä“Å”anas (ShareUpdateExclusiveLock, kas netraucē paralēliem vaicājumiem), pēc tam aizstāt veco indeksu ar jaunu un izdzēst veco indeksu. Tas ļauj novērst indeksa uzpÅ«Å”anos, netraucējot jÅ«su lietojumprogrammai. Ir svarÄ«gi ņemt vērā, ka, atjaunojot indeksus, diska apakÅ”sistēmai bÅ«s papildu slodze.

Tādējādi, ja indeksiem ir veidi, kā novērst uzpÅ«Å”anos ā€œlidojumāā€, tad tabulām tādu nav. Å eit tiek izmantoti dažādi ārējie paplaÅ”inājumi: pg_repack (iepriekÅ” pg_reorg), pgcompact, pgcompacttable un citi. Å ajā rakstā es tos nesalÄ«dzināŔu un runāŔu tikai par pg_repack, kuru pēc dažām izmaiņām mēs izmantojam paÅ”i.

Kā darbojas pg_repack

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi
Teiksim, mums ir pavisam parasta tabula ā€“ ar indeksiem, ierobežojumiem un diemžēl ar uzpÅ«Å”anos. Pirmais pg_repack solis ir izveidot žurnāla tabulu, lai saglabātu datus par visām izmaiņām, kamēr tā darbojas. Trigeris atkārtos Ŕīs izmaiņas katrai ievietoÅ”anai, atjaunināŔanai un dzÄ“Å”anai. Pēc tam tiek izveidota tabula, kas pēc struktÅ«ras ir lÄ«dzÄ«ga oriÄ£inālajai, bet bez indeksiem un ierobežojumiem, lai nepalēninātu datu ievietoÅ”anas procesu.

Pēc tam pg_repack pārsÅ«ta datus no vecās tabulas uz jauno tabulu, automātiski filtrējot visas neatbilstoŔās rindas, un pēc tam izveido indeksus jaunajai tabulai. Visu Å”o darbÄ«bu izpildes laikā žurnāla tabulā uzkrājas izmaiņas.

Nākamais solis ir pārsÅ«tÄ«t izmaiņas uz jauno tabulu. MigrÄ“Å”ana tiek veikta vairākās iterācijās, un, ja žurnāla tabulā ir atlicis mazāk par 20 ierakstiem, pg_repack iegÅ«st spēcÄ«gu bloÄ·Ä“Å”anu, migrē jaunākos datus un aizstāj veco tabulu ar jauno Postgres sistēmas tabulās. Å is ir vienÄ«gais un ļoti Ä«sais brÄ«dis, kad nevarēsi strādāt ar galdu. Pēc tam vecā tabula un tabula ar žurnāliem tiek dzēsta un failu sistēmā tiek atbrÄ«vota vieta. Process ir pabeigts.

Teorētiski viss izskatās lieliski, bet kas notiek praksē? Mēs pārbaudÄ«jām pg_repack bez slodzes un zem slodzes, kā arÄ« pārbaudÄ«jām tā darbÄ«bu priekÅ”laicÄ«gas apstāŔanās gadÄ«jumā (citiem vārdiem sakot, izmantojot Ctrl+C). Visi testi bija pozitÄ«vi.

Mēs devāmies uz pārtikas veikalu - un tad viss neizdevās tā, kā mēs gaidījām.

Pirmā pankūka pārdoŔanā

Pirmajā klasterī mēs saņēmām kļūdu par unikāla ierobežojuma pārkāpumu:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Å im ierobežojumam bija automātiski Ä£enerēts nosaukums index_16508 ā€” to izveidoja pg_repack. Pamatojoties uz tā sastāvā iekļautajiem atribÅ«tiem, mēs noteicām ā€œmÅ«suā€ ierobežojumu, kas tam atbilst. Problēma izrādÄ«jās tāda, ka tas nav gluži parasts ierobežojums, bet gan atlikts (atliktais ierobežojums), t.i. tā pārbaude tiek veikta vēlāk nekā sql komanda, kas noved pie neparedzētām sekām.

Atliktie ierobežojumi: kāpēc tie ir vajadzīgi un kā tie darbojas

Nedaudz teorijas par atliktajiem ierobežojumiem.
Apsvērsim vienkārÅ”u piemēru: mums ir automaŔīnu tabula-uzziņu grāmata ar diviem atribÅ«tiem - automaŔīnas nosaukumu un secÄ«bu direktorijā.
Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique
);



Pieņemsim, ka mums vajadzēja apmainÄ«t pirmo un otro automaŔīnu. VienkārÅ”s risinājums ir atjaunināt pirmo vērtÄ«bu uz otro, bet otro uz pirmo:

begin;
  update cars set ord = 2 where name = 'audi';
  update cars set ord = 1 where name = 'bmw';
commit;

Bet, palaižot Å”o kodu, mēs sagaidām ierobežojumu pārkāpumu, jo vērtÄ«bu secÄ«ba tabulā ir unikāla:

[23305] ERROR: duplicate key value violates unique constraint ā€œuk_carsā€
Detail: Key (ord)=(2) already exists.

Kā es varu darÄ«t savādāk? Pirmā iespēja: pievienojiet papildu vērtÄ«bas aizstāŔanu pasÅ«tÄ«jumam, kas tiek garantēts, ka tabulā neeksistē, piemēram, ā€œ-1ā€. ProgrammÄ“Å”anā to sauc par "divu mainÄ«go vērtÄ«bu apmaiņu ar treÅ”o". VienÄ«gais Ŕīs metodes trÅ«kums ir papildu atjauninājums.

Otrā iespēja: pārveidojiet tabulu, lai pasÅ«tÄ«juma vērtÄ«bai izmantotu peldoŔā komata datu tipu, nevis veselus skaitļus. Pēc tam, atjauninot vērtÄ«bu no 1, piemēram, uz 2.5, pirmais ieraksts automātiski "stāvēs" starp otro un treÅ”o. Å is risinājums darbojas, taču ir divi ierobežojumi. Pirmkārt, tas nedarbosies, ja vērtÄ«ba tiks izmantota kaut kur saskarnē. Otrkārt, atkarÄ«bā no datu veida precizitātes, pirms visu ierakstu vērtÄ«bu pārrēķināŔanas jums bÅ«s ierobežots iespējamo ievietojumu skaits.

TreŔā iespēja: atlikt ierobežojumu, lai tas tiktu pārbaudÄ«ts tikai saistÄ«bu izpildes laikā:

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique deferrable initially deferred
);

Tā kā mÅ«su sākotnējā pieprasÄ«juma loÄ£ika nodroÅ”ina, ka visas vērtÄ«bas izpildes brÄ«dÄ« ir unikālas, tas izdosies.

IepriekÅ” apspriestais piemērs, protams, ir ļoti sintētisks, taču tas atklāj ideju. MÅ«su lietojumprogrammā mēs izmantojam atliktos ierobežojumus, lai ieviestu loÄ£iku, kas ir atbildÄ«ga par konfliktu atrisināŔanu, kad lietotāji vienlaikus strādā ar koplietotiem logrÄ«ku objektiem uz tāfeles. Šādu ierobežojumu izmantoÅ”ana ļauj mums padarÄ«t lietojumprogrammas kodu nedaudz vienkārŔāku.

Kopumā, atkarībā no ierobežojuma veida, Postgres to pārbaudei ir trīs precizitātes līmeņi: rindas, transakcijas un izteiksmes līmeņi.
Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi
Avots: begrifs

CHECK un NOT NULL vienmēr tiek pārbaudÄ«ts rindas lÄ«menÄ«; citiem ierobežojumiem, kā redzams tabulā, ir dažādas iespējas. JÅ«s varat lasÄ«t vairāk Å”eit.

ÄŖsumā apkopojot, atliktie ierobežojumi vairākās situācijās nodroÅ”ina labāk lasāmu kodu un mazāk komandu. Tomēr jums par to ir jāmaksā, sarežģījot atkļūdoÅ”anas procesu, jo kļūdas raÅ”anās brÄ«dis un brÄ«dis, kad jÅ«s par to uzzinājāt, tiek savlaicÄ«gi atdalÄ«ti. Vēl viena iespējamā problēma ir tā, ka plānotājs ne vienmēr var izveidot optimālu plānu, ja pieprasÄ«jums ir saistÄ«ts ar atliktu ierobežojumu.

Pg_repack uzlaboŔana

Mēs esam apskatÄ«juÅ”i, kas ir atliktie ierobežojumi, bet kā tie ir saistÄ«ti ar mÅ«su problēmu? Atcerēsimies kļūdu, ko saņēmām iepriekÅ”:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Tas notiek, kad dati tiek kopēti no žurnāla tabulas uz jaunu tabulu. Tas izskatās dÄ«vaini, jo... žurnāla tabulas dati tiek piesaistÄ«ti kopā ar datiem avota tabulā. Ja tie atbilst sākotnējās tabulas ierobežojumiem, kā viņi var pārkāpt tos paÅ”us ierobežojumus jaunajā tabulā?

Kā izrādās, problēmas sakne slēpjas iepriekŔējā pg_repack solÄ«, kas rada tikai indeksus, bet ne ierobežojumus: vecajai tabulai bija unikāls ierobežojums, un jaunā tā vietā izveidoja unikālu indeksu.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Å eit ir svarÄ«gi atzÄ«mēt, ka, ja ierobežojums ir normāls un nav atlikts, tā vietā izveidotais unikālais indekss ir lÄ«dzvērtÄ«gs Å”im ierobežojumam, jo Unikālie ierobežojumi programmā Postgres tiek ieviesti, izveidojot unikālu indeksu. Bet atliktā ierobežojuma gadÄ«jumā uzvedÄ«ba nav tāda pati, jo indeksu nevar atlikt, un tas vienmēr tiek pārbaudÄ«ts sql komandas izpildes laikā.

Tādējādi problēmas bÅ«tÄ«ba slēpjas pārbaudes ā€œaizkavÄ“Å”anāā€: sākotnējā tabulā tas notiek apņemÅ”anās brÄ«dÄ«, bet jaunajā tabulā ā€“ sql komandas izpildes laikā. Tas nozÄ«mē, ka mums ir jāpārliecinās, ka abos gadÄ«jumos pārbaudes tiek veiktas vienādi: vai nu vienmēr ar kavÄ“Å”anos, vai vienmēr nekavējoties.

Tātad, kādas idejas mums bija?

Izveidojiet indeksu, kas ir līdzīgs atliktajam

Pirmā ideja ir veikt abas pārbaudes tÅ«lÄ«tējā režīmā. Tas var radÄ«t vairākus kļūdaini pozitÄ«vus ierobežojumus, taču, ja to ir maz, tam nevajadzētu ietekmēt lietotāju darbu, jo Ŕādi konflikti viņiem ir normāla situācija. Tās rodas, piemēram, ja divi lietotāji vienlaikus sāk rediģēt vienu un to paÅ”u logrÄ«ku, un otrā lietotāja klientam nav laika saņemt informāciju, ka pirmais lietotājs jau ir bloķējis logrÄ«ka rediģēŔanu. Šādā situācijā serveris atsakās no otrā lietotāja, un tā klients atsauc izmaiņas un bloķē logrÄ«ku. Nedaudz vēlāk, kad pirmais lietotājs pabeigs rediģēŔanu, otrais saņems informāciju, ka logrÄ«ks vairs nav bloķēts un varēs atkārtot savu darbÄ«bu.

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Lai nodroÅ”inātu, ka pārbaudes vienmēr ir neatliktā režīmā, mēs izveidojām jaunu indeksu, kas ir lÄ«dzÄ«gs sākotnējam atliktajam ierobežojumam:

CREATE UNIQUE INDEX CONCURRENTLY uk_tablename__immediate ON tablename (id, index);
-- run pg_repack
DROP INDEX CONCURRENTLY uk_tablename__immediate;

Testa vidē mēs saņēmām tikai dažas paredzamās kļūdas. Veiksmi! Mēs atkal palaidām pg_repack ražoÅ”anas režīmā un saņēmām 5 kļūdas pirmajā klasterÄ« darba stundas laikā. Tas ir pieņemams rezultāts. Tomēr jau otrajā klasterÄ« kļūdu skaits ievērojami palielinājās, un mums bija jāpārtrauc pg_repack.

Kāpēc tas notika? Kļūdas raÅ”anās iespējamÄ«ba ir atkarÄ«ga no tā, cik lietotāju vienlaikus strādā ar vieniem un tiem paÅ”iem logrÄ«kiem. AcÄ«mredzot tajā brÄ«dÄ« bija daudz mazāk konkurences izmaiņu ar pirmajā klasterÄ« glabātajiem datiem nekā pārējos, t.i. mums vienkārÅ”i "paveicās".

Ideja nedarbojās. Tajā brÄ«dÄ« mēs redzējām divus citus risinājumus: pārrakstÄ«t mÅ«su lietojumprogrammas kodu, lai atbrÄ«votos no atliktajiem ierobežojumiem, vai ā€œiemācÄ«tā€ pg_repack strādāt ar tiem. Mēs izvēlējāmies otro.

Aizstāt indeksus jaunajā tabulā ar atliktiem ierobežojumiem no sākotnējās tabulas

PārskatÄ«Å”anas mērÄ·is bija acÄ«mredzams - ja sākotnējā tabulā ir atliktais ierobežojums, tad jaunajai ir jāizveido Ŕāds ierobežojums, nevis indekss.

Lai pārbaudÄ«tu izmaiņas, mēs uzrakstÄ«jām vienkārÅ”u testu:

  • tabula ar atlikto ierobežojumu un vienu ierakstu;
  • ievietot datus cilpā, kas ir pretrunā ar esoÅ”u ierakstu;
  • veikt atjauninājumu ā€“ dati vairs nekonfliktē;
  • veikt izmaiņas.

create table test_table
(
  id serial,
  val int,
  constraint uk_test_table__val unique (val) deferrable initially deferred 
);

INSERT INTO test_table (val) VALUES (0);
FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (0) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    COMMIT;
  END;
END LOOP;

Sākotnējā pg_repack versija vienmēr avarēja pirmajā ievietoÅ”anas reizē, pārveidotā versija strādāja bez kļūdām. Lieliski.

Mēs pārejam uz ražoÅ”anu un atkal saņemam kļūdu tajā paŔā fāzē, kopējot datus no žurnāla tabulas uz jaunu:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Klasiskā situācija: testa vidēs viss strādā, bet ražoÅ”anā ne?!

APPLY_COUNT un divu grupu krustojums

Mēs sākām analizēt kodu burtiski rindiņu pa rindiņai un atklājām svarīgu punktu: dati tiek pārsūtīti no žurnāla tabulas uz jaunu partijās, konstante APPLY_COUNT norādīja partijas lielumu:

for (;;)
{
num = apply_log(connection, table, APPLY_COUNT);

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  /* there might be still some tuples, repeat. */
...
}

Problēma ir tāda, ka dati no sākotnējā darÄ«juma, kurā vairākas operācijas, iespējams, varētu pārkāpt ierobežojumu, pārsÅ«tot, var nonākt divu pakeÅ”u krustpunktā ā€” puse komandu tiks izpildÄ«tas pirmajā partijā, bet otra puse otrajā. Un Å”eit, atkarÄ«bā no jÅ«su veiksmes: ja komandas pirmajā partijā neko nepārkāpj, tad viss ir kārtÄ«bā, bet, ja to dara, rodas kļūda.

APPLY_COUNT ir vienāds ar 1000 ierakstiem, kas izskaidro, kāpēc mÅ«su testi bija veiksmÄ«gi ā€” tie neaptvēra ā€œpakeÅ”u savienojumaā€ gadÄ«jumu. Mēs izmantojām divas komandas - ievietot un atjaunināt, tāpēc tieÅ”i 500 transakcijas no divām komandām vienmēr tika ievietotas partijā, un mums nebija nekādu problēmu. Pēc otrā atjauninājuma pievienoÅ”anas mÅ«su labojums pārstāja darboties:

FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (1) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    UPDATE test_table set val = i where id = v_id; -- one more update
    COMMIT;
  END;
END LOOP;

Tātad nākamais uzdevums ir nodroÅ”ināt, lai dati no sākotnējās tabulas, kas tika mainÄ«ti vienā darÄ«jumā, nonāktu jaunajā tabulā arÄ« viena darÄ«juma ietvaros.

Atteikums no partiju komplektēŔanas

Un atkal mums bija divi risinājumi. Pirmkārt: pilnÄ«bā atteiksimies no sadalÄ«Å”anas pa partijām un pārsÅ«tÄ«sim datus vienā darÄ«jumā. Å Ä« risinājuma priekÅ”rocÄ«ba bija tā vienkārŔība ā€“ nepiecieÅ”amās koda izmaiņas bija minimālas (starp citu, vecākās versijās pg_reorg darbojās tieÅ”i tā). Bet ir problēma - mēs veidojam ilgstoÅ”u darÄ«jumu, un tas, kā jau iepriekÅ” teikts, ir drauds jauna uzpÅ«Å”anās raÅ”anās brÄ«dim.

Otrs risinājums ir sarežģītāks, bet, iespējams, pareizāks: žurnāla tabulā izveidojiet kolonnu ar tās darÄ«juma identifikatoru, kas pievienoja tabulai datus. Pēc tam, kad mēs kopējam datus, mēs varam tos grupēt pēc Ŕī atribÅ«ta un nodroÅ”ināt, ka saistÄ«tās izmaiņas tiek pārsÅ«tÄ«tas kopā. Partija tiks veidota no vairākiem darÄ«jumiem (vai viena liela), un tās lielums mainÄ«sies atkarÄ«bā no tā, cik daudz datu Å”ajos darÄ«jumos tika mainÄ«ts. Ir svarÄ«gi atzÄ«mēt, ka, tā kā dažādu darÄ«jumu dati žurnāla tabulā nonāk nejauŔā secÄ«bā, tos vairs nebÅ«s iespējams nolasÄ«t secÄ«gi, kā tas bija iepriekÅ”. seqscan katram pieprasÄ«jumam ar filtrÄ“Å”anu pēc tx_id ir pārāk dārgs, ir nepiecieÅ”ams indekss, taču tas arÄ« palēninās metodi, jo tā atjaunināŔana ir saistÄ«ta ar papildu izmaksām. Kopumā, kā vienmēr, jums ir nepiecieÅ”ams kaut ko upurēt.

Tāpēc mēs nolēmām sākt ar pirmo iespēju, jo tā ir vienkārŔāka. Pirmkārt, bija jāsaprot, vai ilgstoÅ”s darÄ«jums bÅ«s reāla problēma. Tā kā galvenā datu pārsÅ«tÄ«Å”ana no vecās tabulas uz jauno notiek arÄ« vienā garā darÄ«jumā, jautājums pārvērtās par ā€œcik mēs palielināsim Å”o darÄ«jumu?ā€ Pirmā darÄ«juma ilgums galvenokārt ir atkarÄ«gs no tabulas lieluma. Jaunas ilgums ir atkarÄ«gs no tā, cik izmaiņu uzkrājas tabulā datu pārsÅ«tÄ«Å”anas laikā, t.i. uz slodzes intensitāti. Pg_repack izpilde notika minimālas pakalpojuma slodzes laikā, un izmaiņu apjoms bija nesamērÄ«gi mazs salÄ«dzinājumā ar sākotnējo tabulas izmēru. Nolēmām, ka varam atstāt novārtā jauna darÄ«juma laiku (salÄ«dzinājumam, vidēji tā ir 1 stunda un 2-3 minÅ«tes).

Eksperimenti bija pozitÄ«vi. Uzsākt arÄ« ražoÅ”anu. SkaidrÄ«bas labad Å”eit ir attēls ar vienas datu bāzes lielumu pēc palaiÅ”anas:

Postgres: uzpūŔanās, pg_repack un atliktie ierobežojumi

Tā kā Å”is risinājums mÅ«s pilnÄ«bā apmierināja, tad otro nemēģinājām ieviest, taču apsveram iespēju to apspriest ar paplaÅ”inājuma izstrādātājiem. MÅ«su paÅ”reizējā redakcija diemžēl vēl nav gatava publicÄ“Å”anai, jo problēmu atrisinājām tikai ar unikāliem atliktajiem ierobežojumiem, un pilnvērtÄ«gam ielāpam ir nepiecieÅ”ams nodroÅ”ināt atbalstu citiem veidiem. Mēs ceram, ka varēsim to izdarÄ«t arÄ« turpmāk.

VarbÅ«t jums ir jautājums, kāpēc mēs pat iesaistÄ«jāmies Å”ajā stāstā ar pg_repack modifikāciju un neizmantojām, piemēram, tā analogus? Kādā brÄ«dÄ« arÄ« mēs par to domājām, taču pozitÄ«vā pieredze, izmantojot to agrāk, uz tabulām bez atliktiem ierobežojumiem, motivēja mÅ«s mēģināt izprast problēmas bÅ«tÄ«bu un to novērst. Turklāt arÄ« citu risinājumu izmantoÅ”ana prasa laiku testu veikÅ”anai, tāpēc nolēmām, ka vispirms mēģināsim tajā novērst problēmu un, ja sapratÄ«sim, ka nevaram to izdarÄ«t saprātÄ«gā termiņā, tad sāksim meklēt analogus. .

Atzinumi

Ko mēs varam ieteikt, pamatojoties uz savu pieredzi:

  1. Uzraugiet savu vēdera uzpÅ«Å”anos. Pamatojoties uz uzraudzÄ«bas datiem, varat saprast, cik labi ir konfigurēts autovakuums.
  2. Noregulējiet AUTOVACUUM, lai uzpÅ«Å”anās bÅ«tu pieņemamā lÄ«menÄ«.
  3. Ja uzpÅ«Å”anās joprojām aug un jÅ«s nevarat to pārvarēt, izmantojot gatavus rÄ«kus, nebaidieties izmantot ārējos paplaÅ”inājumus. Galvenais ir visu labi pārbaudÄ«t.
  4. Nebaidieties modificēt ārējos risinājumus atbilstoÅ”i savām vajadzÄ«bām ā€” dažreiz tas var bÅ«t efektÄ«vāk un pat vienkārŔāk nekā mainÄ«t savu kodu.

Avots: www.habr.com

Pievieno komentāru