
Un giorno nel lontano futuro, l'eliminazione automatica dei dati non necessari sarà una delle attività fondamentali dei database management system [1]. Fino ad allora, dobbiamo occuparci noi stessi dell'eliminazione o del trasferimento di dati non necessari su sistemi di archiviazione meno costosi. Supponiamo di voler eliminare diversi milioni di righe. Un compito piuttosto semplice, soprattutto se si conoscono le condizioni e si dispone di un indice adeguato. "DELETE FROM table1 WHERE col1 = :value" — niente di più semplice, vero?
Video:


Faccio parte del comitato di programmazione di Highload sin dal primo anno, cioè dal 2007.
E con Postgres sono dal 2005. L'ho usato in molti progetti.
Il gruppo RuPostgres è attivo anche dal 2007.
Siamo cresciuti a oltre 2100 partecipanti nei meetup. Questo è il secondo posto nel mondo dopo New York, superando San Francisco da tempo.
Vivo in California da alcuni anni. Lavoro soprattutto con aziende americane, inclusi grandi nomi. Sono utenti attivi di Postgres. E lì ci sono sempre delle cose interessanti che accadono.

– è la mia azienda. Ci occupiamo di automatizzare compiti che eliminano i rallentamenti nello sviluppo.
Quando si lavora con Postgres, a volte ci si imbatte in alcuni intoppi. Ad esempio, potrebbe essere necessario attendere che l'amministratore sollevi un ambiente di test, o aspettare che il DBA risponda alle tue richieste. Identifichiamo questi colli di bottiglia nei processi di sviluppo, testing e amministrazione, e cerchiamo di risolverli tramite automazione e nuovi approcci.

Recentemente ho partecipato al VLDB a Los Angeles, la più grande conferenza sulle basi di dati. È stata presentata una relazione su come in futuro i DBMS non solo memorizzeranno i dati, ma li elimineranno anche automaticamente. Questo è un argomento innovativo.
Il volume dei dati nel mondo sta aumentando: i zettabyte sono 1.000.000 petabyte. Si stima già che ci siano oltre 100 zettabyte di dati archiviati nel mondo, e questo numero continua a crescere.

E cosa fare con tutto questo? È chiaro che bisogna eliminare dati. Ecco un link a quella interessante relazione. Tuttavia, al momento non è ancora implementato nei DBMS.
Coloro che sanno contare i soldi desiderano due cose. Vogliono che noi eliminiamo i dati, quindi tecnicamente dobbiamo essere in grado di farlo.

Quello che sto per raccontare è una situazione astratta che include molte situazioni reali, ovvero una sorta di composizione di ciò che è realmente accaduto a me e ai database circostanti molte volte, per molti anni. I problemi sono ovunque e tutti continuano a inciampare su di essi.

Supponiamo di avere un database o più database in crescita. E alcune registrazioni sono ovviamente spazzatura. Ad esempio, l'utente ha iniziato a fare qualcosa, ma non ha completato. E dopo un certo tempo sappiamo che quel lavoro incompiuto non è più necessario conservare. Cioè, vorremmo pulire alcune cose spazzatura per risparmiare spazio, migliorare le prestazioni, ecc.

In generale, ci poniamo l'obiettivo di automatizzare l'eliminazione di elementi specifici, di righe specifiche in una certa tabella.

E abbiamo una query di cui parleremo oggi, ossia l'eliminazione della spazzatura.

Un esperto sviluppatore è stato incaricato di farlo. Ha preso questa query, l'ha verificata da lui – tutto funziona. L'ha testata su staging – tutto ok. L'abbiamo implementata – tutto funziona. Ogni giorno eseguiamo questa operazione – tutto è a posto.

Il database continua a crescere. Ogni giorno l'operazione DELETE inizia a diventare un po' più lenta.

Poi ci rendiamo conto che attualmente abbiamo una campagna di marketing e il traffico sarà molto maggiore, quindi decidiamo di mettere in pausa alcune attività. E dimentichiamo di ripristinarle.

Dopo qualche mese ce ne siamo ricordati. Ma quel developer se ne era andato o era occupato con qualcos'altro, quindi abbiamo affidato il compito a qualcun altro per ripristinarlo.
Ha controllato su dev, su staging – tutto ok. Naturalmente, è necessario anche pulire ciò che si è accumulato. Ha verificato e tutto funziona.

Cosa succede dopo? Dopo tutto si interrompe. Si ferma al punto che, in un certo momento, tutto crolla. Tutti sono scioccati, nessuno capisce cosa stia succedendo. E poi si scopre che il problema era dovuto a questo DELETE.

Cosa è andato storto? Qui c'è un elenco di cose che potrebbero essere andate male. Quale di queste è la più importante?
Ad esempio, non c'è stata una revisione, cioè l'esperto DBA non ha controllato. Con il suo occhio esperto avrebbe subito individuato il problema, inoltre ha accesso a prod, dove si sono accumulati diversi milioni di righe.
Forse hanno effettuato un controllo in modo errato.
Forse l'hardware è obsoleto e c'è bisogno di un upgrade per questo database.
Oppure c'è qualcosa che non va con la stessa base dati, e dobbiamo migrare da Postgres a MySQL.
O forse c'è qualcosa che non funziona con l'operazione.
Magari ci sono errori nell'organizzazione del lavoro e bisognerebbe licenziare qualcuno per assumere delle persone migliori.

Non c'è stata una verifica da parte del DBA. Se ci fosse stato un DBA, avrebbe visto questi milioni di righe e senza alcun esperimento avrebbe detto: «Non si fa così». Immagina se questo codice fosse stato su GitLab o GitHub con un processo di revisione del codice, e non ci fosse stata la possibilità che questa operazione passasse in produzione senza l'approvazione del DBA, evidentemente il DBA avrebbe detto: «Non si può fare».

E avrebbe detto che avreste avuto problemi con il disk IO, tutti i processi sarebbero andati in tilt, ci potrebbero essere dei lock, e blocchereste l'autovacuum per un sacco di minuti, quindi non va bene.

Il secondo errore è che abbiamo controllato nel posto sbagliato. Abbiamo notato, a posteriori, che c'era molta spazzatura di dati accumulata in produzione, mentre lo sviluppatore non aveva dati accumulati in quel database e nemmeno in staging nessuno creava questa spazzatura. Di conseguenza, lì c'erano solo 1.000 righe che sono state elaborate rapidamente.
Comprendiamo che i nostri test sono deboli, cioè il processo che abbiamo in atto non rileva i problemi. Non è stato condotto un esperimento adeguato sulla base dati.
L'esperimento ideale dovrebbe essere condotto su attrezzature simili. Non è sempre possibile farlo con la stessa attrezzatura, ma è fondamentale avere una copia completa del database. Questo è ciò che sostengo da anni. E un anno fa ne ho parlato, potete trovare tutto su YouTube.

Forse la nostra attrezzatura è scadente? Se guardiamo, la latenza è aumentata. Abbiamo visto che l'utilizzo è al 100%. Certo, se avessimo dischi NVMe moderni, probabilmente ci sarebbe stato molto più facile. E forse non saremmo andati in down per questo.
Se avete un ambiente cloud, l'upgrade è semplice. Si attivano nuove repliche su nuova hardware. Switch-over. E va tutto bene. Piuttosto facile.

È possibile toccare i dischi meno? Qui, con l'aiuto di un DBA, affrontiamo un argomento chiamato tuning dei checkpoint. Si scopre che non è stato eseguito il tuning dei checkpoint.
Che cos'è un checkpoint? È presente in qualsiasi DBMS. Quando i dati nella memoria cambiano, non vengono immediatamente scritti sui dischi. L'informazione che i dati sono cambiati viene inizialmente registrata in un log di scrittura anticipata, il write-ahead log. E a un certo punto, il DBMS decide che è ora di scrivere le vere pagine sul disco, così, in caso di crash, si fa meno lavoro REDO. È come in un videogioco. Se veniamo uccisi, riprenderemo il gioco dall'ultimo checkpoint. E tutti i DBMS lo implementano.

Le impostazioni in Postgres sono obsolete. Sono progettate per carichi di dati e operazioni di 10-15 anni fa. E il checkpoint non fa eccezione.
Queste informazioni provengono dal nostro rapporto di controllo di Postgres, cioè un controllo automatico dello stato di salute. E vediamo che c'è un database di diversi terabyte. È evidente che i checkpoint forzati avvengono quasi nel 90% dei casi.
Cosa significa questo? Ci sono due impostazioni. Il checkpoint può avvenire per timeout, ad esempio, ogni 10 minuti. Oppure può verificarsi quando si è raccolto un quantitativo considerevole di dati.
Per impostazione predefinita, max_wal_size è impostato a 1 gigabyte. Di fatto, ciò si verifica realmente in Postgres dopo 300-400 megabyte. Hai modificato così tanti dati e il tuo checkpoint si verifica.
E se nessuno ha modificato il sistema e il servizio è cresciuto, con l'azienda che guadagna un sacco di soldi e ha molte transazioni, il checkpoint si verifica ogni minuto, a volte anche ogni 30 secondi, e a volte si sovrappongono. Questo è davvero un problema.
Dobbiamo fare in modo che avvenga meno frequentemente. Cioè, possiamo aumentare il max_wal_size. E questo farà sì che si verifichi più raramente.
Tuttavia, abbiamo sviluppato una metodologia per farlo in modo più corretto, cioè come prendere decisioni sulla scelta delle impostazioni, basandoci chiaramente su dati concreti.

Di conseguenza, conduciamo due serie di esperimenti sui database.
La prima serie – cambiamo il max_wal_size. E svolgiamo un'operazione di massa. Iniziamo con la configurazione predefinita di 1 gigabyte. E facciamo un massiccio DELETE di milioni di righe.
Si vede quanto sia difficile. Notiamo che il disk IO è molto scadente. Contiamo quanti WAL abbiamo generato, perché è molto importante. Osserviamo quante volte si è verificato il checkpoint. E vediamo che non è positivo.
Iniziamo ad aumentare max_wal_size. Ripetiamo. Aumentiamo, ripetiamo. E così molte volte. In linea di principio, 10 punti sono buoni, dove 1, 2, 4, 8 gigabyte. E osserviamo il comportamento del sistema specifico. È chiaro che l'hardware deve essere come in produzione. Dovete avere gli stessi dischi, la stessa quantità di memoria e le stesse impostazioni di Postgres.
In questo modo possiamo aggiornare il nostro sistema e sapere come si comporterà il DBMS durante un massiccio DELETE problematico e come effettuerà i checkpoint.
Checkpoint in italiano significa punti di controllo.
Esempio: DELETE di diversi milioni di righe per indice, con righe 'disperse' tra le pagine.

Ecco un esempio. Questa è una certa base. E con l'impostazione predefinita di 1 gigabyte per max_wal_size si vede molto chiaramente che i nostri dischi di scrittura vanno in saturazione. Questa immagine è un sintomo tipico di un paziente molto malato, cioè stava realmente male. E qui c'è stata un'unica operazione, e si trattava proprio di un DELETE di diversi milioni di righe.
Se eseguiamo un'operazione del genere in produzione, andremo proprio a collassare, perché si vede che un SOLO DELETE ci manda in saturazione.

And then, where 16 gigabytes are, you can see that the spikes have started. The spikes indicate that things have improved; we're hitting the ceiling, but it's not as bad anymore. There’s now a bit of freedom. On the right is the recording. The number of operations is the second graph. You can see that we can breathe a little easier now that we have 16 gigabytes.

And where 64 gigabytes are available, it’s noticeably better. The spikes are clearly defined, allowing more opportunities for other operations to survive and do something with the disk.
Perché?

I will dive a bit into the details, but the topic of checkpoint tuning could lead to a whole presentation, so I won't overload you, but I will point out some of the challenges involved.
If checkpoints occur too frequently and we update our rows non-sequentially, locating them by index, which is good because we don’t delete the entire table, it can happen that we touch the first page, then the thousandth, and then return to the first. If in between these accesses to the first page a checkpoint has already saved it to disk, it will be saved again since we touched it a second time.
E noi costringeremo il checkpoint a salvarlo molte volte. Si creano operazioni ridondanti per lui.

Ma non è tutto. In Postgres, le pagine pesano 8 kilobyte, mentre in Linux pesano 4 kilobyte. C'è un'impostazione chiamata full_page_writes. Di default, è attivata. Ed è giusto, perché se la disattiviamo, c'è il rischio che in caso di errore venga salvata solo metà della pagina.
Il comportamento della registrazione nel WAL (Write Ahead Log) è tale che, quando si verifica un checkpoint e cambiamo la pagina per la prima volta, nel log entra l'intera pagina, cioè tutti gli 8 kilobyte, anche se abbiamo modificato solo una riga che pesa 100 byte. Siamo costretti a registrare l'intera pagina.
Negli aggiornamenti successivi registreremo solo la tupla specifica, ma alla prima volta registriamo tutto.
E, di conseguenza, se si verifica un altro checkpoint, dobbiamo ricominciare da zero e reinserire tutta la pagina. Con checkpoint frequenti, quando ci muoviamo tra le stesse pagine, full_page_writes = on genera più rispetto a quanto potrebbe, cioè produciamo più WAL. Più viene inviato alle repliche, in archivio e su disco.
E, di conseguenza, si verificano due ridondanze.

Se aumentiamo il max_wal_size, rendiamo più facile il lavoro sia del checkpoint che del wal writer. Ed è fantastico.
Mettiamo un terabyte e viviamo con questo. Qual è il problema? È un problema perché in caso di guasto impiegheremo ore a riprenderci, poiché il checkpoint è avvenuto tanto tempo fa e ci sono state molte modifiche. E dobbiamo effettuare tutto il REDO. Ecco perché facciamo una seconda serie di esperimenti.
Effettuiamo l'operazione e osserviamo quando il checkpoint è vicino a completarsi, facciamo un kill -9 a Postgres appositamente.
Dopo di che, lo riavviamo e guardiamo quanto tempo impiega a avviarsi su questo hardware, cioè quanto REDO deve fare in questa situazione negativa.
Sottolineo per due volte che la situazione è negativa. In primo luogo, siamo caduti proprio prima del completamento del checkpoint, pertanto dobbiamo compensare molto. In secondo luogo, abbiamo svolto un'operazione massiva. E se i checkpoint fossero stati impostati su un timeout, probabilmente ci sarebbe stato meno WAL generato dall'ultimo checkpoint. Cioè, è un doppio fallimento.
Misuriamo una situazione così per diverse dimensioni di max_wal_size e comprendiamo che, se max_wal_size è di 64 gigabyte, in una doppia situazione peggiore ci vorranno 10 minuti per riprendersi. E ci poniamo la domanda: "È accettabile per noi?" È una questione di business. Dobbiamo presentare questa situazione a chi prende le decisioni aziendali e chiedere: "Per quanto tempo possiamo rimanere fermi in caso di problemi? Possiamo rimanere fermi 3-5 minuti nella peggiore delle ipotesi?" E prendere una decisione.
E qui c'è un punto interessante. Abbiamo una serie di presentazioni sulla Patroni alla conferenza. E forse lo state utilizzando. Questo è un sistema di autofailover per Postgres. GitLab e Data Egret ne hanno parlato.
E se avete un autofailover che si attiva dopo 30 secondi, forse possiamo anche rimanere fermi per 10 minuti? Perché a quel punto passeremo alla replica, e tutto andrà bene. È una questione controversa. Non conosco la risposta precisa. Sento solo che non si tratta solo del recupero dopo un guasto.
Se abbiamo un lungo tempo di recupero dopo un guasto, questo ci causerà problemi in molte altre situazioni. Ad esempio, negli stessi esperimenti, quando facciamo qualcosa e a volte dobbiamo aspettare anche 10 minuti.
Non andrei troppo oltre, anche se abbiamo l'autofailover. Di solito, valori come 64, 100 gigabyte sono buoni valori. A volte ha senso scegliere anche valori inferiori. In generale, è una scienza sottile.

Per fare iterazioni, ad esempio max_wal_size =1, 8, devi ripetere l'operazione in massa molte volte. Hai già fatto questo. E vuoi rifarlo sulla stessa base, ma hai già cancellato tutto. Cosa fare?
Più tardi parlerò della nostra soluzione su cosa facciamo per iterare in queste situazioni. Ed è l'approccio migliore.
Ma in questo caso siamo stati fortunati. Se, come scritto qui 'BEGIN, DELETE, ROLLBACK', possiamo ripetere il DELETE. Cioè, se lo abbiamo annullato noi stessi, possiamo ripeterlo. E fisicamente, i tuoi dati rimarranno lì. Non si formerà nemmeno bloat. Puoi fare iterazioni su questi DELETE.
Questo DELETE con ROLLBACK è ideale per il tuning dei checkpoint, anche se non hai un database labs implementato correttamente.

Abbiamo creato una tabella con una sola colonna 'i'. PostgreSQL ha colonne di sistema. Sono invisibili se non richiedi specificamente di vederle. Queste sono: ctid, xmid, xmax.
Ctid è l'indirizzo fisico. Pagina zero, primo tuple nella pagina.
È evidente che dopo il ROOLBACK la tupla è rimasta nello stesso posto. Cioè, possiamo riprovarci e si comporterà allo stesso modo. Questo è fondamentale.

L'Xmax è il tempo di vita della tupla. È stato registrato, ma Postgres sa che questa transazione è stata annullata, quindi sia 0 che la transazione annullata non sono rilevanti. Questo indica che si può iterare e controllare le operazioni massicce di DELETE per osservare il comportamento del sistema. Si possono realizzare laboratori di database per i meno fortunati.

Questo riguarda già i programmatori. Anche i DBA ne parlano, si lamentano sempre dei programmatori: «Perché fate operazioni così lunghe e pesanti?». È un tema completamente diverso e parallelo. Un tempo si parlava di amministrazione, ora invece di sviluppo.
È chiaro che non abbiamo suddiviso. Questo è comprensibile. Non si può effettuare un DELETE di milioni di righe senza suddividerlo. Ci vorranno 20 minuti e tutto si bloccherà. Ma, sfortunatamente, anche gli sviluppatori esperti commettono errori, anche nelle aziende molto grandi.
Perché è importante suddividere?
Se vediamo che il disco è sotto pressione, allora rallentiamo. E se abbiamo suddiviso, possiamo aggiungere delle pause, possiamo rallentare il throttling.
E non bloccheremo gli altri a lungo. In alcuni casi non importa: se stai rimuovendo spazzatura reale con cui nessuno lavora, probabilmente non bloccherai nessuno tranne il lavoro di autovacuum, perché aspetterà che la transazione si completi. Ma se stai rimuovendo qualcosa che qualcuno potrebbe ancora richiedere, allora saranno bloccati, dando vita a una reazione a catena. Nei siti web e nelle applicazioni mobili è necessario evitare transazioni lunghe.

È interessante. Spesso sento gli sviluppatori chiedere: "Quale dimensione del batch dovrei scegliere?".
È chiaro che maggiore è la dimensione del batch, minori sono i costi di transazione, ovvero le spese aggiuntive dovute alle transazioni. Tuttavia, il tempo per questa transazione aumenta.
Ho una regola molto semplice: prendi il più possibile, ma non superare l'esecuzione al secondo.
Perché una sola seconda? La spiegazione è molto semplice e comprensibile per tutti, anche per quelli non tecnici. Vediamo la reazione. Prendiamo 50 millisecondi. Se qualcosa è cambiato, il nostro occhio reagirà. Se è meno, è più difficile. Se qualcosa risponde dopo 100 millisecondi, ad esempio, se hai cliccato con il mouse e ha risposto dopo 100 millisecondi, già percepisci quel piccolo ritardo. Una seconda è già percepita come un rallentamento.
Di conseguenza, se dividiamo le nostre operazioni di massa in blocchi di 10 secondi, corriamo il rischio di bloccare qualcuno. E lui lavorerà per alcuni secondi, e questo le persone lo noteranno. Quindi preferisco non superare un secondo. Ma allo stesso tempo, non voglio suddividerlo in pezzi troppo piccoli, perché il sovraccarico delle transazioni sarebbe evidente. Il database avrebbe più difficoltà e potrebbero sorgere ulteriori problemi diversi.
Stiamo calibrando la dimensione del pacchetto. Possiamo farlo in modo diverso in ogni caso. Si può automatizzare. E verifichiamo l'efficacia dell'elaborazione di un pacchetto. Cioè, eseguiamo un DELETE o un UPDATE di un pacchetto.
Tra l'altro, tutto ciò di cui parlo non riguarda solo il DELETE. Come avrete intuito, si tratta di qualsiasi operazione di massa sui dati.
E stiamo vedendo che il piano è ottimo. È visibile l'indice di scansione, ancor meglio la scansione solo indice. E abbiamo un numero limitato di dati coinvolti. E continua a impiegare meno di un secondo. Super.
Dobbiamo anche assicurarci che non ci siano degradazioni. A volte, i primi lotti funzionano rapidamente e poi tutto va sempre peggio. È un processo che richiede molti test. Per questo sono necessari i database labs.
Dobbiamo anche preparare qualcosa che ci permetta di monitorare correttamente in produzione. Ad esempio, possiamo scrivere nel log il tempo, possiamo annotare dove siamo attualmente e chi abbiamo rimosso al momento. Questo ci permetterà di capire cosa sta succedendo e, nel caso in cui qualcosa vada storto, di trovare rapidamente il problema.
Se abbiamo bisogno di controllare l'efficacia delle richieste e dobbiamo iterare molte volte, c'è questo strumento chiamato bot. È già pronto. Viene utilizzato ogni giorno da decine di sviluppatori. È in grado di fornirti un'enorme base di dati da un terabyte in 30 secondi, la tua copia personale. E puoi cancellare qualcosa e dire RESET, e cancellare di nuovo. Puoi sperimentare in questo modo. Vedo un futuro dietro questo strumento. E noi lo stiamo già facendo.

Quali strategie di suddivisione ci sono? Vedo 3 diverse strategie di suddivisione utilizzate dagli sviluppatori in un lotto.
La prima è molto semplice. Abbiamo un identificatore numerico. E dividiamo in diversi intervalli, e lavoriamo con questo. Il lato negativo è chiaro. Nel primo intervallo, possiamo ottenere 100 righe di spazzatura reale, nel secondo 5 righe o potrebbero anche non esserci, oppure tutte le 1.000 righe potrebbero risultare spazzatura. È un lavoro molto irregolare, ma è facile da suddividere. Prendiamo l'ID massimo e lo suddividiamo. Questo è un approccio naïf.
La seconda strategia è un approccio bilanciato. Viene utilizzata in Gitlab. Abbiamo eseguito una scansione della tabella. Abbiamo identificato i limiti dei lotti ID in modo che ogni lotto contenga esattamente 10.000 record. E li abbiamo messi in una coda. Poi procediamo a elaborarli. Questo può essere fatto in più thread.
Nella prima strategia, tra l'altro, è possibile farlo in più thread. Non è complicato.

Tuttavia, esiste un approccio più elegante e ottimale. Questa è la terza strategia. E quando è possibile, è meglio sceglierla. Lo facciamo sulla base di un indice speciale. In questo caso, sarà probabilmente un indice basato sulle nostre condizioni di spazzatura e sugli ID. Includeremo l'ID, in modo da avere una scansione solo dell'indice, evitando di dover accedere all'heap.
In genere, la scansione solo dell'indice è più rapida rispetto alla scansione dell'indice.

E noi troviamo rapidamente i nostri ID che vogliamo eliminare. Impostiamo il BATCH_SIZE in anticipo. E non li otteniamo solo, li otteniamo in modo speciale e li blocchiamo subito. Ma li blocchiamo in modo tale che, se sono già bloccati, non li blocchiamo di nuovo, ma procediamo e prendiamo i successivi. Questo è un for update skip locked. Questa super funzionalità di Postgres ci consente di lavorare in più thread, se lo desideriamo. Possiamo farlo anche in un singolo thread. Qui c'è il CTE – è una singola query. E nel secondo livello di questo CTE avviene l'eliminazione reale – returning *. Possiamo ritornare l'id, ma è meglio *, se hai pochi dati in ogni riga.

Perché abbiamo bisogno di questo? Ne abbiamo bisogno per rendicontare. Abbiamo ora eliminato così tante righe in realtà. E i nostri limiti per ID o per created_at sono questi. Possiamo fare min e max. Possiamo fare ancora altro. Qui si può inserire molto. E questo è molto utile per il monitoraggio.
C'è un'altra osservazione riguardo all'indice. Se abbiamo deciso che abbiamo bisogno di un indice speciale per questa attività, dobbiamo assicurarci che non rovini gli aggiornamenti dei tuple solo heap. C'è una statistica come questa in Postgres. Puoi controllare in pg_stat_user_tables per la tua tabella. Puoi vedere se vengono utilizzati aggiornamenti hot o meno.
Ci sono situazioni in cui il tuo nuovo indice può semplicemente interrompere gli aggiornamenti. E tutti gli altri aggiornamenti che già funzionano rallenteranno. Non solo perché è comparso l'indice (ogni indice rallenta un po' gli aggiornamenti, ma solo leggermente), ma qui rovinerà anche tutto. E non è possibile fare una specializzazione per questa tabella. Succede di tanto in tanto. È una sottigliezza che pochi ricordano. E si può facilmente inciampare in questo. A volte, è necessario trovare un approccio alternativo e comunque fare a meno di questo nuovo indice, o creare un altro indice, o trovare un altro modo, ad esempio, si può usare un secondo metodo.
Ma questa è la strategia più ottimale, come suddividere in batch e colpire un gruppo con una singola richiesta, eliminando un po' alla volta, ecc.

Transazioni lunghe —
Autovacuum bloccato —
Problema di blocco —
Errore n. 5 grande. Nikolai di Okmeter ha parlato del monitoraggio di Postgres. Purtroppo non esiste un monitoraggio perfetto di Postgres. Qualcuno è più vicino, qualcun altro più lontano. Okmeter è abbastanza vicino all'ideale, ma manca ancora molto e bisogna aggiungere. A questo bisogna essere pronti.
Ad esempio, è meglio monitorare i dead tuples. Se hai molte righe morte nella tabella, allora c'è qualcosa che non va. È meglio reagire adesso, altrimenti potremmo subire una degradazione e potremmo fermarci. Questo può accadere.
Se ci sono molte operazioni di input/output, è chiaro che non va bene.
Le transazioni lunghe sono un altro problema. Non è consigliabile avere transazioni lunghe nelle operazioni OLTP. E qui c'è un link a uno snippet che consente di monitorare le transazioni lunghe.
Perché le transazioni lunghe sono un problema? Perché tutti i lock verranno rilasciati solo alla fine. E blocchiamo tutti. Inoltre, impediamo il funzionamento di autovacuum per tutte le tabelle. Questo non è affatto positivo. Anche se sulla replica hai abilitato il hot standby, resta comunque un problema. In generale, è meglio non permettere mai transazioni lunghe.
Se abbiamo molte tabelle che non vengono vaccinate, è necessario avere un alert. Qui è possibile una situazione del genere. Possiamo indirettamente influenzare il funzionamento di autovacuum. Questo è uno snippet di Avito che ho leggermente migliorato. È diventato uno strumento interessante per vedere cosa succede con autovacuum. Ad esempio, ci sono tabelle in attesa e non arriveranno mai al loro turno. Anche questo dovrebbe essere inserito nel monitoraggio e avere un alert.
E i blocchi di issues. Foresta degli alberi di blocchi. Mi piace prendere qualcosa da qualcun altro e migliorarlo. Qui ho preso un ottimo CTE ricorsivo da Data Egret, che mostra la foresta degli alberi di blocchi. È una buona cosa per la diagnostica. E su questa base si può anche creare un monitoraggio. Ma bisogna farlo con cautela. È necessario impostare un piccolo statement_timeout per sé stessi. E anche un lock_timeout se possibile.

A volte tutti questi errori si sommano.
A mio avviso, l'errore principale qui è organizzativo. È organizzativo perché la tecnologia non è all'altezza. Questo è il numero 2: abbiamo controllato nel posto sbagliato.
Abbiamo controllato nel posto sbagliato perché non avevamo un clone di produzione, su cui sarebbe facile verificare. Il developer potrebbe non avere accesso alla produzione.
E non stavamo controllando lì. Se avessimo controllato lì, avremmo notato noi stessi. Lo sviluppatore avrebbe potuto notare tutto questo senza DBA, se avesse controllato in un buon ambiente, dove i dati sono uguali e disposti in modo identico. Avrebbe notato tutta questa degradazione e ne sarebbe stato imbarazzato.
Ancora sull'autovacuum. Dopo aver effettuato una massiccia pulizia di milioni di righe, è necessario eseguire anche un REPACK. Questo è particolarmente importante per gli indici. Ne risentiranno dopo che avremo fatto tutto quel lavoro di pulizia.
E se vuoi ripristinare il lavoro quotidiano di pulizia, suggerirei di farlo più spesso, ma in modo più a fondo. È possibile farlo ogni minuto o anche più frequentemente, ma in piccole dosi. Inoltre, è necessario impostare il monitoraggio di due cose: che non ci siano errori e che non ci sia un ritardo. Il trucco che ho mostrato risolverà proprio questo problema.

Quello che facciamo è open source. È disponibile su GitLab. E facciamo in modo che le persone possano controllare anche senza DBA. Creiamo un database lab, ovvero il componente di base su cui attualmente lavora Joe. E potete prendere una copia della produzione. Attualmente c'è un'implementazione di Joe per Slack, dove potete dire: «spiega questa query» e ricevere immediatamente il risultato per la vostra copia del database. Potete perfino effettuare un DELETE lì, e nessuno se ne accorgerà.

Possiamo immaginare che abbiate 10 terabyte, e noi realizziamo il database lab con altrettanti 10 terabyte. E con 10 database da 10 terabyte che possono lavorare contemporaneamente, 10 sviluppatori possono lavorare insieme. Ognuno può fare quello che vuole. Può eliminare, abbandonare, e così via. È una vera meraviglia. Di questo parleremo domani.

Questo si chiama thin provisioning. È un approccio innovativo che riduce notevolmente i ritardi nello sviluppo e nei test, migliorando la situazione complessiva. Permette proprio di evitarvi problemi con operazioni di massa.
Esempio: un database di 5 terabyte, copia in meno di 30 secondi. E non dipende nemmeno dalle dimensioni, non importa quanti terabyte ci siano.
Già oggi potete visitare e esplorare i nostri strumenti. Puoi registrarti e dare un'occhiata a ciò che c'è. Puoi anche installare questo bot. È gratuito. Scrivici.
Domande
Spesso, nelle situazioni reali, accade che i dati che devono rimanere nella tabella siano molto meno numerosi di quelli da eliminare. In tal caso, spesso è più facile adottare un approccio in cui si crea un nuovo oggetto, si copiano solo i dati necessari e si tronca la vecchia tabella. È chiaro che ci vuole un approccio programmato per questo passaggio, dato che ci sarà uno switch. Qual è questo approccio?
È un approccio molto valido e una buona sfida. È molto simile a ciò che fa pg_repack, ed è simile a ciò che si deve fare quando si rendono gli ID a 4 byte. Molti framework lo hanno fatto diversi anni fa, e ora le tabelle sono cresciute e devono essere convertite a 8 byte.
Questo compito è piuttosto difficile. Lo abbiamo fatto. E devi essere molto attento. Ci sono dei lock e così via. Ma è fattibile. Cioè, l'approccio standard è quello di pg_repack. Dichiarate una tabella così. E prima di iniziare a caricare i dati con uno snapshot, dovete anche dichiarare un'altra tabella che monitora tutte le modifiche. C'è un trucco, che alcune modifiche potete anche non monitorarle. Ci sono delle sottigliezze. E poi commutate, applicando le modifiche. Ci sarà una breve pausa quando bloccheremo tutti, ma nel complesso è fattibile.
Se guardate pg_repack su GitHub, quando c'era l'obiettivo di convertire l'ID da int 4 a int 8, l'idea era di usare pg_repack stesso. Questo è possibile, ma è un metodo un po' hackerato, ma funziona anche per questo. Potete intervenire nel trigger che usa pg_repack e dire: "Questi dati non ci servono", cioè stiamo trasferendo solo ciò che ci serve. E poi si commuta e basta.
Con questo approccio otteniamo anche una seconda copia della tabella, in cui i dati sono già indicizzati e disposti in modo molto ordinato con indici belli.
Niente bloat, è un buon approccio. Ma so che ci sono tentativi di sviluppare un'automazione per questo, cioè fare una soluzione universale. Posso mettervi in contatto con questa automazione. È scritta in Python, è una buona cosa.
Vengo un po' dal mondo di MySQL, quindi sono qui per ascoltare. E utilizziamo questo approccio.
Ma funziona solo se abbiamo il 90%. Se abbiamo solo il 5%, non è molto utile applicarlo.
Grazie per la presentazione! Se non ci sono risorse per fare una copia completa dell'ambiente di produzione, c'è qualche algoritmo o formula per calcolare il carico o la dimensione?
Ottima domanda. Fino ad ora siamo riusciti a trovare database multi-terabyte. Anche se l'hardware non sarà esattamente lo stesso, ad esempio con meno memoria, meno processore e dischi non del tutto identici, comunque procediamo. Se non c'è altro posto, dobbiamo pensarci. Lasciate che ci pensi fino a domani, se venite, ne parliamo, è una buona domanda.
Grazie per la relazione! Hai iniziato menzionando il fantastico Postgres, che ha certe limitazioni, ma sta evolvendo. Tuttavia, tutto ciò sembra essere un palliativo. Non è in contraddizione con lo sviluppo stesso di Postgres, dove potrebbe apparire qualcosa come un DELETE deferent o altre funzionalità che dovrebbero supportare a livello basso ciò che stiamo cercando di mascherare con metodi strani?
Se in SQL abbiamo detto di eliminare o aggiornare molte righe in un'unica transazione, come può Postgres gestire questo? Siamo fisicamente limitati nelle operazioni. Comunque lo faremo lentamente e bloccheremo nel frattempo, ecc.
Hanno già fatto con gli indici.
Posso ipotizzare che anche il tuning dei checkpoint potrebbe essere automatizzato. Un giorno potrebbe succedere. Ma in tal caso, non capisco molto la domanda.
La questione è se esista un vettore di sviluppo che va in una direzione, mentre la vostra va parallelamente. Cioè, al momento non ci stanno pensando?
Ho parlato dei principi che si possono utilizzare ora. C'è un altro bot , tramite questo si può effettuare un tuning automatizzato del checkpoint. Ci sarà mai in Postgres? Non lo so, al momento non è nemmeno discusso. Siamo ancora lontani da questo. Ma ci sono ricercatori che sviluppano nuovi sistemi. E ci spingono verso indici automatici. Ci sono delle innovazioni. Ad esempio, puoi dare un'occhiata all'auto tuning. Questo seleziona i parametri automaticamente. Ma al momento non può fare il tuning del checkpoint. Ovvero, selezionerà per le performance, shell buffer, ecc.
Per il tuning del checkpoint puoi realizzare qualcosa del genere: se hai mille cluster e diverse macchine, diverse macchine virtuali nel cloud, puoi utilizzare il nostro bot per realizzare l'automazione. E il max_wal_size sarà selezionato automaticamente in base alle tue impostazioni target. Ma purtroppo, al momento, non è nemmeno vicino a essere implementato nel kernel.
Buongiorno! Hai parlato del danno delle lunghe transazioni. Hai accennato che l'autovacuum viene bloccato nel caso di eliminazioni. In che altro modo ci danneggia? Perché parliamo più dello spazio liberato e della possibilità di utilizzarlo. Cosa altro stiamo perdendo?
L'autovacuum non è necessariamente il problema principale qui. La vera preoccupazione è che una transazione lunga può bloccare altre transazioni, il che è molto più pericoloso. Può succedere o meno. Se succede, le conseguenze possono essere gravi. E anche l'autovacuum è un problema. Ci sono due problematiche con le transazioni lunghe in OLTP: i lock e l'autovacuum. E se hai attivato il feedback del hot standby sul replica, potresti anche subire il blocco dell'autovacuum sul master, che arriva dal replica. Ma almeno non ci saranno lock lì. Qui, invece, i lock saranno presenti. Stiamo parlando di modifiche ai dati, quindi i lock sono un aspetto cruciale. Se tutto questo si protrae a lungo, sempre più transazioni vengono bloccate. Possono bloccarsi a vicenda, creando alberi di lock. Ho fornito un collegamento a un frammento. Questo problema diventa evidente più rapidamente rispetto a quello dell'autovacuum, che può solo accumularsi.
Grazie per la presentazione! Hai iniziato il tuo discorso dicendo che i test erano stati eseguiti in modo errato. Hai continuato l'idea che sia necessario utilizzare attrezzature omogenee, con il database esattamente allo stesso modo. Supponiamo che abbiamo fornito al sviluppatore il database. E lui ha eseguito la richiesta. E sembra che tutto vada bene. Ma lui non verifica in live, mentre in live, per esempio, abbiamo un carico del 60-70%. E anche se utilizziamo questa ottimizzazione, il risultato non è dei migliori.
Avere un esperto nel team e collaborare con DBA esperti, che possono fare previsioni su cosa accadrà sotto un carico reale, è fondamentale. Quando semplicemente testiamo le nostre modifiche, vediamo solo una parte del quadro. Tuttavia, un approccio più avanzato sarebbe quello di ripetere gli stessi test, ma con un carico simulato di produzione. Questo sarebbe davvero ottimo. Prima bisogna maturare per arrivare a questo. Abbiamo anche esaminato soltanto le nostre risorse e se ne avessimo abbastanza. È una buona domanda.
Quando eseguiamo già una garbage select e abbiamo, per esempio, un flag di eliminazione.
Questo è ciò che fa autovacuum automaticamente in Postgres.
Ah, lo fa?
Autovacuum è proprio un garbage collector.
Grazie!
Grazie per la presentazione! Esiste un modo per progettare il database fin dall'inizio con la partizionamento, in modo che tutti i dati di scarto vengano separati dalla tabella principale?
Certo, c'è.
Allora, è possibile proteggersi se abbiamo bloccato una tabella che non dovrebbe essere utilizzata?
Certo, c'è. Ma è una questione simile a quella del pollo e dell'uovo. Se sapessimo tutti cosa ci aspetta in futuro, naturalmente, faremmo tutto alla perfezione. Ma il business cambia, appaiono nuove colonne, nuove richieste. E poi – ecco, vogliamo eliminarle. Ma questa è l'ideale situazione, nella vita accade, ma non sempre. In generale, però, è una buona idea. Basta troncare e basta.
Fonte: habr.com
