Implementazione della Replicazione Logica in PostgreSQL: Multi-Master e Sincronizzazione Selettiva dei Dati
Introduzione
La replicazione logica in PostgreSQL consente di replicare tabelle specifiche, righe o anche colonne tra database, abilitando modelli sofisticati di distribuzione dei dati. A differenza della replicazione fisica in streaming che copia l'intero cluster del database, la replicazione logica fornisce un controllo granulare su quali dati vengono replicati e dove.
Replicazione Logica vs Fisica
Replicazione Fisica in Streaming
- Replica l'intero cluster del database
- Replicazione a livello binario
- Repliche in sola lettura
- Richiede la stessa versione di PostgreSQL
- Overhead inferiore
Replicazione Logica
- Replicazione selettiva di tabelle/righe
- Compatibile tra versioni diverse
- Sottoscrittori scrivibili
- Overhead superiore
- Distribuzione flessibile dei dati
Casi d'Uso per la Replicazione Logica
- Distribuzione Selettiva dei Dati: Replicare tabelle specifiche in diverse regioni
- Configurazioni Multi-Master: Database multipli scrivibili con sincronizzazione bidirezionale
- Aggiornamenti tra Versioni: Replicare da versioni vecchie a nuove di PostgreSQL
- Aggregazione dei Dati: Consolidare dati da più fonti
- Conformità GDPR: Replicare solo colonne non sensibili
Prerequisiti e Configurazione
Requisiti di Configurazione
Sul Publisher (Sorgente):
# postgresql.conf
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
Sul Subscriber (Target):
# postgresql.conf
max_replication_slots = 10
max_logical_replication_workers = 10
max_worker_processes = 16
Riavviare PostgreSQL
sudo systemctl restart postgresql
Configurazione di Rete
Assicurarsi che i database possano comunicare:
# Testare la connessione dal subscriber al publisher
psql -h publisher.example.com -U replication_user -d source_db
Configurare pg_hba.conf sul publisher:
# Permettere connessioni di replicazione
host all replication_user subscriber_ip/32 md5
Configurazione Base della Replicazione Logica
Passo 1: Creare l'Utente di Replicazione
Sul Publisher:
CREATE ROLE replication_user WITH REPLICATION LOGIN PASSWORD 'secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO replication_user;
Passo 2: Creare le Tabelle Sorgente
Sul Publisher:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
amount DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO users (username, email) VALUES
('alice', '[email protected]'),
('bob', '[email protected]');
Passo 3: Creare la Pubblicazione
Sul Publisher:
-- Pubblicare tutte le tabelle
CREATE PUBLICATION my_publication FOR ALL TABLES;
-- Oppure pubblicare tabelle specifiche
CREATE PUBLICATION my_publication FOR TABLE users, orders;
-- Oppure con filtri di riga (PostgreSQL 15+)
CREATE PUBLICATION active_users FOR TABLE users WHERE (status = 'active');
Visualizzare le pubblicazioni:
SELECT * FROM pg_publication;
SELECT * FROM pg_publication_tables;
Passo 4: Creare le Tabelle Replica
Sul Subscriber:
-- Le tabelle devono avere una struttura identica
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
amount DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT NOW()
);
Passo 5: Creare la Sottoscrizione
Sul Subscriber:
CREATE SUBSCRIPTION my_subscription
CONNECTION 'host=publisher.example.com port=5432 dbname=source_db user=replication_user password=secure_password'
PUBLICATION my_publication;
Passo 6: Verificare la Replicazione
Sul Publisher:
SELECT * FROM pg_stat_replication;
SELECT * FROM pg_replication_slots;
Sul Subscriber:
SELECT * FROM pg_stat_subscription;
SELECT * FROM users; -- Dovrebbe vedere i dati replicati
Configurazione Avanzata
Replicazione a Livello di Colonna (PostgreSQL 15+)
Replicare solo colonne specifiche:
-- Sul Publisher: Replicare solo colonne non sensibili
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
credit_card VARCHAR(20), -- Non verrà replicata
created_at TIMESTAMP
);
CREATE PUBLICATION customer_basic
FOR TABLE customers (id, name, email, created_at);
Filtraggio delle Righe
Replicare solo record attivi:
CREATE PUBLICATION active_data
FOR TABLE orders WHERE (status IN ('pending', 'processing'));
Distribuzione dei dati regionali:
CREATE PUBLICATION us_customers
FOR TABLE customers WHERE (country = 'US');
CREATE PUBLICATION eu_customers
FOR TABLE customers WHERE (country IN ('UK', 'DE', 'FR'));
Pubblicazioni Multiple
-- Publisher: Creare pubblicazioni multiple
CREATE PUBLICATION oltp_data FOR TABLE users, orders;
CREATE PUBLICATION analytics_data FOR TABLE logs, metrics;
-- Subscriber: Sottoscrivere a pubblicazioni multiple
CREATE SUBSCRIPTION multi_sub
CONNECTION 'host=publisher port=5432 dbname=mydb user=repuser password=pass'
PUBLICATION oltp_data, analytics_data;
Replicazione Bidirezionale (Multi-Master)
Configurazione della Sincronizzazione a Due Vie
Configurazione del Database A:
-- Creare la pubblicazione
CREATE PUBLICATION db_a_pub FOR TABLE shared_table;
-- Sottoscrivere al Database B
CREATE SUBSCRIPTION db_a_sub
CONNECTION 'host=db-b.example.com dbname=mydb user=repuser'
PUBLICATION db_b_pub
WITH (origin = none); -- Previene i loop di replicazione
Configurazione del Database B:
-- Creare la pubblicazione
CREATE PUBLICATION db_b_pub FOR TABLE shared_table;
-- Sottoscrivere al Database A
CREATE SUBSCRIPTION db_b_sub
CONNECTION 'host=db-a.example.com dbname=mydb user=repuser'
PUBLICATION db_a_pub
WITH (origin = none);
Risoluzione dei Conflitti
La replicazione logica utilizza "last write wins" di default:
-- Impostare l'identità della replica per tracciare i conflitti
ALTER TABLE shared_table REPLICA IDENTITY FULL;
Strategie di rilevamento dei conflitti:
- Basato su timestamp: Aggiungere una colonna updated_at
CREATE TABLE shared_table (
id SERIAL PRIMARY KEY,
data TEXT,
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_shared_table_timestamp
BEFORE UPDATE ON shared_table
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();
- Numerazione delle versioni:
CREATE TABLE shared_table (
id SERIAL PRIMARY KEY,
data TEXT,
version INTEGER DEFAULT 1
);
Opzioni di Sincronizzazione Iniziale dei Dati
Opzione 1: Copia Automatica (Default)
-- Il subscriber copia automaticamente i dati esistenti
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=publisher dbname=mydb user=repuser'
PUBLICATION my_pub
WITH (copy_data = true); -- Default
Opzione 2: Sincronizzazione Iniziale Manual
Per grandi dataset, usare pg_dump:
# Dump di tabelle specifiche dal publisher
pg_dump -h publisher.example.com -U postgres -d source_db \n -t users -t orders --no-owner --no-acl > initial_data.sql
# Caricare nel subscriber
psql -h subscriber.example.com -U postgres -d target_db < initial_data.sql
# Creare la sottoscrizione senza copia iniziale
psql -h subscriber.example.com -U postgres -d target_db -c "
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=publisher dbname=source_db user=repuser'
PUBLICATION my_pub
WITH (copy_data = false);
"
Opzione 3: Sincronizzazione Iniziale Parallela
-- Usare più worker per una sincronizzazione iniziale più veloce
CREATE SUBSCRIPTION fast_sync
CONNECTION 'host=publisher dbname=mydb user=repuser'
PUBLICATION my_pub
WITH (
copy_data = true,
streaming = on,
synchronous_commit = off
);
Monitoraggio della Replicazione Logica
Monitoraggio del Publisher
-- Visualizzare gli slot di replicazione
SELECT
slot_name,
plugin,
active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag_size
FROM pg_replication_slots;
-- Visualizzare le connessioni di replicazione attive
SELECT
pid,
usename,
application_name,
client_addr,
state,
sync_state,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)) AS send_lag
FROM pg_stat_replication;
Monitoraggio del Subscriber
-- Visualizzare lo stato della sottoscrizione
SELECT
subname,
pid,
received_lsn,
latest_end_lsn,
pg_size_pretty(pg_wal_lsn_diff(latest_end_lsn, received_lsn)) AS lag
FROM pg_stat_subscription;
-- Controllare gli errori di replicazione
SELECT * FROM pg_stat_subscription WHERE last_msg_receipt_time < NOW() - INTERVAL '5 minutes';
Script di Monitoraggio
#!/bin/bash
# logical-replication-monitor.sh
echo "=== Stato del Publisher ==="
psql -h publisher -d mydb -c "
SELECT slot_name, active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag
FROM pg_replication_slots;"
echo ""
echo "=== Stato del Subscriber ==="
psql -h subscriber -d mydb -c "
SELECT subname, pid,
pg_size_pretty(pg_wal_lsn_diff(latest_end_lsn, received_lsn)) AS lag
FROM pg_stat_subscription;"
Risoluzione dei Problemi
Problema 1: La Sottoscrizione Non Riceve Dati
Controllare lo stato della sottoscrizione:
SELECT subname, pid, subenabled, subconninfo FROM pg_subscription;
Abilitare la sottoscrizione se disabilitata:
ALTER SUBSCRIPTION my_sub ENABLE;
Controllare gli errori:
SELECT * FROM pg_stat_subscription;
Problema 2: Il Lag di Replicazione Sta Crescendo
Identificare le tabelle lente:
SELECT schemaname, tablename, n_tup_ins, n_tup_upd, n_tup_del
FROM pg_stat_user_tables
ORDER BY (n_tup_ins + n_tup_upd + n_tup_del) DESC;
Aumentare i processi worker:
# postgresql.conf sul subscriber
max_logical_replication_workers = 20
max_worker_processes = 30
Problema 3: Bloat dello Slot di Replicazione
Controllare l'uso dello slot:
SELECT
slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
Eliminare gli slot inattivi:
SELECT pg_drop_replication_slot('inactive_slot_name');
Problema 4: Fallimento della Sincronizzazione Iniziale
Riavviare la sincronizzazione iniziale:
-- Eliminare e ricreare la sottoscrizione
DROP SUBSCRIPTION my_sub;
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=publisher dbname=mydb user=repuser'
PUBLICATION my_pub
WITH (copy_data = true);
Ottimizzazione delle Prestazioni
1. Usare un'Identità di Replica Appropriata
-- Default: Solo PRIMARY KEY
ALTER TABLE my_table REPLICA IDENTITY DEFAULT;
-- Completa: Tutte le colonne (overhead maggiore)
ALTER TABLE my_table REPLICA IDENTITY FULL;
-- Indice: Usare un indice unico specifico
CREATE UNIQUE INDEX replica_idx ON my_table(col1, col2);
ALTER TABLE my_table REPLICA IDENTITY USING INDEX replica_idx;
2. Disabilitare i Vincoli Durante la Sincronizzazione Iniziale
-- Disabilitare temporaneamente i trigger per un caricamento iniziale più veloce
ALTER TABLE my_table DISABLE TRIGGER ALL;
-- Dopo il completamento della sincronizzazione iniziale
ALTER TABLE my_table ENABLE TRIGGER ALL;
3. Applicazione Parallela (PostgreSQL 16+)
CREATE SUBSCRIPTION parallel_sub
CONNECTION 'host=publisher dbname=mydb user=repuser'
PUBLICATION my_pub
WITH (streaming = parallel);
Best Practices
- Usare sempre l'identità di replica: Assicurarsi che le tabelle abbiano PRIMARY KEY o un vincolo UNIQUE
- Monitorare il lag di replicazione: Impostare avvisi per lag > 100MB o 5 minuti
- Usare i filtri di riga con attenzione: I filtri vengono valutati sul publisher, influenzando le prestazioni
- Pianificare i conflitti: Implementare il rilevamento dei conflitti nelle configurazioni multi-master
- Testare le procedure di failover: Esercitarsi nel passaggio tra database
- Manutenzione regolare: Pulire gli slot di replicazione vecchi
Conclusione
La replicazione logica fornisce potenti capacità di distribuzione dei dati per PostgreSQL. Vantaggi principali:
- Replicazione selettiva di tabelle e righe
- Compatibilità tra versioni diverse
- Configurazioni multi-master bidirezionali
- Controllo granulare della privacy dei dati
Iniziare con una replicazione semplice a direzione singola, testare accuratamente, quindi espandere a topologie più complesse secondo necessità.