Ti propongo di leggere la trascrizione della relazione di inizio 2016 di Andrey Sal'nikov "Errori tipici nelle applicazioni che portano al bloat in postgresql"
In questa relazione analizzerò gli errori principali nelle applicazioni che si verificano durante la fase di progettazione e scrittura del codice dell'applicazione. Prenderò in considerazione solo quegli errori che portano al bloat in Postgresql. Di norma, questo segna l'inizio della fine delle prestazioni del sistema nel suo complesso, anche se inizialmente non si vedevano presupposti evidenti.

Sono felice di dare il benvenuto a tutti! Questa relazione non è così tecnica come quella precedente del mio collega. È principalmente rivolta agli sviluppatori di sistemi backend, poiché abbiamo un numero abbastanza elevato di clienti. E tutti loro commettono gli stessi errori. Di questi vi parlerò. Spiegherò a cosa portano questi errori fatali e dannosi.

Perché si commettono errori? Ci sono due motivi: per caso, magari andrà bene e per ignoranza di alcuni meccanismi che avvengono a livello tra il database e l'applicazione, e anche all'interno del database stesso.
Vi presenterò tre esempi con orribili immagini di come tutto è andato male. Vi parlerò brevemente del meccanismo che accade. E di come affrontarli quando si sono verificati, e quali metodi preventivi utilizzare per evitare questi errori. Vi parlerò di strumenti ausiliari e vi fornirò link utili.

Ho utilizzato un database di test, dove avevo due tabelle. Una tabella con le fatture dei clienti, l'altra con le operazioni su queste fatture. E con una certa periodicità aggiorniamo i saldi su queste fatture.

I dati della tabella iniziale: è abbastanza piccola, 2 MB. Il tempo di risposta del database e specificamente per la tabella è anch'esso molto buono. E un carico piuttosto buono – 2.000 operazioni al secondo sulla tabella.

E attraverso questa relazione vi mostrerò grafici, per rendere chiaro cosa sta succedendo. Ci saranno sempre 2 diapositive con grafici. La prima diapositiva mostrerà cosa succede in generale sul server.
E in questa situazione vediamo che in effetti la nostra tabella è di piccole dimensioni. L'indice è piccolo, di 2 MB. Questo è il primo grafico a sinistra.
Il tempo medio di risposta del server è anch'esso stabile e ridotto. Questo è il grafico in alto a destra.
Il grafico in basso a sinistra rappresenta le transazioni più lunghe. Vediamo che le transazioni vengono completate rapidamente. E l'auto-vacuum qui non funziona ancora, perché era un test di avvio. Successivamente, funzionerà e sarà utile per noi.

Il secondo slide sarà sempre dedicato alla tabella in esame. In questa situazione, aggiorniamo costantemente i saldi nei conti del cliente. E vediamo che il tempo medio di risposta per l'operazione di aggiornamento è piuttosto buono, meno di un millisecondo. Possiamo notare che le risorse della CPU (questo è il grafico in alto a destra) vengono utilizzate in modo uniforme e con un quantitativo piuttosto ridotto.
Il grafico in basso a destra mostra quanta memoria operativa e di disco stiamo esplorando alla ricerca della riga necessaria, prima di aggiornarla. E il numero di operazioni sulla tabella è di 2000 al secondo, come ho detto all'inizio.

E ora abbiamo una tragedia. Per qualche motivo si verifica una transazione dimenticata di lunga durata. Le cause sono solitamente tutte banali:
- Una delle più comuni è che nel codice dell'applicazione abbiamo iniziato a chiamare un servizio esterno. E quel servizio non ci risponde. Cioè, abbiamo aperto una transazione, effettuato una modifica nel database e poi siamo andati a controllare la posta o ad utilizzare un altro servizio all'interno della nostra infrastruttura, e per qualche motivo non ci risponde. E ci troviamo con una sessione bloccata che è in uno stato - non si sa quando si risolverà.
- La seconda situazione si verifica quando nel codice per qualche motivo abbiamo avuto un exception. E non abbiamo gestito la chiusura della transazione nell'exception. E ci troviamo con una sessione bloccata con una transazione aperta.
- E infine – questo è un altro caso abbastanza comune. È un codice di bassa qualità. Alcuni framework aprono una transazione. Essa rimane bloccata, e potresti non sapere nell'applicazione che è bloccata.
A cosa portano queste cose?
A fatto che le tabelle e gli indici cominciano a gonfiarsi rapidamente. Questo è proprio l'effetto bloat. Per il database, si tradurrà in un aumento drastico del tempo di risposta del database, e ci sarà un aumento del carico sul server del database. E come conseguenza, l'applicazione ne risentirà. Perché se nel codice impiegavi 10 millisecondi per una query al database, 10 millisecondi per la tua logica, la tua funzione impiegava 20 millisecondi. Ma ora ti trovi in una situazione davvero triste.
E vediamo cosa sta succedendo. Il grafico in basso a sinistra mostra che abbiamo una transazione lunga. E se guardiamo il grafico in alto a sinistra, vediamo che la dimensione della tabella è passata da due megabyte a 300 megabyte. Tuttavia, la quantità di dati nella tabella non è cambiata, cioè c'è una grande quantità di spazzatura.

La situazione generale per il tempo medio di risposta del server è cambiata di diversi ordini di grandezza. Cioè, tutte le richieste al server hanno iniziato a rallentare drasticamente. Inoltre, sono stati attivati processi interni di Postgres come l’autovacuum, che stanno cercando di fare qualcosa e consumano risorse.

Cosa succede alla nostra tabella? Anche qui. Il tempo medio di risposta per la tabella è aumentato di diversi ordini di grandezza. In particolare, per quanto riguarda le risorse consumate, vediamo che il carico della CPU è aumentato notevolmente. Questo è il grafico in alto a destra. È aumentato perché la CPU deve esaminare un gran numero di righe inutili alla ricerca di una riga utile. Questo è il grafico in basso a destra. E come risultato, il numero di chiamate al secondo ha iniziato a diminuire drasticamente, perché il database non riesce a gestire il numero di richieste.

Dobbiamo tornare alla normalità. Andiamo su internet e scopriamo che le transazioni lunghe causano problemi. Troviamo e cancelliamo questa transazione. E tutto torna alla normalità. Tutto funziona come dovrebbe.
Ci siamo calmati, ma dopo un po' iniziamo a notare che l'applicazione non funziona come prima dell'incidente. Le richieste vengono comunque elaborate più lentamente, e in modo significativo più lentamente. Un'ottima volta e mezzo più lentamente nel mio esempio specifico. Il carico sul server è anche più alto di quanto fosse prima dell'incidente.

E la domanda è: "Cosa succede al database in quel momento?". Nel database si verifica la seguente situazione. Nel grafico delle transazioni si vede che è fermo e non ci sono davvero transazioni lunghe. Ma le dimensioni della tabella durante l'incidente sono aumentate drammaticamente. E da allora non sono diminuite. Il tempo medio del database si è stabilizzato. E le risposte sembrano viaggiare a una velocità accettabile per noi. L'autovacuum è diventato più attivo e ha iniziato a fare qualcosa con la tabella, perché deve rielaborare un numero maggiore di dati.

Con riferimento alla tabella dei conti in cui stiamo modificando i saldi: il tempo di risposta della richiesta sembra essere tornato alla normalità. Ma in realtà è un'altra volta e mezzo più alto.
E per quanto riguarda il carico sulla CPU, vediamo che il carico non è tornato ai valori necessari prima dell'incidente. Le cause si trovano proprio nel grafico in basso a destra. È evidente che si sta verificando una sovraccumulazione di qualche quantità di memoria. Cioè, per cercare la riga necessaria stiamo consumando risorse del server database durante una scansione di dati inutili. Il numero di transazioni al secondo si è stabilizzato.
In generale sta andando bene, ma la situazione è peggiore di prima. C'è un evidente degrado del database a causa della nostra applicazione che lavora con questo database.

E per capire cosa stia accadendo, se non eravate alla presentazione precedente, faremo ora un po' di teoria. Teoria sul processo interno. A cosa serve l'autovacuum e cosa fa?
In breve, per una migliore comprensione. A un certo punto abbiamo una tabella. Nella tabella ci sono delle righe. Queste righe possono essere attive, vive, necessarie ora. Nella figura sono contrassegnate in verde. E ci sono righe morte, che sono già state elaborate, sono state aggiornate e su di esse sono state create nuove registrazioni. E sono contrassegnate come non più interessanti per il database. Ma rimangono nella tabella a causa delle peculiarità di Postgres.
A cosa serve l'autovacuum? A un certo punto, l'autovacuum si presenta, si rivolge al database e gli chiede: «Per favore, dammi l'id della transazione più vecchia che è attualmente aperta nel database». Il database restituisce questo id. E l'autovacuum, facendo riferimento ad esso, scorre le righe nella tabella. E se vede che alcune righe sono state modificate da transazioni decisamente più vecchie, ha il diritto di contrassegnarle come righe che possiamo riutilizzare in futuro, scrivendo nuovi dati. È un processo in background.
Nel frattempo continuiamo a lavorare con il database, continuiamo a fare modifiche nella tabella. E per queste righe che possiamo riutilizzare, scriviamo nuovi dati. In questo modo otteniamo un ricircolo, cioè emergono costantemente vecchie righe morte e al loro posto scriviamo nuove righe che ci servono. E questo è uno stato normale per il funzionamento di PostgreSQL.

Cosa è successo durante l'incidente? Come è avvenuto questo processo?
Avevamo una tabella in qualche stato, alcune righe vive, alcune morte. È arrivato l'autovacuum. Ha chiesto al database qual è la nostra transazione più antica e qual è il suo id. Ha ottenuto questo id, che può risalire a molte ore fa o a dieci minuti fa. Questo dipende da quanto è forte il carico nel tuo database. E ha cominciato a cercare le righe che può segnare come riutilizzabili. E non ha trovato tali righe nella nostra tabella.
Ma noi nel frattempo continuiamo a lavorare con la tabella. Facciamo qualcosa in essa, aggiorniamo, cambiamo i dati. E cosa può fare il database nel frattempo? Non può fare altro che aggiungere nuove righe alla fine della tabella esistente. E così, la dimensione della tabella inizia ad aumentare.
In realtà, abbiamo bisogno delle righe verdi per lavorare. Ma durante un problema del genere, il nostro percentuale di righe verdi risulta estremamente bassa rispetto all'intero volume della tabella.
Quando eseguiamo una query, il database deve esaminare tutte le righe: sia rosse che verdi, per trovare la riga necessaria. E l'effetto dell'aumento della tabella con dati inutili si chiama "bloat", che consuma anche il nostro spazio su disco. Ricordate, erano 2 MB, sono diventati 300 MB? Ora cambiate megabyte in gigabyte e perderete rapidamente tutte le vostre risorse disco.

Quali possono essere le conseguenze per noi?
- Nel mio esempio, la tabella e l'indice sono cresciuti di 150 volte. Alcuni dei nostri clienti hanno sperimentato casi più fatali, con il semplice spazio su disco che cominciava a finire.
- La dimensione delle tabelle di per sé non diminuirà mai. L'autovacuum in alcuni casi può tagliare la parte finale della tabella, se ci sono solo righe morte. Ma poiché si verifica una rotazione costante, una riga verde può rimanere in fondo e non aggiornarsi, mentre tutte le altre saranno registrate all'inizio della tabella. Ma è un evento così improbabile che non dovresti contare su un diminuzione della dimensione della tua tabella.
- Il database deve filtrare tutta questa montagna di righe inutili. E noi stiamo sprecando risorse disco, sprecando risorse CPU e energia elettrica.
- E questo influisce direttamente sulla nostra applicazione, perché se all'inizio impiegavamo 10 millisecondi per la richiesta, 10 millisecondi per il nostro codice, durante il guasto siamo passati a impiegare un secondo per la richiesta e 10 millisecondi per il codice, cioè la performance dell'applicazione è diminuita di un ordine di grandezza. E quando abbiamo risolto il guasto, abbiamo cominciato a impiegare 20 millisecondi per la richiesta e 10 millisecondi per il codice. Questo significa che siamo comunque scesi a un rendimento di un italiano e mezzo. E tutto questo a causa di una transazione che è rimasta bloccata, probabilmente per nostra colpa.
- E la domanda è: «Come possiamo riportare tutto come prima?», affinché le nostre richieste tornino ad essere rapide come prima del guasto.

Per questo esiste un ciclo di lavori specifico che viene svolto.
Innanzitutto dobbiamo identificare le tabelle problematiche che si sono gonfiate. Ci rendiamo conto che per alcune tabelle la scrittura avviene in modo più attivo, per altre meno attivo. E per questo si utilizza l'estensione . Installando questa estensione, puoi scrivere interrogazioni che ti aiuteranno a trovare le tabelle che si sono gonfiate abbastanza.
Dopo aver trovato queste tabelle, è necessario compattarle. A questo scopo ci sono già degli strumenti. Nella nostra azienda utilizziamo tre strumenti. Il primo è il VACUUM FULL integrato. È brutale, severo e spietato, ma a volte è molto utile. e sono utility di terze parti per la compressione delle tabelle. E sono più rispettosi nei confronti del database.
Vengono utilizzati a seconda di quello che ti risulta più comodo. Ma di questo parlerò alla fine. L'importante è che ci sono tre strumenti. Hai da dove scegliere.
Dopo aver sistemato tutto e averci assicurato che tutto funzionasse correttamente, dobbiamo sapere come prevenire questa situazione in futuro:
- Può essere prevenuta piuttosto facilmente. È importante monitorare la durata delle sessioni sul Server Master. Sessioni particolarmente pericolose in stato di idle in transazione. Sono quelle che hanno aperto una transazione, hanno fatto qualcosa e poi se ne sono andate, oppure sono semplicemente rimaste appese, perse nel codice.
- E per voi, come per sviluppatori, è importante testare il codice nel momento in cui si verificano queste situazioni. Non è difficile farlo. Sarà un controllo utile. Eviterete un gran numero di problemi 'infantili' legati a transazioni lunghe.

In questi grafici volevo mostrarti come è cambiata la tabella e il comportamento del database dopo aver eseguito un VACUUM FULL sulla tabella. Questo non è un ambiente di produzione per me.
La dimensione della tabella è tornata subito a uno stato di lavoro normale, di qualche megabyte. Sul tempo medio di risposta del server, questo non ha avuto un grande impatto.

Ma specificamente sulla nostra tabella di prova, dove abbiamo aggiornato i saldi, vediamo che il tempo medio di risposta per la richiesta di aggiornamento dei dati nella tabella è sceso a livelli pre-allerta. Anche le risorse consumate dal processore per eseguire questa richiesta sono diminuite a livelli pre-allerta. E il grafico in basso a destra mostra che ora troviamo esattamente la riga di cui abbiamo bisogno immediatamente, senza dover scorrere un mucchio di righe morte che c'erano prima della compressione della tabella. E il tempo medio delle richieste è rimasto all'incirca sullo stesso livello. Ma qui credo sia più un errore del mio hardware.

Questa è la prima storia, ed è la più comune. Accade a tutti, indipendentemente dall'esperienza del cliente, anche se gli sviluppatori sono qualificati. Prima o poi succede.
La seconda storia, in cui distribuiamo il carico e ottimizziamo le risorse del server.

- Siamo già cresciuti e siamo diventati ragazzi seri. E capiamo che abbiamo una replica e sarebbe bene bilanciare il carico: scrivere sul Master e leggere dalla replica. Questa situazione di solito si presenta quando vogliamo preparare dei report o delle operazioni ETL. E il business ne è molto contento. Vuole report vari con un sacco di analisi complesse.
- I report richiedono ore, perché un'analisi complessa non può essere calcolata in millisecondi. Noi, come dei bravi ragazzi, scriviamo codice. Effettuiamo inserimenti nell'applicazione, registriamo sul Master e eseguiamo i report sulla replica.
- Distribuiamo il carico.
- Tutto funziona alla grande. Siamo bravi.

E come appare questa situazione? Nello specifico, in questi grafici ho aggiunto anche la durata delle transazioni dalla replica. Tutti gli altri grafici si riferiscono solo al server Master.
La tabella con i report è cresciuta fino a questo punto. Sono diventati di più. Vediamo che il tempo medio di risposta del server è stabile. Vediamo che nella replica abbiamo una transazione lunga che dura 2 ore. Vediamo un funzionamento tranquillo dell’autovacuum, che gestisce le righe morte. E tutto va bene.

Specificamente per la tabella in esame, continuiamo ad aggiornare i saldi sui conti. Anche noi abbiamo un tempo di risposta stabile per la richiesta, un consumo di risorse stabile. Tutto va bene.

Va bene finché non iniziamo a vedere questi report sparire a causa di conflitti con la replicazione. E scompaiono con una certa regolarità.
Ci connettiamo a Internet e iniziamo a leggere perché questo sta succedendo. E troviamo una soluzione.
La prima soluzione è aumentare il ritardo della replicazione. Sappiamo che il nostro report impiega 3 ore. Impostiamo il ritardo della replicazione a 3 ore. Avviamo tutto, ma continuiamo ad avere problemi con i report che a volte scompaiono.
Vogliamo che tutto sia perfetto. Ci spingiamo oltre. E troviamo su Internet una fantastica impostazione – hot_standby_feedback. La attiviamo. Hot_standby_feedback ci consente di mantenere il lavoro dell’autovacuum sul Master. In questo modo ci liberiamo completamente dai conflitti di replicazione. E tutto funziona bene con i report.

E cosa succede nel frattempo al server Master? Al server Master stiamo vivendo un disastro totale. Ora stiamo osservando i grafici da quando ho attivato entrambe queste impostazioni. E vediamo che la sessione sulla replica in qualche modo ha iniziato a influenzare la situazione sul server Master. Influisce davvero, perché ha sospeso l’autovacuum che ripulisce le righe morte. Le dimensioni della tabella sono nuovamente schizzate in alto. Anche il tempo medio di esecuzione delle query su tutto il database è aumentato. Gli autovacuum sono un po' sotto pressione.

Specificamente per la nostra tabella vediamo che anche l'aggiornamento dei dati è schizzato in alto. Anche il consumo di risorse della CPU è aumentato notevolmente. Stiamo di nuovo elaborando un gran numero di righe morte inutili. E il tempo di risposta per questa tabella, il numero di transazioni è diminuito.

Come apparirà se non sappiamo di cosa stavo parlando prima?
- Iniziamo a cercare problemi. Se abbiamo riscontrato problemi nella prima parte, sappiamo che potrebbe essere dovuto a una lunga transazione e andiamo su Master. Il problema è su Master. È instabile. Si surriscalda, ha un Load Average vicino a cento.
- Le richieste lì sono lente, ma non vediamo transazioni prolungate. E non capiamo di cosa si tratti. Non capiamo dove cercare.
- Controlliamo l'hardware del server. Forse il nostro raid è andato in panne. Magari uno stick di memoria è bruciato. Può succedere di tutto. Ma no, i server sono nuovi, tutto funziona perfettamente.
- Correndo qua e là: amministratori, sviluppatori e direttore. Nulla sembra funzionare.
- E a un certo punto tutto inizia improvvisamente a sistemarsi da solo.

Sulla replica, nel frattempo, la richiesta è stata elaborata e ha terminato. Abbiamo ricevuto un rapporto. Il business è ancora soddisfatto. Come vediamo, la tabella è nuovamente cresciuta e non sembra voler diminuire. Sul grafico delle sessioni ho lasciato un pezzo di questa lunga transazione dalla replica, così potete valutare quanto tempo passa finché la situazione si stabilizza.
La sessione è terminata. Solo dopo un po' il server inizia a tornare a un certo ordine. E il tempo medio di risposta per le richieste sul server Master torna alla normalità. Perché, finalmente, l'autovacuum ha avuto la possibilità di pulire e contrassegnare queste righe morte. E ha iniziato a fare il suo lavoro. E tanto velocemente quanto lo fa, tanto velocemente torneremo in ordine.

Sulla tabella oggetto di prova, dove aggiorniamo i saldi, vediamo lo stesso scenario. Anche il tempo medio di aggiornamento del saldo si normalizza gradualmente. Le risorse consume dal processore stanno diminuendo, e anche il numero di transazioni al secondo torna alla normalità. Ma, di nuovo, non alla normalità che avevamo prima dell'incidente.

Riceviamo comunque una flessione delle prestazioni, proprio come nel primo caso, da un'ora e mezza a due volte, a volte anche di più.
Sembra che abbiamo fatto tutto correttamente. Abbiamo distribuito il carico. L'hardware non è fermo. Abbiamo suddiviso le richieste in modo intelligente, ma alla fine è andata male.
- Non attivare hot_standby_feedback? Sì, non è consigliato attivarlo senza motivi particolari. Questo perché questa impostazione influisce direttamente sul server master e sospende il funzionamento dell'autovacuum lì. Attivandolo su una replica e dimenticandosene, potresti compromettere il master e avere grandi problemi con l'applicazione.
- Aumentare max_standby_streaming_delay? Sì, per i report è così. Se hai un report di tre ore e non vuoi che cada a causa dei conflitti di replica, aumenta semplicemente il ritardo. Un report lungo non richiede mai dati che sono appena arrivati nel database. Se è di tre ore, significa che lo esegui su un vecchio periodo di dati. E per te, che sia tre ore di ritardo o sei ore, non farà alcuna differenza, ma in questo modo riceverai report in modo stabile senza problemi di caduta.
- Naturalmente, è necessario monitorare le sessioni prolungate sulle repliche, soprattutto se hai deciso di attivare hot_standby_feedback sulla replica. Perché può succedere di tutto. Hai dato questa replica a uno sviluppatore per testare le query. Ha scritto una query folle. L'ha eseguita e se ne è andato a bere un tè, mentre noi abbiamo ottenuto un master bloccato. Oppure abbiamo fatto girare un'applicazione sbagliata. Le situazioni sono varie. Le sessioni sulle repliche devono essere monitorate con la stessa attenzione di quelle sul master.
- E se hai query rapide e lunghe sulle repliche, in questo caso è meglio suddividerle per distribuire il carico. Questo è un riferimento a streaming_delay. Per le query rapide usa una replica con un piccolo ritardo nella replica. Per le query reportistiche lunghe, utilizza una replica che può avere un ritardo di 6 ore o un giorno. È una situazione del tutto normale.
Eliminiamo le conseguenze nello stesso modo:
- Troviamo le tabelle sovradimensionate.
- E le comprimiamo con lo strumento più adatto a noi.
La seconda storia è finita qui. Passiamo alla terza storia.

Anche questa è abbastanza comune per noi, in cui facciamo una migrazione.

- Qualsiasi prodotto software cresce. Cambiano le richieste verso di esso. Vogliamo sempre progredire. E a volte è necessario aggiornare i dati in una tabella, eseguendo un aggiornamento nell'ambito della nostra migrazione verso una nuova funzionalità che stiamo implementando nel nostro percorso di sviluppo.
- Il vecchio formato dei dati non va bene. Supponiamo che ora ci rivolgiamo alla seconda tabella, dove ho le operazioni su questi conti. E, supponiamo, che fossero in rubli, e abbiamo deciso di aumentare la precisione e operare in kopeck. Per questo dobbiamo eseguire un aggiornamento: moltiplicare il campo con l'importo dell'operazione per cento.
- Nel mondo moderno utilizziamo strumenti automatizzati per il controllo delle versioni delle basi di dati. Supponiamo, . Scriviamo lì la nostra migrazione. La testiamo sulla nostra base dati di prova. Tutto perfetto. L'aggiornamento va a buon fine. Blocca le operazioni per un certo periodo, ma così otteniamo dati aggiornati. E possiamo avviare nuove funzionalità su questo. Abbiamo testato tutto, controllato. Tutto confermato.
- Abbiamo eseguito lavori programmati, abbiamo effettuato la migrazione.

Ecco la migrazione con l'aggiornamento presentata davanti a voi. Poiché queste sono operazioni sui conti, la tabella era di 15 GB. E poiché aggiorniamo ogni riga, con l'aggiornamento abbiamo raddoppiato la dimensione della tabella perché abbiamo riscritto ogni riga.

Durante la migrazione non potevamo fare nulla con questa tabella, poiché tutte le richieste a essa si sono messe in coda e hanno atteso il termine di questo aggiornamento. Ma qui voglio attirare la vostra attenzione sui numeri sull'asse verticale. Cioè, abbiamo un tempo medio di richiesta prima della migrazione di circa 5 millisecondi e un carico sulla CPU, il numero di operazioni bloccanti di lettura dalla memoria del disco inferiore a 7,5.

Abbiamo effettuato la migrazione e abbiamo nuovamente riscontrato problemi.
La migrazione è riuscita, ma:
- La funzionalità precedente è diventata più lenta.
- La tabella è nuovamente cresciuta in dimensioni.
- Il carico sul server è di nuovo aumentato rispetto a prima.
- E, naturalmente, mentre stiamo ancora lavorando su quella funzionalità che funzionava bene, l'abbiamo leggermente migliorata.
E questo è di nuovo bloat che ci complica la vita.

Qui dimostro che la tabella, come nei due casi precedenti, non ha intenzione di tornare alle dimensioni precedenti. Il carico medio sul server sembra essere adeguato.

Se ci rivolgiamo alla tabella dei conti, vediamo che il tempo medio di richiesta è raddoppiato rispetto a questa tabella. Il carico sulla CPU e il numero di righe elaborate in memoria sono saliti oltre 7,5, mentre prima erano inferiori. E nel caso dei processori è raddoppiato, mentre nel caso delle operazioni a blocchi è aumentato di 1,5 volte, cioè abbiamo avuto una degradazione delle prestazioni del server. Di conseguenza, anche le prestazioni della nostra applicazione sono peggiorate. Tuttavia, il numero di chiamate è rimasto più o meno lo stesso.

E qui è fondamentale capire come eseguire correttamente queste migrazioni. E sono necessarie. Eseguiamo queste migrazioni in modo piuttosto costante.
- Tali grandi migrazioni non vengono effettuate automaticamente. Devono sempre essere controllate.
- È necessaria la supervisione da parte di una persona esperta. Se hai un DBA nel team, dovrebbe occuparsene lui. Questo è il suo compito. Se no, la persona più esperta dovrebbe farlo, quella che sa come lavorare con i database.
- Uno schema di database nuovo, anche nel caso in cui aggiorniamo una sola colonna, deve sempre essere preparato a tappe, cioè in anticipo rispetto al rilascio di una nuova versione dell'applicazione:
- Vengono aggiunti nuovi campi in cui scriveremo i dati aggiornati.
- Trasferiamo i dati dal campo vecchio al campo nuovo in piccole porzioni. Perché facciamo questo? Innanzitutto, monitoriamo sempre il processo. Sappiamo che abbiamo già trasferito un certo numero di batch e ci rimane ancora questa quantità.
- Un secondo effetto positivo è che tra ogni batch chiudiamo la transazione, ne apriamo una nuova e questo consente al vacuum automatico di lavorare sulla tabella, segnando le righe inutilizzate per il riutilizzo.
- Per le righe che appariranno durante il funzionamento dell'applicazione (abbiamo ancora l'applicazione vecchia funzionante) aggiungiamo un trigger che scrive i nuovi valori nei nuovi campi. Nel nostro caso, è il valore vecchio moltiplicato per cento.
- Se siamo testardi e vogliamo usare lo stesso campo, allora alla conclusione di tutte le migrazioni e prima del rilascio della nuova versione dell'applicazione, semplicemente rinominiamo i campi. I vecchi con un nome inventato e i nuovi campi vengono rinominati nei vecchi.
- E solo dopo lanciamo la nuova versione dell'applicazione.
E in questo modo non avremo bloat e non avremo un calo delle prestazioni.
Questa è la fine della terza storia.

E ora parlerò un po' più dettagliatamente degli strumenti che ho menzionato nella prima storia.
Prima di cercare il bloat, è necessario installare l'estensione. .
Per non dover inventare le query, noi nel nostro lavoro abbiamo già scritto queste query. Puoi usarle. Qui sono presentate due query.
- La prima funziona piuttosto a lungo, ma ti mostrerà valori esatti del bloat per la tabella.
- La seconda funziona più velocemente ed è molto efficace quando è necessario valutare rapidamente se c'è o meno bloat nella tabella. E devi anche capire che il bloat nelle tabelle Postgres è sempre presente. Questa è una caratteristica del suo modello MVCC.
- E il 20% di bloat è normale per le tabelle nella maggior parte dei casi. Cioè, non dovresti preoccuparti e comprimere questa tabella.
Abbiamo capito come identificare le tabelle che si sono gonfiate, e anche quando si sono gonfiate a causa di dati inutili.
Ora parliamo di come correggere il bloat:
- Se abbiamo una piccola tabella e dischi buoni, cioè se la tabella è di dimensioni inferiori a un gigabyte, è assolutamente possibile utilizzare VACUUM FULL. Ti prenderà un blocco esclusivo sulla tabella per alcuni secondi e poi sarà tutto a posto, ma farà tutto velocemente e in modo rigoroso. Cosa fa VACUUM FULL? Prende un blocco esclusivo sulla tabella e riscrive le righe vive da tabelle più vecchie in una nuova tabella. Alla fine sostituisce i dati. Elimina i file vecchi, sostituisce con quelli nuovi. Ma durante il suo funzionamento prende un blocco esclusivo sulla tabella. Questo significa che non potrai fare nulla con questa tabella: né scrivere, né leggere, né modificarla. E VACUUM FULL richiede spazio aggiuntivo su disco per scrivere i dati.
- Lo strumento successivo . Per principio è molto simile a VACUUM FULL, perché anche lui riscrive i dati dai file più vecchi in quelli nuovi e li sostituisce nella tabella. Tuttavia, non prende un blocco esclusivo sulla tabella all'inizio del suo funzionamento, ma lo prende solo nel momento in cui ha dati pronti per sostituire i file. I requisiti per le risorse di disco sono simili a quelli di VACUUM FULL. Hai bisogno di spazio aggiuntivo su disco, e questo può essere critico se hai tabelle da un terabyte. Inoltre, è piuttosto affamato di CPU, poiché effettua operazioni attive di input-output.
- Il terzo strumento è . È più attenta alle risorse, poiché opera su principi leggermente diversi. La sostanza principale di pgcompacttable è che durante gli aggiornamenti sposta tutte le righe attive all'inizio della tabella. E poi avvia un'operazione di vacuum su questa tabella, perché sappiamo che all'inizio abbiamo righe attive e alla fine righe non più attive. Inoltre, il vacuum stesso taglia questa parte finale, cioè non richiede molto spazio su disco. E, in aggiunta, è possibile ottimizzarlo ulteriormente per le risorse.
Con gli strumenti è tutto.

Se vi interessa approfondire il tema del bloat, ecco alcuni link utili:
- – è una relazione del mio collega. È generale e parla di dove va a finire lo spazio in Postgres durante il suo funzionamento e la sua vita. Contiene anche una parte tecnica molto ampia e dettagliata per gli amministratori di database sul bloat.
- – è un link al nostro repository, dove conserviamo una serie di script utili per controllare lo stato del database. Qui puoi trovare script per la ricerca di bloat.
- e link agli strumenti che ti aiuteranno a ottimizzare le tabelle.
- – è un post del mio collega. Qui analizza in modo piuttosto serio e dettagliato il bloat a un livello vicino a quello degli amministratori.
Ho cercato di presentare una sorta di allerta per i programmatori, poiché sono i nostri clienti diretti e devono capire le conseguenze delle proprie azioni. Spero di esserci riuscito. Grazie per l'attenzione!
Domande
Grazie per la relazione! Hai parlato di come identificare i problemi. Come si possono prevenire? Cioè, ho avuto una situazione in cui le query erano bloccate non solo a causa di chiamate a servizi esterni. C'erano anche join piuttosto complessi. C'erano piccole query innocue che rimanevano bloccate per un giorno e poi iniziavano a fare disastri. Cioè, sembra molto simile a ciò che hai descritto. Come si può monitorare ciò? Devo stare seduto e controllare costantemente quale query è bloccata? Come posso prevenirlo?
In questo caso, è un compito per gli amministratori della tua azienda, non necessariamente per il DBA.
Io sono un amministratore.
In PostgreSQL esiste una vista chiamata pg_stat_activity, che mostra le query bloccate. Puoi vedere da quanto tempo sono bloccate.
Devo entrare ogni 5 minuti per controllare?
Imposta cron e controlla. Se hai una richiesta lunga, invia una email e basta. Cioè, non devi controllare a occhio, puoi automatizzarlo. Riceverai un’email e reagirai di conseguenza. Oppure puoi automatizzare il processo.
Ci sono motivi evidenti per cui questo sta accadendo?
Ne ho elencati alcuni. Altri sono esempi più complessi. E lì la conversazione potrebbe durare a lungo.
Grazie per la presentazione! Volevo chiarire riguardo all'utility pg_repack. Se non fa un blocco esclusivo, allora…
Fa un blocco esclusivo.
… quindi potenzialmente posso perdere dati. La mia applicazione non dovrebbe scrivere nulla in quel momento?
No, funziona tranquillamente con la tabella, cioè pg_repack prima sposta tutte le righe attive. Naturalmente, si verifica una certa scrittura nella tabella. Aggiunge semplicemente questo residuo.
Cioè, alla fine lo fa comunque?
Alla fine prende un blocco esclusivo per sostituire questi file.
Sarà più veloce di VACUUM FULL?
VACUUM FULL, appena avviato, prende subito un blocco esclusivo. E fino a quando non ha terminato, non lo rilascia. Pg_repack prende il blocco esclusivo solo al momento della sostituzione dei file. In quel momento non puoi scrivere, ma i dati non andranno persi, tutto sarà a posto.
Salve! Hai parlato del funzionamento dell'autovacuum. C'era un grafico con celle rosse, gialle e verdi. Cioè, le gialle sono state contrassegnate come eliminate. E di conseguenza, è possibile scrivere qualcosa di nuovo in esse?
Sì. Postgres non elimina le righe. Ha questa specificità. Se aggiorniamo una riga, contrassegniamo quella vecchia come eliminata. Entra l'id della transazione che ha modificato quella riga e scriviamo la nuova riga. Abbiamo sessioni che possono leggerle. A un certo punto, diventano già molto vecchie. E il lavoro dell'autovacuum è quello di scorrere queste righe e contrassegnarle come non necessarie. E puoi riscrivere i dati lì.
Ho capito. Ma la domanda è un po' diversa. Non ho finito. Supponiamo di avere una tabella. Ha campi di dimensione variabile. E se provo a inserire qualcosa di nuovo, potrebbe semplicemente non entrarci nella vecchia cella.
No, in ogni caso l'intera riga viene aggiornata. In Postgres ci sono due modelli di memorizzazione dei dati. Viene scelto in base al tipo di dato. Ci sono dati che vengono memorizzati direttamente nella tabella e ci sono anche dati tos. Si tratta di grandi volumi di dati: testo, json. Questi vengono memorizzati in tabelle separate. E su queste tabelle si verifica la stessa storia con il bloat, cioè esattamente la stessa cosa. Sono semplicemente separati.
Grazie per la presentazione! Quanto è accettabile usare il timeout di dichiarazione per limitare la durata delle richieste?
Molto accettabile. Lo usiamo ovunque. E poiché non abbiamo i nostri servizi, forniamo supporto remoto, abbiamo clienti piuttosto diversi. E tutti sono abbastanza soddisfatti. Cioè, abbiamo lavori in cron che effettuano controlli. Semplicemente viene concordata con il cliente la durata delle sessioni, prima della quale non intervieniamo. Può essere un minuto, può essere 10 minuti. Dipende dal carico sulla base e dal suo obiettivo. Ma per tutti utilizziamo pg_stat_activity.
Grazie per la presentazione! Sto cercando di adattare la vostra presentazione alle mie applicazioni. E sembra che noi iniziamo sempre una transazione, la concludiamo esplicitamente ovunque. Se c'è qualche eccezione, comunque si verifica un rollback. E qui ho iniziato a riflettere. Infatti, una transazione potrebbe avviarsi non esplicitamente. Questo è un suggerimento per la ragazza, probabilmente. Se faccio semplicemente un aggiornamento di un record, la transazione si avvierà in PostgreSQL e si concluderà solo quando avviene la disconnessione della connessione?
Se parliamo ora del livello dell'applicazione, dipende dal driver che stai usando, dall'ORM che viene utilizzato. Ci sono molte impostazioni. Se hai attivato l'auto commit, allora la transazione si avvia e si chiude immediatamente.
Cioè, si chiude immediatamente dopo l'aggiornamento?
Dipende dalle impostazioni. Ho menzionato una impostazione. È l'auto commit attivato. È abbastanza comune. Se è attivato, la transazione si apre e si chiude. Se non hai detto esplicitamente "start transaction" e "end transaction", ma hai semplicemente lanciato la richiesta nella sessione.
Buongiorno! Grazie per la presentazione! Immagina di avere un database che cresce e cresce e qui sul server finisce lo spazio. Ci sono strumenti per risolvere questa situazione?
Lo spazio sul server dovrebbe essere monitorato adeguatamente.
Ad esempio, il DBA è andato a bere un caffè, era in vacanza, ecc.
Quando viene creata una filesystem, viene riservato almeno uno spazio, dove non vengono scritti dati.
E se completamente a zero?
Si chiama proprio reserved space, cioè può essere liberato e, a seconda di quanto grande è stato creato, hai uno spazio libero. Di default non so quanto ci sia. In un altro caso, bisogna inviare dischi per avere spazio per effettuare l'operazione di ripristino. Puoi eliminare una tabella che sai per certo non ti serve.
Non ci sono altri strumenti?
È sempre un lavoro manuale. E sul posto si determina meglio cosa fare, poiché ci sono dati critici e non critici. E per ogni database e applicazione che ci lavora, dipende dal business. Si decide sempre sul posto.
Grazie per la presentazione! Ho due domande. In primo luogo, hai mostrato diapositive in cui si evidenziava che in caso di transazioni bloccate, sia il volume dello spazio tabellare che le dimensioni degli indici aumentano. E poi nella presentazione c'erano molte utility che compattano la tabella. E per l'indice?
Anche loro la compattano.
Ma il vacuum non tocca l'indice?
Alcuni lavorano con l'indice. Ad esempio, pg_rapack, pgcompacttable. Il vacuum ricrea gli indici, li tocca. Con VACUUM FULL, l'essenza è che si riscrive tutto, cioè lavora con tutti.
E la seconda domanda. Non ho capito perché i report sulle repliche dipendono così tanto dalla replica stessa. Mi sembrava che i report fossero lettura e la replica fosse scrittura.
Qual è il conflitto nella replica? Abbiamo un Master, dove avvengono i processi. Abbiamo un autovacuum. Cosa fa di fatto l'autovacuum? Rimuove alcune righe vecchie. Se in quel momento nella replica c'è una richiesta che legge queste righe vecchie, e nel Master succede una situazione in cui l'autovacuum ha contrassegnato queste righe come possibili da sovrascrivere, allora le sovrascriveremo. E ci è arrivato un pacchetto di dati in cui dobbiamo sovrascrivere quelle righe necessarie per la richiesta nella replica, quindi il processo di replica aspetterà il timeout che hai impostato. E poi PostgreSQL deciderà cosa è più importante per lui. E la replica è più importante della richiesta, quindi annullerà la richiesta per eseguire queste modifiche nella replica.
Andrej, ho una domanda. Queste meravigliose grafico che hai mostrato durante la presentazione, sono il risultato di qualche tuo strumento? Come sono stati creati i grafici?
È un servizio .
È un prodotto commerciale?
Sì. È un prodotto commerciale.
Fonte: habr.com
