Postgres: bloat, pg_repack ja deferred constraints

Postgres: bloat, pg_repack ja deferred constraints

Tabelite ja indeksite paisumise (bloat) probleem on hÀsti tuntud ja see esineb mitte ainult Postgresis. On olemas vÔimalusi selle probleemiga tegelemiseks, nÀiteks VACUUM FULL vÔi CLUSTER, kuid need blokeerivad tabelid töötamise ajal, seega ei pruugi neid alati kasutada.

Artiklis kÀsitletakse veidi teooriat selle kohta, kuidas paisumine tekib, kuidas sellega saab vÔidelda, deferred constraints ja probleeme, mida need pÔhjustavad pg_repack laienduse kasutamisel.

See artikkel on kirjutatud minu ettekande pÔhjal PgConf.Russia 2020.

Vaata videot

Miks paisumine tekib

PostgreSQL pĂ”hineb mitme versiooniga mudelil (MVCC). Selle pĂ”himĂ”te on see, et igal tabeli real vĂ”ib olla mitmeid versioone, samas kui tehingud nĂ€evad vaid ĂŒht neist versioonidest, kuid mitte tingimata sama. See vĂ”imaldab mitmel tehingul samal ajal töötada, mĂ”jutamata ĂŒksteist oluliselt.

On ilmselge, et need versioonid tuleb salvestada. Postgres töötab mÀluga lehe kaupa ja leht on minimaalne andmemaht, mida saab lugeda kettalt vÔi kirjutada. KÀsitletakse vÀikest nÀidet, et mÔista, kuidas see toimub.

Oletame, et meil on tabel, kuhu oleme lisanud mitu salvestust. Faili esimesel lehel, kus tabelit hoitakse, on ilmunud uued andmed. Need on ridade elavad versioonid, mis on pÀrast commit'i teistele tehingutele kÀttesaadavad (lihtsuse huvides arvame, et Isolatsiooni tase on Read Committed).

Postgres: bloat, pg_repack ja deferred constraints

SeejĂ€rel uuendasime ĂŒhte salvestust ja mĂ€rkisime seelĂ€bi vana versiooni aegunuks.

Postgres: bloat, pg_repack ja deferred constraints

Samm-sammult, uuendades ja eemaldades ridade versioone, saime lehe, kus umbes pooled andmed on „prĂŒgi“. Need andmed ei ole ĂŒhelegi tehingule nĂ€htavad.

Postgres: bloat, pg_repack ja deferred constraints

Postgres'is on olemas mehhanism VACUUM, mis puhastab aegunud versioonid ja vabastab ruumi uutele andmetele. Kuid kui see ei ole piisavalt agressiivselt seadistatud vĂ”i on hĂ”ivatud muude tabelitega, siis jÀÀvad „prĂŒgiga andmed“ alles ja me peame kasutama lisalehti uutele andmetele.

Nii et meie nÀites koosneb tabel mingil hetkel neljast lehtedest, kuid elavaid andmeid on seal vaid pool. SeetÔttu, kui me tabelisse pöördume, loeme palju rohkem andmeid, kui vajalik.

Postgres: bloat, pg_repack ja deferred constraints

Isegi kui VACUUM eemaldab praegu kÔik ebaolulised ridade versioonid, ei parane olukord dramaatiliselt. Meil on vaba ruumi lehtedes vÔi isegi terveid lehti uute ridade jaoks, kuid me ikkagi loeme rohkem andmeid, kui vajalik.
Muide, kui tĂ€iesti tĂŒhi leht (teine meie nĂ€ites) oleks faili lĂ”pus, saaks VACUUM selle kĂ€rpida. Kuid praegu asub see keset faili, seega ei saa me sellega midagi teha.

Postgres: bloat, pg_repack ja deferred constraints

Kuna selliste tĂŒhi vĂ”i tugevalt hajutatud lehtede arv muutub suureks, mida nimetatakse bloatiks, hakkab see mĂ”jutama jĂ”udlust.

KĂ”ik ĂŒlaltoodud on bloati tekke mehhanism tabelites. Indeksites toimub see umbes samamoodi.

Kas mul on bloat?

On mitmeid viise, kuidas mÀÀrata, kas teil on bloat. Esimese idee kohaselt kasutatakse Postgresi sisemist statistikat, kus on ligikaudne teave tabelites olevate ridade arvu, “elus” ridade arvu jne kohta. Internetist leiab palju variatsioone juba valmis skriptidest. Me vĂ”tsime aluseks skripti PostgreSQL eksperdid, kes saavad hinnata tabelite ja toast ja bloat btree-indeksite bloat’i. Meie kogemuse jĂ€rgi on selle tĂ€psus 10-20%.

Teine vÔimalus on kasutada laiendust pgstattuple, mis vÔimaldab vaadata lehtede sisu ning saada nii hinnangulist kui ka tÀpset bloat'i vÀÀrtust. Kuid teisel juhul tuleb skaneerida kogu tabel.

VĂ€ike bloat, kuni 20%, on meie arvates vastuvĂ”etav. Seda vĂ”ib pidada analoogseks fillfactor’iga tabelites ja indeksites. 50% ja rohkem vĂ”ib ilmuda jĂ”udlusega probleeme.

Bloat’i vastu vĂ”itlemise viisid

Postgresis on mitu sisseehitatud meetodit bloat’i tĂ”kestamiseks, kuid need ei pruugi alati ja kĂ”igile sobida.

Seadistada AUTOVACUUM, et bloat ei tekiks. Ja kui tĂ€psemalt, et see oleks teie jaoks vastuvĂ”etaval tasemel. See vĂ”ib tunduda nagu „kapteni” nĂ”uanne, kuid reaalsus ei ole alati midagi, mida on kerge saavutada. NĂ€iteks, kui teete aktiivset arendust koos pideva andmebaasi struktuuri muutmisega vĂ”i toimub mingisugune andmete migratsioon. Tulemusena vĂ”ib teie koormusprofiil sageli muutuda ja tavaliselt on see erinev erinevate tabelite jaoks. See tĂ€hendab, et peate pidevalt natuke ette töötama ja kohandama AUTOVACUUMi, et vastata iga tabeli muutuvatele vajadustele. Kuid on selge, et seda ei ole kerge teha.

Teine levinud pĂ”hjus, miks AUTOVACUUM ei suuda tabeleid töödelda, on pikaajalised tehingud, mis ei luba tal andmeid kustutada, kuna need on nende tehingute jaoks saadaval. Soovitus on selge: vabaneda „riputatavast“ tehingust ja minimeerida aktiivsete tehingute aega. Kuid kui teie rakenduse koormus on OLAP ja OLTP hĂŒbriid, vĂ”ib teil sama ajal olla nii palju sagedaid uuendusi ja lĂŒhikesi pĂ€ringuid kui ka pikaajalisi operatsioone, nĂ€iteks aruande koostamine. Sellises olukorras tasub kaaluda koormuse jagamist erinevatele andmebaasidele, mis vĂ”imaldab igaĂŒhe tĂ€psemat seadistamist.

Veel ĂŒks nĂ€ide: isegi kui profiil on homogeenne, kuid andmebaas on vĂ€ga kĂ”rge koormuse all, siis isegi kĂ”ige agressiivsem AUTOVACUUM ei pruugi toime tulla ning bloat hakkab tekkima. Skaleerimine (vertikaalne vĂ”i horisontaalne) on ainus lahendus.

Kuidas kÀituda olukorras, kus olete AUTOVACUUMi seadistanud, kuid bloat jÀtkab kasvu.

Meeskond VACUUM FULL restruktureerib tabelite ja indeksite sisu, jĂ€ttes neisse vaid aktuaalsed andmed. Bloat'i kĂ”rvaldamiseks töötab see ideaalselt, kuid selle tĂ€itmise ajal haaratakse tabelile eksklusiivne lukustus (AccessExclusiveLock), mis ei vĂ”imalda selle tabeliga pĂ€ringute tĂ€itmist, isegi select'ide puhul. Kui saate endale lubada teenuse vĂ”i selle osa peatamist mĂ”neks timeks (alates kĂŒmnetest minutidest kuni mitme tunni jooksul sĂ”ltuvalt andmebaasi suurusest ja teie riistvarast), on see variant parim. Kahjuks ei suuda me VACUUM FULL'i planeeritud hoolduse ajal teostada, seega see meetod meile ei sobi.

Meeskond CLUSTER ka restruktureerib tabelite sisu nagu VACUUM FULL, samas lubades mÀÀrata indeksi, mille alusel andmed fĂŒĂŒsiliselt kettal jĂ€rjestatakse (kuid tulevikus uute ridade jaoks ei saa jĂ€rjestust garanteerida). Teatud olukordades on see ĂŒsna hea optimeerimine mitmete pĂ€ringute jaoks – indeksiga mitme kirje lugemiseks. Puuduseks on selle kĂ€su sama omadus nagu VACUUM FULL'il – see blokeerib tabeli töötamise ajal.

Meeskond REINDEX on sarnane kahe eelneva versiooniga, kuid see teostab konkreetse indeksi vĂ”i kĂ”igi tabeli indeksite ĂŒmberkujundamise. Lukustused on veidi nĂ”rgemad: ShareLock tabeli jaoks (takistab muudatusi, kuid lubab selecti teostada) ja AccessExclusiveLock ĂŒmberkujundatavale indeksile (blokib pĂ€ringud, mis kasutavad seda indeksit). Siiski, Postgresi 12. versioonis ilmus parameeter CONCURRENTLY, mis lubab indeksit ĂŒmber kujundada, blokeerimata samal ajal rikkaid lisamisi, muudatusi vĂ”i eemaldamisi.

Varem, Postgresi varasemates versioonides, on vĂ”imalik saavutada sama tulemus, mis on sarnane REINDEX CONCURRENTLY-ile, kasutades CREATE INDEX CONCURRENTLY. See lubab luua indeksi ilma range lukustamiseta (ShareUpdateExclusiveLock, mis ei takista samaaegseid pĂ€ringuid), seejĂ€rel asendada vana indeks uuega ja eemaldada vana indeks. See lubab eemaldada indeksi ĂŒlekasvu, takistamata teie rakenduse tööd. Oluline on arvestada, et indeksite ĂŒmberkujundamise korral on diskipĂ”hises sĂŒsteemis tĂ€iendav koormus.

Seega, kuigi indeksite jaoks on olemas viise ĂŒlekasvu eemaldamiseks „kuumalt“, ei ole tabelite jaoks selliseid meetodeid. Siia tulevad erinevad vĂ€lised laiendused: pg_repack (varasemalt pg_reorg), pgcompact, pgcompacttable ja teised. Selle artikli raames ma ei hakka neid vĂ”rdlema ja rÀÀgin ainult pg_repack'ist, mida pĂ€rast teatavat kohandamist kasutame.

Kuidas pg_repack töötab

Postgres: bloat, pg_repack ja deferred constraints
Oletame, et meil on tĂ€iesti tavaline tabel – koos indeksitega, piirangutega ja, kahjuks, bloat'iga. Esimese sammuna loob pg_repack logitabeli, et salvestada kĂ”iki muudatusi töötamise ajal. Trikkri kaudu replitseeritakse need muudatused iga sisestuse, vĂ€rskenduse ja kustutamise korral. SeejĂ€rel luuakse tabel, mis on struktuuri poolest sarnane algsele, kuid ilma indeksite ja piiranguteta, et mitte andmete sisestamise protsessi aeglustada.

SeejÀrel kannab pg_repack uude tabelisse andmed vanast, filtreerides automaatselt kÔik ebaolulised read ja siis loob uue tabeli jaoks indeksid. KÔigi nende toimingute sooritamise ajal kogunevad logitabelisse muudatused.

JĂ€rgmine samm on muudatuste viimine uude tabelisse. Ülekandeprotsess toimub mitmes etapis, ja kui logitabelis jÀÀb vĂ€hem kui 20 kirjet, seab pg_repack sisse tiheda lukustuse, viib viimased andmed ĂŒle ja asendab vana tabeli uuega Postgresi sĂŒsteemitabelites. See on ainus ja vĂ€ga lĂŒhike hetk, mil te ei saa tabeliga töötada. PĂ€rast seda eemaldatakse vana tabel ja logitabel, vabastades ruumi failisĂŒsteemis. Protsess on lĂ”pule viidud.

Teoreetiliselt tundub kÔik suurepÀrane, aga kuidas see praktikas vÀlja nÀeb? Testisime pg_repack'i ilma koormuseta ja koormuse all, kontrollisime selle toimimist juhusliku peatamise korral (teisisÔnu, kui vajutada Ctrl+C). KÔik testid olid positiivsed.

Siis suundusime tootmisse — ja siin lĂ€ks kĂ”ik nii, nagu me ootame.

Esimene kord tootmises

Esimeses klastris saime vea, mis seondus unikaalse piirangu rikkumisega:

$ ./pg_repack -t tablename -o id
INFO: tabeli "tablename" ĂŒmberpaigutamine
ERROR: pÀring ebaÔnnestus: 
    ERROR: duplikaadi vÔtme vÀÀrtus rikub unikaalset piirangut "index_16508"
DETAIL: VÔti (id, indeks)=(100500, 42) on juba olemas.

See piirangul oli automaatselt genereeritud nimetus index_16508 – selle lĂ”i pg_repack. Selle koosseisu kuuluvaid atribuute analĂŒĂŒsides mÀÀrasime me “meie” piirangu, mis talle vastab. Probleem seisnes aga selles, et see pole tavaline piirang, vaid edasilĂŒkatud (edasilĂŒkatud piirang), s.t. selle kontrollimine toimub hiljem kui sql-kĂ€sk, mis toob kaasa ootamatud tagajĂ€rjed.

EdasilĂŒkatud piirangud: miks need on vajalikud ja kuidas need töötavad

Veidi teooriat edasilĂŒkatud piirangute kohta.
Vaatame lihtsat nĂ€idet: meil on autotĂŒĂŒpide loendustabel, millel on kaks atribuuti – nimetus ja auto jĂ€rjestus loendis.
Postgres: bloat, pg_repack ja deferred constraints

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



Oletame, et meil on vaja vahetada esimese ja teise auto kohti. Lahendus “otseselt” – uuendada esimene vÀÀrtus teiseks ja teine esimeseks:

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

Kuid selle koodi tÀitmisel saame me oodatult piirangu rikkumise, sest tabeli vÀÀrtuste jÀrjekord on ainulaadne:

[23305] VIGA: duubeldatud vĂ”tme vÀÀrtus rikub ainulaadse piirangu “uk_cars”
Detail: Key (ord)=(2) juba eksisteerib.

Kuidas teisiti teha? Esimene variant: lisada tÀiendav vÀÀrtuse asendamine jÀrjestusse, mida tabelis kindlasti ei eksisteeri, nÀiteks "-1". Programmeerimises nimetatakse seda "kahe muutuja vÀÀrtuste vahetuseks kolmanda kaudu". Selle meetodi ainus puudus on tÀiendav uuendus.

Teine variant: projekteerida tabel ĂŒmber, et kasutada jĂ€rjestuse vÀÀrtusena ujuva komaga andmetĂŒĂŒpi, selle asemel et tĂ€isarve. Siis, kui uuendate vÀÀrtust nĂ€iteks 1-lt 2.5-le, asetub esimene kirje automaatselt teise ja kolmanda vahele. See lahendus töötab, kuid sellel on kaks piirangut. Esiteks, see ei sobi, kui vÀÀrtust kasutatakse kuskil kasutajaliideses. Teiseks, sĂ”ltuvalt andmetĂŒĂŒbi tĂ€psusest on teil piiratud arv vĂ”imalikke sisestusi enne, kui tuleb kĂ”ikide kirje vÀÀrtuste ĂŒmberarvutamine.

Kolmas variant: teha piirang viivitamata, et seda kontrollitakse ainult kommiteerimise hetkel:

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

Kuna meie algse pÀringu loogika tagab, et enne kinnitamist on kÔik vÀÀrtused unikaalsed, tÀidetakse see edukalt.

Ülaltoodud nĂ€ide on muidugi vĂ€ga sĂŒnteetiline, kuid ideed see avab. Meie rakenduses kasutame viivitustega piiranguid, et rakendada loogikat, mis lahendab konflikte, kui kasutajad töötavad samaaegselt jagatud objektide vidinatega tahvlil. Selliste piirangute kasutamine vĂ”imaldab meil rakenduskoodi natuke lihtsamaks muuta.

Üldiselt, sĂ”ltuvalt piirangu tĂŒĂŒbist Postgresis, on nende kontrollimisel kolm granulaarsuse taset: rea, tehingu ja vĂ€ljendi tase.
Postgres: bloat, pg_repack ja deferred constraints
Allikas: begriffs

CHECK ja NOT NULL kontrollitakse alati rea tasandil, teiste piirangute puhul, nagu nÀha tabelist, on erinevaid variante. Rohkem teavet saab lugeda. siit.

LĂŒhidalt kokku vĂ”ttes pakuvad viivitatud piirangud teatud olukordades paremini loetavat koodi ja vĂ€hem kĂ€ske. Kuid selle eest tuleb maksta debugeerimisprotsessi keerukuse eest, kuna vea tekkimise hetk ja hetk, mil te sellest teada saate, on ajaliselt lahus. Veel ĂŒks vĂ”imalik probleem on see, et ajakava ei suuda alati koostada optimaalselt plaani, kui pĂ€ringus on viivitatud piirang.

Töötame edasi pg_repackiga

Oleme saanud aru, mis on viivitatud piirangud, kuid kuidas need on seotud meie probleemiga? Tuletagem meelde viga, mille saime varem:

$ ./pg_repack -t tablename -o id
INFO: tabeli "tablename" ĂŒmberpaigutamine
ERROR: pÀring ebaÔnnestus: 
    ERROR: duplikaadi vÔtme vÀÀrtus rikub unikaalset piirangut "index_16508"
DETAIL: VÔti (id, indeks)=(100500, 42) on juba olemas.

See ilmneb siis, kui kopeeritakse andmeid logitabelist uude tabelisse. See tundub kummaline, sest logitabeli andmed kinnitatakse koos algtabeli andmetega. Kui need vastavad algtabeli piirangutele, siis kuidas nad saavad uues tabelis neid samme rikkuda?

Kuidas selgus, peitub probleemi juur pg_repack'i eelnevas etapis, kus luuakse ainult indeksid, kuid mitte piirangud: vanas tabelis oli unikaalne piirang, kuid uues loodi selle asemel unikaalne indeks.

Postgres: bloat, pg_repack ja deferred constraints

Siin on oluline mĂ€rkida, et kui piirang on tavaline, mitte edasilĂŒkatud, siis loodud ainulaadne indeks on sellele piirangule samavÀÀrne, kuna Postgresis rakendatakse ainulaadseid piiranguid ainulaadsete indeksite loomise abil. Kuid edasilĂŒkatud piirangu korral ei ole kĂ€itumine sama, kuna indeks ei saa olla edasilĂŒkatud ning kontrollitakse alati SQL-kĂ€su tĂ€itmise hetkel.

Seega seisneb probleemi sisu kontrollimise „edasilĂŒkatuses”: algses tabelis toimub see kinnitamise hetkel, uues aga SQL-kĂ€su tĂ€itmise hetkel. See tĂ€hendab, et meil on vaja tagada, et kontrollid toimuksid mĂ”lemal juhul sama moodi: kas alati edasilĂŒkatult vĂ”i alati kohe.

Nii et, millised ideed meil olid.

Luua indeks, mis on analoogne edasilĂŒkatule.

Esimene idee on teostada mĂ”lemad kontrollid viivitamatult. See vĂ”ib pĂ”hjustada mĂ”ningaid valepositiivseid piiranguid, kuid kui neid on vĂ€he, ei tohiks see kasutajate tööd mĂ”jutada, kuna sellised konfliktid on nende jaoks normaalne olukord. Need juhtuvad nĂ€iteks siis, kui kaks kasutajat alustavad samal ajal ĂŒhe ja sama vidina redigeerimist, ja teise kasutaja klient ei jĂ”ua saada teavet selle kohta, et vidin on juba esimese kasutaja poolt redigeerimiseks blokeeritud. Sel juhul vastab server teisele kasutajale keeldumisega ja tema klient tagastab muudatused ning blokeerib vidina. Veidi hiljem, kui esimene kasutaja on redigeerimise lĂ”petanud, saab teine teate, et vidin ei ole enam blokeeritud, ja saab oma tegevust korrata.

Postgres: bloat, pg_repack ja deferred constraints

Kuna kontrollid peavad alati olema kiired, lÔime uue indeksi, mis on analoogne originaalsele viivitamatule piirangule:

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

Testkeskkonnas leidsime vaid mÔned oodatud vead. Edu! KÀivitasime uuesti pg_repack tootmises ja saime esimeses klastris tunni töö jooksul 5 viga. See on vastuvÔetav tulemus. Kuid teises klastris suurenes veade arv mitmekordselt ja pidime pg_repacki peatama.

Miks see juhtus? Veatekke tĂ”enĂ€osus sĂ”ltub sellest, kui palju kasutajaid töötab samal ajal samade vidinatega. NĂ€ib, et sel hetkel oli esimeses klastris andmeid, kus konkurentsi muudatusi tehti, oluliselt vĂ€hem kui teistes, st meie lihtsalt „vedas“.

Idee ei toiminud. Sel hetkel nĂ€gime kahte muud lahendust: kirjutada oma rakenduskood ĂŒmber, et loobuda edasi lĂŒkatud piirangutest, vĂ”i „Ôpetada“ pg_repack neid rakendama. Me valisime teise.

Asendada uue tabeli indeksid algse tabeli edasi lĂŒkatud piirangutega.

Paranduse eesmĂ€rk oli ilmselge – kui algsel tabelil on edasi lĂŒkatud piirang, siis tuleb uue jaoks luua selline piirang, mitte indeks.

Meie muudatuste kontrollimiseks kirjutasime lihtsa testi:

  • edastuspiiranguga tabel ja ĂŒks rida;
  • sisestame tsĂŒklis andmed, mis on olemasoleva kirje konfliktis;
  • teeme uuenduse – andmed ei ole enam konfliktis;
  • salvestame muudatused.

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;

Originaalne versioon pg_repack kukkus alati esimesel sisestamisel, muudetud versioon töötas vigadeta. SuurepÀrane.

LÀheme tootmisse ja saame jÀlle vea samas faasis, kui andmed logitabelist uude kopeeritakse:

$ ./pg_repack -t tablename -o id
INFO: tabeli "tablename" ĂŒmberpaigutamine
ERROR: pÀring ebaÔnnestus: 
    ERROR: duplikaadi vÔtme vÀÀrtus rikub unikaalset piirangut "index_16508"
DETAIL: VÔti (id, indeks)=(100500, 42) on juba olemas.

Klassikaline olukord: testkeskkondades töötab kĂ”ik, aga tootmises – ei?!

APPLY_COUNT ja kahe partii ĂŒhendus

Alustasime koodi analĂŒĂŒsimist sĂ”na-sĂ”nalt ja avastasime olulise hetke: andmete ĂŒlekandmine logitabelist uude toimub partii kaupa, konstant APPLY_COUNT nĂ€itas partii suurust:

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

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

Probleem on selles, et algse tehingu andmed, mille raames mitu operatsiooni vĂ”ivad potentsiaalselt piirangut rikkuda, vĂ”ivad siirdumise kĂ€igus sattuda kahe partii piirile – pool kĂ€ske kinnitatakse esimeses partis ja teine pool teises. Ja siin on see lapse mĂ€ng: kui esimeses partis kĂ€sud midagi ei riku, siis on kĂ”ik hĂ€sti, aga kui rikuvad – toimub viga.

APPLY_COUNT on 1000 kirjet, mis selgitab, miks meie testid lĂ€ksid edukalt – nad ei katnud 'partide piiri' juhtumit. Kasutasime kahte kĂ€sku – insert ja update, seega tĂ€pselt 500 tehingut kahe kĂ€suga mahtusid alati partiisse ja me ei kohanud probleeme. PĂ€rast teise update'i lisamist lĂ”petas meie muudatus töötamise:

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; -- veel ĂŒks update
    COMMIT;
  END;
END LOOP;

Seega, jĂ€rgmine ĂŒlesanne on muuta nii, et algse tabeli andmed, mis muudetakse ĂŒhes tehingus, jĂ”uaksid uude tabelisse samuti ĂŒhes tehingus.

Partii loobumisest

Ja meil oli jĂ€lle kaks lahenduse varianti. Esimene: loobume tĂ€iesti partii jagamisest ja teeme andmete ĂŒlekande ĂŒhe tehinguga. Selle lahenduse kasuks rÀÀkis selle lihtsus — vajalikud koodimuudatused on minimaalsed (tuletan meelde, et vanemates versioonides töötas pg_reorg just niimoodi). Kuid probleem on see — me loome pika tehingu, mis, nagu varasemalt mainitud, on uus bloati tekkimise oht.

Teine lahendus on keerulisem, kuid vĂ”ib-olla ka Ă”igem: luua logitabelis veerg, mis sisaldab tehingu identifikaatorit, mis andmed tabelisse lisas. Sel juhul saame andmete kopeerimise ajal rĂŒhmitada need selle atribuudi jĂ€rgi ja tagada, et seotud muudatused kantakse ĂŒle koos. Partii koosneb mitmest tehingust (vĂ”i ĂŒhest suurest) ja selle suurus varieerub sĂ”ltuvalt sellest, kui palju andmeid on nendes tehingutes muudetud. Oluline on mĂ€rkida, et kuna erinevate tehingute andmed satuvad logitabelisse juhuslikus jĂ€rjekorras, ei saa seda enam jĂ€rjestikku lugeda nagu varem. seqscan igas pĂ€ringus, kus filterdame tx_id jĂ€rgi, on liiga kulukas, vajalik indeks, kuid see aeglustaks meetodi tööd ka oma uuendamise kulude tĂ”ttu. KokkuvĂ”ttes tuleb nagu tavaliselt millegi nimel ohverdada.

Nii, otsustasime alustada esimesest variantist, kuna see oli lihtsam. Esiteks pidi selguma, kas pikaajaline tehing on tĂ”eliseks probleemiks. Kuna peamine andmete ĂŒlekandmine vanast tabelist uude toimub samuti ĂŒhe pikaajalise tehingu raames, muutus kĂŒsimus selleks, "kui palju me seda tehingut suurendame?" Esimese tehingu kestus sĂ”ltub peamiselt tabeli suurusest. Uue kestus sĂ”ltub aga sellest, kui palju muudatusi tabelis on kogunenud andmete ĂŒlekandmise aja jooksul, st koormuse intensiivsusest. pg_repack'i kĂ€itamine toimus teenuste minimaalsete koormuste ajal, ja muudatuste maht oli vĂ”rreldes algse tabeli mahuga jĂ€rsult vĂ€ike. Otsustasime, et vĂ”ime uue tehingu kestuse tĂ€helepanuta jĂ€tta (keskmiselt on see 1 tund ja 2-3 minutit).

Eksperimentide tulemused olid positiivsed. Ülesanne seadistuses töötas samuti. Selguse huvides – pilt ĂŒhe andmebaasi suurusest pĂ€rast kĂ€itust:

Postgres: bloat, pg_repack ja deferred constraints

Kuna see lahendus meie vajadustele tĂ€ielikult vastas, siis ei hakanud me teist tĂ”eks rakendama, kuid kaalume vĂ”imalust selle arendajatega arutada. Meie praegune tĂ€iendus, kahjuks, ei ole veel avaldamiseks valmis, kuna oleme lahendanud probleemi ainult unikaalsete edasilĂŒkatud piirangute osas, samas kui tĂ€iendava plaani jaoks on vajalik ka teiste tĂŒĂŒpide toe loomine. Loodame, et Ă”nnestub see tulevikus saavutada.

VĂ”ib-olla tekkis teil kĂŒsimus, miks me ĂŒldse sellesse pg_repacki tĂ€iustamise loosse laskusime, mitte ei kasutanud selle analooge? MĂ”nes mĂ”ttes mĂ”tlesime ka sellele, kuid meie varasem positiivne kogemus selle kasutamisel edasilĂŒkatud piiranguteta tabelites motiveeris meid proovima probleemi sisu mĂ”ista ja seda parandada. Lisaks vĂ”tab teiste lahenduste kasutusele vĂ”tmine samuti aega testimiseks, seetĂ”ttu otsustasime, et kĂ”igepealt proovime probleemi selles lahenduses lahendada, ja kui mĂ”istame, et ei suuda seda mĂ”istliku ajaga teha, siis hakkame analooge kaaluma.

JĂ€reldused

Mida me saame oma kogemuse pÔhjal soovitada:

  1. JÀlgige oma bloat'i. JÀlgimisandmete pÔhjal saate aru, kui hÀsti on autovacuum seadistatud.
  2. Seadistage AUTOVACUUM, et hoida bloat aktsepteeritaval tasemel.
  3. Kui bloat ikkagi kasvab ja te ei suuda sellega “vĂ€ljakutsuvat” lahendustena toime tulla, Ă€rge kartke kasutada vĂ€liseid laiendusi. Peamine on kĂ”ik korralikult testida.
  4. Ärge kartke kohandada vĂ€liseid lahendusi vastavalt oma vajadustele – mĂ”nikord vĂ”ib see olla efektiivsem ja isegi lihtsam kui oma koodi muutmine.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster