Postgres: bloat, pg_repack kaj prokrastitaj limoj

Postgres: bloat, pg_repack kaj prokrastitaj limoj

La efiko de ŝvelaj tabeloj kaj indeksoj (bloat) estas vaste konata kaj ĉeestas ne nur en Postgres. Estas manieroj trakti ĝin "el la skatolo" kiel VACUUM FULL aŭ CLUSTER, sed ili ŝlosas tablojn dum operacio kaj tial ne ĉiam povas esti uzataj.

La artikolo havos iun teorion pri kiel ŝvelaĵo okazas, kiel vi povas trakti ĝin, pri prokrastitaj limoj kaj la problemoj, kiujn ili alportas al uzado de la etendo pg_repack.

Ĉi tiu artikolo baziĝas sur mia parolo ĉe PgConf.Russia 2020.

Kial estas ŝvelaĵo

Postgres baziĝas sur multversia modelo (MVCC). Ĝia esenco estas, ke ĉiu vico en la tabelo povas havi plurajn versiojn, dum transakcioj vidas ne pli ol unu el ĉi tiuj versioj, sed ne nepre la saman. Ĉi tio permesas plurajn transakciojn funkcii samtempe kaj havas malmulte al neniu efiko unu al la alia.

Evidente, ĉiuj ĉi tiuj versioj devas esti konservitaj. Postgres funkcias kun memoro paĝo post paĝo, kaj paĝo estas la minimuma kvanto da datumoj, kiuj povas esti legitaj de disko aŭ skribitaj. Ni rigardu malgrandan ekzemplon por kompreni kiel tio okazas.

Ni diru, ke ni havas tabelon al kiu ni aldonis plurajn registrojn. La unua paĝo de la dosiero kie la tabelo estas konservita havas novajn datumojn. Ĉi tiuj estas vivaj versioj de vicoj, kiuj estas disponeblaj por aliaj transakcioj post la kommit (por simpleco, ni supozos, ke la izoliteca nivelo estas Read Committed).

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Ni tiam ĝisdatigis unu el la enskriboj kaj tiel markis la malnovan version kiel neaktuala.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Paŝo post paŝo, ĝisdatigante kaj forigante vicversiojn, ni ricevis paĝon, en kiu ĉirkaŭ duono de la datumoj estas "rubo". Ĉi tiuj datumoj ne estas videblaj por iu ajn transakcio.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Postgres havas mekanismon VACUUMO, kiu purigas malmodernajn versiojn kaj faras lokon por novaj datumoj. Sed se ĝi ne estas sufiĉe agreseme agordita aŭ estas okupata laboranta en aliaj tabeloj, tiam la "rubdatumoj" restas, kaj ni devas uzi pliajn paĝojn por novaj datumoj.

Do en nia ekzemplo, en iu momento, la tabelo konsistos el kvar paĝoj, sed estos nur duono de la vivaj datumoj en ĝi. Kiel rezulto, alirante la tabelon, ni legos multe pli da datumoj ol necese.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Eĉ se VACUUM nun forigas ĉiujn sensignivajn vicversiojn, la situacio ne draste pliboniĝos. Ni havos liberan spacon en paĝoj aŭ eĉ tutajn paĝojn por novaj linioj, sed ni ankoraŭ legos pli da datumoj ol ni bezonas.
Cetere, se tute malplena paĝo (la dua en nia ekzemplo) estis ĉe la fino de la dosiero, tiam VACUUM povus detranĉi ĝin. Sed nun ŝi estas en la mezo, do oni nenion povas fari kun ŝi.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Kiam la nombro de tiaj malplenaj aŭ tre malabundaj paĝoj fariĝas granda, kio nomiĝas ŝvelaĵo, ĝi komencas influi rendimenton.

Ĉio priskribita supre estas la mekaniko de la apero de ŝvelaĵo en tabeloj. En indeksoj, tio okazas en la sama maniero.

Ĉu mi havas ŝvelaĵon?

Estas pluraj manieroj determini ĉu vi havas ŝvelaĵon. La ideo de la unua estas uzi internan statistikon de Postgres, kiu enhavas proksimumajn informojn pri la nombro da vicoj en tabeloj, la nombro da "vivaj" vicoj, ktp. Estas multaj varioj de pretaj skriptoj en la Interreto. Ni prenis kiel bazon skripto de PostgreSQL-Spertuloj, kiuj povas taksi tabelŝveladon kune kun toast kaj bloat btree-indeksoj. Laŭ nia sperto, ĝia eraro estas 10-20%.

Alia maniero estas uzi la etendon pgstattuple, kiu permesas vin rigardi enen la paĝojn kaj akiri kaj la taksitan kaj la precizan valoron de ŝvelaĵo. Sed en la dua kazo, vi devos skani la tutan tablon.

Malgranda kvanto da ŝvelaĵo, ĝis 20%, estas akceptebla. Ĝi povas esti konsiderata kiel analogo de la plenigfaktoro por tabloj и indeksoj. Je 50% kaj pli, agado problemoj povas komenciĝi.

Manieroj trakti ŝvelaĵon

Estas pluraj eksterordinaraj manieroj trakti ŝvelaĵon en Postgres, sed ili estas malproksimaj de ĉiam kaj eble ne konvenas al ĉiuj.

Agordu AUTOVACUUM por ke ŝvelaĵo ne okazu. Kaj por esti pli preciza, teni ĝin je akceptebla nivelo por vi. Ĉi tio ŝajnas konsilo de "kapitano", sed fakte ĉi tio ne ĉiam estas facile atingi. Ekzemple, vi havas aktivan disvolviĝon kun regula ŝanĝo en la datuma skemo, aŭ ia datummigrado okazas. Kiel rezulto, via ŝarĝa profilo povas ŝanĝiĝi ofte kaj tendencas esti malsama por malsamaj tabeloj. Ĉi tio signifas, ke vi devas konstante esti iom antaŭ la kurbo kaj ĝustigi AUTOVACUUM al la ŝanĝiĝanta profilo de ĉiu tablo. Sed estas evidente, ke ĉi tio ne estas facile fari.

Alia ofta kialo, ke AUTOVACUUM malsukcesas prilabori tabelojn, estas la ĉeesto de longdaŭraj transakcioj, kiuj malhelpas ĝin purigi datumojn pro la fakto, ke ĝi estas disponebla por ĉi tiuj transakcioj. La rekomendo ĉi tie ankaŭ estas evidenta - forigi "pendajn" transakciojn kaj minimumigi la tempon de aktivaj transakcioj. Sed se la ŝarĝo de via aplikaĵo estas hibrido de OLAP kaj OLTP, tiam vi povas samtempe havi multajn oftajn ĝisdatigojn kaj mallongajn demandojn, kaj longdaŭrajn operaciojn - ekzemple konstrui raporton. En tia situacio, vi devus pensi pri disvastigi la ŝarĝon sur malsamaj bazoj, kio permesos al vi agordi ĉiun el ili.

Alia ekzemplo - eĉ se la profilo estas homogena, sed la datumbazo estas sub tre alta ŝarĝo, tiam eĉ la plej agresema AUTOVACUUM eble ne povos elteni, kaj ŝveliĝo okazos. Skalado (vertikala aŭ horizontala) estas la sola solvo.

Kiel esti en situacio kiam vi agordis AUTOVACUUM, sed ŝvelaĵo daŭre kreskas.

teamo VAKUO PLENA rekonstruas la enhavon de tabeloj kaj indeksoj kaj lasas nur ĝisdatajn datumojn en ili. Por forigi ŝvelaĵon, ĝi funkcias perfekte, sed dum ĝia ekzekuto, ekskluziva seruro sur la tablo (AccessExclusiveLock) estas kaptita, kiu ne permesos demandojn al ĉi tiu tablo, eĉ elektojn. Se vi povas permesi ĉesigi vian servon aŭ parton de ĝi dum iom da tempo (de dekoj da minutoj ĝis pluraj horoj depende de la grandeco de la datumbazo kaj via aparataro), tiam ĉi tiu opcio estas la plej bona. Ni, bedaŭrinde, ne havas tempon por ruli VACUUM FULL dum la planita prizorgado, do ĉi tiu metodo ne konvenas al ni.

teamo KLUSO rekonstruas la enhavon de tabeloj same kiel VACUUM FULL, samtempe ebligante al vi specifi indekson laŭ kiu la datumoj estos fizike ordigitaj sur disko (sed la ordo ne estas garantiita por novaj vicoj estonte). En certaj situacioj, ĉi tio estas bona optimumigo por kelkaj demandoj - kun legado de pluraj registroj en la indekso. La malavantaĝo de la komando estas la sama kiel tiu de VACUUM FULL - ĝi ŝlosas la tablon dum operacio.

teamo REINDEX simila al la antaŭaj du, sed rekonstruas specifan indekson aŭ ĉiujn indeksojn sur tablo. Seruroj estas iomete pli malfortaj: ShareLock sur tablo (malhelpas modifojn, sed permesas elektojn) kaj AccessExclusiveLock sur rekonstruebla indekso (blokas demandojn uzante ĉi tiun indekson). Tamen, Postgres 12 enkondukis la opcion Samtempe, kiu ebligas al vi rekonstrui indekson sen bloki samtempan aldonon, modifon aŭ forigon de rekordoj.

En pli fruaj versioj de Postgres, vi povas atingi rezulton similan al REINDEX Samtempe kun KREU INDEKSON SUMATE. Ĝi permesas krei indekson sen forta seruro (ShareUpdateExclusiveLock, kiu ne malhelpas paralelajn demandojn), poste anstataŭigi la malnovan indekson per nova kaj forigi la malnovan indekson. Ĉi tio ebligas al vi forigi indeksan ŝvelaĵon sen malhelpi vian aplikon. Gravas konsideri, ke dum rekonstruado de indeksoj, estos plia ŝarĝo sur la disksubsistemo.

Tiel, se ekzistas manieroj por indeksoj forigi varman ŝvelaĵon, tiam ekzistas neniuj por tabeloj. Jen kie eksteraj etendaĵoj eniras: pg_repack (antaŭe pg_reorg), pgcompact, pgcompactable kaj aliaj. En la kadro de ĉi tiu artikolo, mi ne komparos ilin kaj nur parolos pri pg_repack, kiun, post iom da rafinado, ni uzas hejme.

Kiel pg_repack funkcias

Postgres: bloat, pg_repack kaj prokrastitaj limoj
Ni diru, ke ni havas sufiĉe ordinaran tabelon - kun indeksoj, limigoj kaj, bedaŭrinde, kun ŝvelaĵo. Kiel unua paŝo, pg_repack kreas protokoltabelon por konservi trakon de ĉiuj ŝanĝoj dum ĝi funkcias. La ellasilo reproduktos ĉi tiujn ŝanĝojn al ĉiu enmetaĵo, ĝisdatigo kaj forigo. Tiam estas kreita tabelo, kiu estas simila al la originala en strukturo, sed sen indeksoj kaj limigoj, por ne malrapidigi la procezon de enmeto de datumoj.

Poste, pg_repack transdonas datumojn de la malnova tabelo al la nova tabelo, aŭtomate filtrante ĉiujn malgravajn vicojn, kaj poste kreas indeksojn por la nova tabelo. Dum la ekzekuto de ĉiuj ĉi tiuj operacioj, ŝanĝoj akumuliĝas en la protokolo-tabelo.

La sekva paŝo estas translokigi la ŝanĝojn al la nova tabelo. La migrado estas farita en pluraj ripetoj, kaj kiam restas malpli ol 20 enskriboj en la protokolo-tabelo, pg_repack akiras fortan seruron, migras la plej novajn datumojn kaj anstataŭigas la malnovan tabelon per la nova en la Postgres-sistemaj tabeloj. Ĉi tiu estas la sola kaj tre mallonga momento, kiam vi ne povos labori kun la tablo. Post tio, la malnova tablo kaj la tablo kun protokoloj estas forigitaj kaj spaco estas liberigita en la dosiersistemo. Procezo finita.

En teorio, ĉio aspektas bonega, sed kio pri praktike? Ni testis pg_repack sen ŝarĝo kaj sub ŝarĝo, kaj kontrolis ĝian funkciadon en kazo de antaŭtempa halto (alivorte, per Ctrl+C). Ĉiuj testoj estis pozitivaj.

Ni iris al la pilo — kaj tiam ĉio misfunkciis, kiel ni atendis.

Unua krespo sur vendo

Sur la unua areto, ni ricevis eraron pri unika malobservo:

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

Ĉi tiu limo havis la aŭtomate generitan nomon index_16508, kreita de pg_repack. Per la atributoj inkluzivitaj en ĝia konsisto, ni determinis "nian" limon, kiu respondas al ĝi. La problemo montriĝis, ke ĉi tio ne estas tute ordinara limo, sed malfrua (prokrastita limigo), t.e. ĝia validigo estas farita poste ol la sql-komando, kiu kondukas al neatenditaj sekvoj.

Prokrastitaj limoj: kial ili estas bezonataj kaj kiel ili funkcias

Iom da teorio pri prokrastitaj limoj.
Konsideru simplan ekzemplon: ni havas aŭto-dosierujon kun du atributoj - la nomo kaj ordo de la aŭto en la dosierujo.
Postgres: bloat, pg_repack kaj prokrastitaj limoj

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



Supozu, ke ni bezonus interŝanĝi la unuan kaj duan aŭtojn en lokoj. La fronta solvo estas ĝisdatigi la unuan valoron al la dua, kaj la dua al la unua:

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

Sed kiam ni rulas ĉi tiun kodon, ni atendas malobservon ĉar la ordo de la valoroj en la tabelo estas unika:

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

Kiel fari ĝin malsame? Unua opcio: aldonu plian anstataŭigon de la valoro kun ordo, kiu garantias ne ekzisti en la tabelo, ekzemple "-1". En programado, ĉi tio nomiĝas "interŝanĝi la valorojn de du variabloj tra tria." La sola malavantaĝo de ĉi tiu metodo estas la ekstra ĝisdatigo.

Opcio du: Restrukturi la tabelon por uzi glitkoman datumtipo por la eksponentvaloro anstataŭ entjeroj. Tiam, kiam oni ĝisdatigas la valoron de 1, ekzemple, al 2.5, la unua eniro aŭtomate "staros" inter la dua kaj la tria. Ĉi tiu solvo funkcias, sed estas du limigoj. Unue, ĝi ne funkcios por vi se la valoro estas uzata ie en la interfaco. Due, depende de la precizeco de la datumtipo, vi havos limigitan nombron da eblaj enmetoj antaŭ rekalkuli la valorojn de ĉiuj registroj.

Opcio tri: prokrastu la limon tiel ke ĝi estu kontrolita nur en la momento de la transdono:

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

Ĉar la logiko de nia komenca peto certigas, ke ĉiuj valoroj estas unikaj en la tempo, kiam la transdono estas farita, la transdono sukcesos.

La supre diskutita ekzemplo estas, kompreneble, tre sinteza, sed ĝi malkaŝas la ideon. En nia aplikaĵo, ni uzas prokrastitajn limojn por efektivigi la logikon, kiu respondecas pri solvado de konfliktoj, kiam uzantoj interagas kun komunaj fenestraj objektoj sur la tabulo samtempe. La uzo de tiaj limigoj permesas al ni fari la aplikan kodon iom pli simpla.

Ĝenerale, depende de la limtipo en Postgres, ekzistas tri niveloj de granulareco de ilia validumado: vico, transakcio kaj esprimo.
Postgres: bloat, pg_repack kaj prokrastitaj limoj
fonto: begriffs

CHECK kaj NOT NULL estas ĉiam kontrolitaj ĉe la viconivelo, por aliaj limigoj, kiel videblas el la tabelo, ekzistas malsamaj opcioj. Vi povas legi pli tie.

Por resumi mallonge, prokrastitaj limoj en kelkaj situacioj kondukas al pli legebla kodo kaj malpli da komandoj. Tamen, vi devas pagi por ĉi tio komplikante la sencimigan procezon, ĉar la momento, kiam la eraro okazas kaj la momento, kiam vi ekscias pri ĝi, estas disigitaj ĝustatempe. Alia ebla problemo estas ke la planisto eble ne ĉiam povas konstrui optimuman planon se prokrastita limo estas implikita en la demando.

Pliboniĝo pg_repack

Ni kovris, kio estas prokrastitaj limoj, sed kiel ili rilatas al nia problemo? Memoru la eraron, kiun ni ricevis pli frue:

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

Ĝi okazas kiam datumoj estas kopiitaj de la protokolo-tabelo al nova tabelo. Ĉi tio aspektas strange, ĉar la datumoj en la protokolo-tabelo estas faritaj kune kun la datumoj en la origina tabelo. Se ili kontentigas la limojn de la origina tabelo, kiel ili povas malobservi la samajn limojn en la nova?

Kiel montriĝis, la radiko de la problemo kuŝas en la antaŭa paŝo de pg_repack, kiu kreas nur indeksojn, sed ne limojn: la malnova tabelo havis unikan limigon, kaj la nova kreis unikan indekson anstataŭe.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Estas grave noti ĉi tie, ke se la limo estas normala kaj ne prokrastita, tiam la unika indekso kreita anstataŭ ĝi estas ekvivalenta al ĉi tiu limo, ĉar unikaj limoj en Postgres estas efektivigitaj kreante unikan indekson. Sed en la kazo de prokrastita limo, la konduto ne estas la sama, ĉar la indekso ne povas esti prokrastita kaj ĉiam estas kontrolita kiam la sql-komando estas ekzekutita.

Tiel, la esenco de la problemo kuŝas en la "prokrastita" kontrolo: en la originala tabelo, ĝi okazas en la momento de la kommit, kaj en la nova, en la momento de la ekzekuto de la komando sql. Do ni devas certigi, ke la kontroloj estas faritaj sammaniere en ambaŭ kazoj: aŭ ĉiam prokrastita, aŭ ĉiam tuj.

Do kiajn ideojn ni havis.

Krei indekson similan al prokrastita

La unua ideo estas fari ambaŭ kontrolojn en tuja reĝimo. Ĉi tio povas kaŭzi plurajn falsajn pozitivojn de la limigo, sed se estas malmultaj el ili, tiam ĉi tio ne devus influi la laboron de uzantoj, ĉar tiaj konfliktoj estas normala situacio por ili. Ili okazas, ekzemple, kiam du uzantoj komencas redakti la saman fenestraĵon samtempe, kaj la kliento de la dua uzanto ne havas tempon por ricevi informojn, ke la fenestraĵo jam estas blokita por redaktado de la unua uzanto. En tia situacio, la servilo respondas al la dua uzanto per rifuzo, kaj ĝia kliento retroiras la ŝanĝojn kaj ŝlosas la fenestraĵon. Iom poste, kiam la unua uzanto finos redaktadon, la dua ricevos informon, ke la fenestraĵo ne plu estas blokita, kaj povos ripeti sian agon.

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Por certigi, ke ĉekoj ĉiam estas en neprokrastita reĝimo, ni kreis novan indekson similan al la origina prokrastita limo:

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

En la testa medio, ni ricevis nur kelkajn atendatajn erarojn. Sukceso! Ni kuris pg_repack denove sur prod kaj ricevis 5 erarojn sur la unua areto en horo da laboro. Ĉi tio estas akceptebla rezulto. Tamen, jam sur la dua areto, la nombro da eraroj signife pliiĝis kaj ni devis ĉesigi pg_repack.

Kial ĝi okazis? La probableco de eraro okazas dependas de kiom da uzantoj laboras samtempe kun la samaj fenestraĵoj. Ŝajne, en tiu momento, estis multe malpli da konkurencivaj ŝanĝoj kun la datumoj stokitaj sur la unua areto ol sur la resto, t.e. ni estas nur "bonŝancaj".

La ideo ne funkciis. En tiu momento, ni vidis du aliajn solvojn: reverki nian aplikaĵkodon por forlasi prokrastitajn restriktojn, aŭ "instrui" pg_repack labori kun ili. Ni elektis la duan.

Anstataŭigi indeksojn en nova tabelo per prokrastitaj limoj de origina tabelo

La celo de la revizio estis evidenta - se la origina tabelo havas prokrastan limon, tiam por la nova necesas krei tian limigon, kaj ne indekson.

Por testi niajn ŝanĝojn, ni skribis simplan teston:

  • tablo kun prokrastita limo kaj unu rekordo;
  • ni enmetas datumojn en la buklo, kiu konfliktas kun la ekzistanta rekordo;
  • do an update - la datumoj ne plu konfliktas;
  • fari ŝanĝojn.

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;

La originala versio de pg_repack ĉiam kraŝis ĉe la unua enmetaĵo, la modifita versio funkciis sen eraroj. Bonege.

Ni iras al prod kaj denove ni ricevas eraron en la sama fazo de kopiado de datumoj de la protokolo-tabelo al nova:

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

Klasika situacio: ĉio funkcias en testaj medioj, sed ne en produktado?!

APPLY_COUNT kaj la krucvojo de du aroj

Ni komencis analizi la kodon laŭvorte linion post linio kaj malkovris gravan punkton: datumoj estas transdonitaj de la protokolo-tabelo al la nova en aroj, la APPLY_COUNT konstanto indikis la grandecon de la aro:

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

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

La problemo estas, ke la datumoj de la origina transakcio, en kiu pluraj operacioj eble povas malobservi la limon, povas finiĝi ĉe la krucvojo de du aroj dum la translokigo - duono de la komandoj estos faritaj en la unua aro, kaj la alia duono. en la dua. Kaj jen, kiel bonŝance: se la teamoj en la unua aro ne malobservas ion ajn, tiam ĉio estas en ordo, sed se jes, okazas eraro.

APPLY_COUNT estas egala al 1000 registroj, kio klarigas kial niaj testoj sukcesis - ili ne kovris la kazon de "batchjunction". Ni uzis du komandojn - enmeti kaj ĝisdatigi, do ĝuste 500 transakcioj de du komandoj estis ĉiam metitaj en aro kaj ni ne spertis problemojn. Post aldoni la duan ĝisdatigon, nia redakto ĉesis funkcii:

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;

Do, la sekva tasko estas certigi, ke la datumoj de la originala tabelo, kiu estis ŝanĝita en unu transakcio, eniras en la novan tabelon ankaŭ ene de unu transakcio.

Malakcepto de batado

Kaj ni denove havis du solvojn. Unue: ni rifuzu dividi en aroj entute kaj fari la datumtranslokigon ununura transakcio. En favoro de ĉi tiu decido estis ĝia simpleco - la postulataj kodŝanĝoj estas minimumaj (cetere, en pli malnovaj versioj tiam, pg_reorg funkciis tiel). Sed estas problemo - ni kreas longdaŭran transakcion, kaj ĉi tio, kiel menciite antaŭe, estas minaco al la apero de nova ŝvelaĵo.

La dua solvo estas pli komplika, sed verŝajne pli ĝusta: kreu kolumnon en la protokolo-tabelo kun la identigilo de la transakcio, kiu aldonis datumojn al la tabelo. Tiam kopiante datumojn, ni povas grupigi ĝin per ĉi tiu atributo kaj certigi, ke rilataj ŝanĝoj estas translokigitaj kune. La aro estos formita de pluraj transakcioj (aŭ unu granda) kaj ĝia grandeco varias depende de kiom da datumoj estis ŝanĝitaj en ĉi tiuj transakcioj. Gravas noti, ke ĉar la datumoj de malsamaj transakcioj eniras la protokolon en hazarda ordo, ne plu eblos legi ĝin sinsekve, kiel antaŭe. seqscan sur ĉiu peto filtrita de tx_id estas tro multekosta, vi bezonas indekson, sed ĝi ankaŭ malrapidigos la metodon pro la superkompeto de ĝisdatigo de ĝi. Ĝenerale, kiel ĉiam, vi devas oferi ion.

Do, ni decidis komenci per la unua opcio, kiel pli simpla. Unue, necesis kompreni ĉu longa transakcio estus vera problemo. Ĉar la ĉefa translokigo de datumoj de la malnova tablo al la nova ankaŭ okazas en unu longa transakcio, la demando transformiĝis en "kiom ni pliigos ĉi tiun transakcion?" La daŭro de la unua transakcio dependas ĉefe de la grandeco de la tablo. La daŭro de la nova dependas de kiom da ŝanĝoj akumuliĝos en la tabelo dum la transdono de datumoj, t.e. pri la intenseco de la ŝarĝo. La pg_repack-kuro okazis en tempo de minimuma serva ŝarĝo, kaj la kvanto de ŝanĝo estis nekompareble malgranda kompare kun la origina tabelgrandeco. Ni decidis, ke ni povas neglekti la tempon de nova transakcio (por komparo, la mezumo estas 1 horo kaj 2-3 minutoj).

La eksperimentoj estis pozitivaj. Lanĉo sur vendo ankaŭ. Por klareco, jen bildo kun la grandeco de unu el la bazoj post la kuro:

Postgres: bloat, pg_repack kaj prokrastitaj limoj

Ĉar ĉi tiu solvo tute konvenis al ni, ni ne provis efektivigi la duan, sed ni pripensas la eblecon diskuti ĝin kun la programistoj de la etendaĵo. Nia nuna revizio, bedaŭrinde, ankoraŭ ne estas preta por publikigo, ĉar ni solvis la problemon nur per unikaj prokrastitaj limoj, kaj por plentaŭga flikaĵo, subteno por aliaj tipoj bezonas esti farita. Ni esperas povi fari tion estonte.

Eble vi havas demandon, kial ni eĉ implikiĝis en ĉi tiu rakonto kun la rafinado de pg_repack, kaj ne uzis, ekzemple, ĝiajn analogojn? Iam ni ankaŭ pensis pri tio, sed la pozitiva sperto uzi ĝin pli frue, sur tabloj sen malfruaj limoj, instigis nin provi kompreni la esencon de la problemo kaj ripari ĝin. Krome, uzi aliajn solvojn ankaŭ postulas tempon por fari provojn, do ni decidis, ke ni unue provos ripari la problemon en ĝi, kaj se ni komprenus, ke ni ne povas fari ĝin en racia tempo, tiam ni komencus pripensi analogojn.

trovoj

Kion ni povas rekomendi laŭ nia propra sperto:

  1. Monitoru vian ŝvelaĵon. Surbaze de la monitoraj datumoj, vi povos kompreni kiom bone agordiĝas aŭtomata vakuo.
  2. Agordu AUTOVACUUM por konservi ŝvelaĵon je akceptebla nivelo.
  3. Se la ŝvelaĵo ankoraŭ kreskas kaj vi ne povas trakti ĝin per eksterordinaraj iloj, ne timu uzi eksterajn etendaĵojn. La ĉefa afero estas bone provi ĉion.
  4. Ne timu modifi eksterajn solvojn laŭ viaj bezonoj - foje tio povas esti pli efika kaj eĉ pli facila ol ŝanĝi vian propran kodon.

fonto: www.habr.com

Aldoni komenton