Il modo più intuitivo di aggiornamento del database che si possa pensare è generare una replica in una nuova versione ed eseguire un failover dell'applicazione al suo interno, e in realtà funziona perfettamente in altri motori. Con PostgreSQL, questo era impossibile in modo nativo. Per eseguire gli aggiornamenti è necessario pensare ad altri modi di aggiornare, come l'utilizzo di pg_upgrade, il dumping e il ripristino, o l'utilizzo di alcuni strumenti di terze parti come Slony o Bucardo, tutti con i propri avvertimenti. Ciò è dovuto al modo in cui PostgreSQL ha utilizzato per implementare la replica.
La replica in streaming PostgreSQL (la comune replica PostgreSQL) è una replica fisica che replica le modifiche a livello di byte per byte, creando una copia identica del database in un altro server. Questo metodo ha molte limitazioni quando si pensa a un aggiornamento, poiché semplicemente non è possibile creare una replica in una versione del server diversa o anche in un'architettura diversa.
Da PostgreSQL 10, ha implementato la replica logica incorporata che, a differenza della replica fisica, è possibile replicare tra diverse versioni principali di PostgreSQL. Questo, ovviamente, apre una nuova porta per l'aggiornamento delle strategie.
In questo blog, vedremo come aggiornare PostgreSQL 11 a PostgreSQL 12 senza tempi di inattività utilizzando la replica logica.
Replica logica PostgreSQL
La replica logica è un metodo per replicare gli oggetti dati e le relative modifiche, in base alla loro identità di replica (di solito una chiave primaria). Si basa su una modalità di pubblicazione e sottoscrizione, in cui uno o più abbonati si iscrivono a una o più pubblicazioni su un nodo editore.
Una pubblicazione è un insieme di modifiche generate da una tabella o da un gruppo di tabelle (denominato anche insieme di replica). Il nodo in cui è definita una pubblicazione è denominato editore. Una sottoscrizione è il lato a valle della replica logica. Il nodo in cui è definita una sottoscrizione è denominato abbonato e definisce la connessione a un altro database e insieme di pubblicazioni (una o più) a cui desidera iscriversi. Gli abbonati estraggono i dati dalle pubblicazioni a cui si iscrivono.
La replica logica è costruita con un'architettura simile alla replica fisica dello streaming. È implementato dai processi "walsender" e "applica". Il processo walsender avvia la decodifica logica del WAL e carica il plug-in di decodifica logica standard. Il plugin trasforma le modifiche lette da WAL nel protocollo di replica logica e filtra i dati in base alle specifiche di pubblicazione. I dati vengono quindi trasferiti continuamente utilizzando il protocollo di replica in streaming all'application worker, che mappa i dati su tabelle locali e applica le singole modifiche man mano che vengono ricevute, in un corretto ordine transazionale.
La replica logica inizia acquisendo uno snapshot dei dati nel database dell'editore e copiandolo all'abbonato. I dati iniziali nelle tabelle sottoscritte esistenti vengono catturati e copiati in un'istanza parallela di un tipo speciale di processo di applicazione. Questo processo creerà il proprio slot di replica temporaneo e copierà i dati esistenti. Una volta copiati i dati esistenti, il lavoratore entra in modalità di sincronizzazione, che assicura che la tabella venga portata a uno stato sincronizzato con il processo di applicazione principale trasmettendo in streaming tutte le modifiche avvenute durante la copia dei dati iniziale utilizzando la replica logica standard. Una volta eseguita la sincronizzazione, il controllo della replica della tabella viene restituito al processo di applicazione principale in cui la replica continua normalmente. Le modifiche sull'editore vengono inviate all'abbonato man mano che si verificano in tempo reale.
Come aggiornare PostgreSQL 11 a PostgreSQL 12 usando la replica logica
Configureremo la replica logica tra due diverse versioni principali di PostgreSQL (11 e 12) e, naturalmente, dopo aver eseguito questa operazione, è solo questione di eseguire un failover dell'applicazione nel database con la versione più recente.
Eseguiremo i seguenti passaggi per far funzionare la replica logica:
- Configura il nodo editore
- Configura il nodo abbonato
- Crea l'utente abbonato
- Crea una pubblicazione
- Crea la struttura della tabella nel sottoscrittore
- Crea l'abbonamento
- Verifica lo stato della replica
Allora iniziamo.
Dal lato editore, configureremo i seguenti parametri nel file postgresql.conf:
- ascolta_indirizzi: Su quali indirizzi IP ascoltare. Useremo '*' per tutti.
- wal_level: Determina quante informazioni vengono scritte nel WAL. Lo imposteremo su "logico".
- max_replication_slots :specifica il numero massimo di slot di replica che il server può supportare. Deve essere impostato almeno sul numero di abbonamenti previsti per la connessione, più una riserva per la sincronizzazione delle tabelle.
- max_wal_senders: Specifica il numero massimo di connessioni simultanee da server in standby o client di backup di base in streaming. Dovrebbe essere almeno uguale a max_replication_slots più il numero di repliche fisiche connesse contemporaneamente.
Tieni presente che alcuni di questi parametri richiedono il riavvio del servizio PostgreSQL per essere applicati.
Anche il file pg_hba.conf deve essere modificato per consentire la replica. Devi consentire all'utente di replica di connettersi al database.
Quindi, sulla base di questo, configuriamo il publisher (in questo caso il server PostgreSQL 11) come segue:
postgresql.conf:
listen_addresses = '*'
wal_level = logical
max_wal_senders = 8
max_replication_slots = 4
pg_hba.conf:
# TYPE DATABASE USER ADDRESS METHOD
host all rep1 10.10.10.131/32 md5
Devi cambiare l'utente (in questo esempio rep1), che verrà utilizzato per la replica, e l'indirizzo IP 10.10.10.131/32 per l'IP che corrisponde al tuo nodo PostgreSQL 12.
Sul lato abbonato, richiede anche l'impostazione dei max_replication_slots. In questo caso, dovrebbe essere impostato almeno sul numero di abbonamenti che verranno aggiunti all'abbonato.
Anche gli altri parametri che devono essere impostati qui sono:
- max_logical_replication_workers :specifica il numero massimo di lavoratori di replica logica. Ciò include sia i ruoli di lavoro applicati che quelli di sincronizzazione delle tabelle. I lavoratori di replica logica vengono presi dal pool definito da max_worker_processes. Deve essere impostato almeno al numero di iscrizioni, sempre più una riserva per la sincronizzazione delle tabelle.
- max_worker_processes :Imposta il numero massimo di processi in background che il sistema può supportare. Potrebbe essere necessario adattarlo per adattarsi ai lavoratori di replica, almeno max_logical_replication_workers + 1. Questo parametro richiede un riavvio di PostgreSQL.
Quindi, devi configurare l'abbonato (in questo caso il server PostgreSQL 12) come segue:
postgresql.conf:
listen_addresses = '*'
max_replication_slots = 4
max_logical_replication_workers = 4
max_worker_processes = 8
Poiché questo PostgreSQL 12 sarà presto il nuovo nodo primario, dovresti considerare di aggiungere i parametri wal_level e archive_mode in questo passaggio, per evitare un nuovo riavvio del servizio in seguito.
wal_level = logical
archive_mode = on
Questi parametri saranno utili se desideri aggiungere una nuova replica o per utilizzare i backup PITR.
Nel publisher, devi creare l'utente con cui si collegherà l'abbonato:
world=# CREATE ROLE rep1 WITH LOGIN PASSWORD '*****' REPLICATION;
CREATE ROLE
Il ruolo utilizzato per la connessione di replica deve avere l'attributo REPLICATION. L'accesso per il ruolo deve essere configurato in pg_hba.conf e deve avere l'attributo LOGIN.
Per poter copiare i dati iniziali, il ruolo utilizzato per la connessione di replica deve disporre del privilegio SELECT su una tabella pubblicata.
world=# GRANT SELECT ON ALL TABLES IN SCHEMA public to rep1;
GRANT
Creeremo la pubblicazione pub1 nel nodo editore, per tutte le tabelle:
world=# CREATE PUBLICATION pub1 FOR ALL TABLES;
CREATE PUBLICATION
L'utente che creerà una pubblicazione deve disporre del privilegio CREATE nel database, ma per creare una pubblicazione che pubblichi tutte le tabelle automaticamente, l'utente deve essere un superutente.
Per confermare la pubblicazione creata utilizzeremo il catalogo pg_publication. Questo catalogo contiene informazioni su tutte le pubblicazioni create nel database.
world=# SELECT * FROM pg_publication;
-[ RECORD 1 ]+-----
pubname | pub1
pubowner | 10
puballtables | t
pubinsert | t
pubupdate | t
pubdelete | t
pubtruncate | t
Descrizioni delle colonne:
- pubname :Nome della pubblicazione.
- proprietario del pub :Titolare della pubblicazione.
- Puballtables :se true, questa pubblicazione include automaticamente tutte le tabelle nel database, comprese quelle che verranno create in futuro.
- pubinsert :se true, le operazioni INSERT vengono replicate per le tabelle nella pubblicazione.
- aggiornamento della pubblicazione :se true, le operazioni UPDATE vengono replicate per le tabelle nella pubblicazione.
- pubdelete :se true, le operazioni DELETE vengono replicate per le tabelle nella pubblicazione.
- pubblica :se true, le operazioni TRUNCATE vengono replicate per le tabelle nella pubblicazione.
Poiché lo schema non viene replicato, è necessario eseguire un backup in PostgreSQL 11 e ripristinarlo in PostgreSQL 12. Il backup verrà eseguito solo per lo schema, poiché le informazioni verranno replicate nell'iniziale trasferimento.
In PostgreSQL 11:
$ pg_dumpall -s > schema.sql
In PostgreSQL 12:
$ psql -d postgres -f schema.sql
Una volta che hai il tuo schema in PostgreSQL 12, devi creare la sottoscrizione, sostituendo i valori di host, dbname, user e password con quelli che corrispondono al tuo ambiente.
PostgreSQL 12:
world=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=10.10.10.130 dbname=world user=rep1 password=*****' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
Ciò sopra avvierà il processo di replica, che sincronizza il contenuto della tabella iniziale delle tabelle nella pubblicazione e quindi inizia a replicare le modifiche incrementali a quelle tabelle.
L'utente che crea un abbonamento deve essere un superutente. Il processo di richiesta dell'abbonamento verrà eseguito nel database locale con i privilegi di un superutente.
Per verificare l'abbonamento creato puoi utilizzare il catalogo pg_stat_subscription. Questa visualizzazione conterrà una riga per sottoscrizione per il lavoratore principale (con PID nullo se il lavoratore non è in esecuzione) e righe aggiuntive per i lavoratori che gestiscono la copia dei dati iniziale delle tabelle sottoscritte.
world=# SELECT * FROM pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid | 16422
subname | sub1
pid | 476
relid |
received_lsn | 0/1771668
last_msg_send_time | 2020-09-29 17:40:34.711411+00
last_msg_receipt_time | 2020-09-29 17:40:34.711533+00
latest_end_lsn | 0/1771668
latest_end_time | 2020-09-29 17:40:34.711411+00
Descrizioni delle colonne:
- sottoscrivi :OID dell'abbonamento.
- sottonome :Nome dell'abbonamento.
- pid :ID processo del processo di lavoro in abbonamento.
- relida :OID della relazione che il lavoratore sta sincronizzando; nullo per il lavoratore principale candidato.
- received_lsn :ultima posizione del registro write-ahead ricevuta, il valore iniziale di questo campo è 0.
- last_msg_send_time :Invia l'ora dell'ultimo messaggio ricevuto dal mittente WAL di origine.
- last_msg_receipt_time :Ora di ricezione dell'ultimo messaggio ricevuto dal mittente WAL di origine.
- latest_end_lsn :ultima posizione del registro write-ahead segnalata al mittente WAL di origine.
- latest_end_time :ora dell'ultima posizione del registro write-ahead segnalata al mittente WAL di origine.
Per verificare lo stato della replica nel nodo primario puoi usare pg_stat_replication:
world=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 527
usesysid | 16428
usename | rep1
application_name | sub1
client_addr | 10.10.10.131
client_hostname |
client_port | 35570
backend_start | 2020-09-29 17:40:04.404905+00
backend_xmin |
state | streaming
sent_lsn | 0/1771668
write_lsn | 0/1771668
flush_lsn | 0/1771668
replay_lsn | 0/1771668
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
Descrizioni delle colonne:
- pid :ID processo di un processo mittente WAL.
- usesysid :OID dell'utente connesso a questo processo mittente WAL.
- nome d'uso :nome dell'utente connesso a questo processo mittente WAL.
- nome_applicazione :nome dell'applicazione collegata a questo mittente WAL.
- indirizzo_client :indirizzo IP del client connesso a questo mittente WAL. Se questo campo è nullo, indica che il client è connesso tramite un socket Unix sulla macchina server.
- nome_host_client :nome host del client connesso, come riportato da una ricerca DNS inversa di client_addr. Questo campo non sarà nullo solo per le connessioni IP e solo quando log_hostname è abilitato.
- porta_client :numero della porta TCP utilizzata dal client per la comunicazione con questo mittente WAL o -1 se viene utilizzato un socket Unix.
- backend_start :ora in cui è stato avviato questo processo.
- backend_xmin :l'orizzonte xmin di questo standby riportato da hot_standby_feedback.
- stato :stato del mittente WAL corrente. I valori possibili sono:avvio, recupero, streaming, backup e arresto.
- sent_lsn :Ultima posizione del registro write-ahead inviata su questa connessione.
- write_lsn :Ultima posizione del registro write-ahead scritta su disco da questo server di standby.
- flush_lsn :ultima posizione del registro write-ahead scaricata su disco da questo server di standby.
- replay_lsn :ultima posizione del registro write-ahead riprodotta nel database su questo server di standby.
- write_lag :tempo trascorso tra lo svuotamento locale del WAL recente e la ricezione della notifica che questo server di standby lo ha scritto (ma non lo ha ancora scaricato o applicato).
- flush_lag :tempo trascorso tra lo svuotamento locale del WAL recente e la ricezione della notifica che questo server di standby lo ha scritto e scaricato (ma non ancora applicato).
- replay_lag :tempo trascorso tra lo svuotamento locale del WAL recente e la ricezione della notifica che questo server di standby lo ha scritto, scaricato e applicato.
- sync_priority :Priorità di questo server di standby per essere scelto come standby sincrono in una replica sincrona basata su priorità.
- sync_state :stato sincrono di questo server in standby. I valori possibili sono async, potenziale, sincronizzazione, quorum.
Per verificare quando il trasferimento iniziale è terminato puoi controllare il log di PostgreSQL sull'abbonato:
2020-09-29 17:40:04.403 UTC [476] LOG: logical replication apply worker for subscription "sub1" has started
2020-09-29 17:40:04.411 UTC [477] LOG: logical replication table synchronization worker for subscription "sub1", table "city" has started
2020-09-29 17:40:04.422 UTC [478] LOG: logical replication table synchronization worker for subscription "sub1", table "country" has started
2020-09-29 17:40:04.516 UTC [477] LOG: logical replication table synchronization worker for subscription "sub1", table "city" has finished
2020-09-29 17:40:04.522 UTC [479] LOG: logical replication table synchronization worker for subscription "sub1", table "countrylanguage" has started
2020-09-29 17:40:04.570 UTC [478] LOG: logical replication table synchronization worker for subscription "sub1", table "country" has finished
2020-09-29 17:40:04.676 UTC [479] LOG: logical replication table synchronization worker for subscription "sub1", table "countrylanguage" has finished
O controllando la variabile srsubstate sul catalogo pg_subscription_rel. Questo catalogo contiene lo stato di ogni relazione replicata in ogni sottoscrizione.
world=# SELECT * FROM pg_subscription_rel;
srsubid | srrelid | srsubstate | srsublsn
---------+---------+------------+-----------
16422 | 16386 | r | 0/1771630
16422 | 16392 | r | 0/1771630
16422 | 16399 | r | 0/1771668
(3 rows)
Descrizioni delle colonne:
- srsubid :Riferimento all'abbonamento.
- srrelid :Riferimento alla relazione.
- srsubstate :Codice di stato:i =inizializzazione, d =i dati vengono copiati, s =sincronizzato, r =pronto (replica normale).
- srsublsn :Fine LSN per gli stati s e r.
Puoi inserire alcuni record di test in PostgreSQL 11 e convalidare di averli in PostgreSQL 12:
PostgreSQL 11:
world=# INSERT INTO city (id,name,countrycode,district,population) VALUES (5001,'city1','USA','District1',10000);
INSERT 0 1
world=# INSERT INTO city (id,name,countrycode,district,population) VALUES (5002,'city2','ITA','District2',20000);
INSERT 0 1
world=# INSERT INTO city (id,name,countrycode,district,population) VALUES (5003,'city3','CHN','District3',30000);
INSERT 0 1
PostgreSQL 12:
world=# SELECT * FROM city WHERE id>5000;
id | name | countrycode | district | population
------+-------+-------------+-----------+------------
5001 | city1 | USA | District1 | 10000
5002 | city2 | ITA | District2 | 20000
5003 | city3 | CHN | District3 | 30000
(3 rows)
A questo punto, hai tutto pronto per indirizzare la tua applicazione a PostgreSQL 12.
Per questo, prima di tutto, devi confermare di non avere un ritardo di replica.
Sul nodo principale:
world=# SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) lag FROM pg_stat_replication;
-[ RECORD 1 ]----+-----
application_name | sub1
lag | 0
E ora devi solo cambiare il tuo endpoint dalla tua applicazione o dal sistema di bilanciamento del carico (se ne hai uno) al nuovo server PostgreSQL 12.
Se hai un sistema di bilanciamento del carico come HAProxy, puoi configurarlo utilizzando PostgreSQL 11 come attivo e PostgreSQL 12 come backup, in questo modo:
Quindi, se hai appena chiuso il vecchio nodo primario in PostgreSQL 11, il server di backup, in questo caso in PostgreSQL 12, inizia a ricevere il traffico in modo trasparente per l'utente/applicazione.
Al termine della migrazione, puoi eliminare l'abbonamento nel tuo nuovo nodo primario in PostgreSQL 12:
world=# DROP SUBSCRIPTION sub1;
NOTICE: dropped replication slot "sub1" on publisher
DROP SUBSCRIPTION
E verifica che sia stato rimosso correttamente:
world=# SELECT * FROM pg_subscription_rel;
(0 rows)
world=# SELECT * FROM pg_stat_subscription;
(0 rows)
Limitazioni
Prima di utilizzare la replica logica, tieni presente le seguenti limitazioni:
- Lo schema del database ei comandi DDL non vengono replicati. Lo schema iniziale può essere copiato usando pg_dump --schema-only.
- I dati della sequenza non vengono replicati. I dati nelle colonne seriali o di identità supportate da sequenze verranno replicati come parte della tabella, ma la sequenza stessa mostrerebbe comunque il valore iniziale sull'abbonato.
- La replica dei comandi TRUNCATE è supportata, ma è necessario prestare una certa attenzione quando si troncano gruppi di tabelle collegate da chiavi esterne. Quando si replica un'azione di troncamento, il sottoscrittore troncherà lo stesso gruppo di tabelle troncato nell'editore, specificato in modo esplicito o raccolto implicitamente tramite CASCADE, meno le tabelle che non fanno parte della sottoscrizione. Funzionerà correttamente se tutte le tabelle interessate fanno parte della stessa sottoscrizione. Ma se alcune tabelle da troncare sull'abbonato hanno collegamenti di chiave esterna a tabelle che non fanno parte della stessa (o qualsiasi) sottoscrizione, l'applicazione dell'azione tronca sull'abbonato avrà esito negativo.
- Gli oggetti grandi non vengono replicati. Non esiste una soluzione alternativa, a parte la memorizzazione dei dati in tabelle normali.
- La replica è possibile solo dalle tabelle di base alle tabelle di base. Ovvero, le tabelle sul lato pubblicazione e sottoscrizione devono essere tabelle normali, non viste, viste materializzate, tabelle radice di partizione o tabelle esterne. Nel caso delle partizioni, puoi replicare una gerarchia di partizioni uno a uno, ma al momento non puoi replicare su una configurazione partizionata in modo diverso.
Conclusione
Mantenere aggiornato il tuo server PostgreSQL eseguendo aggiornamenti regolari è stato un compito necessario ma difficile fino alla versione 10 di PostgreSQL. Per fortuna ora è una storia diversa grazie alla replica logica.
In questo blog abbiamo fatto una breve introduzione alla replica logica, una funzionalità di PostgreSQL introdotta nativamente nella versione 10, e ti abbiamo mostrato come può aiutarti a portare a termine questo aggiornamento da PostgreSQL 11 a PostgreSQL 12 challenge con una strategia zero tempi di inattività.