Antipattern di PostgreSQL: combattiamo contro le orde di "mortiferi"

Le caratteristiche del funzionamento dei meccanismi interni di PostgreSQL lo rendono molto veloce in alcune situazioni e «non molto» in altre. Oggi ci concentreremo su un esempio classico del conflitto tra come funziona un DBMS e ciò che fa lo sviluppatore con esso — UPDATE vs principi MVCC.

Brevemente, la trama di un ottimo articolo:

Quando una riga viene modificata con il comando UPDATE, in realtà vengono eseguite due operazioni: DELETE e INSERT. Nella versione attuale della riga viene impostato xmax, pari al numero della transazione che ha eseguito l'UPDATE. Poi viene creata una una nuova versione stessa riga; il valore xmin corrisponde al valore xmax della versione precedente.

Dopo un certo periodo, dopo il completamento di questa transazione, la versione vecchia o nuova, a seconda di COMMIT/ROLLBACK, verrà riconosciuta come «tuples morte» (dead tuples) nel corso della VACUUM scansione della tabella e verranno rimosse.

Antipattern di PostgreSQL: combattiamo contro le orde di "mortiferi"

Ma questo non accadrà immediatamente, mentre i problemi con i «morti» possono sorgere molto rapidamente — durante aggiornamenti ripetuti o massivi delle registrazioni in una grande tabella, e poco dopo ci si può trovare nella situazione in cui anche VACUUM non potrà aiutare..

#1: I Like To Move It

Supponiamo che il tuo metodo sulla logica di business funzioni normalmente, e improvvisamente realizzi che sarebbe necessario aggiornare il campo X in un certo record:

UPDATE tbl SET X =  WHERE pk = $1;

Successivamente, durante l'esecuzione, si scopre che anche il campo Y dovrebbe essere aggiornato:

UPDATE tbl SET Y =  WHERE pk = $1;

… e poi anche Z — perché farsi degli scrupoli?

UPDATE tbl SET Z =  WHERE pk = $1;

Quante versioni di questa registrazione abbiamo ora nel database? Ah, 4 pezzi! Di cui una attuale e 3 dovranno essere rimosse da voi [auto]VACUUM.

Non fatelo! Utilizzate l'aggiornamento di tutti i campi con una sola query — quasi sempre la logica del metodo può essere modificata in questo modo:

UPDATE tbl SET X = , Y = , Z =  WHERE pk = $1;

#2: Use IS DISTINCT FROM, Luke!

Quindi, avete davvero voglia di aggiornare moltissime registrazioni nella tabella (durante l'applicazione di uno script o di un convertitore, per esempio). E nello script c'è qualcosa di simile:

UPDATE tbl SET X =  WHERE pk BETWEEN $1 AND $2;

Circa in questa forma, la query si incontra abbastanza spesso e quasi sempre non per riempire un nuovo campo vuoto, ma per correggere alcuni errori nei dati. A tal proposito, la correttezza dei dati già esistenti non viene affatto considerata — e sarebbe un peccato! Quindi la registrazione viene riscritta, anche se conteneva esattamente ciò che si desiderava — ma perché? Correggiamo:

UPDATE tbl SET X =  WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM ;

Molti non sono a conoscenza dell'esistenza di un operatore così fantastico, quindi ecco una guida per IS DISTINCT FROM e altri operatori logici a supporto:
Antipattern di PostgreSQL: combattiamo contro le orde di "mortiferi"
… e un po' sulle operazioni con espressioni complesse ROW()- espressioni:
Antipattern di PostgreSQL: combattiamo contro le orde di "mortiferi"

#3: А я милого узнаю по… блокировке

Vengono avviati due processi paralleli identici, ognuno dei quali cerca di contrassegnare la registrazione come "in lavorazione":

UPDATE tbl SET processing = TRUE WHERE pk = $1;

Anche se questi processi eseguono cose indipendenti tra loro, nel contesto di un ID, in questa richiesta il secondo cliente verrà "bloccato" fino al termine della prima transazione.

Soluzione n. 1: il problema è ridotto al precedente

Basta aggiungere nuovamente IS DISTINCT FROM:

UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;

In questo modo, la seconda richiesta non cambierà nulla nel database, dato che è già "tutto a posto" — pertanto non si verificherà alcun blocco. Successivamente, il fatto che la registrazione non venga trovata viene gestito nell'algoritmo applicativo.

Soluzione n. 2: advisory locks

Un argomento vasto per un articolo separato, dove si può leggere su modalità di applicazione e "trappole" delle blocchi raccomandativi.

Soluzione n. 3: chiamate senza [d]ubbio

È certo che dovrebbe succedere da parte vostra lavoro simultaneo su un unico record? Или вы все-таки накосячили с алгоритмами вызовов бизнес-логики со стороны клиента, например? А если подумать?..

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster