De meest intuïtieve manier die je kunt bedenken om een database te upgraden, is door een replica in een nieuwe versie te genereren en een failover van de applicatie erin uit te voeren, en eigenlijk werkt het perfect in andere engines. Met PostgreSQL was dit vroeger onmogelijk op een native manier. Om upgrades uit te voeren, moest je andere manieren van upgraden bedenken, zoals het gebruik van pg_upgrade, dumpen en herstellen, of het gebruik van tools van derden, zoals Slony of Bucardo, die allemaal hun eigen kanttekeningen hebben. Dit komt door de manier waarop PostgreSQL replicatie implementeerde.
PostgreSQL-streamingreplicatie (de gebruikelijke PostgreSQL-replicatie) is een fysieke replicatie die de wijzigingen byte-by-byte repliceert, waardoor een identieke kopie van de database op een andere server wordt gemaakt. Deze methode heeft veel beperkingen bij het bedenken van een upgrade, omdat je simpelweg geen replica kunt maken in een andere serverversie of zelfs in een andere architectuur.
Sinds PostgreSQL 10 heeft het ingebouwde logische replicatie geïmplementeerd die u, in tegenstelling tot fysieke replicatie, kunt repliceren tussen verschillende hoofdversies van PostgreSQL. Dit opent natuurlijk een nieuwe deur voor het upgraden van strategieën.
In deze blog zullen we zien hoe u uw PostgreSQL 11 kunt upgraden naar PostgreSQL 12 zonder downtime met behulp van logische replicatie.
Logische PostgreSQL-replicatie
Logische replicatie is een methode voor het repliceren van gegevensobjecten en hun wijzigingen op basis van hun replicatie-identiteit (meestal een primaire sleutel). Het is gebaseerd op een publicatie- en abonnementsmodus, waarbij een of meer abonnees zich abonneren op een of meer publicaties op een uitgeversknooppunt.
Een publicatie is een reeks wijzigingen die zijn gegenereerd op basis van een tabel of een groep tabellen (ook wel een replicatieset genoemd). Het knooppunt waar een publicatie is gedefinieerd, wordt uitgever genoemd. Een abonnement is de downstream-kant van logische replicatie. Het knooppunt waar een abonnement is gedefinieerd, wordt de abonnee genoemd en definieert de verbinding met een andere database en reeks publicaties (een of meer) waarop het zich wil abonneren. Abonnees halen gegevens uit de publicaties waarop ze geabonneerd zijn.
Logische replicatie is gebouwd met een architectuur die vergelijkbaar is met fysieke streamingreplicatie. Het wordt geïmplementeerd door "walsender" en "apply"-processen. Het walsender-proces start de logische decodering van de WAL en laadt de standaard plug-in voor logische decodering. De plug-in transformeert de gelezen wijzigingen van WAL naar het logische replicatieprotocol en filtert de gegevens volgens de publicatiespecificatie. De gegevens worden vervolgens continu overgedragen met behulp van het streaming-replicatieprotocol naar de toepassingswerker, die de gegevens toewijst aan lokale tabellen en de individuele wijzigingen toepast zodra ze worden ontvangen, in een correcte transactievolgorde.
Logische replicatie begint met het maken van een momentopname van de gegevens in de uitgeversdatabase en kopiëren naar de abonnee. De initiële gegevens in de bestaande geabonneerde tabellen worden snapshots genomen en gekopieerd in een parallelle instantie van een speciaal soort toepassingsproces. Dit proces maakt een eigen tijdelijke replicatiesleuf en kopieert de bestaande gegevens. Zodra de bestaande gegevens zijn gekopieerd, gaat de werknemer naar de synchronisatiemodus, die ervoor zorgt dat de tabel in een gesynchroniseerde staat wordt gebracht met het hoofdtoepassingsproces door alle wijzigingen die tijdens de eerste gegevenskopie hebben plaatsgevonden, te streamen met behulp van standaard logische replicatie. Zodra de synchronisatie is voltooid, wordt de controle over de replicatie van de tabel teruggegeven aan het hoofdtoepassingsproces waar de replicatie gewoon doorgaat. De wijzigingen op de uitgever worden in realtime naar de abonnee verzonden.
PostgreSQL 11 upgraden naar PostgreSQL 12 met logische replicatie
We gaan logische replicatie configureren tussen twee verschillende hoofdversies van PostgreSQL (11 en 12), en natuurlijk, nadat dit werkt, is het alleen een kwestie van een applicatie-failover uitvoeren in de database met de nieuwere versie.
We gaan de volgende stappen uitvoeren om logische replicatie aan het werk te zetten:
- Configureer het uitgeversknooppunt
- Configureer het abonneeknooppunt
- Maak de abonneegebruiker
- Maak een publicatie
- Maak de tabelstructuur in de abonnee
- Maak het abonnement aan
- Controleer de replicatiestatus
Dus laten we beginnen.
Aan de uitgeverskant gaan we de volgende parameters configureren in het postgresql.conf-bestand:
- listen_addresses: Op welk(e) IP-adres(sen) te luisteren. We gebruiken '*' voor iedereen.
- wal_level: Bepaalt hoeveel informatie naar de WAL wordt geschreven. We gaan het op "logisch" zetten.
- max_replication_slots :Specificeert het maximum aantal replicatieslots dat de server kan ondersteunen. Het moet worden ingesteld op ten minste het aantal abonnementen dat naar verwachting verbinding zal maken, plus enige reserve voor tafelsynchronisatie.
- max_wal_senders: Specificeert het maximum aantal gelijktijdige verbindingen van standby-servers of streaming-basisback-upclients. Het moet worden ingesteld op ten minste hetzelfde als max_replication_slots plus het aantal fysieke replica's dat tegelijkertijd is verbonden.
Houd er rekening mee dat voor sommige van deze parameters een herstart van de PostgreSQL-service vereist was.
Het bestand pg_hba.conf moet ook worden aangepast om replicatie mogelijk te maken. U moet de replicatiegebruiker toestemming geven om verbinding te maken met de database.
Laten we op basis hiervan de uitgever (in dit geval de PostgreSQL 11-server) als volgt configureren:
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
U moet de gebruiker wijzigen (in dit voorbeeld rep1), die zal worden gebruikt voor replicatie, en het IP-adres 10.10.10.131/32 voor het IP-adres dat overeenkomt met uw PostgreSQL 12-knooppunt.
Aan de abonneezijde moeten ook de max_replication_slots worden ingesteld. In dit geval moet het worden ingesteld op ten minste het aantal abonnementen dat aan de abonnee wordt toegevoegd.
De andere parameters die hier ook moeten worden ingesteld zijn:
- max_logical_replication_workers :Specificeert het maximum aantal logische replicatiewerknemers. Dit omvat zowel werknemers voor toepassing als tabelsynchronisatie. Werknemers voor logische replicatie worden genomen uit de pool die is gedefinieerd door max_worker_processes. Het moet worden ingesteld op ten minste het aantal abonnementen, plus wat reserve voor de tafelsynchronisatie.
- max_worker_processes :Stelt het maximum aantal achtergrondprocessen in dat het systeem kan ondersteunen. Het moet mogelijk worden aangepast om plaats te bieden aan replicatiewerkers, ten minste max_logical_replication_workers + 1. Voor deze parameter is een PostgreSQL-herstart vereist.
Dus u moet de abonnee (in dit geval de PostgreSQL 12-server) als volgt configureren:
postgresql.conf:
listen_addresses = '*'
max_replication_slots = 4
max_logical_replication_workers = 4
max_worker_processes = 8
Aangezien deze PostgreSQL 12 binnenkort het nieuwe primaire knooppunt zal zijn, moet u overwegen om de parameters wal_level en archive_mode in deze stap toe te voegen om een nieuwe herstart van de service later te voorkomen.
wal_level = logical
archive_mode = on
Deze parameters zijn handig als u een nieuwe replica wilt toevoegen of als u PITR-back-ups wilt gebruiken.
In de uitgever moet u de gebruiker maken waarmee de abonnee verbinding zal maken:
world=# CREATE ROLE rep1 WITH LOGIN PASSWORD '*****' REPLICATION;
CREATE ROLE
De rol die wordt gebruikt voor de replicatieverbinding moet het kenmerk REPLICATION hebben. Toegang voor de rol moet worden geconfigureerd in pg_hba.conf en moet het kenmerk LOGIN hebben.
Om de initiële gegevens te kunnen kopiëren, moet de rol die wordt gebruikt voor de replicatieverbinding het SELECT-privilege hebben op een gepubliceerde tabel.
world=# GRANT SELECT ON ALL TABLES IN SCHEMA public to rep1;
GRANT
We zullen pub1-publicatie maken in het uitgeversknooppunt, voor alle tabellen:
world=# CREATE PUBLICATION pub1 FOR ALL TABLES;
CREATE PUBLICATION
De gebruiker die een publicatie maakt, moet het CREATE-privilege in de database hebben, maar om een publicatie te maken die alle tabellen automatisch publiceert, moet de gebruiker een supergebruiker zijn.
Om de gemaakte publicatie te bevestigen, gaan we de pg_publication-catalogus gebruiken. Deze catalogus bevat informatie over alle publicaties die in de database zijn aangemaakt.
world=# SELECT * FROM pg_publication;
-[ RECORD 1 ]+-----
pubname | pub1
pubowner | 10
puballtables | t
pubinsert | t
pubupdate | t
pubdelete | t
pubtruncate | t
Kolombeschrijvingen:
- pubnaam :Naam van de publicatie.
- pubeigenaar :Eigenaar van de publicatie.
- publtafels :Indien waar, bevat deze publicatie automatisch alle tabellen in de database, inclusief alle tabellen die in de toekomst zullen worden gemaakt.
- pubinsert :Indien waar, worden INSERT-bewerkingen gerepliceerd voor tabellen in de publicatie.
- pubupdate :Indien waar, worden UPDATE-bewerkingen gerepliceerd voor tabellen in de publicatie.
- pubdelete :Indien waar, worden DELETE-bewerkingen gerepliceerd voor tabellen in de publicatie.
- pubtruncate :Indien waar, worden TRUNCATE-bewerkingen gerepliceerd voor tabellen in de publicatie.
Omdat het schema niet wordt gerepliceerd, moet u een back-up maken in PostgreSQL 11 en deze herstellen in uw PostgreSQL 12. De back-up wordt alleen gemaakt voor het schema, aangezien de informatie in de eerste overzetten.
In PostgreSQL 11:
$ pg_dumpall -s > schema.sql
In PostgreSQL 12:
$ psql -d postgres -f schema.sql
Zodra u uw schema in PostgreSQL 12 heeft, moet u het abonnement maken en de waarden host, dbname, gebruiker en wachtwoord vervangen door de waarden die overeenkomen met uw omgeving.
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
Het bovenstaande start het replicatieproces, dat de initiële tabelinhoud van de tabellen in de publicatie synchroniseert en vervolgens begint met het repliceren van incrementele wijzigingen in die tabellen.
De gebruiker die een abonnement aanmaakt, moet een supergebruiker zijn. Het proces voor het aanvragen van een abonnement wordt uitgevoerd in de lokale database met de rechten van een supergebruiker.
Om het aangemaakte abonnement te verifiëren, kun je de pg_stat_subscription-catalogus gebruiken. Deze weergave bevat één rij per abonnement voor de hoofdwerker (met null-PID als de werknemer niet actief is) en extra rijen voor werknemers die de eerste gegevenskopie van de geabonneerde tabellen verwerken.
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
Kolombeschrijvingen:
- sub-lid :OID van het abonnement.
- subnaam :Naam van het abonnement.
- pid :Proces-ID van het abonnementswerkproces.
- relid :OID van de relatie die de werknemer synchroniseert; null voor de belangrijkste sollicitant.
- received_lsn :Laatst ontvangen loglocatie voor vooruitschrijven, de initiële waarde van dit veld is 0.
- last_msg_send_time :Verzendtijd van het laatste bericht ontvangen van de oorspronkelijke WAL-afzender.
- last_msg_receipt_time :Tijdstip van ontvangst van het laatste bericht dat is ontvangen van de oorspronkelijke WAL-afzender.
- nieuwste_end_lsn :Laatste write-ahead loglocatie gerapporteerd aan oorspronkelijke WAL-afzender.
- laatste_eindtijd :Tijd van de laatste voorschrijfloglocatie gerapporteerd aan de oorspronkelijke WAL-afzender.
Om de status van replicatie in het primaire knooppunt te verifiëren, kunt u pg_stat_replication gebruiken:
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
Kolombeschrijvingen:
- pid :Proces-ID van een WAL-afzenderproces.
- usesysid :OID van de gebruiker die is aangemeld bij dit WAL-afzenderproces.
- gebruiksnaam :Naam van de gebruiker die is aangemeld bij dit WAL-afzenderproces.
- toepassingsnaam :Naam van de applicatie die is verbonden met deze WAL-afzender.
- client_addr :IP-adres van de client die is aangesloten op deze WAL-zender. Als dit veld null is, geeft dit aan dat de client is verbonden via een Unix-socket op de servermachine.
- client_hostname :Hostnaam van de verbonden client, zoals gerapporteerd door een reverse DNS-lookup van client_addr. Dit veld is alleen niet-null voor IP-verbindingen en alleen als log_hostname is ingeschakeld.
- client_port :TCP-poortnummer dat de client gebruikt voor communicatie met deze WAL-zender, of -1 als een Unix-socket wordt gebruikt.
- backend_start :Tijd waarop dit proces is gestart.
- backend_xmin :De xmin-horizon van deze stand-by, gerapporteerd door hot_standby_feedback.
- staat :Huidige WAL-afzenderstatus. De mogelijke waarden zijn:opstarten, inhalen, streamen, back-up en stoppen.
- sent_lsn :Laatste schrijf-ahead loglocatie verzonden via deze verbinding.
- write_lsn :Laatste voorschrijfloglocatie die door deze standby-server naar schijf is geschreven.
- flush_lsn :Laatste write-ahead loglocatie gewist naar schijf door deze standby-server.
- replay_lsn :De locatie van het laatst teruggeschreven logboek is opnieuw afgespeeld in de database op deze standby-server.
- write_lag :tijd verstreken tussen het lokaal wissen van recente WAL en het ontvangen van de melding dat deze standby-server het heeft geschreven (maar het nog niet heeft gewist of toegepast).
- flush_lag :tijd verstreken tussen het lokaal wissen van recente WAL en het ontvangen van de melding dat deze standby-server het heeft geschreven en gewist (maar nog niet heeft toegepast).
- replay_lag :tijd verstreken tussen het lokaal wissen van recente WAL en het ontvangen van de melding dat deze standby-server deze heeft geschreven, gewist en toegepast.
- sync_priority :Prioriteit van deze standby-server om te worden gekozen als de synchrone standby in een op prioriteit gebaseerde synchrone replicatie.
- sync_state :Synchrone status van deze standby-server. De mogelijke waarden zijn async, potential, sync, quorum.
Om te controleren wanneer de eerste overdracht is voltooid, kunt u het PostgreSQL-logboek op de abonnee controleren:
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
Of controleer de srsubstate-variabele in de pg_subscription_rel-catalogus. Deze catalogus bevat de status voor elke gerepliceerde relatie in elk abonnement.
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)
Kolombeschrijvingen:
- srsubid :Verwijzing naar abonnement.
- srrelid :Verwijzing naar relatie.
- srsubstate :Statuscode:i =initialiseren, d =gegevens worden gekopieerd, s =gesynchroniseerd, r =gereed (normale replicatie).
- srsublsn :Beëindig LSN voor s en r toestanden.
Je kunt enkele testrecords invoegen in je PostgreSQL 11 en valideren dat je ze in je PostgreSQL 12 hebt:
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)
Op dit moment heeft u alles klaar om uw toepassing naar uw PostgreSQL 12 te verwijzen.
Hiervoor moet je allereerst bevestigen dat je geen replicatievertraging hebt.
Op het primaire knooppunt:
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
En nu hoeft u alleen uw eindpunt van uw toepassing of load balancer (als u die heeft) te wijzigen naar de nieuwe PostgreSQL 12-server.
Als je een load balancer zoals HAProxy hebt, kun je deze configureren met de PostgreSQL 11 als actief en de PostgreSQL 12 als back-up, op deze manier:
Dus, als je gewoon het oude primaire knooppunt in PostgreSQL 11 afsluit, de back-upserver, in dit geval in PostgreSQL 12, begint het verkeer op een transparante manier voor de gebruiker/toepassing te ontvangen.
Aan het einde van de migratie kunt u het abonnement in uw nieuwe primaire node in PostgreSQL 12 verwijderen:
world=# DROP SUBSCRIPTION sub1;
NOTICE: dropped replication slot "sub1" on publisher
DROP SUBSCRIPTION
En controleer of het correct is verwijderd:
world=# SELECT * FROM pg_subscription_rel;
(0 rows)
world=# SELECT * FROM pg_stat_subscription;
(0 rows)
Beperkingen
Voordat u de logische replicatie gebruikt, moet u rekening houden met de volgende beperkingen:
- Het databaseschema en de DDL-opdrachten worden niet gerepliceerd. Het initiële schema kan worden gekopieerd met pg_dump --schema-only.
- Volgordegegevens worden niet gerepliceerd. De gegevens in seriële of identiteitskolommen die worden ondersteund door reeksen, worden gerepliceerd als onderdeel van de tabel, maar de reeks zelf geeft nog steeds de startwaarde van de abonnee weer.
- Replicatie van TRUNCATE-commando's wordt ondersteund, maar enige voorzichtigheid is geboden bij het afkappen van groepen tabellen die zijn verbonden door externe sleutels. Bij het repliceren van een afkapactie zal de abonnee dezelfde groep tabellen afkappen die op de uitgever is afgekapt, ofwel expliciet gespecificeerd of impliciet verzameld via CASCADE, minus tabellen die geen deel uitmaken van het abonnement. Dit werkt correct als alle betrokken tabellen deel uitmaken van hetzelfde abonnement. Maar als sommige tabellen die op de abonnee moeten worden afgekapt, externe-sleutelkoppelingen hebben naar tabellen die geen deel uitmaken van hetzelfde (of een willekeurig) abonnement, dan zal de toepassing van de afkapactie op de abonnee mislukken.
- Grote objecten worden niet gerepliceerd. Daar is geen oplossing voor, behalve het opslaan van gegevens in normale tabellen.
- Replicatie is alleen mogelijk van basistabellen naar basistabellen. Dat wil zeggen, de tabellen aan de publicatie- en aan de abonnementszijde moeten normale tabellen zijn, geen views, gematerialiseerde views, partitie-roottabellen of vreemde tabellen. In het geval van partities kunt u een partitiehiërarchie één-op-één repliceren, maar u kunt momenteel niet repliceren naar een anders gepartitioneerde opstelling.
Conclusie
Het up-to-date houden van je PostgreSQL-server door regelmatige upgrades uit te voeren was tot de PostgreSQL 10-versie een noodzakelijke maar moeilijke taak. Gelukkig is het nu een ander verhaal dankzij logische replicatie.
In deze blog hebben we een korte inleiding gegeven tot logische replicatie, een PostgreSQL-functie die native is geïntroduceerd in versie 10, en we hebben je laten zien hoe het je kan helpen deze upgrade van PostgreSQL 11 naar PostgreSQL 12-uitdaging te volbrengen zonder downtime-strategie.