Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

V článku vám řeknu, jak jsme k problematice odolnosti proti chybám PostgreSQL přistoupili, proč se pro nás stala důležitou a co se nakonec stalo.

Máme velmi nabitou službu: 2,5 milionu uživatelů po celém světě, více než 50 tisíc aktivních uživatelů každý den. Servery jsou umístěny v Amazone v jednom regionu Irska: 100+ různých serverů neustále pracuje, z toho téměř 50 s databázemi.

Celý backend je velká monolitická stavová Java aplikace, která udržuje stálé websocket spojení s klientem. Když na stejné desce pracuje několik uživatelů současně, všichni vidí změny v reálném čase, protože každou změnu zapisujeme do databáze. Do našich databází máme asi 10 tisíc požadavků za sekundu. Při špičkovém zatížení v Redis zapisujeme 80-100 XNUMX požadavků za sekundu.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Proč jsme přešli z Redis na PostgreSQL

Zpočátku naše služba spolupracovala s Redis, úložištěm hodnot klíč-hodnota, které ukládá všechna data do paměti RAM serveru.

Výhody Redis:

  1. Vysoká rychlost odezvy, protože vše je uloženo v paměti;
  2. Snadné zálohování a replikace.

Nevýhody Redis pro nás:

  1. Neexistují žádné skutečné transakce. Pokusili jsme se je simulovat na úrovni naší aplikace. Bohužel to ne vždy fungovalo dobře a vyžadovalo to psaní velmi složitého kódu.
  2. Množství dat je omezeno velikostí paměti. S narůstajícím množstvím dat poroste paměť a nakonec narazíme na charakteristiky vybrané instance, což v AWS vyžaduje zastavení naší služby, aby se změnil typ instance.
  3. Je nutné neustále udržovat nízkou úroveň latence, protože. máme velmi velký počet žádostí. Optimální úroveň zpoždění je pro nás 17-20 ms. Na úrovni 30-40 ms dostáváme dlouhé odpovědi na požadavky naší aplikace a degradaci služby. Bohužel se nám to stalo v září 2018, kdy jedna z instancí s Redis z nějakého důvodu obdržela latenci 2x větší než obvykle. Abychom problém vyřešili, zastavili jsme službu v poledne z důvodu neplánované údržby a nahradili problematickou instanci Redis.
  4. Je snadné získat nekonzistenci dat i s drobnými chybami v kódu a pak strávit spoustu času psaním kódu pro opravu těchto dat.

Vzali jsme v úvahu nevýhody a uvědomili jsme si, že musíme přejít na něco pohodlnějšího, s normálními transakcemi a menší závislostí na latenci. Provedl výzkum, analyzoval mnoho možností a vybral PostgreSQL.

Již 1,5 roku přecházíme na novou databázi a přesunuli jsme jen malou část dat, takže nyní pracujeme současně s Redis a PostgreSQL. Je napsáno více informací o fázích přesunu a přepínání dat mezi databázemi článek mého kolegy.

Když jsme se poprvé začali stěhovat, naše aplikace pracovala přímo s databází a přistupovala k hlavnímu Redis a PostgreSQL. Cluster PostgreSQL se skládal z hlavního serveru a repliky s asynchronní replikací. Takto vypadalo schéma databáze:
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Implementace PgBouncer

Zatímco jsme se stěhovali, produkt se také vyvíjel: zvýšil se počet uživatelů a počet serverů, které pracovaly s PostgreSQL, a začala nám chybět připojení. PostgreSQL vytváří samostatný proces pro každé připojení a spotřebovává zdroje. Do určitého bodu můžete zvýšit počet připojení, jinak existuje šance na dosažení suboptimálního výkonu databáze. Ideální možností v takové situaci by bylo zvolit správce připojení, který bude stát před základnou.

Pro správce připojení jsme měli dvě možnosti: Pgpool a PgBouncer. První z nich ale nepodporuje transakční režim práce s databází, proto jsme zvolili PgBouncer.

Nastavili jsme následující schéma práce: naše aplikace přistupuje k jednomu PgBounceru, za kterým jsou Master PostgreSQL a za každým masterem je jedna replika s asynchronní replikací.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Zároveň jsme nemohli uložit celé množství dat do PostgreSQL a rychlost práce s databází pro nás byla důležitá, proto jsme začali PostgreSQL shardovat na aplikační úrovni. Poměrně pohodlné je k tomu výše popsané schéma: při přidávání nového shardu PostgreSQL stačí aktualizovat konfiguraci PgBouncer a aplikace může s novým shardem ihned pracovat.

Selhání PgBouncer

Toto schéma fungovalo až do okamžiku, kdy zemřela jediná instance PgBouncer. Jsme v AWS, kde všechny instance běží na hardwaru, který pravidelně umírá. V takových případech se instance jednoduše přesune na nový hardware a znovu funguje. To se stalo s PgBouncer, ale stal se nedostupným. Výsledkem tohoto pádu byla nedostupnost naší služby po dobu 25 minut. AWS doporučuje pro takové situace využít redundanci na straně uživatele, která u nás v té době nebyla implementována.

Poté jsme vážně přemýšleli o odolnosti proti chybám clusterů PgBouncer a PostgreSQL, protože podobná situace by se mohla stát s jakoukoli instancí v našem účtu AWS.

Schéma odolnosti proti chybám PgBouncer jsme vytvořili následovně: všechny aplikační servery přistupují k Network Load Balancer, za nímž jsou dva PgBouncery. Každý PgBouncer se dívá na stejný master PostgreSQL každého fragmentu. Pokud dojde znovu k selhání instance AWS, veškerý provoz je přesměrován přes jiný PgBouncer. Přepnutí při selhání Network Load Balancer zajišťuje AWS.

Toto schéma usnadňuje přidávání nových serverů PgBouncer.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Vytvořte cluster s podporou převzetí služeb při selhání PostgreSQL

Při řešení tohoto problému jsme zvažovali různé možnosti: self-written failover, repmgr, AWS RDS, Patroni.

Samostatně psané skripty

Mohou monitorovat práci masteru a v případě jeho selhání povýšit repliku na master a aktualizovat konfiguraci PgBouncer.

Výhodou tohoto přístupu je maximální jednoduchost, protože si skripty píšete sami a přesně chápete, jak fungují.

nevýhody:

  • Master možná nezemřel, místo toho mohlo dojít k selhání sítě. Failover, aniž by si toho byl vědom, povýší repliku na master, zatímco starý master bude pokračovat v práci. V důsledku toho dostaneme dva servery do role master a nebudeme vědět, který z nich má nejnovější aktuální data. Tato situace se také nazývá split-brain;
  • Zůstali jsme bez odezvy. V naší konfiguraci master a jedna replika se po přepnutí replika posune nahoru na master a my už repliky nemáme, takže musíme ručně přidat novou repliku;
  • Potřebujeme další monitorování operace převzetí služeb při selhání, zatímco máme 12 shardů PostgreSQL, což znamená, že musíme monitorovat 12 clusterů. S rostoucím počtem fragmentů musíte také pamatovat na aktualizaci převzetí služeb při selhání.

Samostatně napsané převzetí služeb při selhání vypadá velmi složitě a vyžaduje netriviální podporu. S jedním PostgreSQL clusterem by to byla nejjednodušší možnost, ale neškáluje se, takže pro nás není vhodný.

Repmgr

Replication Manager pro clustery PostgreSQL, který může spravovat provoz clusteru PostgreSQL. Zároveň nemá automatické převzetí služeb při selhání z krabice, takže pro práci budete muset na hotové řešení napsat svůj vlastní „balíček“. Takže všechno může být ještě složitější než u skriptů napsaných sami, takže jsme Repmgr ani nezkoušeli.

AWS RDS

Podporuje vše, co potřebujeme, ví, jak vytvářet zálohy a udržuje fond připojení. Má automatické přepínání: když master zemře, replika se stane novým masterem a AWS změní dns záznam na nový master, zatímco repliky mohou být umístěny v různých AZ.

Mezi nevýhody patří nedostatek jemných úprav. Jako příklad jemného ladění: naše instance mají omezení pro připojení tcp, což bohužel nelze provést v RDS:

net.ipv4.tcp_keepalive_time=10
net.ipv4.tcp_keepalive_intvl=1
net.ipv4.tcp_keepalive_probes=5
net.ipv4.tcp_retries2=3

AWS RDS je navíc téměř dvakrát dražší než běžná cena instance, což byl hlavní důvod pro opuštění tohoto řešení.

Patroni

Toto je pythonová šablona pro správu PostgreSQL s dobrou dokumentací, automatickým převzetím služeb při selhání a zdrojovým kódem na githubu.

Výhody Patroni:

  • Každý konfigurační parametr je popsán, je jasné, jak funguje;
  • Automatické převzetí služeb při selhání funguje ihned po vybalení;
  • Psáno v pythonu, a protože sami hodně píšeme v pythonu, bude pro nás snazší řešit problémy a možná i pomoci rozvoji projektu;
  • Plně spravuje PostgreSQL, umožňuje změnit konfiguraci na všech uzlech clusteru najednou a pokud je pro použití nové konfigurace nutné cluster restartovat, lze to provést znovu pomocí Patroni.

nevýhody:

  • Z dokumentace není jasné, jak s PgBouncerem správně pracovat. I když je těžké to nazvat mínusem, protože úkolem Patroni je spravovat PostgreSQL a jak budou probíhat připojení k Patroni, to už je náš problém;
  • Existuje několik příkladů implementace Patroni na velkých objemech, zatímco existuje mnoho příkladů implementace od nuly.

V důsledku toho jsme zvolili Patroni k vytvoření clusteru s podporou převzetí služeb při selhání.

Proces implementace Patroni

Před Patroni jsme měli 12 shardů PostgreSQL v konfiguraci jednoho hlavního serveru a jedné repliky s asynchronní replikací. Aplikační servery přistupovaly k databázím přes Network Load Balancer, za kterým byly dvě instance s PgBouncer a za nimi všechny PostgreSQL servery.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Pro implementaci Patroni jsme potřebovali vybrat konfiguraci clusteru distribuovaného úložiště. Patroni pracuje s distribuovanými konfiguračními úložnými systémy, jako jsou etcd, Zookeeper, Consul. Právě máme na trhu plnohodnotný cluster Consul, který funguje ve spojení s Vaultem a už ho nepoužíváme. Skvělý důvod, proč začít používat Consul k zamýšlenému účelu.

Jak Patroni spolupracuje s konzulem

Máme klastr Consul, který se skládá ze tří uzlů, a klastr Patroni, který se skládá z vůdce a repliky (v Patroni se master nazývá vůdce klastru a otroci se nazývají repliky). Každá instance clusteru Patroni neustále posílá informace o stavu clusteru konzulovi. Proto od Consul můžete vždy zjistit aktuální konfiguraci Patroni clusteru a kdo je v tuto chvíli vedoucí.

Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Pro připojení Patroni k Consul stačí prostudovat oficiální dokumentaci, která říká, že je třeba zadat hostitele ve formátu http nebo https, v závislosti na tom, jak pracujeme s Consul, a schéma připojení, volitelně:

host: the host:port for the Consul endpoint, in format: http(s)://host:port
scheme: (optional) http or https, defaults to http

Vypadá to jednoduše, ale zde začínají úskalí. S Consul pracujeme přes zabezpečené připojení přes https a naše konfigurace připojení bude vypadat takto:

consul:
  host: https://server.production.consul:8080 
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

Ale to nejde. Při spuštění se Patroni nemůže připojit k Consul, protože se stejně snaží projít http.

Zdrojový kód Patroni pomohl vyřešit problém. Dobře, že je to napsané v pythonu. Ukazuje se, že parametr hostitele není nijak analyzován a protokol musí být specifikován ve schématu. Takto pro nás vypadá pracovní konfigurační blok pro práci s Consul:

consul:
  host: server.production.consul:8080
  scheme: https
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

konzul-šablona

Pro konfiguraci jsme tedy zvolili úložiště. Nyní musíme porozumět tomu, jak PgBouncer změní svou konfiguraci, když změní vedoucí v clusteru Patroni. Na tuto otázku není v dokumentaci žádná odpověď, protože. tam práce s PgBouncerem v zásadě popsána není.

Při hledání řešení jsme našli článek (bohužel si nepamatuji název), kde bylo napsáno, že Сonsul-template hodně pomohl při spárování PgBouncer a Patroni. To nás přimělo prozkoumat, jak funguje Consul-template.

Ukázalo se, že Consul-template neustále monitoruje konfiguraci clusteru PostgreSQL v Consul. Když se vedoucí změní, aktualizuje konfiguraci PgBouncer a odešle příkaz k jejímu opětovnému načtení.

Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Velkou výhodou šablony je, že je uložena jako kód, takže při přidávání nového shardu stačí provést nový commit a šablonu aktualizovat automaticky, s podporou principu Infrastructure as code.

Nová architektura s Patroni

V důsledku toho jsme dostali následující schéma práce:
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Všechny aplikační servery přistupují k balanceru → jsou za ním dvě instance PgBouncer → na každé instanci se spustí Consul-template, která monitoruje stav každého clusteru Patroni a monitoruje relevanci konfigurace PgBouncer, která posílá požadavky aktuálnímu vedoucímu každého shluku.

Ruční testování

Toto schéma jsme spustili před jeho spuštěním v malém testovacím prostředí a zkontrolovali jsme fungování automatického přepínání. Otevřeli tabuli, posunuli nálepku a v tu chvíli „zabili“ vůdce shluku. V AWS je to stejně jednoduché jako vypnutí instance přes konzolu.

Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Nálepka se vrátila zpět během 10-20 sekund a poté se opět začala normálně pohybovat. To znamená, že cluster Patroni fungoval správně: změnil vůdce, odeslal informace do Сonsul a Сonsul-template tyto informace okamžitě vyzvedl, nahradil konfiguraci PgBouncer a odeslal příkaz k opětovnému načtení.

Jak přežít ve vysoké zátěži a minimalizovat prostoje?

Všechno funguje perfektně! Jsou tu ale nové otázky: Jak to bude fungovat při vysoké zátěži? Jak vše rychle a bezpečně rozjet ve výrobě?

Testovací prostředí, na kterém provádíme zátěžové testování, nám pomáhá odpovědět na první otázku. Z hlediska architektury je zcela identický s produkcí a vygeneroval testovací data, která se objemem přibližně rovnají produkci. Rozhodneme se prostě „zabít“ jednoho z mistrů PostgreSQL během testu a uvidíme, co se stane. Předtím je ale důležité zkontrolovat automatické rolování, protože na tomto prostředí máme několik shardů PostgreSQL, takže dostaneme skvělé testování konfiguračních skriptů před výrobou.

Oba úkoly vypadají ambiciózně, ale máme PostgreSQL 9.6. Můžeme okamžitě upgradovat na 11.2?

Rozhodli jsme se to udělat ve 2 krocích: nejprve upgradujte na 11.2 a poté spusťte Patroni.

Aktualizace PostgreSQL

Chcete-li rychle aktualizovat verzi PostgreSQL, použijte volbu -k, ve kterém se pevné odkazy vytvářejí na disku a není potřeba kopírovat vaše data. Na základech 300-400 GB trvá aktualizace 1 sekundu.

Máme spoustu střípků, takže aktualizace musí probíhat automaticky. Za tímto účelem jsme napsali příručku Ansible, která za nás obstará celý proces aktualizace:

/usr/lib/postgresql/11/bin/pg_upgrade 
<b>--link </b>
--old-datadir='' --new-datadir='' 
 --old-bindir=''  --new-bindir='' 
 --old-options=' -c config_file=' 
 --new-options=' -c config_file='

Zde je důležité poznamenat, že před zahájením upgradu jej musíte provést s parametrem --šekabyste se ujistili, že můžete upgradovat. Náš skript také provádí náhradu konfigurací po dobu upgradu. Náš skript byl dokončen za 30 sekund, což je vynikající výsledek.

Spusťte Patroni

Chcete-li vyřešit druhý problém, stačí se podívat na konfiguraci Patroni. Oficiální úložiště má příklad konfigurace s initdb, který je zodpovědný za inicializaci nové databáze při prvním spuštění Patroni. Ale protože již máme hotovou databázi, jednoduše jsme tuto sekci z konfigurace odstranili.

Když jsme začali instalovat Patroni na již existující cluster PostgreSQL a spouštěli jej, narazili jsme na nový problém: oba servery začínaly jako vedoucí. Patroni neví nic o raném stavu clusteru a pokouší se spustit oba servery jako dva samostatné clustery se stejným názvem. Chcete-li tento problém vyřešit, musíte odstranit adresář s daty na podřízeném zařízení:

rm -rf /var/lib/postgresql/

To je třeba udělat pouze na otroku!

Když je připojena čistá replika, Patroni vytvoří vůdce základní zálohy a obnoví jej do repliky a poté dožene aktuální stav podle protokolů wall.

Dalším problémem, na který jsme narazili, je, že všechny clustery PostgreSQL jsou ve výchozím nastavení pojmenovány jako hlavní. Když každý cluster neví nic o druhém, je to normální. Ale když chcete používat Patroni, pak všechny clustery musí mít jedinečný název. Řešením je změna názvu clusteru v konfiguraci PostgreSQL.

zátěžový test

Spustili jsme test, který simuluje uživatelskou zkušenost na deskách. Když zátěž dosáhla naší průměrné denní hodnoty, zopakovali jsme úplně stejný test, jednu instanci jsme s lídrem PostgreSQL vypnuli. Automatické převzetí služeb při selhání fungovalo, jak jsme očekávali: Patroni změnil vůdce, Consul-template aktualizoval konfiguraci PgBouncer a poslal příkaz k opětovnému načtení. Podle našich grafů v Grafaně bylo jasné, že dochází ke zpožděním 20-30 sekund a malému množství chyb ze serverů spojených s připojením k databázi. To je normální situace, takové hodnoty jsou přijatelné pro náš failover a jsou rozhodně lepší než výpadek služby.

Uvedení Patroni do výroby

V důsledku toho jsme přišli s následujícím plánem:

  • Nasaďte Consul-template na servery PgBouncer a spusťte;
  • Aktualizace PostgreSQL na verzi 11.2;
  • Změňte název clusteru;
  • Spuštění klastru Patroni.

Zároveň nám naše schéma umožňuje udělat první bod téměř kdykoli, můžeme postupně odstranit každý PgBouncer z práce a nasadit a spustit na něm consul-template. Tak jsme to udělali.

Pro rychlé nasazení jsme použili Ansible, protože jsme již otestovali všechny playbooky v testovacím prostředí a doba provedení celého skriptu byla od 1,5 do 2 minut pro každý fragment. Mohli bychom zavést vše postupně do každého fragmentu bez zastavení naší služby, ale museli bychom vypnout každý PostgreSQL na několik minut. V tomto případě uživatelé, jejichž data jsou na tomto datovém fragmentu, nemohli v tuto chvíli plně pracovat, a to je pro nás nepřijatelné.

Východiskem z této situace byla plánovaná údržba, která probíhá každé 3 měsíce. Toto je okno pro plánovanou práci, kdy zcela vypneme naši službu a upgradujeme instance naší databáze. Do dalšího okna zbýval týden a my jsme se rozhodli prostě počkat a připravit se dál. Během čekací doby jsme se navíc zabezpečili: pro každý shard PostgreSQL jsme shromáždili náhradní repliku pro případ, že by se nepodařilo zachovat nejnovější data, a pro každý shard přidali novou instanci, která by se měla stát novou replikou v clusteru Patroni, aby nedošlo k provedení příkazu k odstranění dat. To vše pomohlo minimalizovat riziko chyb.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Restartovali jsme naši službu, vše fungovalo, jak mělo, uživatelé pokračovali v práci, ale na grafech jsme zaznamenali abnormálně vysoké zatížení serverů Consul.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Proč jsme to neviděli v testovacím prostředí? Tento problém velmi dobře ilustruje, že je nutné řídit se principem Infrastructure as code a vylepšit celou infrastrukturu, od testovacích prostředí až po produkci. Jinak je velmi snadné získat problém, který máme. Co se stalo? Consul se nejprve objevil v produkčním prostředí a poté v testovacích prostředích, v důsledku toho v testovacích prostředích byla verze Consul vyšší než v produkčním prostředí. Právě v jednom z vydání byl vyřešen únik CPU při práci s consul-template. Proto jsme jednoduše aktualizovali Consul, čímž jsme problém vyřešili.

Restartujte cluster Patroni

Dostali jsme však nový problém, o kterém jsme ani netušili. Při aktualizaci Consul jednoduše odebereme uzel Consul z clusteru pomocí příkazu consul leave → Patroni se připojí k jinému serveru Consul → vše funguje. Když jsme se však dostali k poslední instanci clusteru Consul a poslali jsme do něj příkaz k odchodu konzula, všechny clustery Patroni se jednoduše restartovaly a v protokolech jsme viděli následující chybu:

ERROR: get_cluster
Traceback (most recent call last):
...
RetryFailedError: 'Exceeded retry deadline'
ERROR: Error communicating with DCS
<b>LOG: database system is shut down</b>

Cluster Patroni nebyl schopen načíst informace o svém klastru a restartoval se.

Abychom našli řešení, kontaktovali jsme autory Patroni prostřednictvím problému na githubu. Navrhli vylepšení našich konfiguračních souborů:

consul:
 consul.checks: []
bootstrap:
 dcs:
   retry_timeout: 8

Podařilo se nám replikovat problém v testovacím prostředí a otestovat tyto možnosti tam, ale bohužel nefungovaly.

Problém stále zůstává nevyřešen. Plánujeme vyzkoušet následující řešení:

  • Použijte Consul-agent na každé instanci clusteru Patroni;
  • Opravte problém v kódu.

Chápeme, kde se stala chyba: problém je pravděpodobně v použití výchozího časového limitu, který není přepsán prostřednictvím konfiguračního souboru. Když je z clusteru odebrán poslední server Consul, celý cluster Consul přestane reagovat na více než sekundu, z tohoto důvodu nemůže Patroni získat stav clusteru a celý cluster zcela restartuje.

Naštěstí jsme nenarazili na další chyby.

Výsledky používání Patroni

Po úspěšném spuštění Patroni jsme přidali další repliku do každého clusteru. Nyní je v každém clusteru zdání kvora: jeden vůdce a dvě repliky, pro bezpečnostní síť v případě rozdělení mozku při přepínání.
Failover Cluster PostgreSQL + Patroni. Zkušenosti s implementací

Patroni na výrobě pracoval více než tři měsíce. Za tu dobu nám už stihl pomoci. Nedávno v AWS zemřel vedoucí jednoho z clusterů, fungovalo automatické převzetí služeb při selhání a uživatelé pokračovali v práci. Patroni svůj hlavní úkol splnil.

Malé shrnutí použití Patroni:

  • Snadné změny konfigurace. Stačí změnit konfiguraci na jedné instanci a ta se vytáhne na celý cluster. Pokud je k použití nové konfigurace vyžadován restart, Patroni vás bude informovat. Patroni dokáže restartovat celý cluster jediným příkazem, což je také velmi pohodlné.
  • Automatické převzetí služeb při selhání funguje a již nám dokázalo pomoci.
  • Aktualizace PostgreSQL bez výpadku aplikace. Nejprve musíte aktualizovat repliky na novou verzi, poté změnit odkaz v clusteru Patroni a aktualizovat starý odkaz. V tomto případě dojde k nezbytnému testování automatického převzetí služeb při selhání.

Zdroj: www.habr.com

Přidat komentář