Postgres: bloat, pg_repack an deferred Contrainten

Postgres: bloat, pg_repack an deferred Contrainten

Den Effekt vum Bloat op DĂ«scher an Indexen ass wĂ€it bekannt an ass prĂ€sent net nĂ«mmen am Postgres. Et gi WeeĂ«r mat et aus der KĂ«scht ze kĂ«mmeren, wĂ©i VACUUM VOLLZÄIT oder CLUSTER, mĂ€ si SpĂ€r DĂ«scher wĂ€hrend Operatioun an dofir kann net Ă«mmer benotzt ginn.

Den Artikel enthÀlt eng kleng Theorie iwwer wéi Bloat geschitt, wéi Dir et kÀmpfe kënnt, iwwer ofgeschloss AschrÀnkungen an d'Problemer déi se fir d'Benotzung vun der pg_repack Extensioun bréngen.

Dësen Artikel ass geschriwwen baséiert op meng Ried op PgConf.Russia 2020.

Spillt Video

Firwat geschitt Bloat?

Postgres baséiert op engem Multi-Version Modell (MVCC). Seng Essenz ass, datt all Zeil an der Tabell e puer Versiounen hunn kann, iwwerdeems Transaktiounen gesinn net méi wéi eng vun dëse Versiounen, awer net onbedéngt déi selwecht. Dëst erlaabt e puer Transaktiounen glÀichzÀiteg ze schaffen an hunn quasi keen Impakt op all aner.

Natierlech mussen all dës Versioune gespÀichert ginn. Postgres schafft mat Erënnerung SÀit fir SÀit an eng SÀit ass de Mindestbetrag vun Daten, déi vun der Disk gelies oder geschriwwe kënne ginn. Loosst eis e klengt Beispill kucken fir ze verstoen wéi dëst geschitt.

Loosst eis soen, mir hunn en DĂ«sch, un deem mir e puer Rekorder bĂ€igefĂŒĂŒgt hunn. Nei DonnĂ©eĂ«n sinn op der Ă©ischter SĂ€it vun der Datei opgetaucht wou den DĂ«sch gespĂ€ichert ass. DĂ«st sinn Live Versioune vu Reihen, dĂ©i fir aner Transaktiounen no engem Engagement disponibel sinn (fir SimplicitĂ©it wĂ€erte mir dovun ausgoen datt d'Isolatiounsniveau Read Committed ass).

Postgres: bloat, pg_repack an deferred Contrainten

Mir hunn dann eng vun den Entréen aktualiséiert, an domat déi al Versioun als net méi relevant markéiert.

Postgres: bloat, pg_repack an deferred Contrainten

Schrëtt fir Schrëtt, d'Aktualiséierung an d'LÀschen vun der Reieversioun, hu mir op eng SÀit opgehalen, an dÀr ongeféier d'Halschent vun den Donnéeën "Dreck" ass. Dës Donnéeë sinn net fir all Transaktioun sichtbar.

Postgres: bloat, pg_repack an deferred Contrainten

Postgres huet e Mechanismus VAKUUM, dĂ©i verouderte Versioune botzt a Plaz fir nei DonnĂ©eĂ«n mĂ©cht. Awer wann et net aggressiv genuch konfigurĂ©iert ass oder beschĂ€ftegt ass an aneren DĂ«scher ze schaffen, da bleift "MĂŒlldaten" a mir mussen zousĂ€tzlech SĂ€iten fir nei DonnĂ©eĂ«n benotzen.

Also an eisem Beispill, iergendwann wÀert den Dësch aus véier SÀiten besteet, awer nëmmen d'Halschent dovun enthÀlt Live-Daten. Als Resultat, wann Dir op den Dësch kënnt, liesen mir vill méi Daten wéi néideg.

Postgres: bloat, pg_repack an deferred Contrainten

Och wann VACUUM elo all irrelevant Rei Versiounen lÀscht, wÀert d'Situatioun net dramatesch verbesseren. Mir wÀerten frÀi Plaz op SÀiten oder souguer ganze SÀiten fir nei Zeile hunn, mÀ mir wÀerten nach méi Daten liesen wéi néideg.
Iwwregens, wann eng komplett eidel SÀit (déi zweet an eisem Beispill) um Enn vun der Datei stoung, da kéint VACUUM se ofschneiden. Mee elo ass si an der Mëtt, sou kann nÀischt mat hir gemaach ginn.

Postgres: bloat, pg_repack an deferred Contrainten

Wann d'Zuel vun esou eidel oder héich spatzen SÀiten grouss gëtt, déi bloat genannt gëtt, fÀnkt et un d'Leeschtung ze beaflossen.

Alles uewen beschriwwen ass d'Mechanik vun der Optriede vun bloat an Dëscher. An Indexen geschitt dat op vill déi selwecht Manéier.

Hunn ech bloat?

Et gi verschidde Weeër fir ze bestëmmen ob Dir Bloat hutt. D'Iddi vun der éischter ass intern Postgres Statistiken ze benotzen, déi geschÀtzte Informatioun iwwer d'Zuel vun de Reihen an den Dëscher, d'Zuel vun de "live" Reihen, asw. Mir hunn als Basis geholl Schrëft aus PostgreSQL Experten, déi bloat Dëscher zesumme mat Toast an bloat btree Indexen evaluéieren kann. An eiser Erfahrung ass sÀi Feeler 10-20%.

Eng aner Manéier ass d'Extensioun ze benotzen pgstattuple, wat Iech erlaabt Iech an de SÀiten ze kucken a souwuel e geschÀtzte wéi och e genee Bloat-WÀert ze kréien. Awer am zweete Fall musst Dir de ganzen Dësch scannen.

Mir betruechten e klenge BloatwÀert, bis zu 20%, akzeptabel. Et kann als Analog vu Fillfaktor ugesi ginn Dëscher О Indizes. Bei 50% a méi héich kënnen d'Leeschtungsproblemer ufÀnken.

Weeër fir Bloat ze bekÀmpfen

Postgres huet verschidde Weeër fir mat Bloat aus der Këscht ze këmmeren, awer si sinn net ëmmer gëeegent fir jiddereen.

KonfigurĂ©ieren AUTOVACUUM sou datt Bloat net geschitt. Oder mĂ©i prĂ€zis, fir et op engem Niveau ze halen, deen Iech akzeptabel ass. DĂ«st schĂ©ngt wĂ©i de "KapitĂ€n" Berodung, awer a Wierklechkeet ass dĂ«st net Ă«mmer einfach ze erreechen. Zum Beispill hutt Dir eng aktiv EntwĂ©cklung mat reegelmĂ©issegen Ännerungen am Dateschema, oder eng Zort Datemigratioun fĂ«nnt statt. Als Resultat kann Äre Laaschtprofil dacks Ă€nneren a wĂ€ert typesch vun DĂ«sch zu DĂ«sch variĂ©ieren. DĂ«st bedeit datt Dir stĂ€nneg e bĂ«ssen viru muss schaffen an AUTOVACUUM un de verĂ€nnerleche Profil vun all DĂ«sch ajustĂ©ieren. Awer natierlech ass dĂ«st net einfach ze maachen.

En anere gemeinsame Grond firwat AUTOVACUUM net mat DĂ«scher hale kann ass well et laang lafende Transaktioune gĂ«tt, dĂ©i et verhĂ«nneren datt d'DonnĂ©eĂ«n opmaachen, dĂ©i fir dĂ«s Transaktioune verfĂŒgbar sinn. D'Empfehlung hei ass och offensichtlech - lass vun "dangling" Transaktiounen lass an d'ZĂ€it vun aktive Transaktiounen minimisĂ©ieren. Awer wann d'Laascht op Är Applikatioun en Hybrid vun OLAP an OLTP ass, da kĂ«nnt Dir glĂ€ichzĂ€iteg vill heefeg Updates a kuerz Ufroen hunn, souwĂ©i laangfristeg Operatiounen - zum Beispill e Bericht bauen. An esou enger Situatioun ass et derwĂ€ert ze denken iwwer d'Verbreedung vun der Belaaschtung iwwer verschidde Basen, wat fir jiddereng vun hinnen mĂ©i Feintuning erlaabt.

En anert Beispill - och wann de Profil homogen ass, awer d'Datebank ass ënner enger ganz héijer Belaaschtung, da kann och den aggressivsten AUTOVACUUM net eens ginn, an e Bloat wÀert optrieden. Skaléieren (vertikal oder horizontal) ass déi eenzeg Léisung.

Wat ze maachen an enger Situatioun wou Dir AUTOVACUUM ageriicht hutt, awer de Bloat wuesse weider.

Equipe VAKUUM VOLLZÄIT baut den Inhalt vun DĂ«scher an Indexen op a lĂ©isst nĂ«mmen relevant DonnĂ©eĂ«n dran. Fir bloat ze eliminĂ©ieren, funktionnĂ©iert et perfekt, awer wĂ€hrend senger AusfĂ©ierung gĂ«tt en exklusive SpĂ€r op den DĂ«sch erfaasst (AccessExclusiveLock), wat net erlaabt Ufroen op dĂ«sem DĂ«sch auszefĂ©ieren, och wielt. Wann Dir Iech leeschte kĂ«nnt Äre Service oder en Deel dovun fir eng ZĂ€it ze stoppen (vun ZĂ©nger vu Minutten bis e puer Stonnen ofhĂ€ngeg vun der GrĂ©isst vun der Datebank an Ärer Hardware), dann ass dĂ«s Optioun dĂ©i bescht. Leider hu mir keng ZĂ€it fir VACUUM FULL wĂ€hrend dem geplangten Ënnerhalt ze lafen, sou datt dĂ«s Method net fir eis gĂ«eegent ass.

Equipe CLUSTER Rebuilds den Inhalt vun DĂ«scher an dĂ©i selwecht Art a Weis wĂ©i VACUUM VOLLZÄIT, mee erlaabt Iech en Index ze uginn no deem d'DonnĂ©eĂ«n kierperlech op Scheif bestallt ginn (awer an Zukunft ass d'Uerdnung net garantĂ©iert fir nei Zeile). A bestĂ«mmte Situatiounen ass dĂ«st eng gutt Optimisatioun fir eng Zuel vu Ufroen - mat der Liesung vu multiple records no Index. Den Nodeel vum Kommando ass d'selwecht wĂ©i dee vum VACUUM FULL - et gespaart den DĂ«sch wĂ€rend der Operatioun.

Equipe REINDEX Ă€hnlech wĂ©i dĂ©i zwee virdrun, awer baut e spezifeschen Index oder all Indizes vun der Tabell op. SpĂ€ren sinn liicht mĂ©i schwaach: ShareLock op den DĂ«sch (verhĂ«nnert Ännerungen, mee erlaabt auswielen) an AccessExclusiveLock op den Index nees opgebaut (blockĂ©iert Ufroen mat dĂ«sem Index). WĂ©i och Ă«mmer, an der 12. Versioun vum Postgres ass e Parameter opgetaucht GEMEENSCHT, wat Iech erlaabt den Index opzebauen ouni glĂ€ichzĂ€iteg Zousatz, Ännerung oder LĂ€schen vun Opzeechnungen ze blockĂ©ieren.

A frĂ©iere Versioune vu Postgres kĂ«nnt Dir e Resultat erreechen Ă€hnlech wĂ©i REINDEX CONCURRENTLY benotzt SCHAFFT INDEX glĂ€ichzĂ€iteg. Et erlaabt Iech en Index ouni strikt Sperrung ze kreĂ©ieren (ShareUpdateExclusiveLock, deen net mat parallelen Ufroen stĂ©iert), ersetzt dann den alen Index mat engem neien an lĂ€scht den alen Index. DĂ«st erlaabt Iech Indexbloat ze eliminĂ©ieren ouni Är Applikatioun ze stĂ©ieren. Et ass wichteg ze berĂŒcksichtegen datt wann Dir Indexen nei opbaut et eng zousĂ€tzlech Belaaschtung op den Disk Subsystem gĂ«tt.

Also, wann et fir Indizes WeeĂ«r ginn fir Bloat "op der Flucht" ze eliminĂ©ieren, da gĂ«tt et keng fir DĂ«scher. DĂ«st ass wou verschidde extern Extensiounen an d'Spill kommen: pg_repack (frĂ©ier pg_reorg), pg kompakt, pg kompakttabel an anerer. An dĂ«sem Artikel wĂ€ert ech se net verglĂ€ichen a wĂ€ert nĂ«mmen iwwer pg_repack schwĂ€tzen, dĂ©i, no e puer Ännerungen, mir selwer benotzen.

Wéi funktionéiert pg_repack

Postgres: bloat, pg_repack an deferred Contrainten
Loosst eis soen datt mir e ganz gewĂ©inleche DĂ«sch hunn - mat Indexen, Restriktiounen a leider mat Bloat. Den Ă©ischte SchrĂ«tt vum pg_repack ass eng Logtabel ze kreĂ©ieren fir Daten iwwer all Ännerungen ze spĂ€icheren wĂ€rend se leeft. Den AuslĂ©iser replizĂ©iert dĂ«s Ännerungen fir all Insert, Update a LĂ€schen. Da gĂ«tt en DĂ«sch erstallt, Ă€hnlech wĂ©i dĂ©i ursprĂ©nglech a Struktur, awer ouni Indexen a Restriktiounen, fir de Prozess vun der AfĂ©ierung vun DonnĂ©eĂ«n net ze luesen.

Als nĂ€chst transferĂ©iert pg_repack d'DonnĂ©eĂ«n vun der aler Tabell op den neien DĂ«sch, filtert automatesch all irrelevant Reihen aus, an erstellt dann Indexe fir den neien DĂ«sch. WĂ€rend der AusfĂ©ierung vun all dĂ«sen Operatiounen accumulĂ©ieren Ännerungen an der Logtabel.

De nĂ€chste SchrĂ«tt ass d'Ännerungen op den neien DĂ«sch ze transferĂ©ieren. D'Migratioun gĂ«tt iwwer e puer Iteratiounen duerchgefouert, a wann et manner wĂ©i 20 EntrĂ©en an der Logtabel bleiwen, kritt pg_repack e staarke SpĂ€r, migrĂ©iert dĂ©i lescht DonnĂ©eĂ«n an ersetzt dĂ©i al Tabell mat der neier an de Postgres System DĂ«scher. DĂ«st ass dĂ©i eenzeg a ganz kuerz ZĂ€it wou Dir net fĂ€eg sidd mam DĂ«sch ze schaffen. Duerno ginn dĂ©i al DĂ«sch an den DĂ«sch mat Logbicher gelĂ€scht a Plaz am Dateiesystem frĂ€igelooss. De Prozess ass fĂ€erdeg.

Alles gesÀit gutt an der Theorie, awer wat geschitt an der Praxis? Mir getest pg_repack ouni Laascht an ënner Laascht, a kontrolléiert seng Operatioun am Fall vun virzÀitegen Stoppen (an anere Wierder, benotzt Ctrl + C). All Tester ware positiv.

Mir sinn an de LiewensmëttelgeschÀft gaang - an dunn ass alles net gaangen wéi mir et erwaart hunn.

Éischt Pancake am Verkaf

Am éischte StÀrekoup hu mir e Feeler iwwer eng Violatioun vun enger eenzegaarteger AschrÀnkung kritt:

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

Dës Begrenzung hat en auto-generéierten Numm index_16508 - et gouf vum pg_repack erstallt. Baséierend op d'Attributer, déi a senger Zesummesetzung abegraff sinn, hu mir "eis" AschrÀnkung festgeluecht, déi domat entsprécht. De Problem huet sech erausgestallt datt dëst net eng ganz ordinÀr Limitatioun ass, mee eng ofgeschloss (ausgestallte BeschrÀnkung), d.h. seng Verifizéierung gëtt méi spéit gemaach wéi de sql Kommando, wat zu onerwaarte Konsequenzen féiert.

Deferred Contrainten: firwat si gebraucht ginn a wéi se funktionnéieren

Eng kleng Theorie iwwer ofgeschloss Restriktiounen.
Loosst eis en einfacht Beispill betruechten: mir hunn en Dësch-Referenzbuch vun Autoen mat zwee Attributer - den Numm an d'Uerdnung vum Auto am Verzeechnes.
Postgres: bloat, pg_repack an deferred Contrainten

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



Loosst eis soen, mir hu missen déi éischt an zweet Autoen austauschen. Déi einfach Léisung ass den éischte WÀert op den zweeten ze aktualiséieren, an den zweeten op den éischten:

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

Awer wa mir dëse Code lafen, erwaarden mir eng Zwangsverletzung well d'Uerdnung vun de WÀerter an der Tabell eenzegaarteg ass:

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

WĂ©i kann ech et anescht maachen? Optioun eent: FĂŒĂŒgt en zousĂ€tzleche WĂ€ertersatz fir eng Bestellung dĂ©i garantĂ©iert net an der Tabell existĂ©iert, zum Beispill "-1". An der ProgrammĂ©ierung gĂ«tt dĂ«st "d'WĂ€erter vun zwou Variabelen duerch eng DrĂ«ttel austauschen." Deen eenzegen Nodeel vun dĂ«ser Method ass den zousĂ€tzlechen Update.

Optioun zwee: Redesign den Dësch fir e Floating Point Datentyp fir den UerderwÀert ze benotzen anstatt ganz Zuelen. Dann, wann Dir de WÀert vun 1 aktualiséiert, zum Beispill, op 2.5, wÀert déi éischt Entrée automatesch tëscht dem zweeten an drëtten "Stand". Dës Léisung funktionnéiert, awer et ginn zwou AschrÀnkungen. Als éischt funktionnéiert et net fir Iech wann de WÀert iergendwou an der Interface benotzt gëtt. Zweetens, ofhÀngeg vun der PrÀzisioun vum Datetyp, hutt Dir eng limitéiert Zuel vu méiglechen Inserts ier Dir d'WÀerter vun allen Opzeechnungen nei berechnen.

Optioun drÀi: maacht d'Zwang ausgesat sou datt se nëmmen am Moment vum Engagement iwwerpréift gëtt:

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

Zënter der Logik vun eiser initialer Ufro garantéiert datt all WÀerter eenzegaarteg sinn zum ZÀitpunkt vum Engagement, wÀert et geléngen.

D'Beispill hei uewen diskutéiert ass natierlech ganz synthetesch, awer et weist d'Iddi op. An eiser Applikatioun benotze mir ausgestallte Contrainten fir Logik ëmzesetzen déi verantwortlech ass fir Konflikter ze léisen wann d'Benotzer glÀichzÀiteg mat gemeinsame Widgetobjekter um Bord schaffen. Mat esou Restriktiounen erlaabt eis den Applikatiounscode e bësse méi einfach ze maachen.

Am Allgemengen, ofhÀngeg vun der Aart vun der Zwang, huet Postgres drÀi Niveaue vu Granularitéit fir se ze kontrolléieren: Reihen, Transaktiouns- an Ausdrockniveauen.
Postgres: bloat, pg_repack an deferred Contrainten
Source: begriffs

CHECK an NET NULL ginn ëmmer um Zeilniveau gepréift; fir aner Restriktiounen, wéi aus der Tabell gesi ka ginn, ginn et verschidden Optiounen. Dir kënnt méi liesen hei.

Fir kuerz ze resuméieren, ofgeschloss AschrÀnkungen an enger Zuel vu Situatioune bidden méi liesbare Code a manner Kommandoen. Wéi och ëmmer, Dir musst dofir bezuelen andeems Dir den Debuggingprozess komplizéiert, well de Moment wou de Feeler geschitt an de Moment wou Dir doriwwer erausfënnt, sinn an der ZÀit getrennt. En anere méigleche Problem ass datt de Scheduler net ëmmer fÀeg ass en optimalen Plang ze konstruéieren wann d'Ufro eng ausgestallte BeschrÀnkung involvéiert.

Verbesserung vun pg_repack

Mir hunn iwwerdeckt wat ausgestallte Contrainten sinn, awer wéi bezéie se sech mat eisem Problem? Loosst eis de Feeler erënneren dee mir virdru krut:

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

Et geschitt wann Daten aus engem Logtabel op eng nei Tabell kopéiert ginn. Dëst gesÀit komesch aus well ... d'Donnéeën am Logtabel sinn zesumme mat den Daten an der Quelltabel engagéiert. Wann se d'AschrÀnkungen vun der ursprénglecher Tabell erfëllen, wéi kënne se déi selwecht Contrainten an der neier verletzen?

Wéi et sech erausstellt, lÀit d'Wurzel vum Problem am virege Schrëtt vum pg_repack, deen nëmmen Indexen erstellt, awer net AschrÀnkungen: déi al Tabell hat eng eenzegaarteg AschrÀnkung, an deen neien huet amplaz en eenzegaartegen Index erstallt.

Postgres: bloat, pg_repack an deferred Contrainten

Et ass wichteg hei ze bemierken datt wann d'Begrenzung normal ass an net ausgesat ass, dann ass den eenzegaartegen Index erstallt amplaz glÀichwÀerteg mat dëser AschrÀnkung, well Eenzegaarteg Contrainten am Postgres ginn ëmgesat andeems en eenzegaartegen Index erstallt gëtt. Awer am Fall vun enger ausgesater BeschrÀnkung ass d'Verhalen net datselwecht, well den Index kann net ausgesat ginn an ëmmer kontrolléiert gëtt zur ZÀit wou de sql Kommando ausgefouert gëtt.

Also lÀit d'Essenz vum Problem an der "Verzögerung" vum Scheck: an der ursprénglecher Tabell geschitt et zum ZÀitpunkt vun der Verpflichtung, an an der neier Tabell zur ZÀit wou de sql Kommando ausgefouert gëtt. Dat heescht, mir mussen dofir suergen, datt d'Kontrollen a béide FÀll d'selwecht gemaach ginn: entweder ëmmer verspéit oder ëmmer direkt.

Also wat Iddien hu mir?

Erstellt en Index Àhnlech wéi deferred

DĂ©i Ă©ischt Iddi ass bĂ©id Kontrollen am direkten Modus auszefĂ©ieren. DĂ«st kann e puer falsch positiv Restriktiounen generĂ©ieren, awer wann et e puer vun hinnen sinn, sollt dĂ«st d'Aarbecht vun de Benotzer net beaflossen, well sou Konflikter eng normal Situatioun fir si sinn. Si geschĂ©ien, zum Beispill, wann zwee Benotzer ufĂ€nken de selwechte Widget glĂ€ichzĂ€iteg z'Ă€nneren, an de Client vum zweete Benotzer huet keng ZĂ€it fir Informatioun ze krĂ©ien, datt de Widget scho fir d'Ännerung vum Ă©ischte Benotzer blockĂ©iert ass. An esou enger Situatioun refusĂ©iert de Server den zweete Benotzer, a sĂ€i Client rullt d'Ännerungen zrĂ©ck a blockĂ©iert de Widget. E bĂ«sse mĂ©i spĂ©it, wann den Ă©ischte Benotzer d'Änneren ofgeschloss huet, kritt deen zweeten Informatioun datt de Widget net mĂ©i blockĂ©iert ass a kĂ«nnen hir Handlung widderhuelen.

Postgres: bloat, pg_repack an deferred Contrainten

Fir sécherzestellen datt d'Schecken ëmmer am Net-deferred Modus sinn, hu mir en neien Index erstallt Àhnlech wéi déi ursprénglech deferred Constraint:

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

Am Testëmfeld krute mir nëmmen e puer erwaart Feeler. Erfolleg! Mir lafen pg_repack erëm op Produktioun a krut 5 Feeler op den éischte StÀrekoup an enger Stonn Aarbecht. Dëst ass en akzeptabelt Resultat. Wéi och ëmmer, schonn um zweete Cluster ass d'Zuel vun de Feeler dÀitlech eropgaang a mir hu misse pg_repack stoppen.

Firwat ass et geschitt? D'Wahrscheinlechkeet datt e Feeler geschitt hĂ€nkt dovun of wĂ©i vill Benotzer mat deeneselwechte Widgets zur selwechter ZĂ€it schaffen. Anscheinend gouf et dee Moment vill manner kompetitiv Ännerungen mat den Daten, dĂ©i um Ă©ischte Cluster gespĂ€ichert sinn, wĂ©i op deenen aneren, d.h. mir waren just "GlĂ©ck".

D'Iddi huet net geschafft. Zu deem ZÀitpunkt hu mir zwou aner Léisunge gesinn: eis Applikatiounscode nei schreiwen fir ausgestallte Contrainten ze verzichten, oder "léieren" pg_repack fir mat hinnen ze schaffen. Mir hunn déi zweet gewielt.

Ersetzen d'Indexen an der neier Tabell mat ofgeschlossene Contrainten aus der ursprénglecher Tabell

Den Zweck vun der Revisioun war offensichtlech - wann den ursprénglechen Dësch eng ofgeschloss BeschrÀnkung huet, da musst Dir fir déi nei esou eng AschrÀnkung erstellen an net en Index.

Fir eis Ännerungen ze testen, hu mir en einfachen Test geschriwwen:

  • DĂ«sch mat enger ausgeschlosser Zwang an engem Rekord;
  • Daten an eng Loop setzen, dĂ©i mat engem existente Rekord konfliktt;
  • maachen en Update - d'DonnĂ©eĂ«n net mĂ©i Konflikter;
  • d'Ännerungen engagĂ©ieren.

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;

Déi ursprénglech Versioun vum pg_repack ass ëmmer um éischten Insert erofgefall, déi geÀnnert Versioun huet ouni Feeler geschafft. Super.

Mir ginn op d'Produktioun a kréien erëm e Feeler an der selwechter Phase fir Daten aus der Logtabel op eng nei ze kopéieren:

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

Klassesch Situatioun: alles funktionnéiert an Testëmfeld, awer net an der Produktioun?!

APPLY_COUNT an d'KrÀizung vun zwee Chargen

Mir hunn ugefaang de Code wuertwiertlech Zeil fir Zeil ze analyséieren an e wichtege Punkt entdeckt: Daten ginn aus der Logtabel op eng nei a Chargen transferéiert, d'APPLY_COUNT Konstant huet d'Gréisst vun der Batch uginn:

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

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

De Problem ass datt d'DonnĂ©eĂ«n vun der ursprĂ©nglecher Transaktioun, an dĂ€r verschidde Operatioune potenziell d'Begrenzung verletze kĂ«nnen, wann se iwwerdroe ginn, kĂ«nnen op der KrĂ€izung vun zwee Chargen ophalen - d'Halschent vun de Kommandoen ginn an der Ă©ischter Partie engagĂ©iert, an dĂ©i aner Halschent an der zweeter. An hei, ofhĂ€ngeg vun Ärem GlĂ©ck: wann d'Equipen nĂ€ischt an der Ă©ischter Partie verletzen, dann ass alles gutt, awer wann se et maachen, geschitt e Feeler.

APPLY_COUNT ass glĂ€ich wĂ©i 1000 Opzeechnungen, wat erklĂ€ert firwat eis Tester erfollegrĂ€ich waren - si hunn net de Fall vun "Batch KrĂ€izung" ofgedeckt. Mir hunn zwee Kommandoen benotzt - Insert an Update, also genee 500 Transaktioune vun zwee Kommandoen goufen Ă«mmer an enger Batch plazĂ©iert a mir hu keng Problemer erlieft. Nodeems mir den zweeten Update bĂ€igefĂŒĂŒgt hunn, ass eis Ännerung opgehalen ze schaffen:

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;

Also, déi nÀchst Aufgab ass sécherzestellen datt Daten aus der ursprénglecher Tabell, déi an enger Transaktioun geÀnnert goufen, an der neier Tabell och an enger Transaktioun ophalen.

Refus vu Batching

An erĂ«m hu mir zwou LĂ©isungen. Als Ă©ischt: loosst eis d'PartitionĂ©ierung a Chargen komplett opginn an Daten an enger Transaktioun transferĂ©ieren. De Virdeel vun dĂ«ser LĂ©isung war seng Einfachheet - dĂ©i nĂ©ideg Code Ännerungen waren minimal (iwwregens, an eeler Versiounen pg_reorg huet genau esou geschafft). Awer et gĂ«tt e Problem - mir kreĂ©ieren eng laangjĂ€hreg Transaktioun, an dĂ«st, wĂ©i virdru gesot, ass eng Bedrohung fir d'Entstoe vun engem neie Bloat.

DĂ©i zweet LĂ©isung ass mĂ©i komplex, awer wahrscheinlech mĂ©i korrekt: erstellt eng Kolonn an der Logtabel mat dem IdentifizĂ©ierer vun der Transaktioun, dĂ©i Daten un den DĂ«sch bĂ€igefĂŒĂŒgt huet. Dann, wa mir Daten kopĂ©ieren, kĂ«nne mir se mat dĂ«sem Attribut gruppĂ©ieren an dofir suergen datt verbonne Ännerunge matenee transferĂ©iert ginn. D'Batch gĂ«tt aus e puer Transaktiounen (oder enger grousser) geformt a seng GrĂ©isst wĂ€ert variĂ©ieren jee wĂ©i vill Daten an dĂ«sen Transaktiounen geĂ€nnert goufen. Et ass wichteg ze bemierken datt zĂ«nter Daten aus verschiddenen Transaktiounen an enger zoufĂ€lleger Uerdnung an de Logtabel erakommen, et net mĂ©i mĂ©iglech ass se sequentiell ze liesen, wĂ©i et virdru war. seqscan fir all Ufro mat Filteren duerch tx_id ass ze deier, en Index ass nĂ©ideg, awer et wĂ€ert och d'Method verlangsamen wĂ©inst der Iwwerschlag fir se ze aktualisĂ©ieren. Am Allgemengen, wĂ©i Ă«mmer, musst Dir eppes opferen.

Also hu mir beschloss mat der Ă©ischter Optioun unzefĂ€nken, well et mĂ©i einfach ass. Als Ă©ischt war et nĂ©ideg ze verstoen ob eng laang Transaktioun e richtege Problem wier. ZĂ«nter datt den Haapttransfer vun Daten vum alen DĂ«sch op den neien och an enger laanger Transaktioun geschitt, ass d'Fro Ă«mgewandelt an "wĂ©i vill wĂ€erte mir dĂ«s Transaktioun erhĂ©ijen?" D'Dauer vun der Ă©ischter Transaktioun hĂ€nkt haaptsĂ€chlech op der GrĂ©isst vum DĂ«sch. D'Dauer vun engem neien hĂ€nkt dovun of wĂ©i vill Ännerungen an der Tabell wĂ€hrend dem DatenĂŒbertragung accumulĂ©ieren, d.h. op d'IntensitĂ©it vun der Laascht. De pg_repack Laf ass wĂ€hrend enger ZĂ€it vu minimaler Servicebelaaschtung geschitt, an de Volume vun den Ännerungen war onproportional kleng am Verglach mat der ursprĂ©nglecher GrĂ©isst vum DĂ«sch. Mir hunn dĂ©cidĂ©iert datt mir d'ZĂ€it vun enger neier Transaktioun vernolĂ©issegen (zum Verglach, am DuerchschnĂ«tt ass et 1 Stonn an 2-3 Minutten).

D'Experimenter ware positiv. Lancéiere och op Produktioun. Fir Kloerheet, hei ass e Bild mat der Gréisst vun enger vun den Datenbanken nodeems se lafen:

Postgres: bloat, pg_repack an deferred Contrainten

Well mir mat dĂ«ser LĂ©isung komplett zefridde waren, hu mir net probĂ©iert dĂ©i zweet Ă«mzesetzen, awer mir iwwerleeĂ«n d'MĂ©iglechkeet et mat den ExtensiounsentwĂ©ckler ze diskutĂ©ieren. Eis aktuell Versioun, leider, ass nach net prett fir VerĂ«ffentlechung, well mir de Problem nĂ«mmen mat eenzegaartege deferred Restriktiounen gelĂ©ist, a fir eng voll-vollwĂ€erteg Patch ass et nĂ©ideg ËnnerstĂ«tzung fir aner Zorte ze bidden. Mir hoffen dat an Zukunft kĂ«nnen ze maachen.

VlĂ€icht hutt Dir eng Fro, firwat hu mir iwwerhaapt an dĂ«ser Geschicht mat der Ännerung vum pg_repack involvĂ©iert, an hunn net zum Beispill seng Analoga benotzt? Irgendwann hu mir och iwwer dĂ«st geduecht, awer dĂ©i positiv Erfarung fir et frĂ©ier ze benotzen, op DĂ«scher ouni ofgeschloss AschrĂ€nkungen, huet eis motivĂ©iert fir d'Essenz vum Problem ze verstoen an ze fixĂ©ieren. ZousĂ€tzlech brauch d'Benotzung vun anere LĂ©isungen och ZĂ€it fir Tester ze maachen, also hu mir beschloss datt mir als Ă©ischt probĂ©ieren de Problem dran ze fixĂ©ieren, a wa mir gemierkt hunn datt mir dat net an enger raisonnabeler ZĂ€it kĂ©inte maachen, da gĂ©ife mir no Analoga kucken .

Conclusiounen

Wat mir recommandéieren op Basis vun eiser eegener Erfahrung:

  1. Iwwerwaacht Är Bloat. BasĂ©ierend op Iwwerwaachungsdaten, kĂ«nnt Dir verstoen wĂ©i gutt Autovakuum konfigurĂ©iert ass.
  2. Ajustéiert AUTOVACUUM fir Bloat op engem akzeptablen Niveau ze halen.
  3. Wann de Bloat nach ëmmer wiisst an Dir kënnt et net iwwerwannen mat Out-of-the-Box Tools, fÀert net extern Extensiounen ze benotzen. Den Haapt Saach ass alles gutt ze testen.
  4. Gitt net Angscht extern LĂ©isungen ze Ă€nneren fir Är Bedierfnesser ze passen - heiansdo kann dĂ«st mĂ©i effektiv an nach mĂ©i einfach sinn wĂ©i Ären eegene Code z'Ă€nneren.

Source: will.com

Kaaft zouverlĂ€sseg Hosting fir Site mat DDoS Schutz, VPS VDS Server đŸ”„ Kaaft zouverlĂ©issegt WebsĂ€ithosting mat DDoS-Schutz, VPS VDS Server | ProHoster