Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Mae effaith bloat ar dablau a mynegeion yn hysbys iawn ac mae'n bresennol nid yn unig yn Postgres. Mae yna ffyrdd o ddelio ag ef y tu allan i'r bocs, fel WACUUM LLAWN neu CLUSTER, ond maent yn cloi byrddau yn ystod gweithrediad ac felly ni ellir eu defnyddio bob amser.

Bydd yr erthygl yn cynnwys ychydig o theori am sut mae bloat yn digwydd, sut y gallwch chi ei frwydro, am gyfyngiadau gohiriedig a'r problemau a ddaw yn eu sgil wrth ddefnyddio'r estyniad pg_repack.

Ysgrifennwyd yr erthygl hon yn seiliedig ar fy araith yn PgConf.Rwsia 2020.

Pam mae bloat yn digwydd?

Mae Postgres yn seiliedig ar fodel aml-fersiwn (MVCC). Ei hanfod yw y gall pob rhes yn y tabl gael sawl fersiwn, tra bod trafodion yn gweld dim mwy nag un o'r fersiynau hyn, ond nid o reidrwydd yr un un. Mae hyn yn caniatáu i nifer o drafodion weithio ar yr un pryd a chael fawr ddim effaith ar ei gilydd.

Yn amlwg, mae angen storio'r holl fersiynau hyn. Mae Postgres yn gweithio gyda chof fesul tudalen a thudalen yw'r lleiafswm o ddata y gellir ei ddarllen o ddisg neu ei ysgrifennu. Gadewch i ni edrych ar enghraifft fach i ddeall sut mae hyn yn digwydd.

Gadewch i ni ddweud bod gennym dabl yr ydym wedi ychwanegu sawl cofnod ato. Mae data newydd wedi ymddangos ar dudalen gyntaf y ffeil lle mae'r tabl yn cael ei storio. Mae'r rhain yn fersiynau byw o resi sydd ar gael i drafodion eraill ar ôl ymrwymiad (er mwyn symlrwydd, byddwn yn cymryd yn ganiataol mai Read Committed yw'r lefel ynysu).

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Yna fe wnaethom ddiweddaru un o'r cofnodion, a thrwy hynny nodi nad oedd yr hen fersiwn yn berthnasol mwyach.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Cam wrth gam, gan ddiweddaru a dileu fersiynau rhes, daethom i ben â thudalen lle mae tua hanner y data yn “sbwriel”. Nid yw'r data hwn yn weladwy i unrhyw drafodiad.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Mae gan Postgres fecanwaith VACUUM, sy'n glanhau fersiynau anarferedig ac yn gwneud lle i ddata newydd. Ond os nad yw wedi'i ffurfweddu'n ddigon ymosodol neu os yw'n brysur yn gweithio mewn tablau eraill, yna mae "data sothach" yn parhau, ac mae'n rhaid i ni ddefnyddio tudalennau ychwanegol ar gyfer data newydd.

Felly yn ein hesiampl ni, ar ryw adeg mewn amser bydd y tabl yn cynnwys pedair tudalen, ond dim ond hanner ohono fydd yn cynnwys data byw. O ganlyniad, wrth gyrchu'r tabl, byddwn yn darllen llawer mwy o ddata nag sydd angen.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Hyd yn oed os yw VACUUM bellach yn dileu pob fersiwn rhes amherthnasol, ni fydd y sefyllfa'n gwella'n ddramatig. Bydd gennym le rhydd mewn tudalennau neu hyd yn oed dudalennau cyfan ar gyfer rhesi newydd, ond byddwn yn dal i ddarllen mwy o ddata nag sydd angen.
Gyda llaw, pe bai tudalen hollol wag (yr ail un yn ein hesiampl) ar ddiwedd y ffeil, yna byddai VACUUM yn gallu ei thocio. Ond nawr mae hi yn y canol, felly ni ellir gwneud dim â hi.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Pan fydd nifer y tudalennau gwag neu brin iawn yn dod yn fawr, a elwir yn bloat, mae'n dechrau effeithio ar berfformiad.

Mae popeth a ddisgrifir uchod yn fecanwaith o'r achosion o bloat mewn tablau. Mewn mynegeion mae hyn yn digwydd yn yr un ffordd fwy neu lai.

Oes gen i bloat?

Mae sawl ffordd o benderfynu a oes gennych bloat. Syniad y cyntaf yw defnyddio ystadegau mewnol Postgres, sy'n cynnwys gwybodaeth fras am nifer y rhesi mewn tablau, nifer y rhesi "byw", ac ati. Gallwch ddod o hyd i lawer o amrywiadau o sgriptiau parod ar y Rhyngrwyd. Cymerasom fel sail sgript gan PostgreSQL Experts, sy'n gallu gwerthuso tablau bloat ynghyd â mynegeion btree tost a bloat. Yn ein profiad ni, ei gamgymeriad yw 10-20%.

Ffordd arall yw defnyddio'r estyniad pgstattuple, sy'n eich galluogi i edrych y tu mewn i'r tudalennau a chael amcangyfrif ac union werth chwyddedig. Ond yn yr ail achos, bydd yn rhaid i chi sganio'r tabl cyfan.

Rydym yn ystyried gwerth bloat bach, hyd at 20%, yn dderbyniol. Gellir ei ystyried fel analog o ffactor llenwi ar gyfer byrddau и mynegeion. Ar 50% ac uwch, gall problemau perfformiad ddechrau.

Ffyrdd o frwydro yn erbyn chwyddedig

Mae gan Postgres sawl ffordd o ddelio â bloat allan o'r bocs, ond nid ydynt bob amser yn addas i bawb.

Ffurfweddu AUTOVACUUM fel nad yw chwydd yn digwydd. Neu'n fwy manwl gywir, i'w gadw ar lefel sy'n dderbyniol i chi. Mae hwn yn ymddangos fel cyngor “capten”, ond mewn gwirionedd nid yw hyn bob amser yn hawdd ei gyflawni. Er enghraifft, mae gennych ddatblygiad gweithredol gyda newidiadau rheolaidd i'r sgema data, neu mae rhyw fath o fudo data yn digwydd. O ganlyniad, gall eich proffil llwyth newid yn aml a bydd yn amrywio o dabl i dabl fel arfer. Mae hyn yn golygu bod angen i chi weithio ychydig ymlaen yn gyson ac addasu AWTOVACUUM i broffil newidiol pob tabl. Ond yn amlwg nid yw hyn yn hawdd i'w wneud.

Rheswm cyffredin arall pam na all AWTOVACUUM gadw i fyny â thablau yw oherwydd bod trafodion hirdymor sy'n ei atal rhag glanhau'r data sydd ar gael i'r trafodion hynny. Mae'r argymhelliad yma hefyd yn amlwg - cael gwared ar drafodion “hongian” a lleihau amser trafodion gweithredol. Ond os yw'r llwyth ar eich cais yn hybrid o OLAP ac OLTP, yna gallwch chi gael llawer o ddiweddariadau aml ac ymholiadau byr ar yr un pryd, yn ogystal â gweithrediadau hirdymor - er enghraifft, adeiladu adroddiad. Mewn sefyllfa o'r fath, mae'n werth meddwl am wasgaru'r llwyth ar draws gwahanol seiliau, a fydd yn caniatáu ar gyfer mireinio pob un ohonynt yn fwy manwl.

Enghraifft arall - hyd yn oed os yw'r proffil yn homogenaidd, ond mae'r gronfa ddata o dan lwyth uchel iawn, yna efallai na fydd hyd yn oed y AWTOVACUUM mwyaf ymosodol yn ymdopi, a bydd chwydd yn digwydd. Graddio (fertigol neu lorweddol) yw'r unig ateb.

Beth i'w wneud mewn sefyllfa lle rydych chi wedi sefydlu AWTOVACUUM, ond mae'r chwydd yn parhau i dyfu.

Tîm LLAWN GWAG yn ailadeiladu cynnwys tablau a mynegeion ac yn gadael data perthnasol yn unig ynddynt. Er mwyn dileu bloat, mae'n gweithio'n berffaith, ond yn ystod ei weithrediad mae clo unigryw ar y bwrdd yn cael ei ddal (AccessExclusiveLock), na fydd yn caniatáu gweithredu ymholiadau ar y bwrdd hwn, hyd yn oed yn dewis. Os gallwch chi fforddio atal eich gwasanaeth neu ran ohono am beth amser (o ddegau o funudau i sawl awr yn dibynnu ar faint y gronfa ddata a'ch caledwedd), yna'r opsiwn hwn yw'r gorau. Yn anffodus, nid oes gennym amser i redeg VACUUM LLAWN yn ystod y gwaith cynnal a chadw a drefnwyd, felly nid yw'r dull hwn yn addas i ni.

Tîm CLUSTER Yn ailadeiladu cynnwys tablau yn yr un modd â GWAGLU LLAWN, ond yn caniatáu ichi nodi mynegai y bydd y data'n cael ei archebu'n gorfforol ar ddisg yn unol â hi (ond yn y dyfodol nid yw'r gorchymyn wedi'i warantu ar gyfer rhesi newydd). Mewn rhai sefyllfaoedd, mae hwn yn optimeiddio da ar gyfer nifer o ymholiadau - gyda darllen cofnodion lluosog yn ôl mynegai. Mae anfantais y gorchymyn yr un fath ag un VACUUM LLAWN - mae'n cloi'r bwrdd yn ystod y llawdriniaeth.

Tîm REINDEX debyg i'r ddau flaenorol, ond yn ailadeiladu mynegai penodol neu holl fynegai'r tabl. Mae cloeon ychydig yn wannach: mae ShareLock ar y bwrdd (yn atal addasiadau, ond yn caniatáu dewis) ac AccessExclusiveLock ar y mynegai yn cael ei ailadeiladu (yn rhwystro ymholiadau gan ddefnyddio'r mynegai hwn). Fodd bynnag, yn y fersiwn 12fed o Postgres ymddangosodd paramedr YN GYDOL, sy'n eich galluogi i ailadeiladu'r mynegai heb rwystro ychwanegu, addasu neu ddileu cofnodion ar yr un pryd.

Mewn fersiynau cynharach o Postgres, gallwch chi gael canlyniad tebyg i ddefnyddio REINDEX AR Y CYD CREU MYNEGAI AR GYDOL OES. Mae'n caniatáu ichi greu mynegai heb gloi llym (ShareUpdateExclusiveLock, nad yw'n ymyrryd ag ymholiadau cyfochrog), yna disodli'r hen fynegai gydag un newydd a dileu'r hen fynegai. Mae hyn yn caniatáu ichi ddileu bloat mynegai heb ymyrryd â'ch cais. Mae'n bwysig ystyried, wrth ailadeiladu mynegeion, y bydd llwyth ychwanegol ar yr is-system ddisg.

Felly, os ar gyfer mynegeion mae yna ffyrdd o ddileu chwydd “ar y pry”, yna nid oes dim ar gyfer tablau. Dyma lle mae amrywiaeth o estyniadau allanol yn dod i rym: pg_ail-bacio (pg_reorg gynt), pgcompact, pgcompacttable ac eraill. Yn yr erthygl hon, ni fyddaf yn eu cymharu a byddaf yn siarad am pg_repack yn unig, yr ydym, ar ôl rhywfaint o addasiad, yn ein defnyddio ein hunain.

Sut mae pg_repack yn gweithio

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig
Gadewch i ni ddweud bod gennym dabl cwbl gyffredin - gyda mynegeion, cyfyngiadau ac, yn anffodus, gyda bloat. Cam cyntaf pg_repack yw creu tabl log i storio data am yr holl newidiadau tra ei fod yn rhedeg. Bydd y sbardun yn ailadrodd y newidiadau hyn ar gyfer pob mewnosodiad, diweddariad a dileu. Yna crëir tabl, tebyg i'r un gwreiddiol o ran strwythur, ond heb fynegeion a chyfyngiadau, er mwyn peidio ag arafu'r broses o fewnosod data.

Nesaf, mae pg_repack yn trosglwyddo'r data o'r hen dabl i'r tabl newydd, gan hidlo'r holl resi amherthnasol yn awtomatig, ac yna'n creu mynegeion ar gyfer y tabl newydd. Wrth gyflawni'r holl weithrediadau hyn, mae newidiadau'n cronni yn y tabl log.

Y cam nesaf yw trosglwyddo'r newidiadau i'r tabl newydd. Perfformir y mudo dros sawl iteriad, a phan fo llai nag 20 cofnod ar ôl yn y tabl log, mae pg_repack yn caffael clo cryf, yn mudo'r data diweddaraf, ac yn disodli'r hen dabl gyda'r un newydd yn nhablau system Postgres. Dyma'r unig amser byr iawn pan na fyddwch yn gallu gweithio gyda'r bwrdd. Ar ôl hyn, mae'r hen fwrdd a'r bwrdd gyda logiau yn cael eu dileu ac mae lle yn cael ei ryddhau yn y system ffeiliau. Mae'r broses wedi'i chwblhau.

Mae popeth yn edrych yn wych mewn theori, ond beth sy'n digwydd yn ymarferol? Fe wnaethon ni brofi pg_repack heb lwyth ac o dan lwyth, a gwirio ei weithrediad rhag ofn y byddai'n stopio'n gynnar (mewn geiriau eraill, gan ddefnyddio Ctrl+C). Roedd pob prawf yn bositif.

Aethon ni i'r siop fwyd - ac yna aeth popeth ddim fel roedden ni'n disgwyl.

Crempog gyntaf ar werth

Ar y clwstwr cyntaf cawsom gamgymeriad ynghylch torri cyfyngiad unigryw:

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

Roedd gan y cyfyngiad hwn mynegai enw wedi'i gynhyrchu'n awtomatig_16508 - cafodd ei greu gan pg_repack. Yn seiliedig ar y priodoleddau sydd wedi'u cynnwys yn ei gyfansoddiad, penderfynasom "ein" cyfyngiad sy'n cyfateb iddo. Y broblem oedd nad yw hwn yn gyfyngiad cwbl gyffredin, ond yn gyfyngiad gohiriedig (cyfyngiad gohiriedig), h.y. mae ei ddilysiad yn cael ei berfformio yn hwyrach na'r gorchymyn sql, sy'n arwain at ganlyniadau annisgwyl.

Cyfyngiadau gohiriedig: pam mae eu hangen a sut maent yn gweithio

Ychydig o ddamcaniaeth am gyfyngiadau gohiriedig.
Gadewch i ni ystyried enghraifft syml: mae gennym lyfr cyfeirio tabl o geir gyda dwy nodwedd - enw a threfn y car yn y cyfeiriadur.
Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

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



Gadewch i ni ddweud bod angen i ni gyfnewid y ceir cyntaf a'r ail. Yr ateb syml yw diweddaru'r gwerth cyntaf i'r ail, a'r ail i'r cyntaf:

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

Ond pan fyddwn yn rhedeg y cod hwn, rydym yn disgwyl toriad cyfyngiad oherwydd bod trefn y gwerthoedd yn y tabl yn unigryw:

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

Sut alla i ei wneud yn wahanol? Opsiwn un: ychwanegu gwerth ychwanegol yn lle archeb y gellir ei warantu na fydd yn bodoli yn y tabl, er enghraifft “-1”. Mewn rhaglennu, gelwir hyn yn “gyfnewid gwerthoedd dau newidyn trwy draean.” Yr unig anfantais o'r dull hwn yw'r diweddariad ychwanegol.

Opsiwn dau: Ailgynllunio'r tabl i ddefnyddio math o ddata pwynt arnawf ar gyfer gwerth archeb yn lle cyfanrifau. Yna, wrth ddiweddaru'r gwerth o 1, er enghraifft, i 2.5, bydd y cofnod cyntaf yn "sefyll" yn awtomatig rhwng yr ail a'r trydydd. Mae'r ateb hwn yn gweithio, ond mae dau gyfyngiad. Yn gyntaf, ni fydd yn gweithio i chi os defnyddir y gwerth rhywle yn y rhyngwyneb. Yn ail, yn dibynnu ar drachywiredd y math o ddata, bydd gennych nifer cyfyngedig o fewnosodiadau posibl cyn ailgyfrifo gwerthoedd yr holl gofnodion.

Opsiwn tri: gohirio’r cyfyngiad fel ei fod yn cael ei wirio ar adeg ymrwymo yn unig:

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

Gan fod rhesymeg ein cais cychwynnol yn sicrhau bod yr holl werthoedd yn unigryw ar adeg ymrwymo, bydd yn llwyddo.

Mae'r enghraifft a drafodwyd uchod, wrth gwrs, yn synthetig iawn, ond mae'n datgelu'r syniad. Yn ein cais, rydym yn defnyddio cyfyngiadau gohiriedig i weithredu rhesymeg sy'n gyfrifol am ddatrys gwrthdaro pan fydd defnyddwyr ar yr un pryd yn gweithio gyda gwrthrychau teclyn a rennir ar y bwrdd. Mae defnyddio cyfyngiadau o'r fath yn ein galluogi i wneud cod y cais ychydig yn symlach.

Yn gyffredinol, yn dibynnu ar y math o gyfyngiad, mae gan Postgres dair lefel o ronynnedd i'w gwirio: lefelau rhes, trafodiad a mynegiant.
Postgres: bloat, pg_repack a chyfyngiadau gohiriedig
Ffynhonnell: begriffs

Mae CHECK ac NOT NULL bob amser yn cael eu gwirio ar lefel y rhes; ar gyfer cyfyngiadau eraill, fel y gwelir o'r tabl, mae yna wahanol opsiynau. Gallwch ddarllen mwy yma.

I grynhoi'n fyr, mae cyfyngiadau gohiriedig mewn nifer o sefyllfaoedd yn darparu cod mwy darllenadwy a llai o orchmynion. Fodd bynnag, mae'n rhaid i chi dalu am hyn trwy gymhlethu'r broses ddadfygio, gan fod yr eiliad y mae'r gwall yn digwydd a'r eiliad y byddwch chi'n dod i wybod amdano yn cael eu gwahanu mewn amser. Problem bosibl arall yw ei bod yn bosibl na fydd y trefnydd bob amser yn gallu llunio'r cynllun gorau posibl os yw'r cais yn ymwneud â chyfyngiad gohiriedig.

Gwella pg_repack

Rydym wedi sôn am beth yw cyfyngiadau gohiriedig, ond sut maent yn berthnasol i'n problem? Gadewch i ni gofio'r gwall a gawsom yn gynharach:

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

Mae'n digwydd pan fydd data'n cael ei gopïo o dabl log i dabl newydd. Mae hyn yn edrych yn rhyfedd oherwydd... mae'r data yn y tabl log wedi'i ymrwymo ynghyd â'r data yn y tabl ffynhonnell. Os ydynt yn bodloni cyfyngiadau'r tabl gwreiddiol, sut y gallant dorri'r un cyfyngiadau yn yr un newydd?

Fel y digwyddodd, mae gwraidd y broblem yn gorwedd yn y cam blaenorol o pg_repack, sy'n creu mynegeion yn unig, ond nid cyfyngiadau: roedd gan yr hen dabl gyfyngiad unigryw, a chreodd yr un newydd fynegai unigryw yn lle hynny.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Mae'n bwysig nodi yma, os yw'r cyfyngiad yn normal a heb ei ohirio, yna mae'r mynegai unigryw a grëwyd yn lle hynny yn cyfateb i'r cyfyngiad hwn, oherwydd Gweithredir cyfyngiadau unigryw yn Postgres trwy greu mynegai unigryw. Ond yn achos cyfyngiad gohiriedig, nid yw'r ymddygiad yr un peth, oherwydd ni ellir gohirio'r mynegai a chaiff ei wirio bob amser ar yr adeg y gweithredir y gorchymyn sql.

Felly, mae hanfod y broblem yn gorwedd yn "oedi" y siec: yn y tabl gwreiddiol mae'n digwydd ar adeg ymrwymo, ac yn y tabl newydd ar yr adeg y gweithredir y gorchymyn sql. Mae hyn yn golygu bod angen i ni sicrhau bod y gwiriadau'n cael eu cynnal yr un fath yn y ddau achos: naill ai'n cael eu gohirio bob amser, neu bob amser yn syth.

Felly pa syniadau oedd gennym ni?

Creu mynegai tebyg i ohiriedig

Y syniad cyntaf yw cynnal y ddau wiriad yn y modd uniongyrchol. Gall hyn gynhyrchu nifer o gyfyngiadau positif ffug, ond os nad oes llawer ohonynt, ni ddylai hyn effeithio ar waith defnyddwyr, gan fod gwrthdaro o'r fath yn sefyllfa arferol iddynt. Maent yn digwydd, er enghraifft, pan fydd dau ddefnyddiwr yn dechrau golygu'r un teclyn ar yr un pryd, ac nid oes gan gleient yr ail ddefnyddiwr amser i dderbyn gwybodaeth bod y teclyn eisoes wedi'i rwystro i'w olygu gan y defnyddiwr cyntaf. Mewn sefyllfa o'r fath, mae'r gweinydd yn gwrthod yr ail ddefnyddiwr, ac mae ei gleient yn rholio'r newidiadau yn ôl ac yn blocio'r teclyn. Ychydig yn ddiweddarach, pan fydd y defnyddiwr cyntaf yn cwblhau golygu, bydd yr ail yn derbyn gwybodaeth nad yw'r teclyn bellach wedi'i rwystro a bydd yn gallu ailadrodd ei weithred.

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Er mwyn sicrhau bod gwiriadau bob amser yn y modd heb ei ohirio, rydym wedi creu mynegai newydd tebyg i'r cyfyngiad gwreiddiol a ohiriwyd:

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

Yn yr amgylchedd prawf, dim ond ychydig o wallau disgwyliedig a gawsom. Llwyddiant! Fe wnaethom redeg pg_repack eto ar gynhyrchu a chael 5 gwall ar y clwstwr cyntaf mewn awr o waith. Mae hwn yn ganlyniad derbyniol. Fodd bynnag, eisoes ar yr ail glwstwr cynyddodd nifer y gwallau yn sylweddol a bu'n rhaid i ni roi'r gorau i pg_repack.

Pam y digwyddodd? Mae'r tebygolrwydd y bydd gwall yn digwydd yn dibynnu ar faint o ddefnyddwyr sy'n gweithio gyda'r un teclynnau ar yr un pryd. Mae’n debyg, bryd hynny roedd llawer llai o newidiadau cystadleuol gyda’r data wedi’i storio ar y clwstwr cyntaf nag ar y lleill, h.y. dim ond “lwcus” oedden ni.

Wnaeth y syniad ddim gweithio. Ar y pwynt hwnnw, gwelsom ddau ateb arall: ailysgrifennu ein cod cais i ddileu cyfyngiadau gohiriedig, neu “teach” pg_repack i weithio gyda nhw. Fe wnaethon ni ddewis yr ail un.

Disodli mynegeion yn y tabl newydd gyda chyfyngiadau gohiriedig o'r tabl gwreiddiol

Roedd pwrpas yr adolygiad yn amlwg - os oes gan y tabl gwreiddiol gyfyngiad gohiriedig, yna ar gyfer yr un newydd mae angen i chi greu cyfyngiad o'r fath, ac nid mynegai.

I brofi ein newidiadau, fe wnaethon ni ysgrifennu prawf syml:

  • tabl gyda chyfyngiad gohiriedig ac un cofnod;
  • mewnosod data mewn dolen sy'n gwrthdaro â chofnod sy'n bodoli;
  • gwneud diweddariad – nid yw'r data bellach yn gwrthdaro;
  • ymrwymo'r newidiadau.

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;

Roedd y fersiwn wreiddiol o pg_repack bob amser yn damwain ar y mewnosodiad cyntaf, roedd y fersiwn wedi'i haddasu yn gweithio heb wallau. Gwych.

Rydym yn mynd i gynhyrchu ac eto yn cael gwall ar yr un cam o gopïo data o'r tabl log i un newydd:

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

Sefyllfa glasurol: mae popeth yn gweithio mewn amgylcheddau prawf, ond nid wrth gynhyrchu?!

APPLY_COUNT a chyffordd dau swp

Dechreuon ni ddadansoddi'r cod yn llythrennol fesul llinell a darganfod pwynt pwysig: mae data'n cael ei drosglwyddo o'r tabl log i un newydd mewn sypiau, roedd y cysonyn APPLY_COUNT yn nodi maint y swp:

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

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

Y broblem yw y gall y data o'r trafodiad gwreiddiol, lle gallai sawl gweithrediad o bosibl dorri'r cyfyngiad, pan gaiff ei drosglwyddo, ddod i ben ar gyffordd dau swp - bydd hanner y gorchmynion yn cael eu cyflawni yn y swp cyntaf, a'r hanner arall yn yr ail. Ac yma, yn dibynnu ar eich lwc: os na fydd y timau'n torri unrhyw beth yn y swp cyntaf, yna mae popeth yn iawn, ond os gwnânt hynny, mae gwall yn digwydd.

Mae APPLY_COUNT yn hafal i 1000 o gofnodion, sy'n esbonio pam roedd ein profion yn llwyddiannus - nid oeddent yn ymdrin ag achos “swp-gyffordd”. Fe wnaethon ni ddefnyddio dau orchymyn - mewnosod a diweddaru, felly roedd union drafodion 500 o ddau orchymyn bob amser yn cael eu gosod mewn swp ac ni chawsom unrhyw broblemau. Ar ôl ychwanegu'r ail ddiweddariad, stopiodd ein golygiad weithio:

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;

Felly, y dasg nesaf yw sicrhau bod data o'r tabl gwreiddiol, a newidiwyd mewn un trafodiad, yn dod i ben yn y tabl newydd hefyd o fewn un trafodiad.

Gwrthod rhag sypynnu

Ac eto cawsom ddau ateb. Yn gyntaf: gadewch i ni roi'r gorau yn llwyr i rannu'n sypiau a throsglwyddo data mewn un trafodiad. Mantais yr ateb hwn oedd ei symlrwydd - roedd y newidiadau cod gofynnol yn fach iawn (gyda llaw, mewn fersiynau hŷn roedd pg_reorg yn gweithio'n union fel 'na). Ond mae yna broblem - rydyn ni'n creu trafodiad hirdymor, ac mae hyn, fel y dywedwyd yn flaenorol, yn fygythiad i ymddangosiad bloat newydd.

Mae'r ail ateb yn fwy cymhleth, ond mae'n debyg yn fwy cywir: creu colofn yn y tabl log gyda dynodwr y trafodiad a ychwanegodd ddata at y tabl. Yna, pan fyddwn yn copïo data, gallwn ei grwpio yn ôl y nodwedd hon a sicrhau bod newidiadau cysylltiedig yn cael eu trosglwyddo gyda'i gilydd. Bydd y swp yn cael ei ffurfio o nifer o drafodion (neu un mawr) a bydd ei faint yn amrywio yn dibynnu ar faint o ddata a newidiwyd yn y trafodion hyn. Mae'n bwysig nodi, gan fod data o wahanol drafodion yn mynd i mewn i'r tabl log mewn trefn ar hap, ni fydd bellach yn bosibl ei ddarllen yn olynol, fel yr oedd o'r blaen. seqscan ar gyfer pob cais gyda hidlo gan tx_id yn rhy ddrud, mae angen mynegai, ond bydd hefyd yn arafu'r dull oherwydd gorbenion ei ddiweddaru. Yn gyffredinol, fel bob amser, mae angen i chi aberthu rhywbeth.

Felly, penderfynasom ddechrau gyda'r opsiwn cyntaf, gan ei fod yn symlach. Yn gyntaf, roedd angen deall a fyddai trafodiad hir yn broblem wirioneddol. Gan fod y prif drosglwyddo data o'r hen dabl i'r un newydd hefyd yn digwydd mewn un trafodiad hir, mae'r cwestiwn wedi'i drawsnewid yn "faint fyddwn ni'n cynyddu'r trafodiad hwn?" Mae hyd y trafodiad cyntaf yn dibynnu'n bennaf ar faint y tabl. Mae hyd un newydd yn dibynnu ar faint o newidiadau sy’n cronni yn y tabl yn ystod y trosglwyddiad data, h.y. ar ddwysedd y llwyth. Digwyddodd y rhediad pg_repack yn ystod cyfnod o lwyth gwasanaeth lleiaf posibl, ac roedd nifer y newidiadau yn anghymesur o fach o'i gymharu â maint gwreiddiol y tabl. Fe wnaethom benderfynu y gallem esgeuluso amser trafodiad newydd (er mwyn cymharu, ar gyfartaledd mae'n 1 awr a 2-3 munud).

Roedd yr arbrofion yn gadarnhaol. Lansio ar gynhyrchu hefyd. Er eglurder, dyma lun gyda maint un o'r cronfeydd data ar ôl rhedeg:

Postgres: bloat, pg_repack a chyfyngiadau gohiriedig

Gan ein bod yn gwbl fodlon â'r datrysiad hwn, ni wnaethom geisio gweithredu'r ail un, ond rydym yn ystyried y posibilrwydd o'i drafod gyda datblygwyr yr estyniad. Yn anffodus, nid yw ein hadolygiad presennol yn barod i'w gyhoeddi eto, gan mai dim ond gyda chyfyngiadau gohiriedig unigryw y gwnaethom ddatrys y broblem, ac ar gyfer ardal lawn mae angen darparu cefnogaeth i fathau eraill. Rydym yn gobeithio gallu gwneud hyn yn y dyfodol.

Efallai bod gennych chi gwestiwn, pam wnaethon ni hyd yn oed gymryd rhan yn y stori hon gydag addasiad pg_repack, ac na wnaethom, er enghraifft, ddefnyddio ei analogau? Ar ryw adeg fe wnaethom feddwl am hyn hefyd, ond fe wnaeth y profiad cadarnhaol o’i ddefnyddio’n gynharach, ar fyrddau heb gyfyngiadau gohiriedig, ein hysgogi i geisio deall hanfod y broblem a’i thrwsio. Yn ogystal, mae defnyddio atebion eraill hefyd yn gofyn am amser i gynnal profion, felly fe wnaethom benderfynu y byddem yn ceisio trwsio'r broblem ynddo yn gyntaf, a phe byddem yn sylweddoli na allem wneud hyn mewn amser rhesymol, yna byddem yn dechrau edrych ar analogau. .

Canfyddiadau

Yr hyn y gallwn ei argymell yn seiliedig ar ein profiad ein hunain:

  1. Monitro eich bloat. Yn seiliedig ar ddata monitro, gallwch ddeall pa mor dda y mae autovacuum wedi'i ffurfweddu.
  2. Addaswch AWTOVACUUM i gadw chwydd ar lefel dderbyniol.
  3. Os yw'r bloat yn dal i dyfu ac na allwch ei oresgyn gan ddefnyddio offer y tu allan i'r bocs, peidiwch â bod ofn defnyddio estyniadau allanol. Y prif beth yw profi popeth yn dda.
  4. Peidiwch â bod ofn addasu atebion allanol i weddu i'ch anghenion - weithiau gall hyn fod yn fwy effeithiol a hyd yn oed yn haws na newid eich cod eich hun.

Ffynhonnell: hab.com

Ychwanegu sylw