Postgres: bloat, pg_repack e vincoli deferred

Postgres: bloat, pg_repack e vincoli deferred

L'effetto di gonfiore delle tabelle e degli indici (bloat) è ampiamente conosciuto e presente non solo in Postgres. Ci sono modi per affrontarlo “out of the box”, come VACUUM FULL o CLUSTER, ma bloccano le tabelle durante l'operazione e quindi non possono sempre essere utilizzati.

L'articolo conterrà un po' di teoria su come si verifica il bloat, come affrontarlo, sui vincoli deferred e sui problemi che questi comportano nell'uso dell'estensione pg_repack.

Questo articolo è basato su una mia presentazione PgConf.Russia 2020.

Guarda il video

Perché si verifica il bloat

Alla base di Postgres c'è un modello multi-versione (MVCC). La sua essenza è che ogni riga in una tabella può avere più versioni, mentre le transazioni vedono al massimo una di queste versioni, ma non necessariamente la stessa. Questo consente a più transazioni di lavorare contemporaneamente senza influenzarsi a vicenda.

È ovvio che tutte queste versioni devono essere conservate. Postgres lavora in memoria pagina per pagina e una pagina è il volume minimo di dati che può essere letto dal disco o scritto. Esaminiamo un piccolo esempio per capire come avviene.

Supponiamo di avere una tabella alla quale abbiamo aggiunto alcune registrazioni. Nella prima pagina del file che contiene la tabella, sono apparsi nuovi dati. Queste sono le versioni attive delle righe, disponibili per altre transazioni dopo il commit (per semplificare, supponiamo che il livello di isolamento sia Read Committed).

Postgres: bloat, pg_repack e vincoli deferred

Poi abbiamo aggiornato una delle registrazioni, marcando così la versione precedente come obsoleta.

Postgres: bloat, pg_repack e vincoli deferred

Passo dopo passo, aggiornando e eliminando le versioni delle righe, abbiamo ottenuto una pagina in cui circa metà dei dati è “spazzatura”. Questi dati non sono visibili per alcuna transazione.

Postgres: bloat, pg_repack e vincoli deferred

In Postgres esiste un meccanismo VACUUM, che elimina le versioni obsolete e libera spazio per nuovi dati. Ma se non è impostato in modo sufficientemente aggressivo o è occupato a lavorare su altre tabelle, i “dati spazzatura” rimangono, costringendoci a utilizzare pagine supplementari per i nuovi dati.

Così, nel nostro esempio, ad un certo punto la tabella consisterà in quattro pagine, ma ci saranno solo dati attivi in metà di essa. Di conseguenza, quando accediamo alla tabella, leggeremo molti più dati del necessario.

Postgres: bloat, pg_repack e vincoli deferred

Anche se attualmente VACUUM rimuovesse tutte le versioni obsolete delle righe, la situazione non migliorerebbe radicalmente. Avremmo spazio libero nelle pagine o addirittura intere pagine per nuove righe, ma continueremmo a leggere più dati di quanti ne necessitiamo.
A proposito, se una pagina completamente vuota (la seconda nel nostro esempio) si trovasse alla fine del file, VACUUM sarebbe in grado di tranciarla. Ma attualmente si trova nel mezzo, quindi non possiamo fare nulla al riguardo.

Postgres: bloat, pg_repack e vincoli deferred

Quando il numero di queste pagine vuote o fortemente sparse diventa grande, quello che chiamiamo bloat inizia a influenzare le prestazioni.

Tutto ciò che è stato descritto sopra è la meccanica dell'insorgere del bloat nelle tabelle. Negli indici accade all'incirca nello stesso modo.

Ho del bloat?

Ci sono diversi modi per determinare se hai del bloat. L'idea del primo è utilizzare le statistiche interne di Postgres, che contengono informazioni approssimative sul numero di righe nelle tabelle, il numero di righe 'vivi' e altro. In internet puoi trovare molte variazioni di script già pronti. Noi ci basiamo su script di PostgreSQL Experts, che può valutare il bloat delle tabelle insieme ai toast e al bloat degli indici btree. Dalla nostra esperienza, il suo margine di errore è del 10-20%.

Un altro modo è utilizzare l'estensione pgstattuple, che permette di guardare all'interno delle pagine e ottenere sia un valore stimato che uno preciso del bloat. Ma nel secondo caso sarà necessario scandagliare l'intera tabella.

Un valore di bloat basso, fino al 20%, lo consideriamo accettabile. Può essere considerato analogo al fillfactor per tabelle e indici. Con il 50% e oltre, potrebbero iniziare a sorgere problemi di prestazioni.

Metodi per combattere il bloat

In Postgres ci sono diversi modi per affrontare il bloat 'di fabbrica', tuttavia non sempre sono adatti a tutti.

Configurare AUTOVACUUM per evitare che si verifichi bloat. E se vogliamo essere precisi, perché rimanga a un livello accettabile per voi. Sembra un consiglio da “capitano”, ma in realtà non è sempre facile da raggiungere. Ad esempio, se state lavorando attivamente e modificando frequentemente lo schema dei dati o se c’è una migrazione dei dati. Di conseguenza, il vostro profilo di carico può cambiare frequentemente e, di norma, è diverso per diverse tabelle. Quindi, dovete lavorare costantemente un po' in anticipo e adattare AUTOVACUUM al profilo variabile di ciascuna tabella. Ma è ovvio che non è semplice.

Un’altra ragione comune per cui AUTOVACUUM non riesce a gestire le tabelle è la presenza di transazioni lunghe, che non gli permettono di eliminare i dati perché sono accessibili a queste transazioni. Il consiglio è anche qui ovvio: liberarsi delle transazioni “sospese” e minimizzare il tempo delle transazioni attive. Ma se il carico sulla vostra applicazione è un ibrido OLAP e OLTP, allora potreste avere allo stesso tempo sia numerosi aggiornamenti frequenti e query brevi, sia operazioni lunghe, come la generazione di un report. In questa situazione, vale la pena considerare la distribuzione del carico su diverse basi di dati, consentendo una regolazione più fine di ciascuna di esse.

Un altro esempio: anche se il profilo è omogeneo, se il database è sotto un carico molto alto, allora anche il più aggressivo AUTOVACUUM potrebbe non farcela, e si può verificare un bloat. Lo scaling (verticale o orizzontale) è l'unica soluzione.

Cosa fare però se avete configurato AUTOVACUUM, ma il bloat continua a crescere.

Team VACUUM FULL ricostruisce il contenuto delle tabelle e degli indici e mantiene solo i dati attuali. Per eliminare il bloat funziona perfettamente, ma durante la sua esecuzione blocca la tabella con un blocco esclusivo (AccessExclusiveLock), che non permette di eseguire query su questa tabella, nemmeno select. Se potete permettervi di fermare il vostro servizio o parte di esso per un certo periodo (da decine di minuti a diverse ore a seconda delle dimensioni del database e dell’hardware), allora questa opzione è la migliore. Purtroppo, non riusciamo a eseguire VACUUM FULL nei tempi pianificati di manutenzione, quindi questo metodo non fa per noi.

Team CLUSTER riorganizza anche il contenuto delle tabelle, come fa VACUUM FULL, consentendo di specificare un indice secondo il quale i dati saranno ordinati fisicamente su disco (ma in futuro non è garantito l'ordine per le nuove righe). In determinate situazioni, questa è una buona ottimizzazione per diverse query – con la lettura di più record tramite indice. Lo svantaggio del comando è lo stesso di VACUUM FULL – blocca la tabella durante l'esecuzione.

Team REINDEX è simile ai due precedenti, ma esegue la ricostruzione di un indice specifico o di tutti gli indici di una tabella. I blocchi sono leggermente più deboli: ShareLock sulla tabella (impedisce le modifiche, ma consente di eseguire select) e AccessExclusiveLock sull'indice ricostruito (blocca le query che utilizzano questo indice). Tuttavia, nella versione 12 di Postgres è stato introdotto un parametro CONCURRENTLY, che consente di ricostruire l'indice senza bloccare l'aggiunta, la modifica o l'eliminazione di record in parallelo.

Nelle versioni precedenti di Postgres si poteva ottenere un risultato simile a REINDEX CONCURRENTLY utilizzando CREATE INDEX CONCURRENTLY. Consente di creare un indice senza blocchi severi (ShareUpdateExclusiveLock, che non ostacola le query in parallelo), quindi sostituire il vecchio indice con il nuovo e rimuovere il vecchio indice. Questo consente di eliminare il bloat degli indici senza ostacolare il lavoro della tua applicazione. È importante considerare che durante la ricostruzione degli indici ci sarà un carico aggiuntivo sul sottosistema di disco.

Quindi, se ci sono modi per eliminare il bloat degli indici “a caldo”, per le tabelle non ci sono. Qui entrano in gioco varie estensioni esterne: pg_repack (precedentemente pg_reorg), pgcompact, pgcompacttable e altri. In questo articolo non li confronterò e parlerò solo di pg_repack, che dopo alcune modifiche utilizziamo internamente.

Come funziona pg_repack

Postgres: bloat, pg_repack e vincoli deferred
Supponiamo di avere una tabella piuttosto normale – con indici, vincoli e, purtroppo, con bloat. Il primo passo di pg_repack crea una log-table per memorizzare i dati su tutte le modifiche durante l'esecuzione. Un trigger replicherà queste modifiche su ogni insert, update e delete. Quindi viene creata una tabella, simile a quella originale nella struttura, ma senza indici e vincoli, per non rallentare il processo di inserimento dei dati.

Successivamente, pg_repack sposta i dati dalla vecchia tabella in una nuova, filtrando automaticamente tutte le righe non pertinenti e poi crea indici per la nuova tabella. Durante l'esecuzione di tutte queste operazioni, le modifiche si accumulano nel log della tabella.

Il passo successivo consiste nel trasferire le modifiche nella nuova tabella. Il trasferimento avviene in più iterazioni, e quando rimangono meno di 20 record nel log della tabella, pg_repack acquisisce un blocco rigoroso, trasferisce i dati finali e sostituisce la vecchia tabella con la nuova nelle tabelle di sistema di Postgres. Questo è l'unico e brevissimo momento in cui non potrai lavorare con la tabella. Dopo di che, la vecchia tabella e la tabella con i log vengono eliminate, liberando spazio nel filesystem. Il processo è completato.

In teoria, tutto sembra ottimo, ma quali sono i risultati nella pratica? Abbiamo testato pg_repack senza carico e sotto carico, controllando il suo funzionamento in caso di arresto prematuro (in parole povere, con Ctrl+C). Tutti i test sono stati positivi.

Siamo andati in produzione — e qui tutto è andato diversamente da come ci aspettavamo.

Il primo tentativo in produzione

Nel primo cluster abbiamo ricevuto un errore di violazione del vincolo univoco:

$ ./pg_repack -t tablename -o id
INFO: riesame della tabella "tablename"
ERRORE: query fallita:
    ERRORE: valore della chiave duplicata viola il vincolo univoco "index_16508"
DETTAGLI: Chiave (id, index)=(100500, 42) già esiste.

Questo vincolo aveva un nome autogenerato index_16508 – creato da pg_repack. Dalla combinazione di attributi inclusi, abbiamo identificato il nostro vincolo corrispondente. Il problema era che non si trattava di un vincolo del tutto normale, ma di un vincolo differito (deferred constraint), ovvero la sua verifica viene eseguita successivamente al comando SQL, il che porta a conseguenze inaspettate.

Vincoli differiti: a cosa servono e come funzionano

Un po' di teoria sui vincoli differiti.
Consideriamo un semplice esempio: abbiamo una tabella di riferimento per automobili con due attributi – nome e ordine dell'automobile nel riferimento.
Postgres: bloat, pg_repack e vincoli deferred

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



Supponiamo che ci occorra scambiare il primo e il secondo automobile. La soluzione "diretta" è aggiornare il primo valore con il secondo e il secondo con il primo:

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

Ma nell'esecuzione di questo codice ci aspettiamo di ricevere una violazione del vincolo, poiché l'ordine dei valori nella tabella è univoco:

[23305] ERRORE: il valore della chiave duplicato viola il vincolo univoco “uk_cars”
Dettagli: La chiave (ord)=(2) esiste già.

Come fare diversamente? Prima opzione: aggiungere una sostituzione del valore per un ordine che non esiste sicuramente nella tabella, ad esempio “-1”. In programmazione, questo è chiamato “scambio di valori tra due variabili tramite una terza”. L'unico svantaggio di questo metodo è l'ulteriore aggiornamento.

Seconda opzione: riprogettare la tabella per utilizzare un tipo di dato con punto mobile per il valore dell'ordine anziché numeri interi. Così, durante l'aggiornamento del valore da 1, ad esempio, a 2.5, il primo record si “posizionerà” automaticamente tra il secondo e il terzo. Questa soluzione funziona, ma ci sono due limitazioni. Prima di tutto, non funzionerà se il valore è usato da qualche parte nell'interfaccia. In secondo luogo, a seconda della precisione del tipo di dato, avrai un numero limitato di possibili inserimenti prima di dover ricalcolare i valori di tutti i record.

Terza opzione: rendere il vincolo posticipato, in modo che venga verificato solo al momento del commit:

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

Poiché la logica della nostra query iniziale garantisce che al momento del commit tutti i valori siano unici, essa verrà eseguita con successo.

L'esempio sopra considerato è ovviamente molto sintetico, ma illustra l'idea. Nella nostra applicazione utilizziamo vincoli posticipati per implementare la logica che si occupa di risolvere i conflitti quando più utenti lavorano simultaneamente su oggetti-strumenti comuni sulla bacheca. L'uso di tali vincoli ci consente di semplificare leggermente il codice applicativo.

In generale, a seconda del tipo di vincolo in Postgres, ci sono tre livelli di granularità per la loro verifica: livello di riga, di transazione e di espressione.
Postgres: bloat, pg_repack e vincoli deferred
Fonte: begriffs

CHECK e NOT NULL vengono sempre verificati a livello di riga, per gli altri vincoli, come si può vedere dalla tabella, ci sono diverse opzioni. Maggiori dettagli possono essere letti qui.

In sintesi, le restrizioni differite in determinate situazioni producono codice più comprensibile e meno comandi. Tuttavia, ciò comporta una complicazione nel processo di debugging, poiché il momento in cui si verifica l'errore e il momento in cui lo si scopre sono distanti nel tempo. Un'altra possibile problematica è che il pianificatore non riesce sempre a costruire un piano ottimale se nella query è presente una restrizione differita.

Sviluppo di pg_repack

Abbiamo chiarito cosa sono le restrizioni differite, ma come si collegano al nostro problema? Ricordiamo l'errore che abbiamo ricevuto in precedenza:

$ ./pg_repack -t tablename -o id
INFO: riesame della tabella "tablename"
ERRORE: query fallita:
    ERRORE: valore della chiave duplicata viola il vincolo univoco "index_16508"
DETTAGLI: Chiave (id, index)=(100500, 42) già esiste.

Si verifica nel momento in cui i dati vengono copiati dalla tabella di registro in una nuova tabella. Sembra strano, poiché i dati nella tabella di registro vengono mantenuti insieme ai dati della tabella originale. Se soddisfano le restrizioni della tabella originale, come possono violare le stesse restrizioni nella nuova?

Come si è scoperto, la radice del problema risiede nel passaggio precedente del lavoro di pg_repack, dove vengono creati solo gli indici, ma non le restrizioni: nella vecchia tabella c'era una restrizione unica e nella nuova è stato creato un indice unico al suo posto.

Postgres: bloat, pg_repack e vincoli deferred

È importante notare che se la restrizione è normale e non differita, l'indice unico creato al suo posto è equivalente a quella restrizione, poiché le restrizioni uniche in Postgres sono realizzate attraverso la creazione di un indice unico. Tuttavia, nel caso di una restrizione differita, il comportamento non è lo stesso, perché l'indice non può essere differito e viene sempre verificato al momento dell'esecuzione del comando SQL.

Pertanto, la sostanza del problema consiste nella "differita" della verifica: nella tabella originale essa avviene al momento del commit, mentre nella nuova si verifica al momento dell'esecuzione del comando SQL. Quindi dobbiamo fare in modo che le verifiche vengano eseguite in modo identico in entrambi i casi: o sempre differite, o sempre immediatamente.

Quindi, quali idee abbiamo avuto.

Creare un indice simile a deferred

La prima idea è di eseguire entrambe le verifiche in modalità immediata. Questo potrebbe generare alcuni falsi positivi con le restrizioni, ma se saranno pochi, non dovrebbe influire sul lavoro degli utenti, poiché tali conflitti sono una situazione normale per loro. Si verificano, ad esempio, quando due utenti iniziano a modificare simultaneamente lo stesso widget e il client del secondo utente non riesce a ricevere informazioni sul fatto che il widget sia già bloccato per la modifica dal primo utente. In questa situazione, il server risponde al secondo utente con un rifiuto e il suo client annulla le modifiche e blocca il widget. Poco dopo, quando il primo utente conclude la modifica, il secondo riceve la notizia che il widget non è più bloccato e può ripetere la sua azione.

Postgres: bloat, pg_repack e vincoli deferred

Per assicurare che le verifiche siano sempre in modalità urgente, abbiamo creato un nuovo indice, simile alla restrizione ritardata originale:

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

Nell'ambiente di test abbiamo riscontrato solo alcuni errori previsti. Successo! Abbiamo riavviato pg_repack in produzione e abbiamo ottenuto 5 errori nel primo cluster in un'ora di lavoro. Questo è un risultato accettabile. Tuttavia, nel secondo cluster il numero di errori è aumentato notevolmente e abbiamo dovuto fermare pg_repack.

Perché è successo? La probabilità di errore dipende da quanti utenti lavorano simultaneamente sugli stessi widget. A quanto pare, in quel momento, i dati memorizzati nel primo cluster avevano molte meno modifiche concorrenti rispetto agli altri, ovvero ci è andata semplicemente "bene".

L'idea non ha funzionato. A quel punto abbiamo visto altre due opzioni per risolvere il problema: riscrivere il nostro codice applicativo per rinunciare alle restrizioni ritardate, oppure "insegnare" a pg_repack a lavorare con esse. Abbiamo scelto la seconda.

Sostituire gli indici nel nuovo tavolo con restrizioni ritardate dalla tabella originale

L'obiettivo della modifica era chiaro: se la tabella originale ha una restrizione ritardata, allora per la nuova si devono creare tali restrizioni e non un indice.

Per verificare le nostre modifiche, abbiamo scritto un semplice test:

  • tabella con una restrizione ritardata e una registrazione;
  • inseriamo in un ciclo dati che confliggono con la registrazione esistente;
  • facciamo un update – i dati non entrano più in conflitto;
  • committiamo le modifiche.

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 versione originale di pg_repack falliva al primo insert, la versione aggiornata ha funzionato senza errori. Ottimo.

Andiamo in produzione e riceviamo di nuovo un errore nella stessa fase di copiatura dei dati dalla tabella di log a quella nuova:

$ ./pg_repack -t tablename -o id
INFO: riesame della tabella "tablename"
ERRORE: query fallita:
    ERRORE: valore della chiave duplicata viola il vincolo univoco "index_16508"
DETTAGLI: Chiave (id, index)=(100500, 42) già esiste.

Situazione classica: tutto funziona negli ambienti di test, ma in produzione — no?!

APPLY_COUNT e giunzione di due batch

Abbiamo iniziato ad analizzare il codice riga per riga e abbiamo scoperto un punto importante: il trasferimento dei dati dalla tabella di log alla nuova avviene in batch, la costante APPLY_COUNT indicava la dimensione del batch:

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

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  
/* ci potrebbero essere ancora dei tuple, ripeti. */
...
}

Il problema è che i dati della transazione originale, in cui diverse operazioni potrebbero potenzialmente violare il vincolo, durante il trasferimento possono finire nella giunzione di due batch – metà dei comandi sarà committata nel primo batch e l'altra metà nel secondo. E qui dipende dalla fortuna: se i comandi nel primo batch non violano nulla, va tutto bene, ma se violano — si verifica un errore.

APPLY_COUNT è pari a 1000 record, il che spiega perché i nostri test sono stati superati con successo – non coprivano il caso della “giunzione dei batch”. Abbiamo utilizzato due comandi – insert e update, quindi esattamente 500 transazioni con due comandi sono sempre state inserite nel batch e non abbiamo riscontrato problemi. Dopo aver aggiunto un secondo update, la nostra modifica ha smesso di funzionare:

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; -- un altro update
    COMMIT;
  END;
END LOOP;

Quindi, il prossimo obiettivo è fare in modo che i dati della tabella originale, che sono stati modificati in una sola transazione, vengano trasferiti nella nuova tabella anch'essi all'interno di una sola transazione.

Abbandono del batching

E anche noi avevamo di nuovo due opzioni di soluzione. Prima: rinunciamo completamente alla suddivisione in batch e trasferiamo i dati con una singola transazione. A favore di questa soluzione c'era la sua semplicità: le modifiche al codice richieste erano minime (tra l'altro, nelle versioni più vecchie funzionava esattamente così pg_reorg). Ma c'è un problema: creiamo una transazione lunga, e questo, come detto in precedenza, rappresenta una minaccia per l'insorgere di nuovo bloat.

La seconda soluzione è più complessa, ma probabilmente più corretta: creare nella tabella log una colonna con l'identificatore della transazione che ha aggiunto i dati nella tabella. In questo modo, durante la copia dei dati, potremo raggrupparli secondo questo attributo e garantire che le modifiche correlate vengano trasferite insieme. Il batch sarà composto da più transazioni (o una grande) e la sua dimensione varierà a seconda di quanto dati sono cambiati in queste transazioni. È importante notare che poiché i dati di transazioni diverse entrano nella tabella log in modo casuale, non sarà più possibile leggerla in modo sequenziale come prima. seqscan ad ogni richiesta con filtro per tx_id è troppo costoso, serve un indice, ma anche questo rallenterà il metodo a causa dei costi di aggiornamento. Insomma, come sempre, bisogna sacrificare qualcosa.

Quindi, abbiamo deciso di iniziare dalla prima opzione, essendo la più semplice. Prima di tutto, era necessario capire se una transazione lunga sarebbe stata un problema reale. Poiché il trasferimento principale dei dati dalla vecchia tabella a quella nuova avviene anch'esso in una transazione lunga, la questione si è trasformata in "quanto aumenteremo questa transazione?" La durata della prima transazione dipende principalmente dalla dimensione della tabella. La durata della nuova dipende da quanti cambiamenti si accumuleranno nella tabella durante il trasferimento dei dati, cioè dall'intensità del carico. L'esecuzione di pg_repack è avvenuta durante il carico minimo del servizio, e il volume delle modifiche era incomparabilmente ridotto rispetto al volume iniziale della tabella. Abbiamo deciso che potevamo trascurare il tempo della nuova transazione (in media questo è di 1 ora e 2-3 minuti).

Gli esperimenti sono stati positivi. Anche l'esecuzione in produzione è andata bene. Per maggiore chiarezza - un'immagine con la dimensione di uno dei database dopo l'esecuzione:

Postgres: bloat, pg_repack e vincoli deferred

Poiché questa soluzione ci ha completamente soddisfatti, non abbiamo provato a realizzare la seconda, ma stiamo considerando la possibilità di discuterne con gli sviluppatori dell'estensione. La nostra attuale modifica, sfortunatamente, non è ancora pronta per la pubblicazione, poiché abbiamo risolto il problema solo con i vincoli di ritardo unici, e per una patch completa è necessaria la supporto di altri tipi. Speriamo di riuscire a farlo in futuro.

Potresti chiederti perché ci siamo lanciati in questa storia di modifica di pg_repack e non abbiamo scelto di utilizzare i suoi equivalenti, ad esempio? A un certo punto anche noi abbiamo pensato a questo, ma l'esperienza positiva del suo utilizzo in precedenza, su tabelle senza vincoli di ritardo, ci ha motivati a cercare di capire la sostanza del problema e correggerlo. Inoltre, per utilizzare altre soluzioni è necessario anche del tempo per effettuare test, quindi abbiamo deciso di provare a risolvere prima il problema in esso, e se capiamo che non saremo in grado di farlo in tempi ragionevoli, allora inizieremo a considerare gli equivalenti.

Conclusioni

Cosa possiamo raccomandare sulla base della nostra esperienza:

  1. Monitora il tuo bloat. Sulla base dei dati di monitoraggio potrai capire quanto bene è configurato autovacuum.
  2. Configura AUTOVACUUM per mantenere il bloat a un livello accettabile.
  3. Se il bloat cresce e non riesci a combatterlo con strumenti "out of the box", non temere di utilizzare estensioni esterne. L'importante è testare tutto con attenzione.
  4. Non aver paura di adattare soluzioni esterne alle tue esigenze: a volte può essere più efficace e persino più semplice rispetto alla modifica del tuo stesso codice.

Fonte: habr.com

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