Replica Logica su PostgreSQL 10


Logical Replication

Tra le novità introdotte da PostgreSQL 10 sicuramente una delle più importanti è l’implementazione della replica logica nel core di Postgres.

Molti di voi avranno già provato o almeno sentito parlare di pglogical, estensione di PostgreSQL che fornisce il supporto per la replica logica, che, grazie al logical decoding dei WAL (presente dalla 9.4), permette di replicare una selezione di tabelle da un database ad un altro.

In questo articolo vedremo come adesso questo è possibile con PostgreSQL 10 e quali sono le differenze con pglogical. Inoltre, viene assunto che il lettore abbia una preparazione di base sulle nozioni di amministrazione di PostgreSQL e di manipolazione dei file di configurazione e di sicurezza.

replica_logica

Cos’è?

La replica logica si basa sull’architettura Pubblicazione/Sottoscrizione, si differenziano quindi due ruoli: un publisher e un subscriber. A differenza di quella fisica nella quale vengono replicate tutte le modifiche a livello binario, la replica logica permette di filtrare i cambiamenti da replicare scegliendo le tabelle e una combinazione di operazioni su di esse tra UPDATE, INSERT e/o DELETE. Per esempio è possibile replicare solo le UPDATE di una singola tabella da un server ad un altro a scopo di analisi e/o di aggregazione dei dati.

Come funziona

Sul publisher viene definita una “pubblicazione”, ovvero una selezione delle tabelle (o tutte) di un database a cui vengono associate le operazioni da replicare ai subscriber. Grazie alla decodifica logica dei WAL le operazioni sono ricostruite traducendo il codice binario e vengono selezionate solo quelle definite nella “pubblicazione” per essere trasmesse ai subscriber.

Configurazione

L’installazione di default predispone già impostate alcune delle opzioni necessarie a configurare la replica logica.

Per quanto riguarda il publisher:

  • max_replication_slots = 10
    Almeno uno per ogni subscriber più alcuni per l’inizializzazione delle tabelle.
  • max_wal_senders = 10
    Almeno uno per ogni replication_slot più quelli necessari per le repliche fisiche (es.: Barman).

Per quanto riguarda il subscriber:

  • max_logical_replication_workers = 4
    Uno per sottoscrizione, più alcuni da considerare per la sincronizzazione delle tabelle.
  • max_worker_processes = 10
    Almeno uno per ogni replication workers più uno.

[NOTA] Queste però non bastano per attivare la replica logica. Quello che manca, infatti, sono le informazioni all’interno del codice dei WAL necessarie per ricostruire le operazioni da filtrare e inviare ai subscriber. Per ottenere queste informazioni è necessario impostare sul publisher il parametro wal_level a logical e avviare (o riavviare) il servizio.

L’unico cambiamento che dobbiamo apportare è il seguente parametro sul publisher pippo-pg10:

  • wal_level = logical

I valori preimpostati sono più che sufficienti per questo test. Tuttavia, va tenuto conto che sono parametri che vengono valorizzati all’avvio del servizio e quindi ogni ulteriore cambiamento viene applicato solo dopo un nuovo riavvio. In questa ottica è consigliabile impostare valori adeguati al numero di pubblicazioni e sottoscrizioni previsti, prima di dover riavviare il servizio in produzione.

Preparazione

Consideriamo un caso reale di utilizzo della replica logica, prendendo di esempio un cluster con due server PostgreSQL 10:

  • pippo-pg10, ip: 192.168.42.110, publisher
  • paperino-pg10, ip: 192.168.42.210, subscriber

Sul server pippo-pg10 sono presenti le tabelle che devono essere replicate su un database del server paperino-pg10, precedentemente create con pgbench, un tool molto utile per misurare le prestazioni di PostgreSQL.

[root@pippo-pg10 ~]# sudo -iu postgres
-bash-4.2$ psql
psql (10beta4)
Type "help" for help.
postgres=# \c pgbench
You are now connected to database "pgbench" as user "postgres".
pgbench=# \d
               List of relations
 Schema |       Name       | Type  |   Owner
--------+------------------+-------+------------
 public | pgbench_accounts | table | user_bench
 public | pgbench_branches | table | user_bench
 public | pgbench_history  | table | user_bench
 public | pgbench_tellers  | table | user_bench
(4 rows)

Creazione Tabelle

A causa delle restrizioni sulla replicazione non è possibile replicare la definizione di una tabella tanto meno di un database. Per questo occorre replicare manualmente la definizione sul server di sottoscrizione, per esempio partendo da un dump dello schema del database pgbench dal publisher:

-bash-4.2$ pg_dump -s -f pgbench.sql

Quindi creare l’utente e il database di destinazione nell’istanza PostgreSQL di paperino-pg10 (operazioni assenti nello script appena estratto):

[root@paperino-pg10 ~]# sudo -iu postgres
-bash-4.2$ createuser user_bench
-bash-4.2$ createdb pgbench -O user_bench

[NOTA] Non è necessario che l’utente e il database abbiano lo stesso nome, ma in questo caso si utilizza lo stesso nome per semplicità.

Adesso è possibile applicare lo script SQL sul server paperino-pg10:

-bash-4.2$ psql -d pgbench  < pgbench.sql
SET
SET
SET
SET
SET
SET
SET
SET
SET
CREATE EXTENSION
COMMENT
SET
SET
SET
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE

Creazione Ruolo Replicazione

Nell’istanza pippo-pg10 è necessario creare il ruolo dedicato alla replicazione e definire opportunamente la regola nel pg_hba.conf che permetta la connessione dal subscriber paperino-pg10.

pgbench=# create role replica superuser replication;
CREATE ROLE

Creazione Pubblicazione

Una pubblicazione è definita come un set di cambiamenti su una o più tabelle appartenenti ad un solo database. Si possono creare più pubblicazioni in un solo database scegliendo di replicare una tra le operazioni di INSERT, UPDATE e DELETE o una loro combinazione. Ogni tabella può appartenere a più pubblicazioni e una singola pubblicazione può avere più sottoscrizioni.

In questo esempio assumiamo di voler replicare tutte le operazioni di modifica su tutte le tabelle di un database. Per creare questo tipo di pubblicazione è necessario essere superutenti (maggiori dettagli):

pgbench=# CREATE PUBLICATION pgbench_alltables FOR ALL TABLES;
CREATE PUBLICATION

Creazione Sottoscrizione

Una sottoscrizione viene definita su un nodo PostgreSQL, chiamato subscriber, con una connection string verso il server publisher e una lista di una o più pubblicazioni alle quali sottoscriversi. Il nome della sottoscrizione viene trasmesso al publisher come riconoscimento della connessione se non è definito il campo application_name nella stringa di connessione.

Per creare una sottoscrizione è necessario essere superutenti (maggiori dettagli).

Su paperino-pg10:

pgbench=# CREATE SUBSCRIPTION pgbench_rep CONNECTION 'dbname=pgbench host=192.168.42.110 user=replica' PUBLICATION pgbench_alltables;
NOTICE:  created replication slot "pgbench_rep" on publisher
CREATE SUBSCRIPTION

[NOTA] Un replication slot viene creato per ogni sottoscrizione, più uno temporaneo per la sincronizzazione iniziale dei dati.

Adesso è possibile controllare che l’inizializzazione dei dati nel subscriber rispecchi la situazione nel publisher:

sul publisher pippo-pg10

postgres=# \c pgbench
You are now connected to database "pgbench" as user "postgres".
pgbench=# select * from pg_publication;
      pubname      | pubowner | puballtables | pubinsert | pubupdate | pubdelete
-------------------+----------+--------------+-----------+-----------+-----------
 pgbench_alltables |       10 | t            | t         | t         | t
(1 row)
pgbench=# select * from pgbench_branches limit 5;
 bid | bbalance | filler
-----+----------+--------
   1 |        0 |
   2 |        0 |
   3 |        0 |
   4 |        0 |
   5 |        0 |
(5 rows)

pgbench=# select * from pgbench_tellers limit 5;
 tid | bid | tbalance | filler
-----+-----+----------+--------
   1 |   1 |        0 |
   2 |   1 |        0 |
   3 |   1 |        0 |
   4 |   1 |        0 |
   5 |   1 |        0 |
(5 rows)

e sul subscriber paperino-pg10

postgres=# \c pgbench
You are now connected to database "pgbench" as user "postgres".
pgbench=# select * from pg_subscription;
 subdbid |   subname   | subowner | subenabled |                   subconninfo                   | subslotname | subsynccommit |   subpublications
---------+-------------+----------+------------+-------------------------------------------------+-------------+---------------+---------------------
   16532 | pgbench_rep |       10 | t          | dbname=pgbench host=192.168.42.110 user=replica | pgbench_rep | off           | {pgbench_alltables}
(1 row)
pgbench=# select * from pgbench_branches limit 5;
 bid | bbalance | filler
-----+----------+--------
   1 |        0 |
   2 |        0 |
   3 |        0 |
   4 |        0 |
   5 |        0 |
(5 rows)

pgbench=# select * from pgbench_tellers limit 5;
 tid | bid | tbalance | filler
-----+-----+----------+--------
   1 |   1 |        0 |
   2 |   1 |        0 |
   3 |   1 |        0 |
   4 |   1 |        0 |
   5 |   1 |        0 |
(5 rows)

Le tabelle sul server paperino-pg10 sono state inizializzate!

Test Replica

Il test consiste nel verificare che le modifiche apportate alle tabelle sul server pippo-pg10 siano effettivamente replicate su paperino-pg10.

Su pippo-pg10:

pgbench=# BEGIN;
BEGIN
pgbench=# SELECT * FROM pgbench_accounts WHERE aid = 1;
 aid | bid | abalance |                                        filler
-----+-----+----------+--------------------------------------------------------------------------------------
   1 |   1 |        0 |
(1 row)

pgbench=# UPDATE pgbench_accounts SET filler = 'Jonny was here' WHERE aid = 1;
UPDATE 1
pgbench=# select * from pgbench_accounts where aid = 1;
 aid | bid | abalance |                                        filler
-----+-----+----------+--------------------------------------------------------------------------------------
   1 |   1 |        0 | Jonny was here
(1 row)
pgbench=# COMMIT;
COMMIT
pgbench=#

Una volta eseguito il COMMIT nel WAL viene scritta l’operazione UPDATE appena eseguita e replicata al subscriber:

pgbench=# select * from pgbench_accounts where aid = 1;
 aid | bid | abalance |                                        filler
-----+-----+----------+--------------------------------------------------------------------------------------
   1 |   1 |        0 | Jonny was here
(1 row)

Attenzione! Le operazioni di UPDATE e DELETE sono replicate al subscriber solo sulle tabelle che possiedono una REPLICA IDENTITY, che di default è la chiave primaria. La tabella history di pgbench non sarebbe stata replicata in caso avessimo creato una pubblicazione limitata alle UPDATE e/o DELETE, perché non possiede una chiave primaria.

Considerazioni Conclusive

Replica Logica vs. Replica Fisica

La replica logica a differenza della replica fisica di un’istanza in hot-standby mode, non è da considerarsi un’architettura Master/Standby in quanto il subscriber è egli stesso un master, permettendo quindi le scritture su tutte le tabelle. Può essere comunque utilizzata in maniera simile usando utenti con permessi di sola lettura sul subscriber fino al prossimo switchover.

Per l’architettura multi-master, nella quale le modifiche vengono effettuate concorrentemente su più nodi, si rimanda alla lettura del sistema BDR.

Questa tecnologia permette di migrare da una major version di PostgreSQL ad un’altra, al contrario della replica fisica a causa dell’incompatibilità binaria tra differenti versioni.

Differenze con pglogical

L’implementazione nel core di PostgreSQL 10 della replica logica è un’eredità del progetto pglogical, che, essendo più maturo, possiede molte funzionalità in più. Tra le caratteristiche più importanti di pglogical vi è la possibilità di replicare le modifiche allo schema di un database e le DDL, quindi le definizioni delle tabelle. Inoltre è possibile configurare la risoluzione dei conflitti, mentre con PostgreSQL 10, in caso di conflitto la replica logica si interrompe ed è necessario l’intervento umano per la sua risoluzione. Un’altra funzionalità che rende pglogical molto potente è la possibilità di replicare specifiche righe e/o colonne di una tabella.

Commenti

L’inizializzazione dei dati nelle tabelle del subscriber è una funzionalità molto utile, ma è possibile scegliere di non effettuarla durante la creazione della sottoscrizione.

Anche la replica logica può essere sottoposta alle stesse configurazioni di sincronizzazione delle transazioni tra standby e master, in modo da avere una perdita dati zero. Sono sufficienti gli stessi parametri della replica fisica per ottenere la sincronizzazione tra master e standby.

Nell’esempio di questo articolo, per attivare la replica logica sincrona è sufficiente configurare i seguenti parametri:

synchronous_commit = remote_apply
synchronous_standby_names = pgbench_rep

e riavviare PostgreSQL.

Il valore del parametro synchronous_standby_name corrisponde al nome della sottoscrizione. Maggiori dettagli sulla configurazione del parametro synchronous_standby_names.

Adesso mi rimane una domanda: qual è la differenza di velocità fra la replica fisica e quella logica? Potrebbe essere l’interessante argomento del mio prossimo articolo.

This Post Has 0 Comments

Leave A Reply