Postgres: bloat, pg_repack ir atidėtieji apribojimai

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Išsipūtimo poveikis lentelėms ir indeksams yra plačiai žinomas ir pasireiškia ne tik Postgres. Yra būdų, kaip su juo susidoroti, pvz., VACUUM FULL arba CLUSTER, tačiau jie užrakina stalus veikimo metu ir todėl ne visada gali būti naudojami.

Straipsnyje bus pateikta šiek tiek teorijos apie tai, kaip atsiranda išsipūtimas, kaip galite su juo kovoti, apie atidėtus apribojimus ir problemas, kurias jie sukelia naudojant plėtinį pg_repack.

Šis straipsnis parašytas remiantis mano kalba „PgConf.Russia 2020“.

Kodėl atsiranda pilvo pūtimas?

Postgres yra pagrįstas kelių versijų modeliu (MVCC). Jo esmė ta, kad kiekviena lentelės eilutė gali turėti kelias versijas, o operacijos mato ne daugiau kaip vieną iš šių versijų, bet nebūtinai tą pačią. Tai leidžia kelioms operacijoms atlikti vienu metu ir praktiškai neturi įtakos viena kitai.

Akivaizdu, kad visas šias versijas reikia saugoti. „Postgres“ dirba su atmintimi puslapis po puslapio, o puslapis yra mažiausias duomenų kiekis, kurį galima nuskaityti iš disko arba įrašyti. Pažvelkime į nedidelį pavyzdį, kad suprastume, kaip tai vyksta.

Tarkime, kad turime lentelę, prie kurios pridėjome kelis įrašus. Pirmajame failo puslapyje, kuriame saugoma lentelė, pasirodė nauji duomenys. Tai yra tiesioginės eilučių versijos, kurios pasiekiamos kitoms operacijoms po įsipareigojimo (paprastumo dėlei manysime, kad izoliacijos lygis yra Read Committed).

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Tada atnaujinome vieną iš įrašų, taip pažymėdami senąją versiją kaip nebeaktualią.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Žingsnis po žingsnio, atnaujindami ir ištrindami eilučių versijas, gavome puslapį, kuriame maždaug pusė duomenų yra „šiukšlės“. Šių duomenų nemato jokia operacija.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Postgres turi mechanizmą VAKUUMAS, kuri išvalo pasenusias versijas ir suteikia vietos naujiems duomenims. Bet jei jis nėra pakankamai agresyviai sukonfigūruotas arba yra užsiėmęs darbu kitose lentelėse, tada lieka „šiukšlių duomenys“, o naujiems duomenims turime naudoti papildomus puslapius.

Taigi mūsų pavyzdyje tam tikru momentu lentelę sudarys keturi puslapiai, tačiau tik pusėje bus pateikti tiesioginiai duomenys. Dėl to, prieidami prie lentelės, perskaitysime daug daugiau duomenų nei reikia.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Net jei dabar VACUUM ištrins visas nesusijusias eilučių versijas, situacija nepagerės dramatiškai. Turėsime laisvos vietos puslapiuose ar net ištisuose puslapiuose naujoms eilutėms, bet vis tiek skaitysime daugiau duomenų nei reikia.
Beje, jei failo pabaigoje būtų visiškai tuščias puslapis (antrasis mūsų pavyzdyje), tada VACUUM galėtų jį apkarpyti. Tačiau dabar ji yra viduryje, todėl su ja nieko negalima padaryti.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Kai tokių tuščių ar labai retų puslapių skaičius tampa didelis, o tai vadinama išsipūtimu, tai pradeda daryti įtaką našumui.

Viskas, kas aprašyta aukščiau, yra pūtimo atsiradimo lentelėse mechanika. Indeksuose tai vyksta beveik taip pat.

Ar turiu pilvo pūtimą?

Yra keletas būdų, kaip nustatyti, ar turite pilvo pūtimą. Pirmojo idėja yra naudoti vidinę Postgres statistiką, kurioje yra apytikslė informacija apie eilučių skaičių lentelėse, "gyvų" eilučių skaičių ir kt. Internete galite rasti daugybę paruoštų scenarijų variantų. Mes ėmėme kaip pagrindą scenarijus iš PostgreSQL Experts, kuri gali įvertinti bloat lenteles kartu su toast ir bloat btree indeksais. Mūsų patirtis rodo, kad jo paklaida yra 10-20%.

Kitas būdas yra naudoti plėtinį pgstattuple, kuri leidžia pažvelgti į puslapių vidų ir gauti apytikslę ir tikslią išsipūtimo vertę. Tačiau antruoju atveju turėsite nuskaityti visą lentelę.

Manome, kad maža išsipūtimo vertė, iki 20%, priimtina. Jis gali būti laikomas užpildymo faktoriaus analogu stalai и indeksai. 50% ir daugiau gali prasidėti našumo problemos.

Kovos su pilvo pūtimu būdai

Postgres turi keletą būdų, kaip kovoti su pilvo pūtimu, tačiau jie ne visada tinka visiems.

Sukonfigūruokite AUTOVACUUM, kad neatsirastų išsipūtimas. O tiksliau – išlaikyti tau priimtiną lygį. Tai atrodo kaip „kapitono“ patarimas, tačiau iš tikrųjų tai ne visada lengva pasiekti. Pavyzdžiui, jūs aktyviai kuriate ir reguliariai keičiate duomenų schemą arba vyksta tam tikras duomenų perkėlimas. Todėl jūsų apkrovos profilis gali dažnai keistis ir paprastai skirsis įvairiose lentelėse. Tai reiškia, kad reikia nuolat šiek tiek padirbėti į priekį ir AUTOVACUUM pritaikyti prie kiekvieno stalo besikeičiančio profilio. Bet akivaizdu, kad tai padaryti nėra lengva.

Kita dažna priežastis, kodėl AUTOVACUUM negali neatsilikti nuo lentelių, yra tai, kad yra ilgai vykdomų operacijų, kurios neleidžia išvalyti toms operacijoms prieinamų duomenų. Rekomendacija čia taip pat akivaizdi - atsikratykite „kabančių“ sandorių ir sumažinkite aktyvių operacijų laiką. Bet jei jūsų programos apkrova yra OLAP ir OLTP hibridas, vienu metu galite atlikti daug dažnų atnaujinimų ir trumpų užklausų, taip pat atlikti ilgalaikes operacijas, pavyzdžiui, kurti ataskaitą. Esant tokiai situacijai, verta pagalvoti apie apkrovos paskirstymą skirtingoms bazėms, kurios leis tiksliau sureguliuoti kiekvieną iš jų.

Kitas pavyzdys - net jei profilis yra vienalytis, bet duomenų bazė yra labai apkrauta, tai net ir agresyviausias AUTOVACUUM gali nesusitvarkyti, ir atsiras pūtimas. Vienintelis sprendimas yra mastelio keitimas (vertikalus arba horizontalus).

Ką daryti, jei nustatėte AUTOVACUUM, bet pilvo pūtimas ir toliau auga.

Komanda VAKUUMAS PILNAS atkuria lentelių ir indeksų turinį ir palieka juose tik atitinkamus duomenis. Kad pašalintų išsipūtimą, jis puikiai veikia, tačiau jo vykdymo metu yra užfiksuotas išskirtinis lentelės užraktas (AccessExclusiveLock), kuris neleis vykdyti užklausų šioje lentelėje, net pasirenka. Jei galite sau leisti sustabdyti paslaugą ar jos dalį kuriam laikui (nuo dešimčių minučių iki kelių valandų, priklausomai nuo duomenų bazės dydžio ir aparatinės įrangos), ši parinktis yra geriausia. Deja, mes neturime laiko paleisti VACUUM FULL planinės priežiūros metu, todėl šis būdas mums netinka.

Komanda KLASTERIS Atkuria lentelių turinį taip pat, kaip ir VACUUM FULL, tačiau leidžia nurodyti indeksą, pagal kurį duomenys bus fiziškai užsakyti diske (tačiau ateityje naujoms eilutėms užsakymas negarantuojamas). Tam tikrose situacijose tai yra geras daugelio užklausų optimizavimas – nuskaitant kelis įrašus pagal indeksą. Komandos trūkumas yra toks pat kaip ir VACUUM FULL – darbo metu užrakina stalą.

Komanda REINDEKSAS panašus į ankstesnius du, bet atkuria konkretų indeksą arba visus lentelės indeksus. Užraktai yra šiek tiek silpnesni: „ShareLock“ lentelėje (neleidžia modifikuoti, bet leidžia pasirinkti) ir „AccessExclusiveLock“ atkuriamoje rodyklėje (blokuoja užklausas naudojant šį indeksą). Tačiau 12-oje Postgres versijoje atsirado parametras TUO pat metu, kuri leidžia atkurti indeksą neblokuojant tuo pačiu metu atliekamo įrašų pridėjimo, keitimo ar trynimo.

Ankstesnėse „Postgres“ versijose galite pasiekti rezultatą, panašų į REINDEX, naudojant tuo pačiu metu KURTI RODYKLĄ VIENU pat metu. Tai leidžia sukurti indeksą be griežto užrakinimo (ShareUpdateExclusiveLock, kuris netrukdo lygiagrečioms užklausoms), tada pakeisti seną indeksą nauju ir ištrinti senąjį indeksą. Tai leidžia pašalinti indekso išsipūtimą netrukdant jūsų programai. Svarbu atsižvelgti į tai, kad atkuriant indeksus bus papildoma apkrova disko posistemiui.

Taigi, jei indeksams yra būdų, kaip pašalinti išsipūtimą „skraidydami“, tada lentelėms jų nėra. Čia pradeda veikti įvairūs išoriniai plėtiniai: pg_repack (anksčiau pg_reorg), pgcompact, pgkompaktiškas ir kiti. Šiame straipsnyje aš jų nelyginsiu ir kalbėsiu tik apie pg_repack, kurį, šiek tiek pakeitę, naudojame patys.

Kaip veikia pg_repack

Postgres: bloat, pg_repack ir atidėtieji apribojimai
Tarkime, turime visiškai įprastą lentelę – su indeksais, apribojimais ir, deja, su pūtimu. Pirmasis pg_repack veiksmas yra sukurti žurnalo lentelę, kurioje būtų saugomi duomenys apie visus pakeitimus, kol ji veikia. Trigeris pakartos šiuos pakeitimus kiekvieną kartą įterpiant, atnaujinant ir ištrinant. Tada sukuriama lentelė, savo struktūra panaši į pradinę, bet be indeksų ir apribojimų, kad nesulėtėtų duomenų įterpimo procesas.

Tada pg_repack perkelia duomenis iš senosios lentelės į naują lentelę, automatiškai išfiltruodama visas nesusijusias eilutes, o tada sukuria naujos lentelės indeksus. Vykdant visas šias operacijas, pakeitimai kaupiasi žurnalo lentelėje.

Kitas žingsnis yra perkelti pakeitimus į naują lentelę. Perkėlimas atliekamas per kelias iteracijas, o kai žurnalo lentelėje liko mažiau nei 20 įrašų, pg_repack įgauna stiprų užraktą, perkelia naujausius duomenis ir pakeičia seną lentelę nauja Postgres sistemos lentelėse. Tai vienintelis ir labai trumpas laikas, kai negalėsite dirbti su stalu. Po to senoji lentelė ir lentelė su žurnalais ištrinamos ir failų sistemoje atlaisvinama vieta. Procesas baigtas.

Teoriškai viskas atrodo puiku, bet kas nutinka praktiškai? Išbandėme pg_repack be apkrovos ir apkrovos bei patikrinome jo veikimą priešlaikinio sustabdymo atveju (kitaip tariant, naudojant Ctrl+C). Visi testai buvo teigiami.

Nuėjome į maisto parduotuvę – tada viskas klostėsi ne taip, kaip tikėjomės.

Parduodamas pirmasis blynas

Pirmajame klasteryje gavome klaidą dėl unikalaus apribojimo pažeidimo:

$ ./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.

Šis apribojimas turėjo automatiškai sugeneruotą pavadinimą index_16508 – jį sukūrė pg_repack. Remdamiesi atributais, įtrauktais į jo sudėtį, nustatėme jį atitinkantį „mūsų“ apribojimą. Problema buvo ta, kad tai nėra visiškai įprastas apribojimas, o atidėtas (atidėtas apribojimas), t.y. jo patikrinimas atliekamas vėliau nei sql komanda, o tai sukelia netikėtų pasekmių.

Atidėtieji apribojimai: kodėl jie reikalingi ir kaip jie veikia

Šiek tiek teorijos apie atidėtus apribojimus.
Panagrinėkime paprastą pavyzdį: turime automobilių lentelę-nuorodų knygą su dviem atributais - automobilio pavadinimu ir tvarka kataloge.
Postgres: bloat, pg_repack ir atidėtieji apribojimai

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



Tarkime, reikėjo sukeisti pirmą ir antrą mašinas. Paprastas sprendimas yra atnaujinti pirmąją reikšmę į antrąją, o antrąją į pirmąją:

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

Tačiau vykdydami šį kodą tikimės apribojimo pažeidimo, nes reikšmių tvarka lentelėje yra unikali:

[23305] ERROR: duplicate key value violates unique constraint “uk_cars”
Detail: Key (ord)=(2) already exists.

Kaip galiu tai padaryti kitaip? Pirmas variantas: pridėkite papildomos vertės pakeitimą prie užsakymo, kurio lentelėje garantuotai nėra, pvz., „-1“. Programavime tai vadinama „dviejų kintamųjų reikšmių keitimu į trečdalį“. Vienintelis šio metodo trūkumas yra papildomas atnaujinimas.

Antras variantas: perkurkite lentelę, kad užsakymo vertei būtų naudojamas slankiojo kablelio duomenų tipas, o ne sveikieji skaičiai. Tada, pavyzdžiui, atnaujinant reikšmę nuo 1 iki 2.5, pirmasis įrašas automatiškai „stos“ tarp antrojo ir trečiojo. Šis sprendimas veikia, tačiau yra du apribojimai. Pirma, ji neveiks, jei reikšmė bus naudojama kažkur sąsajoje. Antra, priklausomai nuo duomenų tipo tikslumo, prieš perskaičiuodami visų įrašų reikšmes turėsite ribotą galimų įterpimų skaičių.

Trečias variantas: atidėti apribojimą, kad jis būtų patikrintas tik įsipareigojimo metu:

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

Kadangi mūsų pradinės užklausos logika užtikrina, kad visos reikšmės įsipareigojimo metu yra unikalios, ji bus sėkminga.

Aukščiau aptartas pavyzdys, žinoma, labai sintetinis, tačiau atskleidžia idėją. Savo programoje naudojame atidėtus apribojimus, kad įgyvendintume logiką, kuri yra atsakinga už konfliktų sprendimą, kai vartotojai vienu metu dirba su bendrinamų valdiklių objektais lentoje. Tokių apribojimų naudojimas leidžia šiek tiek supaprastinti programos kodą.

Apskritai, atsižvelgiant į apribojimo tipą, Postgres turi tris detalumo lygius, skirtus jiems patikrinti: eilutės, operacijos ir išraiškos lygiai.
Postgres: bloat, pg_repack ir atidėtieji apribojimai
Šaltinis: begrifai

CHECK ir NOT NULL visada tikrinami eilutės lygiu; kitiems apribojimams, kaip matyti iš lentelės, yra skirtingų parinkčių. Galite paskaityti daugiau čia.

Trumpai apibendrinant, atidėtieji apribojimai daugelyje situacijų suteikia geriau skaitomą kodą ir mažiau komandų. Tačiau už tai turite sumokėti apsunkindami derinimo procesą, nes klaidos atsiradimo momentas ir momentas, kai apie tai sužinote, yra atskirti laiku. Kita galima problema yra ta, kad planuotojas ne visada gali sudaryti optimalų planą, jei užklausa yra susijusi su atidėtu apribojimu.

Pg_repack tobulinimas

Apžvelgėme, kas yra atidėtieji apribojimai, bet kaip jie susiję su mūsų problema? Prisiminkime anksčiau gautą klaidą:

$ ./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.

Tai atsitinka, kai duomenys nukopijuojami iš žurnalo lentelės į naują lentelę. Tai atrodo keistai, nes... žurnalo lentelės duomenys yra įpareigoti kartu su duomenimis šaltinio lentelėje. Jei jie atitinka pirminės lentelės apribojimus, kaip jie gali pažeisti tuos pačius apribojimus naujojoje?

Kaip paaiškėjo, problemos šaknis slypi ankstesniame pg_repack veiksme, kuris sukuria tik indeksus, bet ne apribojimus: senoji lentelė turėjo unikalų apribojimą, o naujoji vietoj jo sukūrė unikalų indeksą.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Čia svarbu pažymėti, kad jei apribojimas yra normalus ir nėra atidėtas, tada vietoj jo sukurtas unikalus indeksas yra lygiavertis šiam apribojimui, nes Unikalūs „Postgres“ apribojimai įgyvendinami sukuriant unikalų indeksą. Tačiau atidėto apribojimo atveju elgsena nėra tokia pati, nes indeksas negali būti atidėtas ir visada tikrinamas tuo metu, kai vykdoma sql komanda.

Taigi problemos esmė slypi patikrinimo „vėlavime“: pradinėje lentelėje jis įvyksta commit metu, o naujoje – sql komandos vykdymo metu. Tai reiškia, kad turime užtikrinti, kad abiem atvejais patikrinimai būtų atliekami vienodai: arba visada vėluojant, arba visada nedelsiant.

Taigi kokių idėjų turėjome?

Sukurkite indeksą, panašų į atidėtą

Pirma idėja yra atlikti abu patikrinimus tiesioginiu režimu. Tai gali sukelti keletą klaidingai teigiamų apribojimų, tačiau jei jų yra mažai, tai neturėtų turėti įtakos vartotojų darbui, nes tokie konfliktai jiems yra įprasta situacija. Jie atsiranda, pavyzdžiui, kai du vartotojai vienu metu pradeda redaguoti tą patį valdiklį, o antrojo vartotojo klientas nespėja gauti informacijos, kad valdiklis jau yra užblokuotas pirmojo vartotojo redaguoti. Esant tokiai situacijai, serveris atsisako antrojo vartotojo, o jo klientas atšaukia pakeitimus ir blokuoja valdiklį. Šiek tiek vėliau, kai pirmasis vartotojas baigs redaguoti, antrasis gaus informaciją, kad valdiklis nebėra užblokuotas ir galės pakartoti savo veiksmą.

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Siekdami užtikrinti, kad patikrinimai visada būtų neatidėti, sukūrėme naują indeksą, panašų į pradinį atidėtą apribojimą:

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

Bandomojoje aplinkoje gavome tik keletą laukiamų klaidų. Sėkmė! Mes vėl paleidome pg_repack gamyboje ir gavome 5 klaidas pirmajame klasteryje per darbo valandą. Tai priimtinas rezultatas. Tačiau jau antrajame klasteryje klaidų skaičius žymiai išaugo ir turėjome sustabdyti pg_repack.

Kodėl taip atsitiko? Klaidos tikimybė priklauso nuo to, kiek vartotojų vienu metu dirba su tais pačiais valdikliais. Matyt, tuo momentu konkurencinių pokyčių su pirmajame klasteryje saugomais duomenimis buvo daug mažiau nei kituose, t.y. mums tiesiog „pasisekė“.

Idėja nepasiteisino. Tuo metu matėme du kitus sprendimus: perrašyti programos kodą, kad nebūtų atidėtų apribojimų, arba „išmokyti“ pg_repack dirbti su jais. Pasirinkome antrą.

Pakeiskite indeksus naujoje lentelėje su atidėtais apribojimais iš pradinės lentelės

Revizijos tikslas buvo akivaizdus – jei pirminėje lentelėje yra atidėtas apribojimas, tai naujajai reikia sukurti tokį apribojimą, o ne indeksą.

Norėdami patikrinti savo pakeitimus, parašėme paprastą testą:

  • lentelė su atidėtu apribojimu ir vienu įrašu;
  • įterpti duomenis į kilpą, kuri prieštarauja esamam įrašui;
  • atlikti atnaujinimą – duomenys nebeprieštarauja;
  • atlikti pakeitimus.

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;

Pradinė pg_repack versija visada užgesdavo pirmą kartą įterpiant, modifikuota versija veikė be klaidų. Puiku.

Mes pereiname prie gamybos ir vėl gauname klaidą tuo pačiu etapu kopijuodami duomenis iš žurnalo lentelės į naują:

$ ./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.

Klasikinė situacija: viskas veikia bandomojoje aplinkoje, bet ne gamyboje?!

APPLY_COUNT ir dviejų partijų sandūra

Pradėjome analizuoti kodą pažodžiui eilutė po eilutės ir atradome svarbų dalyką: duomenys iš žurnalo lentelės į naują perkeliami partijomis, konstanta APPLY_COUNT nurodė partijos dydį:

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

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

Problema ta, kad pradinės operacijos, kurioje kelios operacijos gali pažeisti apribojimą, duomenys, kai jie perduodami, gali atsidurti dviejų partijų sandūroje – pusė komandų bus įvykdyta pirmoje partijoje, o kita pusė. antrajame. Ir čia, priklausomai nuo jūsų sėkmės: jei komandos pirmoje partijoje nieko nepažeidžia, tada viskas yra gerai, bet jei taip, įvyksta klaida.

APPLY_COUNT yra lygus 1000 įrašų, o tai paaiškina, kodėl mūsų bandymai buvo sėkmingi – jie neapėmė „paketinės sankryžos“ atvejo. Naudojome dvi komandas – įterpti ir atnaujinti, todėl į paketą visada buvo patalpinta lygiai 500 dviejų komandų transakcijų ir jokių problemų nepatyrėme. Pridėjus antrąjį naujinimą, mūsų redagavimas nustojo veikti:

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;

Taigi, kita užduotis – įsitikinti, kad duomenys iš pradinės lentelės, kuri buvo pakeista per vieną operaciją, patektų į naują lentelę ir vienos operacijos metu.

Atsisakymas pakuoti

Ir vėl turėjome du sprendimus. Pirma: visiškai atsisakykime skaidymo į paketus ir perkelkime duomenis per vieną operaciją. Šio sprendimo privalumas buvo jo paprastumas – reikalingi kodo pakeitimai buvo minimalūs (beje, senesnėse versijose pg_reorg veikė būtent taip). Tačiau yra problema – kuriame ilgalaikį sandorį, ir tai, kaip buvo sakyta anksčiau, kelia grėsmę naujam išsipūtimui.

Antrasis sprendimas yra sudėtingesnis, bet tikriausiai teisingesnis: žurnalo lentelėje sukurkite stulpelį su operacijos, kuri į lentelę įtraukė duomenis, identifikatoriumi. Tada, kai kopijuosime duomenis, galime juos sugrupuoti pagal šį atributą ir užtikrinti, kad susiję pakeitimai būtų perkelti kartu. Paketas bus suformuotas iš kelių operacijų (arba vienos didelės) ir jos dydis skirsis priklausomai nuo to, kiek šiose operacijose buvo pakeista duomenų. Svarbu atkreipti dėmesį, kad kadangi skirtingų operacijų duomenys į žurnalo lentelę patenka atsitiktine tvarka, jų nebebus galima nuskaityti nuosekliai, kaip buvo anksčiau. Seqscan kiekvienai užklausai su filtravimu pagal tx_id yra per brangus, reikalingas indeksas, bet tai taip pat sulėtins metodą dėl jo atnaujinimo išlaidų. Apskritai, kaip visada, reikia kažką paaukoti.

Taigi, mes nusprendėme pradėti nuo pirmojo varianto, nes jis yra paprastesnis. Pirmiausia reikėjo suprasti, ar ilgas sandoris nebus tikra problema. Kadangi pagrindinis duomenų perkėlimas iš senos lentelės į naują taip pat vyksta per vieną ilgą operaciją, klausimas transformavosi į „kiek padidinsime šią operaciją? Pirmosios operacijos trukmė daugiausia priklauso nuo lentelės dydžio. Naujo trukmė priklauso nuo to, kiek duomenų perdavimo metu lentelėje susikaupia pakeitimų, t.y. dėl apkrovos intensyvumo. Pg_repack vykdymas įvyko minimalios paslaugos apkrovos metu, o pakeitimų apimtis buvo neproporcingai maža, palyginti su pradiniu lentelės dydžiu. Nusprendėme, kad galime nepaisyti naujos operacijos laiko (palyginimui vidutiniškai 1 valanda ir 2-3 minutės).

Eksperimentai buvo teigiami. Taip pat pradėti gaminti. Aiškumo dėlei čia yra paveikslėlis su vienos iš duomenų bazių dydžiu po paleidimo:

Postgres: bloat, pg_repack ir atidėtieji apribojimai

Kadangi buvome visiškai patenkinti šiuo sprendimu, antrojo diegti nebandėme, tačiau svarstome galimybę jį aptarti su plėtinių kūrėjais. Mūsų dabartinė versija, deja, dar neparengta publikuoti, nes problemą išsprendėme tik unikaliais atidėtais apribojimais, o norint gauti visavertį pataisą, būtina suteikti kitų tipų palaikymą. Tikimės, kad galėsime tai padaryti ateityje.

Galbūt jums kyla klausimas, kodėl mes net įsitraukėme į šią istoriją modifikuodami pg_repack, o, pavyzdžiui, nenaudojome jo analogų? Kažkuriuo metu ir mes apie tai galvojome, tačiau teigiama patirtis naudojant anksčiau, ant lentelių be atidėtų apribojimų, paskatino mus pabandyti suprasti problemos esmę ir ją išspręsti. Be to, naudojant kitus sprendimus taip pat reikia laiko atlikti bandymus, todėl nusprendėme, kad pirmiausia pabandysime sutvarkyti joje esančią problemą, o jei suprasime, kad negalime to padaryti per protingą laiką, pradėsime ieškoti analogų. .

išvados

Ką galime rekomenduoti, remdamiesi savo patirtimi:

  1. Stebėkite savo išsipūtimą. Remdamiesi stebėjimo duomenimis, galite suprasti, kaip gerai sukonfigūruotas automatinis vakuumas.
  2. Sureguliuokite AUTOVACUUM, kad išsipūtimas būtų priimtinas.
  3. Jei pilvo pūtimas vis dar auga ir negalite jo įveikti naudodami jau paruoštus įrankius, nebijokite naudoti išorinių plėtinių. Svarbiausia viską gerai išbandyti.
  4. Nebijokite modifikuoti išorinių sprendimų, kad jie atitiktų jūsų poreikius – kartais tai gali būti efektyviau ir net lengviau nei pakeisti savo kodą.

Šaltinis: www.habr.com

Добавить комментарий