Natív PostgreSQL replikáció

A Unix/Linux szerverek üzemeltetése wikiből
A lap korábbi változatát látod, amilyen KornAndras (vitalap | szerkesztései) 2012. december 13., 14:22-kor történt szerkesztése után volt.

Tartalomjegyzék

1 Bevezetés

A PostgreSQL 9.0-ás verziójától kezdve támogatja a natív replikációt, ami talán az egyik legjobban várt funkciója hosszú idők óta. Eddig is léteztek más replikációs megoldások (PgPool II, Slony, stb.), amelyek akár többlet funkcionalitással is rendelkezhettek (load balancing), mégsem tudják pótolni egy natív megoldás előnyeit. A jelen szócikk ezen témakörbe nyújt egy kis betekintést.

A replikáció konfigurálásához két debian Linux operációs rendszert használok csomagból telepített PostgreSQL 9.1 verziókkal. Az alaprendszer squeeze, ehhez a szükséges binárisokat a wheezy tárolóiból töltöttem le (csak hozzá kell adni a tárolókat a sources.list-hez). A két szerver közvetlenül is össze lesz kötve egymással (172.16.0.0/24-es hálózat).

Fájl:postgres repl.png

2 Követelmények

2.1 Replikáció követelményei

A replikációnak több követelménye van, ami nem csak a PostgreSQL verziószámára terjed ki:

  • a replikációban részt vevő operációs rendszereknek azonos architektúrájúnak kell lennie
  • legalább PostgreSQL 9.0

Azonos architektúrának számít, ha mind amd64, i386 stb. Látszik hogy nem szabad keverni 32 és 64 bites rendszereket, csak tisztán az egyiket, ill. tisztán a másikat használhatjuk. Törekedni kell a PostgreSQL-t is azonos verzión tartani. Ha frissítjük, célszerű először a slave rendszerrel kezdeni, mert valószínűbb, hogy egy új rendszer képes feldolgozni egy régebbi üzeneteit, mint fordítva.

2.2 Failover követelményei

  • egy virtuális IP cím

A virtuális IP címen (VIP) osztoznak a szerverek. Ha a címet épp birtokló szerver elérhetetlenné válik, a másik szerver átveszi tőle a VIP-et.

3 A replikáció működése

A PostgreSQL replikáció a WAL (Write Ahead Log) szegmensek átvitelén alapul. A WAL tömören annyi, hogy mielőtt egy tranzakciót érvényesítenénk az adatbazison (pl. egy rekordot beszúrnánk), a tranzakciót leíró WAL szegmenst egy olyan perzisztens tárra írjuk, ami áramkimaradás, rendszerleállás, egyszóval hiba esetén is hibátlanul visszaolvasható. Egy WAL szegmens mérete tipikusan 16 Mb, de ettől különböző értéket is megadhatunk. A perzisztens tárra írás után az adatbázis feldolgozza a WAL fájlokat.

4 Replikáció fajtái

A replikáció fajtáit többféleképp is csoportosíthatjuk. Az egyik szempont lehetne az, hogy egyszerre hány adatbázist tudunk írni a clusterünkben, a másik hogy a módosításokra milyen feltételekkel válaszolunk.

4.1 Master - slave

A legegyszerűbb megvalósítás a master - slave alapú felállás. Ekkor a több adatbázisunkból mindig csak egyet írhatunk (master), viszont az olvasások történhetnek a master-ből vagy bármely csak olvasható (slave) adatbázisból. Ha egy load balancing célszoftvert is alkalmazunk, akkor megtehetjük azt, hogy az INSERT és UPDATE műveletek a master-re továbbítódjanak, míg az olvasások megoszlanak a slave-ek között.

4.2 Master - master avagy multi master

A másik felállás szerint az összes adatbázisba írhatunk és az egyes beírt rekordokat az adatbázisok maguk között replikálják. Ez a bonyolultabb eset, a PostgreSQL-ben nem található erre megvalósítás, de egyéb szoftverekkel elérhető (pl. Bucardo). A 9.3+ verziótól kezdve lehet elérhető lesz.

4.3 Fájl alapú

A fájl alapú replikáció a WAL szegmensek átmásolásával érhető el. A master szerver az író/módosító műveletek hatására WAL szegmenseket generál, ezeket a fájlokat kell átmásolni a slave szerverekre. Az átmásolást nekünk kell megoldani, erre általában rsync-et használnak. A hátránya, hogy explicit átmásolás nélkül nincs replikáció. Egy cron-ból történő rsync meg tudja oldani a másolást, de ez legalább egy perces késleltetést jelent és nem túl szép. A WAL szegmensek mérete általában 16 MB. Ebből következik az a hátrány is, hogy amíg egy ilyen fájl nem "töltődött" fel teljesen, nem is másolgatjuk át, hiszen akkor többször jelennének meg ugyanazok a műveletek a slave oldalon. Tehát amíg nem történik egy WAL-nyi változtatást, nincs értelme átmásolni a WAL szemgenst, így jelentős késleltetést szenvedhet el a replikáció alacsony írással rendelkező adatbázisok esetén.

4.4 Streaming

A streaming replikáció során egy wal sender nevű process fog elindulni a master szerverünkön, míg a slave-eken egy-egy wal receiver. A céljuk a WAL szegmensek rekordjainak folyamatos átvitele a master-ről a slave-ekre. Ezzel a megoldással nem kell rsync-elnünk, az adatbázisok lerendezik egymás között a rekordok továbbítását. Az előnye érezhetően a nagyon alacsony késleltetés és egyszerűbb is mint az rsync-es megoldás.

4.5 Aszinkron

Aszinkron replikáció esetén az egyes WAL rekordok a masteren történő megjelenése és a slave-eken történő feldolgozás között lehet egy kis késletetés. Ez azt jelenti, hogy egy módosító műveletet a master azelőtt nyugtáz, mielőtt a változtatásoknak megfelelő WAL rekordok a slave-ekre eljutottok volna és végrehajtották volna őket.

4.6 Szinkron

Szinkron replikáció esetén kijelölhetünk egy! (csakis egy) slave szervert, akivel a master szinkron kapcsolatot fog felépíteni. Szinkron kapcsolat alatt azt értem, hogy egy módosító műveletet a master mindaddíg nem igazol vissza, míg azt a szinkron slave társa végre nem hajtotta. Ez is egy (remélhetőleg kis) késletetést fog a rendszerbe vinni, hiszen a WAL szegmens rekordjait át kell másolni, ugyanazt a műveletet két helyen is végre kell hajtani, a szinkron slavenek vissza kell ígazolni és a master csak ezután tudja nyugtázni a tranzakciót. Ha ilyen replikáción gondolkodunk, mindenképp olyan slave-et válasszunk szinkron társnak a master mellé, ami minél gyorsabb és minél kisebb válaszidejű kapcsolaton elérhető. Minimális követelménynek tekinthető, hogy legalább egy LAN-on legyen a két szerver.

4.7 Kaszkádosított

A kaszkádosított replikáció egy kicsit kilóg a sorból abban az értelemben, hogy ez inkább replikációs terheléselosztásnak tekinthető. A PostgreSQL 9.2-es verziójától kezdődően használhatjuk, a segítségével nem kell az összes slave-nek a masterről replikálnia, az mintegy "hullám" végighaladhat a rendszerünkön. Képzeljünk el egy olyan esetet, amikor van van masterünk, mellette egy szinkron slave és tfh. két slave replikál a szinkron slave-ről, megint néhány az előzző slave-ekről és így tovább. Ezzel a mastert jelentősen tehermentesítettük. Érezzük, hogy ez a feature főleg nagy clusterek építésekor hasznos, ahol az olvasások jól szétoszthatók.

5 Streaming aszinkron replikáció konfigurálása

Először is fel kell telepítenünk mindkét gépünkön a PostgreSQL-t:

apt-get install postgresql-9.1

Ez a parancs feltelepíti a szükséges függőségeket is, a konfigurációs állományokat a /etc/postgresql/9.1/main alá helyezi. A legfontosabb konfig fájl a postgresql.conf és a pg_hba.conf. Az előbbiben állíthatjuk be a PostgreSQL paramétereit, az utóbbiban pedig a hozzáféréseket szabályozhatjuk a szolgáltatott adatbázisokhoz. Mindkét állományra szükségünk lesz a továbbiakban. Először is a master és a slave szerveren állítsuk be, hogy a megfelelő IP címeken figyeljenek. Ehhez módosítsuk a postgresql.conf fájl ezen sorát

#listen_addresses = 'localhost'         # what IP address(es) to listen on;

erre:

listen_addresses = 'localhost, 192.168.100.10'              # what IP address(es) to listen on;

(természetesen az IP címeket az adott gépnek megfelelően kell behelyettesíteni.) A replikáció lebonyolítására szükségünk van egy felhasználóra, akinek a nevében ez folyni fog. Megtehetjük, hogy a beépített postgres usert használjuk, de ha törekszünk az épp elegendő szintű jogosultságok kiosztására (Principle of least privileges), akkor létre kell hoznunk egy új usert. Ehhez váltsunk át a postgres felhasználóra, indítsunk egy psql shellt és hozzuk létre a usert:

su - postgres
psql
CREATE ROLE replicator WITH LOGIN REPLICATION ENCRYPTED PASSWORD 'vasbeton';

Ezzel a megoldással a replikáló userünknek pont annyi joga lesz, mint amire szüksége van. Egy user (role + LOGIN) létrehozásakor a pg_hba.conf fájlban fel kell vennünk egy rekordot, hogy milyen paraméterek mellett engedélyezzük a bejelentkezését. Adjuk hozzá tehát a masteren a pg_hba.conf állományhoz az alábbi sort:

host	replication	replicator	192.168.100.20/32	md5

Ez azt jelenti, hogy a replication adatbázishoz a replicator (ezt mi hoztuk létre) user a 192.168.100.20-as IP címről csatlakozhat, a jelszavát pedig md5 hashelve küldi el titkosítatlanul. Most állítsuk be a masteren, hogy tudjon a hot standby párjáról. Ehhez szerkesszük a postgresql.conf fájlt:

wal_level = hot_standby   # engedélyezi a read-only lekérdezéseket a host standby rendszeren

max_wal_senders = 5       # egyszerre max. ennyi standby szerver kapcsolódhat a masterre

# ennyi WAL szegmenst fog megőrizni a master a pg_xlog könyvtárban.
# Erre azért van szükség, hogy az online backup és a streaming replikáció megkezdése között 
# nehogy törölje a master ezeket a fájlokat. A WAL szegmensek általában 16 MB méretűek.
wal_keep_segments = 16    

# %f jelenti a fájlnevet, a %p pedig a path-t. Fontos, hogy a pg_xlog 
#  könyvtárat használjuk, mert ide kerülnek a WAL szegmensek
archive_command = 'cp -i %p /var/lib/postgresql/9.1/main/pg_xlog/%f'

archive_mode = on

Ezután indítsuk el a masteren a portgrest és készítsünk róla egy backupot, amit átmásolunk a slave-re:

su - postgres
# az rsync miatt érdemes eljátszani a szervereink postgres felhasználói
# között az SSH kulcscserét. Akár egy scriptet is írhatunk a célra, megéri, mert az üzemeltetés
# során használnunk kell párszor.
psql -c "SELECT pg_start_backup('label', true)"
cd /var/lib/postgresql/9.1/main
rsync -ave ssh /var/lib/postgresql/9.1/main/ 192.168.100.20:/var/lib/postgresql/9.1/main \\
  --exclude postmaster.opts  --exclude server.crt  --exclude server.key  --exclude postmaster.pid
psql -c "SELECT pg_stop_backup()"

Most térjünk vissza a slave szerverhez. A pg_hba.conf-hoz adjuk hozzá az alábbi sort:

host	replication	replicator	192.168.100.10/32	md5

A postgresql.conf-ban pedig állítsuk át az alábbi sort:

hot_standby = off

erre:

hot_standby = on

Létre kell hoznunk egy recovery.conf állományt a $PGDATA alatt. Ez debian alaptelepítés esetén a /var/lib/postgresql/9.1/main. A recovery.conf tartalma legyen ez:

# streaming replikációnál mindig on
standby_mode          = 'on'

primary_conninfo      = 'host=172.16.0.1 port=5432 user=replicator password=vasbeton'

# ez indítja a failovert
trigger_file = '/tmp/pgsql.trigger'

# a masteren lévő archive_command párja:
restore_command = 'cp -i /var/lib/postgresql/9.1/main/pg_xlog/%f %p'

Ha ezzel megvagyunk, a munka "aszinkron" része már készen is van. Indítsuk újra az adatbázisainkat, és nézzük meg az alábbi lekérdezést a masteren:

postgres=# select application_name, state, sent_location, write_location, sync_state from pg_stat_replication ;
 application_name |   state   | sent_location | write_location | sync_state 
------------------+-----------+---------------+----------------+------------
 walreceiver      | streaming | 0/8001650     | 0/8001650      | async
(1 row)

Láthatjuk, hogy a streaming aszinkron replikáció működik. Érdemes lehet megnézni a wal sender folyamatot a masteren:

root@dbtest1:~# ps ax | grep wal
1442 ?        Ss     0:00 postgres: wal writer process                                                                                         
1460 ?        Ss     0:00 postgres: wal sender process replicator 192.168.100.20(53859) streaming 0/8001650 

A slave-en a WAL receiver process-t figyelhetjük meg:

root@dbtest2:~# ps ax  | grep wal
 1329 ?        Ss     0:00 postgres: wal receiver process   streaming 0/8001650 

6 Streaming szinkron replikáció konfigurálása

A szinkron replikáció beállítása az aszinkronéra épül, így először a fenti pont szerinti lépéseket kell végrehajtani. Ha ez kész, már szinte célba is értünk, hiszen csak egy-egy paramétert kell módosítani a masteren és a slave-en. Lássuk ezeket! A masteren a synchronous_standby_names paramétert kell beállítanunk a postgresql.conf-ban a szinkron standby szerverünk nevére. Nem kell megegyeznie az FQDN-ével, látni fogjuk hogy a túloldalon is meg kell adnunk ezt a nevet. Az is megkötés, hogy egyszerre csak egy szinkron standby szerver kapcsolódhat a masterre.

synchronous_standby_names = 'dbtest2'

A slave oldalán szintén nagyon egyszerű a helyzet, a recovery.conf primary_connfinfo paraméterét kell bővítenünk egy application_name opcióval:

# aszinkron esetben:
#primary_conninfo      = 'host=192.168.100.10 port=5432 user=replicator password=vasbeton'

# szinkron esetben
primary_conninfo      = 'host=191.168.100.10 port=5432 user=replicator password=vasbeton application_name=dbtest2'
<pre>

Meg is volnánk :) Nézzük meg a masteren a pg_stat_replication view tartalmát, változott-e:
<pre>
postgres=# select application_name, state, sent_location, write_location, sync_state from pg_stat_replication;
application_name |   state   | sent_location | write_location | sync_state 
------------------+-----------+---------------+----------------+------------
 dbtest2          | streaming | 0/80016A8     | 0/80016A8      | sync
(1 row)

Érdekes tartalma van a lognak (/var/log/postgresql/postgresql-9.1-main.log) is (felül a frissebb bejegyzések):

2012-12-10 23:58:58 CET LOG:  standby "dbtest2" is now the synchronous standby with priority 1
2012-12-10 23:58:53 CET LOG:  incomplete startup packet
2012-12-10 23:58:53 CET LOG:  database system is ready to accept connections
2012-12-10 23:58:53 CET LOG:  autovacuum launcher started
2012-12-10 23:58:53 CET LOG:  database system was shut down at 2012-12-10 23:58:28 CET

Az incomplete startup packet rémisztő lehet, de azt a replikáló szerver okozza a pollinggal. A slave szerveren is érdemes szétnézni:

2012-12-10 23:58:58 CET LOG:  streaming replication successfully connected to primary
2012-12-10 23:58:58 CET LOG:  database system is ready to accept read only connections
2012-12-10 23:58:58 CET LOG:  consistent recovery state reached at 0/8001650

7 Magas rendelkezésre állás virtuális IP-vel

A nagyobb rendszereket üzemeltetők gyakran elgondolkodnak azon, hogyan tehetnék még megbízhatóbbá rendszerüket: mit tegyenek, egy szerver kiesése esetén, esetleg tudják-e ezt a cselekvést automatizálni. A következő bekezdés egy ilyen módszert ragad ki a sok megoldás közül.

7.1 Mi az a virtuális IP cím?

A virtuális IP cím (röviden VIP) egy olyan IP cím, ami nem kimondottan egy szerverhez vagy egy hálózati kártyához van rendelve, hanem több alkalmazás, szerver osztozik rajta. Ha egyidejűleg csak egyetlen géphez rendeljük hozzá, majd a gép kiesése esetén egy másik géphez ami képes átvenni a funkcióját, lényegében egy VIP alapú failover megoldást kapunk.

7.2 Hogyan tudom konfigurálni?

A VIP automatikus kezeléséhez a keepalived programot fogom használni. A keepalived egy "útválasztó" program, amivel terheléelosztást (loadbalancing) és magas rendelkezésreállást (High Availability - HA) tudunk megvalósítani. Egy adatbázis esetében a terheléselosztás nem olyan egyszerű feladat, mint egy webszerver esetében, ezért erre más megoldásokat szoktak használni (pl. pgpool). Itt én csak a HA rész beállítását fogom bemutatni.

7.3 Automatikus failover

A keepalived telepítéséhez futtassuk az alábbi parancsot a masteren és a slave-en egyaránt:

apt-get install keepalived

A /etc/keepalived alatt fogja keresni a keepalived.conf-ot, amit nekünk kell létrehoznunk. A master esetében a fájl tartalma legyen ez:

vrrp_instance VI_1 {
  # kezdeti állapot
  state MASTER
  interface eth0

  # egy NIC-et több vrrpd is használhat, ezzel lehet őket
  # megkülönböztetni
  virtual_router_id 1

  # master választáskor a minél magasabb prioritással lehetünk masterek
  # a dokumentáció szerint ha egy szervert nagyon szeretnénk masternek,
  # akkor a prioritása legyen 50-el magasabb mint a többié
  priority 100
  
  # ezt a paramétert itt is meg kell adni, hogy a "feltámadása" esetén 
  # ne vegye el a VIP-et a slave-től
  nopreempt

  # VIP
  virtual_ipaddress {
    192.168.100.100/24 dev eth0 label eth0:vip
  }
}

A slave esetén a keepalived.conf tartalma:

vrrp_instance VI_1 {
  # kezdeti állapot
  state BACKUP
  interface eth0

  # ha a slave megkapta a VIP-et és a master feléled, akkor is
  # megtartja a VIP-et
  nopreempt

  # egy NIC-et több vrrpd is használhat, ezzel lehet őket
  # megkülönböztetni
  virtual_router_id 1

  # master választáskor a minél magasabb prioritással lehetünk masterek
  # a dokumentáció szerint ha egy szervert nagyon szeretnénk masternek,
  # akkor a prioritása legyen 50-el magasabb mint a többié
  priority 50

  # VIP
  virtual_ipaddress {
    192.168.100.100/24 dev eth0 label eth0:vip
  }

  # slave -> master átmenet
  notify_master "/etc/keepalived/to_master.sh"

  # master -> slave átmenet
  notify_slave "/etc/keepalived/to_slave.sh"
}

A to_master.sh pedig így néz ki:

#!/bin/bash

# létrehozza a triggerfájlt -> a slave masterre vált és írhatóvá válik
touch /tmp/pgsql.trigger

A to_slave tartalma:

#!/bin/bash

# törli a triggerfájlt
rm /tmp/pgsql.trigger

Amikor a slave átveszi a VIP-et, az azért fordulhat elő, mert a master elérhetetlenné válik (vagy rossz esetben a slave...), ekkor létrehoz egy trigger fájlt amit a PostrgreSQL-t a failover mechanizmus elindítására készteti. Az standby adatbázis a WAL szegmensek feldolgozása után a recovery.conf-ot recovery.done-ra nevezi át és átveszi a master szerepét. A failover-t nem csak a trigger fájllal kényszeríthetjük ki, a pg_ctl promote parancs ugyanazt az eredményt éri el, ekkor nincs szükség trigger fájlra sem.

Ezután a preferált (lényegében csak ez van kigoldozva) mód a helyreállítás manuális levezénylése. Amikor az eredeti master újra elérhetővé válik, át kell állítanunk slave módba, majd az ismertetett backup módszerrel konzisztens állapotba hozni a két adatbázist. Ezzel meggyorsíthatjuk a folyamatot, nem kell a WAL szegmenseket végigolvasnia és feldolgoznia.

Még nem vagyunk teljesen kész, a VIP-ről ugyanis nem lehet elérni az adatbázist. Ennek az orvoslására használhatjuk az iptables-t. Kell egy rule, ami a VIP 5432-es portjának a forgalmát átdbja egy másik portra. A legszebb az lenne, ha a loopback interfész 5432-es portjára továbbítaná, de ezt nem siekerült megvalósítani, helyette egy másik hálózati interfészre továbbítottam a csomagokat. Ha az alábbi parancsokat a /etc/rc.local-ba írjuk, akkor újraindítás után is megmaradnak:

#!/bin/bash

# törli a filter tábla tartalmát
iptables -F
# törli a nat tábla tartalmát
iptables -F -t nat

#DST_ADDR="127.0.0.1"
DST_ADDR="172.16.0.1"
VIP_ADDR="192.168.100.100"
DST_PORT="5432"

# a DNAT cél gondoskodik arról, hogy a válasz csomagokat is megkaphassuk
iptables -t nat -A PREROUTING -p tcp --dst $VIP_ADDR --dport $DST_PORT -j DNAT --to-destination $DST_ADDR:$DST_PORT

A /proc/sys/net/ipv4/ip_forward tartalmának 1-be állítására nincs szükség. A slave szerveren értelemszerűen a saját IP címét kell megadni a DST_ADDR változóban.

7.4 A failover tesztelése

A VIP failover késleltetésének bemutatására a jól ismert ping programot használtam. A teszt abból állt, hogy a VIP-et pingeltem, közben pedig lekapcsoltam az eth0-ás hálózati interfészét a VIP-et épp birtokló szervernek, lényegében egy kábel kihúzást szimuláltam. A válaszidők között látszik a VIP átadása, de csak egy válasznak (icmp_req=171) növelte meg a válaszidejét néhány ms-el:

64 bytes from 192.168.100.100: icmp_req=163 ttl=64 time=0.861 ms
64 bytes from 192.168.100.100: icmp_req=164 ttl=64 time=1.22 ms
64 bytes from 192.168.100.100: icmp_req=165 ttl=64 time=0.697 ms
64 bytes from 192.168.100.100: icmp_req=170 ttl=64 time=2.30 ms
64 bytes from 192.168.100.100: icmp_req=171 ttl=64 time=6.54 ms
64 bytes from 192.168.100.100: icmp_req=172 ttl=64 time=0.310 ms
64 bytes from 192.168.100.100: icmp_req=173 ttl=64 time=0.534 ms

Nézzük meg psql shellel is, mi történik VIP átadáskor. Az inet_server_addr() SQL függvénnyel lekérdezhetjük, hogy a PostgreSQL szervernek melyik IP címére csatlakoztunk. A VIP átadás után ennek meg kell változnia. Lássuk mi az eredmény, ha a VIP-et a master birtokolja:

root@debian:~# psql -h 192.168.100.100 -U pgbench -d pgbench
psql (9.1.4, server 9.1.6)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.

pgbench=> select inet_server_addr();
 inet_server_addr 
------------------
 172.16.0.1
(1 row)

A válasz logikus, hiszen a VIP-ről erre az IP címre továbbítjuk a csomagokat. A master szerver leállításával elkezdődik a failover folyamat, aminek eredményeként a volt slave megkapja a VIP-et és masterré állítja magát:

root@debian:~# psql -h 192.168.100.100 -U pgbench -d pgbench
psql (9.1.4, server 9.1.6)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.

pgbench=> select inet_server_addr();
 inet_server_addr 
------------------
 172.16.0.2
(1 row)

A PostgreSQL fejlesztő csapata úgy gondolom jó irányban halad, hogy elkezdtek dolgozni natív replikáción. A 9.3+ verziótól akár a multi master replikáció is megjelenhet. Beépített HA megoldásra nem igazán lehet számítani, úgy gondolják hogy arra már léteznek egyéb, third party szoftverek.

Személyes eszközök