PostgreSQL non utilizza il meccanismo di aggiornamento IN-PLACE, quindi secondo il modo in cui sono progettati i comandi DELETE e UPDATE,
- Ogni volta che vengono eseguite operazioni DELETE, contrassegna la tupla esistente come DEAD invece di rimuovere fisicamente quelle tuple.
- Allo stesso modo, ogni volta che viene eseguita l'operazione UPDATE, contrassegna la tupla esistente corrispondente come DEAD e inserisce una nuova tupla (es. operazione UPDATE =DELETE + INSERT).
Quindi ogni comando DELETE e UPDATE risulterà in una tupla DEAD, che non verrà mai utilizzata (a meno che non ci siano transazioni parallele). Queste tuple morte porteranno a un utilizzo di spazio aggiuntivo non necessario anche se il numero di record effettivi è uguale o inferiore. Questo è anche chiamato rigonfiamento dello spazio in PostgreSQL. Poiché PostgreSQL è ampiamente utilizzato come sistema di database relazionale di tipo OLTP, dove vengono eseguite frequenti operazioni INSERT, UPDATE e DELETE, ci saranno molte tuple DEAD e quindi le conseguenze corrispondenti. Quindi PostgreSQL richiedeva un forte meccanismo di manutenzione per gestire queste tuple DEAD. VACUUM è il processo di manutenzione che si occupa di gestire la tupla DEAD insieme ad alcune attività in più utili per ottimizzare il funzionamento di VACUUM. Comprendiamo un po' di terminologia da usare più avanti in questo blog.
Mappa della visibilità
Come suggerisce il nome, mantiene le informazioni di visibilità sulle pagine contenenti solo tuple che sono note per essere visibili a tutte le transazioni attive. Per ogni pagina viene utilizzato un bit. Se il bit è impostato a 1 significa che tutte le tuple della pagina corrispondente sono visibili. Il bit impostato su 0 significa che non c'è spazio libero sulla pagina data e le tuple possono essere visibili a tutte le transazioni.
La mappa di visibilità viene mantenuta per ogni relazione (tabella e indice) e viene associata alle relazioni principali, ovvero se il nome del nodo del file di relazione è 12345, il file di visibilità viene archiviato nel file parallelo 12345_vm.
Mappa dello spazio libero
Mantiene informazioni sullo spazio libero contenenti dettagli sullo spazio disponibile nella relazione. Questo è anche memorizzato nel file parallelo al file principale della relazione, ad esempio se il nome del nodo del file della relazione è 12345, il file della mappa dello spazio libero viene archiviato nel file parallelo 12345_fsm.
Blocca tupla
PostgreSQL utilizza 4 byte per memorizzare l'ID transazione, il che significa che è possibile generare un massimo di 2 miliardi di transazioni prima che venga eseguito il wrapping. Ora considera ancora in questo momento qualche tupla contiene l'id della transazione iniziale, diciamo 100, quindi per la nuova transazione (che utilizza la transazione avvolta) diciamo 5, l'id della transazione 100 guarderà al futuro e non sarà in grado di vedere i dati aggiunti /modificato da esso anche se in realtà era in passato. Per evitare questa transazione speciale, viene assegnato l'id FrozenTransactionId (uguale a 2). Questo ID transazione speciale è sempre considerato nel passato e sarà visibile a tutte le transazioni.
VUOTO
Il compito principale di VACUUM è recuperare lo spazio di archiviazione occupato dalle tuple DEAD. Lo spazio di archiviazione recuperato non viene restituito al sistema operativo, ma viene semplicemente deframmentato all'interno della stessa pagina, quindi sono semplicemente disponibili per essere riutilizzati dal futuro inserimento di dati all'interno della stessa tabella. Mentre l'operazione VACUUM viene eseguita su una tabella particolare, è possibile eseguire contemporaneamente altre operazioni di LETTURA/SCRITTURA sulla stessa tabella poiché il blocco esclusivo non viene eseguito sulla tabella specifica. Nel caso in cui non venga specificato il nome di una tabella, VACUUM verrà eseguito su tutte le tabelle del database. L'operazione VACUUM esegue di seguito una serie di operazioni all'interno di un blocco ShareUpdateExclusive:
- Scansiona tutte le pagine di tutte le tabelle (o tabella specificata) del database per ottenere tutte le tuple morte.
- Blocca le vecchie tuple se necessario.
- Rimuovi la tupla dell'indice che punta alle rispettive tuple DEAD.
- Rimuovi le tuple DEAD di una pagina corrispondente a una tabella specifica e riassegna le tuple live nella pagina.
- Aggiorna la mappa dello spazio libero (FSM) e la mappa della visibilità (VM).
- Tronca l'ultima pagina se possibile (se c'erano tuple DEAD che sono state liberate).
- Aggiorna tutte le tabelle di sistema corrispondenti.
Come possiamo vedere dai precedenti passaggi di lavoro per VACUUM, è chiaro che si tratta di un'operazione molto costosa in quanto deve elaborare tutte le pagine della relazione. Quindi è assolutamente necessario saltare le possibili pagine che non richiedono l'aspirazione. Poiché la mappa di visibilità (VM) fornisce informazioni sulla pagina in cui se non c'è spazio libero, si può presumere che non sia necessario il vuoto della pagina corrispondente e quindi questa pagina può essere tranquillamente saltata.
Dato che VACUUM attraversa comunque tutte le pagine e tutte le loro tuple, quindi coglie l'occasione per svolgere un altro importante compito di congelamento delle tuple di qualificazione.
VUOTO completo
Come discusso nella sezione precedente, anche se VACUUM rimuove tutte le tuple DEAD e deframmenta la pagina per un uso futuro, non aiuta a ridurre la memoria complessiva della tabella poiché lo spazio in realtà non viene rilasciato al sistema operativo. Supponiamo in una tabella tbl1 che lo spazio di archiviazione totale abbia raggiunto 1,5 GB e di questo 1 GB occupato dalla tupla morta, dopo VACUUM sarà disponibile un altro GB circa per l'ulteriore inserimento della tupla, ma lo spazio di archiviazione totale rimarrà pari a 1,5 GB.
Full VACUUM risolve questo problema liberando effettivamente spazio e riportandolo al sistema operativo. Ma questo ha un costo. A differenza di VACUUM, FULL VACUUM non consente il funzionamento in parallelo in quanto richiede un blocco esclusivo sulla relazione che viene sottoposta a FULL VACUUM. Di seguito sono riportati i passaggi:
- Prende il blocco esclusivo sulla relazione.
- Crea un file di archiviazione vuoto parallelo.
- Copia tutte le tuple live dallo spazio di archiviazione corrente allo spazio di archiviazione appena allocato.
- Quindi libera lo spazio di archiviazione originale.
- Libera il lucchetto.
Quindi, come è chiaro anche dai passaggi, avrà spazio di archiviazione richiesto solo per i dati rimanenti.
ASPIRAZIONE AUTOMATICA
Invece di eseguire VACUUM manualmente, PostgreSQL supporta un demone che attiva periodicamente VACUUM automaticamente. Ogni volta che VACUUM si riattiva (per impostazione predefinita 1 minuto) richiama più lavori (a seconda della configurazione dei processi autovacuum_worker).
Gli operatori di aspirapolvere automatico eseguono processi VACUUM contemporaneamente per le rispettive tabelle designate. Poiché VACUUM non ha alcun blocco esclusivo sulle tabelle, non ha (o minimo) un impatto sull'altro lavoro del database.
La configurazione di Auto-VACUUM dovrebbe essere eseguita in base al modello di utilizzo del database. Non dovrebbe essere troppo frequente (poiché sprecherà il risveglio dei lavoratori poiché potrebbero non esserci o troppo poche tuple morte) o troppo ritardato (causa molte tuple morte insieme e quindi il rigonfiamento della tabella).
VUOTO o VUOTO completo
Idealmente, l'applicazione del database dovrebbe essere progettata in modo che non sia necessario il FULL VACUUM. Come spiegato in precedenza, FULL VACUUM ricrea lo spazio di archiviazione e ripristina i dati, quindi se ci sono solo meno tuple morte, lo spazio di archiviazione verrà ricreato immediatamente per ripristinare tutti i dati originali. Inoltre, poiché FULL VACUUM ha un blocco esclusivo sulla tabella, blocca tutte le operazioni sulla tabella corrispondente. Quindi fare FULL VACUUM a volte può rallentare il database generale.
In sintesi, il VUOTO completo dovrebbe essere evitato a meno che non sia noto che la maggior parte dello spazio di archiviazione è dovuto a tuple morte. L'estensione PostgreSQL pg_freespacemap può essere usata per avere un buon suggerimento sullo spazio libero.
Vediamo un esempio del processo VACUUM spiegato.
Per prima cosa, creiamo una tabella demo1:
postgres=# create table demo1(id int, id2 int);
CREATE TABLE
E inserisci alcuni dati lì:
postgres=# insert into demo1 values(generate_series(1,10000), generate_series(1,
10000));
INSERT 0 10000
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
npages | average_freespace_ratio
--------+-------------------------
45 | 0.00
(1 row)
Ora, cancelliamo i dati:
postgres=# delete from demo1 where id%2=0;
DELETE 5000
E esegui un aspirapolvere manuale:
postgres=# vacuum demo1;
VACUUM
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
npages | average_freespace_ratio
--------+-------------------------
45 | 45.07
(1 row)
Questo spazio libero è ora disponibile per essere riutilizzato da PostgreSQL, ma se vuoi rilasciare quello spazio al sistema operativo, esegui:
postgres=# vacuum full demo1;
VACUUM
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
npages | average_freespace_ratio
--------+-------------------------
23 | 0.00
(1 row)
Conclusione
E questo è stato un breve esempio di come funziona il processo VACUUM. Fortunatamente, grazie al processo di aspirazione automatica, il più delle volte e in un comune ambiente PostgreSQL, non è necessario pensarci perché è gestito dal motore stesso.