Postgres: bloat, pg_repack ja lykätyt rajoitukset

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Turvotuksen vaikutus taulukoihin ja indekseihin tunnetaan laajalti, eikä se ole läsnä vain Postgresissa. On olemassa tapoja käsitellä sitä heti valmiina, kuten VACUUM FULL tai CLUSTER, mutta ne lukitsevat pöydät käytön aikana, joten niitä ei voi aina käyttää.

Artikkeli sisältää pienen teorian siitä, kuinka turvotus tapahtuu, kuinka voit torjua sitä, viivästetyistä rajoituksista ja ongelmista, joita ne tuovat pg_repack-laajennuksen käyttöön.

Tämä artikkeli on kirjoitettu pohjalta minun puheeni PgConf.Russia 2020 -tapahtumassa.

Miksi turvotusta esiintyy?

Postgres perustuu moniversiomalliin (MVCC). Sen olemus on, että jokaisella taulukon rivillä voi olla useita versioita, kun taas tapahtumat eivät näe useampaa kuin yhtä näistä versioista, mutta eivät välttämättä samaa. Tämän ansiosta useat tapahtumat voivat toimia samanaikaisesti eikä niillä käytännössä ole vaikutusta toisiinsa.

On selvää, että kaikki nämä versiot on tallennettava. Postgres toimii muistin kanssa sivu sivulta ja sivu on pienin tietomäärä, joka voidaan lukea levyltä tai kirjoittaa. Katsotaanpa pientä esimerkkiä ymmärtääksemme, kuinka tämä tapahtuu.

Oletetaan, että meillä on taulukko, johon olemme lisänneet useita tietueita. Uutta tietoa on ilmestynyt tiedoston ensimmäiselle sivulle, johon taulukko on tallennettu. Nämä ovat reaaliaikaisia ​​versioita riveistä, jotka ovat muiden tapahtumien käytettävissä sitoutumisen jälkeen (yksinkertaisuuden vuoksi oletamme, että eristystaso on Read Committed).

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Päivitimme sitten yhden merkinnöistä ja merkitsimme siten vanhan version tarpeettomaksi.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Askel askeleelta, päivittämällä ja poistamalla riviversioita, päädyimme sivulle, jonka tiedoista noin puolet on "roskaa". Nämä tiedot eivät näy millekään tapahtumalle.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Postgresilla on mekanismi VACUUM, joka puhdistaa vanhentuneet versiot ja tekee tilaa uusille tiedoille. Mutta jos sitä ei ole konfiguroitu tarpeeksi aggressiivisesti tai se on kiireinen muissa taulukoissa, niin "roskadataa" jää jäljelle, ja meidän on käytettävä lisäsivuja uusille tiedoille.

Joten esimerkissämme taulukko koostuu jossain vaiheessa neljästä sivusta, mutta vain puolet siitä sisältää reaaliaikaista dataa. Tämän seurauksena luemme taulukkoa käytettäessä paljon enemmän tietoja kuin on tarpeen.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Vaikka VACUUM nyt poistaisi kaikki merkityksettömät riviversiot, tilanne ei parane dramaattisesti. Meillä on vapaata tilaa sivuilla tai jopa kokonaisilla sivuilla uusille riveille, mutta luemme silti enemmän dataa kuin on tarpeen.
Muuten, jos täysin tyhjä sivu (esimerkissämme toinen) olisi tiedoston lopussa, VACUUM voisi leikata sen. Mutta nyt hän on keskellä, joten hänelle ei voida tehdä mitään.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Kun tällaisten tyhjien tai erittäin harvalukuisten sivujen määrä kasvaa suureksi, mitä kutsutaan bloatiksi, se alkaa vaikuttaa suorituskykyyn.

Kaikki edellä kuvattu on turvotuksen esiintymisen mekaniikka taulukoissa. Indekseissä tämä tapahtuu pitkälti samalla tavalla.

Onko minulla turvotusta?

On olemassa useita tapoja määrittää, onko sinulla turvotusta. Ensimmäisen ideana on käyttää sisäisiä Postgres-tilastoja, jotka sisältävät likimääräisiä tietoja taulukoiden rivien määrästä, "elävien" rivien määrästä jne. Internetistä löytyy monia muunnelmia valmiista skripteistä. Otimme pohjaksi käsikirjoitus PostgreSQL Expertsistä, joka voi arvioida bloat-taulukoita sekä toast- ja bloat btree -indeksiä. Kokemuksemme mukaan sen virhe on 10-20%.

Toinen tapa on käyttää laajennusta pgstattuple, jonka avulla voit katsoa sivujen sisään ja saada sekä arvioidun että tarkan bloat-arvon. Mutta toisessa tapauksessa sinun on skannattava koko taulukko.

Pidämme pientä turvotusta, jopa 20 %, hyväksyttävänä. Sitä voidaan pitää täyttötekijän analogina taulukointipalvelut и indeksit. Suorituskykyongelmia voi ilmetä 50 %:lla tai sitä korkeammalla.

Keinot torjua turvotusta

Postgresilla on useita tapoja käsitellä turvotusta heti valmiina, mutta ne eivät aina sovi kaikille.

Määritä AUTOVACUUM niin, että turvotusta ei tapahdu. Tai tarkemmin sanottuna pitää se sinulle hyväksyttävällä tasolla. Tämä vaikuttaa "kapteenin" neuvolta, mutta todellisuudessa tätä ei aina ole helppo saavuttaa. Sinulla on esimerkiksi aktiivinen kehitystyö säännöllisillä muutoksilla tietoskeemaan tai jonkinlainen tietojen siirto on meneillään. Tämän seurauksena kuormitusprofiilisi voi muuttua usein ja vaihtelee tyypillisesti taulukosta toiseen. Tämä tarkoittaa, että sinun on jatkuvasti työskenneltävä hieman eteenpäin ja säädettävä AUTOVACUUM kunkin pöydän muuttuvaan profiiliin. Mutta ilmeisesti tämä ei ole helppoa.

Toinen yleinen syy siihen, miksi AUTOVACUUM ei voi pysyä taulukoiden mukana, on se, että on olemassa pitkäaikaisia ​​tapahtumia, jotka estävät sitä puhdistamasta kyseisten tapahtumien käytettävissä olevia tietoja. Suositus on myös ilmeinen - päästä eroon "roikkuvista" tapahtumista ja minimoi aktiivisten tapahtumien aika. Mutta jos sovelluksesi kuormitus on OLAP:n ja OLTP:n yhdistelmä, sinulla voi olla samanaikaisesti monia toistuvia päivityksiä ja lyhyitä kyselyitä sekä pitkäaikaisia ​​toimintoja - esimerkiksi raportin luomista. Tällaisessa tilanteessa kannattaa miettiä kuorman hajauttamista eri alustojen kesken, mikä mahdollistaa kunkin niistä paremman hienosäädön.

Toinen esimerkki - vaikka profiili olisi homogeeninen, mutta tietokanta on erittäin suuren kuormituksen alaisena, edes aggressiivisin AUTOVACUUM ei ehkä selviä, ja turvotusta tapahtuu. Skaalaus (pysty tai vaaka) on ainoa ratkaisu.

Mitä tehdä tilanteessa, jossa olet asettanut AUTOVACUUMin, mutta turvotus kasvaa edelleen.

Joukkue MUMI TÄYNNÄ rakentaa uudelleen taulukoiden ja hakemistojen sisällön ja jättää niihin vain olennaiset tiedot. Turvotuksen eliminoimiseksi se toimii täydellisesti, mutta sen suorittamisen aikana pöydälle kaapataan eksklusiivinen lukitus (AccessExclusiveLock), joka ei salli kyselyjen suorittamista tässä taulukossa, jopa valitsee. Jos sinulla on varaa keskeyttää palvelusi tai osa siitä joksikin aikaa (kymmenistä minuuteista useisiin tunteihin tietokannan koosta ja laitteistostasi riippuen), tämä vaihtoehto on paras. Valitettavasti meillä ei ole aikaa ajaa VACUUM FULL -toimintoa määräaikaishuollon aikana, joten tämä menetelmä ei sovellu meille.

Joukkue CLUSTER Rakentaa taulukoiden sisällön uudelleen samalla tavalla kuin VACUUM FULL, mutta antaa mahdollisuuden määrittää indeksin, jonka mukaan tiedot tilataan fyysisesti levylle (mutta jatkossa järjestystä ei taata uusille riveille). Tietyissä tilanteissa tämä on hyvä optimointi useille kyselyille - lukemalla useita tietueita indeksin mukaan. Komennon haittapuoli on sama kuin VACUUM FULL -komennossa - se lukitsee pöydän käytön aikana.

Joukkue REINDEKSI samanlainen kuin kaksi edellistä, mutta rakentaa uudelleen tietyn indeksin tai kaikki taulukon indeksit. Lukot ovat hieman heikompia: ShareLock taulukossa (estää muutokset, mutta sallii valinnan) ja AccessExclusiveLock uudelleen rakennettavassa hakemistossa (estää tätä hakemistoa käyttävät kyselyt). Postgresin 12. versiossa kuitenkin ilmestyi parametri SAMANAIKAISESTI, jonka avulla voit rakentaa hakemiston uudelleen estämättä samanaikaista tietueiden lisäämistä, muokkaamista tai poistamista.

Postgresin aiemmissa versioissa voit saavuttaa samanlaisen tuloksen kuin REINDEX SAMANAIKAINEN käyttämällä LUO HAKEMISTO SAMANAIKAISESTI. Sen avulla voit luoda indeksin ilman tiukkaa lukitusta (ShareUpdateExclusiveLock, joka ei häiritse rinnakkaisia ​​kyselyitä), sitten korvata vanha hakemisto uudella ja poistaa vanha hakemisto. Tämän avulla voit poistaa indeksin turvotuksen häiritsemättä sovellustasi. On tärkeää ottaa huomioon, että indeksien uudelleenrakentamisen yhteydessä levyalijärjestelmää kuormitetaan lisää.

Siten, jos indekseille on olemassa tapoja poistaa turvotus "lennossa", niin niitä ei ole taulukoissa. Tässä tulevat esiin erilaiset ulkoiset laajennukset: pg_repack (aiemmin pg_reorg), pgcompact, pgcompactable ja muut. Tässä artikkelissa en vertaile niitä, vaan puhun vain pg_repackista, jota käytämme jonkin muun muokkauksen jälkeen itse.

Kuinka pg_repack toimii

Postgres: bloat, pg_repack ja lykätyt rajoitukset
Oletetaan, että meillä on täysin tavallinen taulukko - indekseillä, rajoituksilla ja valitettavasti bloatilla. Pg_repackin ensimmäinen vaihe on luoda lokitaulukko, joka tallentaa tiedot kaikista muutoksista sen ollessa käynnissä. Liipaisin toistaa nämä muutokset jokaisen lisäyksen, päivityksen ja poiston yhteydessä. Sitten luodaan taulukko, joka on rakenteeltaan samanlainen kuin alkuperäinen, mutta ilman indeksejä ja rajoituksia, jotta tietojen lisäämisprosessi ei hidastu.

Seuraavaksi pg_repack siirtää tiedot vanhasta taulukosta uuteen taulukkoon suodattaen automaattisesti pois kaikki epäolennaiset rivit ja luo sitten indeksit uudelle taulukolle. Kaikkien näiden toimintojen suorittamisen aikana muutokset kerääntyvät lokitaulukkoon.

Seuraava vaihe on siirtää muutokset uuteen taulukkoon. Siirto suoritetaan useiden iteraatioiden aikana, ja kun lokitaulukossa on vähemmän kuin 20 merkintää jäljellä, pg_repack saa vahvan lukituksen, siirtää uusimmat tiedot ja korvaa vanhan taulukon uudella Postgres-järjestelmätaulukoissa. Tämä on ainoa ja hyvin lyhyt aika, jolloin et voi työskennellä pöydän kanssa. Tämän jälkeen vanha taulukko ja lokitaulukko poistetaan ja tiedostojärjestelmästä vapautuu tilaa. Prosessi on valmis.

Teoriassa kaikki näyttää hyvältä, mutta mitä tapahtuu käytännössä? Testasimme pg_repack ilman kuormitusta ja kuormituksen alaisena ja tarkistimme sen toiminnan ennenaikaisen pysäytyksen yhteydessä (eli Ctrl+C). Kaikki testit olivat positiivisia.

Menimme ruokakauppaan - ja sitten kaikki ei mennyt niin kuin odotimme.

Ensimmäinen pannukakku myynnissä

Ensimmäisessä klusterissa saimme virheilmoituksen ainutlaatuisen rajoitteen rikkomisesta:

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

Tällä rajoituksella oli automaattisesti luotu nimi index_16508 - sen loi pg_repack. Sen koostumukseen sisältyvien attribuuttien perusteella määritimme sitä vastaavan "meidän" rajoitteen. Ongelmaksi osoittautui, että tämä ei ole täysin tavallinen rajoitus, vaan lykätty (viivästetty rajoitus), eli sen tarkistus suoritetaan myöhemmin kuin sql-komento, mikä johtaa odottamattomiin seurauksiin.

Viivästetyt rajoitukset: miksi niitä tarvitaan ja miten ne toimivat

Pieni teoria viivästetyistä rajoituksista.
Tarkastellaanpa yksinkertaista esimerkkiä: meillä on autojen taulukko-viitekirja, jossa on kaksi attribuuttia - auton nimi ja järjestys hakemistossa.
Postgres: bloat, pg_repack ja lykätyt rajoitukset

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



Oletetaan, että meidän piti vaihtaa ensimmäinen ja toinen auto. Yksinkertainen ratkaisu on päivittää ensimmäinen arvo toiseen ja toinen ensimmäiseen:

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

Mutta kun suoritamme tämän koodin, odotamme rajoitusten rikkomista, koska taulukon arvojen järjestys on ainutlaatuinen:

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

Miten voin tehdä sen toisin? Vaihtoehto yksi: lisää tilaukseen lisäarvo, jota taatusti ei ole taulukossa, esimerkiksi "-1". Ohjelmoinnissa tätä kutsutaan "kahden muuttujan arvojen vaihtamiseksi kolmanneksi". Tämän menetelmän ainoa haittapuoli on lisäpäivitys.

Vaihtoehto kaksi: Suunnittele taulukko uudelleen käyttämään liukulukutietotyyppiä tilausarvona kokonaislukujen sijaan. Kun sitten päivitetään arvo esimerkiksi arvosta 1 arvoon 2.5, ensimmäinen merkintä "seisoi" automaattisesti toisen ja kolmannen välissä. Tämä ratkaisu toimii, mutta siinä on kaksi rajoitusta. Ensinnäkin se ei toimi sinulle, jos arvoa käytetään jossain käyttöliittymässä. Toiseksi, tietotyypin tarkkuudesta riippuen sinulla on rajoitettu määrä mahdollisia lisäyksiä ennen kaikkien tietueiden arvojen uudelleenlaskentaa.

Vaihtoehto kolme: tee rajoituksesta lykätty niin, että se tarkistetaan vain sitomishetkellä:

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

Koska alkuperäisen pyyntömme logiikka varmistaa, että kaikki arvot ovat ainutlaatuisia sitoutumishetkellä, se onnistuu.

Edellä käsitelty esimerkki on tietysti hyvin synteettinen, mutta se paljastaa idean. Sovelluksessamme käytämme viivästettyjä rajoituksia toteuttaaksemme logiikkaa, joka on vastuussa ristiriitojen ratkaisemisesta, kun käyttäjät työskentelevät samanaikaisesti jaettujen widget-objektien kanssa. Tällaisten rajoitusten avulla voimme tehdä sovelluskoodista hieman yksinkertaisemman.

Yleisesti ottaen rajoitteen tyypistä riippuen Postgresilla on kolme tarkkuustasoa niiden tarkistamiseen: rivi-, tapahtuma- ja lauseketasot.
Postgres: bloat, pg_repack ja lykätyt rajoitukset
Lähde: begriffit

CHECK ja NOT NULL tarkistetaan aina rivitasolla; muille rajoituksille, kuten taulukosta näkyy, on erilaisia ​​vaihtoehtoja. Voit lukea lisää täällä.

Lyhyesti sanottuna, viivästetyt rajoitukset useissa tilanteissa tarjoavat luettavamman koodin ja vähemmän komentoja. Sinun on kuitenkin maksettava tästä monimutkaisemalla virheenkorjausprosessia, koska virheen ilmenemishetki ja sen selville saaminen eroavat ajallisesti. Toinen mahdollinen ongelma on, että ajoittaja ei välttämättä aina pysty rakentamaan optimaalista suunnitelmaa, jos pyyntöön liittyy viivästetty rajoitus.

pg_repackin parannus

Olemme käsitelleet, mitä lykätyt rajoitukset ovat, mutta miten ne liittyvät ongelmaamme? Muistakaamme aiemmin saamamme virhe:

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

Se tapahtuu, kun tietoja kopioidaan lokitaulukosta uuteen taulukkoon. Tämä näyttää oudolta, koska... lokitaulukon tiedot sitovat yhdessä lähdetaulukon tietojen kanssa. Jos ne täyttävät alkuperäisen taulukon rajoitukset, kuinka ne voivat rikkoa samoja rajoituksia uudessa taulukossa?

Kuten käy ilmi, ongelman juuret ovat pg_repackin edellisessä vaiheessa, joka luo vain indeksejä, mutta ei rajoituksia: vanhassa taulukossa oli ainutlaatuinen rajoitus, ja uusi loi sen sijaan ainutlaatuisen indeksin.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Tässä on tärkeää huomata, että jos rajoitus on normaali eikä lykätty, sen sijaan luotu ainutlaatuinen indeksi vastaa tätä rajoitusta, koska Postgresin ainutlaatuiset rajoitukset toteutetaan luomalla yksilöllinen indeksi. Mutta viivästetyn rajoituksen tapauksessa käyttäytyminen ei ole sama, koska indeksiä ei voida lykätä ja se tarkistetaan aina, kun sql-komento suoritetaan.

Siten ongelman ydin piilee tarkistuksen "viiveessä": alkuperäisessä taulukossa se tapahtuu commit-hetkellä ja uudessa taulukossa kun sql-komento suoritetaan. Tämä tarkoittaa, että meidän on varmistettava, että tarkastukset suoritetaan molemmissa tapauksissa samalla tavalla: joko aina viivästyneenä tai aina välittömästi.

Mitä ideoita meillä sitten oli?

Luo viivästetyn kaltainen hakemisto

Ensimmäinen idea on suorittaa molemmat tarkastukset välittömässä tilassa. Tämä voi aiheuttaa useita vääriä positiivisia rajoituksia, mutta jos niitä on vähän, sen ei pitäisi vaikuttaa käyttäjien työhön, koska tällaiset ristiriidat ovat heille normaali tilanne. Niitä esiintyy esimerkiksi silloin, kun kaksi käyttäjää alkaa editoida samaa widgetiä samaan aikaan, eikä toisen käyttäjän asiakas ehdi vastaanottamaan tietoa, että widget on jo estänyt ensimmäisen käyttäjän muokkaamasta. Tällaisessa tilanteessa palvelin kieltäytyy toisesta käyttäjästä ja sen asiakas peruuttaa muutokset ja estää widgetin. Hieman myöhemmin, kun ensimmäinen käyttäjä viimeistelee muokkauksen, toinen saa tiedon, että widget ei ole enää estetty, ja voi toistaa toimintonsa.

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Varmistaaksemme, että tarkistukset ovat aina ei-lykätyssä tilassa, loimme uuden indeksin, joka on samanlainen kuin alkuperäinen lykätty rajoitus:

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

Testiympäristössä saimme vain muutaman odotetun virheen. Menestys! Suoritimme pg_repackin uudelleen tuotannossa ja saimme 5 virhettä ensimmäisessä klusterissa tunnin työskentelyn aikana. Tämä on hyväksyttävä tulos. Kuitenkin jo toisessa klusterissa virheiden määrä kasvoi merkittävästi ja jouduimme pysäyttämään pg_repack.

Miksi se tapahtui? Virheen esiintymisen todennäköisyys riippuu siitä, kuinka monta käyttäjää työskentelee samojen widgetien kanssa samanaikaisesti. Ilmeisesti tuolloin ensimmäiseen klusteriin tallennettujen tietojen kanssa oli paljon vähemmän kilpailullisia muutoksia kuin muissa, ts. olimme vain "onnekkaita".

Idea ei toiminut. Tuolloin näimme kaksi muuta ratkaisua: kirjoita sovelluskoodimme uudelleen, jotta vältytään viivästetyistä rajoituksista, tai "opeta" pg_repack toimimaan niiden kanssa. Valitsimme toisen.

Korvaa indeksit uudessa taulukossa alkuperäisen taulukon viivästetyillä rajoituksilla

Tarkistuksen tarkoitus oli ilmeinen - jos alkuperäisessä taulukossa on viivästetty rajoitus, sinun on luotava uudelle rajoitukselle sellainen rajoitus, ei indeksi.

Testaaksemme muutoksiamme kirjoitimme yksinkertaisen testin:

  • taulukko viivästetyllä rajoituksella ja yhdellä tietueella;
  • lisää tietoja silmukkaan, joka on ristiriidassa olemassa olevan tietueen kanssa;
  • tee päivitys – tiedot eivät enää ole ristiriidassa;
  • tehdä muutokset.

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;

Alkuperäinen pg_repack-versio kaatui aina ensimmäisellä lisäyksellä, muokattu versio toimi ilman virheitä. Loistava.

Siirrymme tuotantoon ja saamme jälleen virheen samassa vaiheessa, kun kopioimme tietoja lokitaulukosta uuteen:

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

Klassinen tilanne: kaikki toimii testiympäristöissä, mutta ei tuotannossa?!

APPLY_COUNT ja kahden erän risteys

Aloimme analysoida koodia kirjaimellisesti rivi riviltä ja huomasimme tärkeän kohdan: tiedot siirretään lokitaulukosta uuteen erissä, vakio APPLY_COUNT osoitti erän koon:

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

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

Ongelmana on, että alkuperäisen tapahtuman tiedot, joissa useat toiminnot voivat mahdollisesti rikkoa rajoitusta, voivat siirrettynä päätyä kahden erän risteykseen - puolet komennoista sitoutuu ensimmäisessä erässä ja toinen puoli toisessa. Ja tässä, tuuristasi riippuen: jos joukkueet eivät riko mitään ensimmäisessä erässä, kaikki on kunnossa, mutta jos rikkovat, tapahtuu virhe.

APPLY_COUNT on yhtä suuri kuin 1000 tietuetta, mikä selittää, miksi testimme onnistuivat - ne eivät kattaneet "eräliitoksen" tapausta. Käytimme kahta komentoa - lisää ja päivitä, joten täsmälleen 500 kahden komennon tapahtumaa asetettiin aina erään, eikä meillä ollut ongelmia. Toisen päivityksen lisäämisen jälkeen muokkaus lakkasi toimimasta:

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;

Joten seuraava tehtävä on varmistaa, että alkuperäisen taulukon tiedot, jotka on muutettu yhdessä tapahtumassa, päätyvät uuteen taulukkoon myös yhden tapahtuman sisällä.

Kieltäytyminen erästä

Ja taas meillä oli kaksi ratkaisua. Ensinnäkin: hylätään kokonaan osiointi eriin ja siirretään tiedot yhdessä tapahtumassa. Tämän ratkaisun etuna oli sen yksinkertaisuus - tarvittavat koodimuutokset olivat minimaalisia (muin, vanhemmissa versioissa pg_reorg toimi juuri niin). Mutta on ongelma - olemme luomassa pitkäkestoista kauppaa, ja tämä, kuten aiemmin todettiin, on uhka uuden paisun syntymiselle.

Toinen ratkaisu on monimutkaisempi, mutta luultavasti oikeampi: luo lokitaulukkoon sarake, jossa on sen tapahtuman tunniste, joka lisäsi tietoja taulukkoon. Sitten kun kopioimme tietoja, voimme ryhmitellä ne tämän määritteen mukaan ja varmistaa, että liittyvät muutokset siirretään yhteen. Erä muodostetaan useista tapahtumista (tai yhdestä suuresta) ja sen koko vaihtelee sen mukaan, kuinka paljon tietoja näissä tapahtumissa on muutettu. On tärkeää huomata, että koska eri tapahtumien tiedot tulevat lokitaulukkoon satunnaisessa järjestyksessä, niitä ei enää voida lukea peräkkäin, kuten ennen. seqscan jokaiselle pyynnölle tx_id-suodatuksella on liian kallista, tarvitaan indeksi, mutta se myös hidastaa menetelmää sen päivittämisen ylimääräisten kustannusten vuoksi. Yleensä, kuten aina, sinun on uhrattava jotain.

Joten päätimme aloittaa ensimmäisestä vaihtoehdosta, koska se on yksinkertaisempi. Ensin oli tarpeen ymmärtää, olisiko pitkä kauppa todellinen ongelma. Koska tietojen pääasiallinen siirto vanhasta taulukosta uuteen tapahtuu myös yhdessä pitkässä tapahtumassa, kysymys muuttui "kuinka paljon lisäämme tätä tapahtumaa?" Ensimmäisen tapahtuman kesto riippuu pääasiassa pöydän koosta. Uuden kesto riippuu siitä, kuinka monta muutosta taulukkoon kertyy tiedonsiirron aikana, ts. kuormituksen voimakkuudesta. pg_repack-ajo tapahtui vähäisen palvelukuormituksen aikana, ja muutosten määrä oli suhteettoman pieni verrattuna taulukon alkuperäiseen kokoon. Päätimme, että voimme jättää huomioimatta uuden tapahtuman ajan (vertailun vuoksi keskimäärin 1 tunti ja 2-3 minuuttia).

Kokeet olivat positiivisia. Aloita myös tuotanto. Selvyyden vuoksi tässä on kuva, jossa on yhden tietokannan koko ajon jälkeen:

Postgres: bloat, pg_repack ja lykätyt rajoitukset

Koska olimme täysin tyytyväisiä tähän ratkaisuun, emme yrittäneet ottaa käyttöön toista, mutta harkitsemme mahdollisuutta keskustella siitä laajennuksen kehittäjien kanssa. Nykyinen versiomme ei valitettavasti ole vielä valmis julkaistavaksi, koska ratkaisimme ongelman vain ainutlaatuisilla viivästetyillä rajoituksilla, ja täysimittaista korjaustiedostoa varten on tarpeen tarjota tukea muille tyypeille. Toivomme, että voimme tehdä tämän tulevaisuudessa.

Ehkä sinulla on kysymys, miksi edes sekaantuimme tähän tarinaan pg_repack-muokkauksella, emmekä esimerkiksi käyttäneet sen analogeja? Jossain vaiheessa mietimme myös tätä, mutta positiivinen kokemus sen käytöstä aikaisemmin, pöydillä ilman viivästettyjä rajoituksia, motivoi meitä yrittämään ymmärtää ongelman ydin ja korjata se. Lisäksi muiden ratkaisujen käyttäminen vaatii myös aikaa testien tekemiseen, joten päätimme, että yritämme ensin korjata sen ongelman ja jos ymmärrämme, että emme voi tehdä sitä kohtuullisessa ajassa, niin ryhdymme etsimään analogeja. .

Tulokset

Mitä voimme suositella omien kokemustemme perusteella:

  1. Tarkkaile turvotustasi. Valvontatietojen perusteella voit ymmärtää, kuinka hyvin automaattinen tyhjiö on konfiguroitu.
  2. Säädä AUTOVACUUM pitääksesi turvotuksen hyväksyttävällä tasolla.
  3. Jos turvotus kasvaa edelleen, etkä voi voittaa sitä valmiilla työkaluilla, älä pelkää käyttää ulkoisia laajennuksia. Pääasia on testata kaikki hyvin.
  4. Älä pelkää muokata ulkoisia ratkaisuja tarpeidesi mukaan - joskus tämä voi olla tehokkaampaa ja jopa helpompaa kuin oman koodin vaihtaminen.

Lähde: will.com

Lisää kommentti