Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

A l'article us explicaré com vam abordar el tema de la tolerància a errors de PostgreSQL, per què es va fer important per a nosaltres i què va passar al final.

Tenim un servei molt carregat: 2,5 milions d'usuaris a tot el món, més de 50 usuaris actius cada dia. Els servidors es troben a Amazone, en una regió d'Irlanda: més de 100 servidors diferents estan en funcionament constantment, dels quals gairebé 50 tenen bases de dades.

Tot el backend és una gran aplicació Java monolítica amb estat que manté una connexió constant de websocket amb el client. Quan diversos usuaris treballen al mateix tauler alhora, tots veuen els canvis en temps real, perquè escrivim cada canvi a la base de dades. Tenim unes 10 sol·licituds per segon a les nostres bases de dades. A la càrrega màxima a Redis, escrivim entre 80 i 100 XNUMX sol·licituds per segon.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Per què hem canviat de Redis a PostgreSQL

Inicialment, el nostre servei funcionava amb Redis, una botiga de valor-clau que emmagatzema totes les dades a la memòria RAM del servidor.

Avantatges de Redis:

  1. Alta velocitat de resposta, perquè tot s'emmagatzema a la memòria;
  2. Facilitat de còpia de seguretat i rèplica.

Contres de Redis per a nosaltres:

  1. No hi ha transaccions reals. Hem intentat simular-los a nivell de la nostra aplicació. Malauradament, això no sempre va funcionar bé i va requerir escriure un codi molt complex.
  2. La quantitat de dades està limitada per la quantitat de memòria. A mesura que augmenta la quantitat de dades, la memòria creixerà i, al final, ens trobarem amb les característiques de la instància seleccionada, que a AWS requereix aturar el nostre servei per canviar el tipus d'instància.
  3. Cal mantenir constantment un nivell de latència baix, perquè. tenim un gran nombre de peticions. El nivell de retard òptim per a nosaltres és de 17-20 ms. A un nivell de 30-40 ms, obtenim respostes llargues a les sol·licituds de la nostra aplicació i la degradació del servei. Malauradament, això ens va passar el setembre de 2018, quan una de les instàncies amb Redis per algun motiu va rebre una latència 2 vegades més que l'habitual. Per resoldre el problema, vam aturar el servei a mig dia per manteniment no programat i vam substituir la instància problemàtica de Redis.
  4. És fàcil obtenir incoherències de dades fins i tot amb errors menors al codi i després passar molt de temps escrivint codi per corregir aquestes dades.

Hem tingut en compte els contres i ens hem adonat que calia passar a alguna cosa més convenient, amb transaccions normals i menys dependència de la latència. Va investigar, va analitzar moltes opcions i va triar PostgreSQL.

Ja fa 1,5 anys que ens movem a una nova base de dades i només hem mogut una petita part de les dades, així que ara estem treballant simultàniament amb Redis i PostgreSQL. Hi ha més informació sobre les etapes de moviment i canvi de dades entre bases de dades l'article del meu company.

Quan ens vam començar a moure, la nostra aplicació funcionava directament amb la base de dades i accedia al mestre Redis i PostgreSQL. El clúster PostgreSQL constava d'un mestre i una rèplica amb replicació asíncrona. Així es veia l'esquema de la base de dades:
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Implementació de PgBouncer

Mentre ens movíem, el producte també s'anava desenvolupant: el nombre d'usuaris i el nombre de servidors que treballaven amb PostgreSQL van augmentar i vam començar a faltar connexions. PostgreSQL crea un procés independent per a cada connexió i consumeix recursos. Podeu augmentar el nombre de connexions fins a un punt determinat, en cas contrari, hi ha la possibilitat d'obtenir un rendiment de la base de dades subòptim. L'opció ideal en aquesta situació seria triar un gestor de connexions que es col·loqui davant de la base.

Teníem dues opcions per al gestor de connexions: Pgpool i PgBouncer. Però el primer no admet el mode transaccional de treballar amb la base de dades, així que vam triar PgBouncer.

Hem configurat el següent esquema de treball: la nostra aplicació accedeix a un PgBouncer, darrere del qual hi ha els mestres PostgreSQL, i darrere de cada mestre hi ha una rèplica amb rèplica asíncrona.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Al mateix temps, no vam poder emmagatzemar tota la quantitat de dades a PostgreSQL i la velocitat de treball amb la base de dades era important per a nosaltres, així que vam començar a dividir PostgreSQL a nivell d'aplicació. L'esquema descrit anteriorment és relativament convenient per a això: en afegir un nou fragment PostgreSQL, n'hi ha prou amb actualitzar la configuració de PgBouncer i l'aplicació pot funcionar immediatament amb el nou fragment.

Conversió per error de PgBouncer

Aquest esquema va funcionar fins al moment en què va morir l'única instància de PgBouncer. Estem a AWS, on totes les instàncies s'executen amb un maquinari que mor periòdicament. En aquests casos, la instància simplement es mou a un maquinari nou i torna a funcionar. Això va passar amb PgBouncer, però no va estar disponible. El resultat d'aquesta caiguda va ser la indisponibilitat del nostre servei durant 25 minuts. AWS recomana utilitzar la redundància de l'usuari per a aquestes situacions, que en aquell moment no estava implementada al nostre país.

Després d'això, vam pensar seriosament en la tolerància a errors dels clústers PgBouncer i PostgreSQL, perquè podria passar una situació similar amb qualsevol instància del nostre compte d'AWS.

Hem construït l'esquema de tolerància a errors PgBouncer de la següent manera: tots els servidors d'aplicacions accedeixen al Network Load Balancer, darrere del qual hi ha dos PgBouncers. Cada PgBouncer mira el mateix mestre PostgreSQL de cada fragment. Si es torna a produir una instància d'AWS, tot el trànsit es redirigeix ​​a través d'un altre PgBouncer. AWS proporciona la migració per error de Network Load Balancer.

Aquest esquema fa que sigui fàcil afegir nous servidors PgBouncer.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Creeu un clúster de failover PostgreSQL

A l'hora de resoldre aquest problema, hem considerat diferents opcions: failover auto-escrit, repmgr, AWS RDS, Patroni.

Guions escrits per ells mateixos

Poden supervisar el treball del mestre i, si falla, promocionar la rèplica al mestre i actualitzar la configuració de PgBouncer.

Els avantatges d'aquest enfocament són la màxima simplicitat, perquè escriviu els guions vosaltres mateixos i enteneu exactament com funcionen.

Contres:

  • És possible que el mestre no hagi mort, sinó que s'hagi produït una fallada de xarxa. El failover, sense saber-ho, promourà la rèplica al mestre, mentre que el mestre antic continuarà treballant. Com a resultat, obtindrem dos servidors en la funció de mestre i no sabrem quin d'ells té les darreres dades actualitzades. Aquesta situació també s'anomena split-brain;
  • Ens vam quedar sense resposta. En la nostra configuració, el mestre i una rèplica, després de canviar, la rèplica es mou cap a la mestra i ja no tenim rèpliques, així que hem d'afegir-ne una nova manualment;
  • Necessitem un seguiment addicional de l'operació de failover, mentre que tenim 12 fragments PostgreSQL, el que significa que hem de supervisar 12 clústers. Amb un augment del nombre de fragments, també heu de recordar actualitzar la migració per error.

La migració per error autoescrita sembla molt complicada i requereix un suport no trivial. Amb un únic clúster PostgreSQL, aquesta seria l'opció més fàcil, però no s'escala, de manera que no ens convé.

Repmgr

Gestor de rèplica per a clústers PostgreSQL, que pot gestionar el funcionament d'un clúster PostgreSQL. Al mateix temps, no té una migració automàtica fora de la caixa, de manera que per treballar haureu d'escriure el vostre propi "embolcall" a la part superior de la solució acabada. Així que tot pot resultar encara més complicat que amb els guions escrits per nosaltres mateixos, així que ni tan sols vam provar Repmgr.

AWS RDS

Admet tot el que necessitem, sap fer còpies de seguretat i manté un conjunt de connexions. Té canvi automàtic: quan el mestre mor, la rèplica es converteix en el nou mestre i AWS canvia el registre dns al nou mestre, mentre que les rèpliques es poden localitzar en diferents AZ.

Els desavantatges inclouen la manca d'ajustaments fins. Com a exemple d'ajustament: les nostres instàncies tenen restriccions per a les connexions tcp, que, malauradament, no es poden fer a RDS:

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

A més, AWS RDS és gairebé el doble de car que el preu de la instància normal, que va ser el motiu principal per abandonar aquesta solució.

Patroni

Aquesta és una plantilla Python per gestionar PostgreSQL amb una bona documentació, migració automàtica per error i codi font a github.

Avantatges de Patroni:

  • Es descriu cada paràmetre de configuració, queda clar com funciona;
  • La migració automàtica per error funciona fora de la caixa;
  • Escrit en python, i com que nosaltres mateixos escrivim molt en python, ens serà més fàcil fer front als problemes i, potser, fins i tot ajudar al desenvolupament del projecte;
  • Gestiona completament PostgreSQL, us permet canviar la configuració de tots els nodes del clúster alhora, i si cal reiniciar el clúster per aplicar la nova configuració, podeu tornar a fer-ho amb Patroni.

Contres:

  • No queda clar a la documentació com treballar correctament amb PgBouncer. Tot i que és difícil dir-ho negatiu, perquè la tasca de Patroni és gestionar PostgreSQL, i com aniran les connexions a Patroni ja és el nostre problema;
  • Hi ha pocs exemples d'implementació de Patroni en grans volums, mentre que hi ha molts exemples d'implementació des de zero.

Com a resultat, vam escollir Patroni per crear un clúster de migració per error.

Procés d'implantació de Patroni

Abans de Patroni, teníem 12 fragments PostgreSQL en una configuració d'un mestre i una rèplica amb rèplica asíncrona. Els servidors d'aplicacions accedien a les bases de dades a través del Network Load Balancer, darrere del qual hi havia dues instàncies amb PgBouncer, i darrere hi havia tots els servidors PostgreSQL.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Per implementar Patroni, havíem de seleccionar una configuració de clúster d'emmagatzematge distribuït. Patroni treballa amb sistemes d'emmagatzematge de configuració distribuïts com etcd, Zookeeper, Consul. Només tenim un clúster Cònsol complet al mercat, que funciona conjuntament amb Vault i ja no l'utilitzem. Una gran raó per començar a utilitzar Consul per al propòsit previst.

Com treballa Patroni amb Consul

Tenim un clúster Cònsol, que consta de tres nodes, i un clúster Patroni, que consta d'un líder i una rèplica (a Patroni, el mestre s'anomena líder del clúster, i els esclaus s'anomenen rèpliques). Cada instància del clúster Patroni envia constantment informació sobre l'estat del clúster al Cònsol. Per tant, des de Cònsol sempre es pot conèixer la configuració actual del clúster Patroni i qui és el líder en aquests moments.

Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Per connectar Patroni a Consul, n'hi ha prou d'estudiar la documentació oficial, que diu que cal especificar un host en format http o https, segons com treballem amb Consul, i l'esquema de connexió, opcionalment:

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

Sembla senzill, però aquí comencen les trampes. Amb Consul, treballem amb una connexió segura mitjançant https i la nostra configuració de connexió serà així:

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

Però això no funciona. A l'inici, Patroni no es pot connectar al Consul, perquè intenta passar per http de totes maneres.

El codi font de Patroni va ajudar a resoldre el problema. Tant de bo està escrit en python. Resulta que el paràmetre amfitrió no s'analitza de cap manera i el protocol s'ha d'especificar a l'esquema. Així és com ens sembla el bloc de configuració de treball per treballar amb Consul:

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

cònsol-plantilla

Per tant, hem escollit l'emmagatzematge per a la configuració. Ara hem d'entendre com canviarà PgBouncer la seva configuració quan canviï el líder al clúster Patroni. No hi ha resposta a aquesta pregunta a la documentació, perquè. allà, en principi, no es descriu el treball amb PgBouncer.

A la recerca d'una solució, vam trobar un article (desgraciadament no recordo el títol) on s'escrivia que Сonsul-template va ajudar molt a combinar PgBouncer i Patroni. Això ens va portar a investigar com funciona la plantilla Consul.

Va resultar que Consul-template supervisa constantment la configuració del clúster PostgreSQL a Consul. Quan el líder canvia, actualitza la configuració de PgBouncer i envia una ordre per tornar-la a carregar.

Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Un gran avantatge de la plantilla és que s'emmagatzema com a codi, de manera que quan s'afegeix un nou fragment, n'hi ha prou amb fer una nova confirmació i actualitzar la plantilla automàticament, donant suport al principi d'Infraestructura com a codi.

Nova arquitectura amb Patroni

Com a resultat, hem obtingut el següent esquema de treball:
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Tots els servidors d'aplicacions accedeixen a l'equilibrador → hi ha dues instàncies de PgBouncer darrere d'ell → a cada instància, es llança Consul-template, que supervisa l'estat de cada clúster Patroni i supervisa la rellevància de la configuració de PgBouncer, que envia sol·licituds al líder actual. de cada clúster.

Prova manual

Vam executar aquest esquema abans de llançar-lo en un petit entorn de prova i vam comprovar el funcionament de la commutació automàtica. Van obrir el tauler, van moure l'adhesiu i en aquell moment van "matar" el líder del clúster. A AWS, això és tan senzill com tancar la instància mitjançant la consola.

Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

L'adhesiu va tornar en 10-20 segons i després va començar a moure's amb normalitat. Això vol dir que el clúster Patroni va funcionar correctament: va canviar el líder, va enviar la informació a Сonsul i Сonsul-template va recollir immediatament aquesta informació, va substituir la configuració de PgBouncer i va enviar l'ordre per tornar a carregar.

Com sobreviure amb una càrrega elevada i mantenir el temps d'inactivitat mínim?

Tot funciona perfectament! Però hi ha noves preguntes: com funcionarà amb una càrrega elevada? Com es pot desplegar de manera ràpida i segura tot en producció?

L'entorn de prova en què fem proves de càrrega ens ajuda a respondre la primera pregunta. És completament idèntic a la producció pel que fa a l'arquitectura i ha generat dades de prova que són aproximadament iguals en volum a la producció. Decidim simplement "matar" un dels mestres de PostgreSQL durant la prova i veure què passa. Però abans d'això, és important comprovar el rolling automàtic, perquè en aquest entorn tenim diversos fragments de PostgreSQL, de manera que obtindrem una excel·lent prova dels scripts de configuració abans de la producció.

Les dues tasques semblen ambicioses, però tenim PostgreSQL 9.6. Podem actualitzar immediatament a 11.2?

Decidim fer-ho en 2 passos: primer actualitzem a 11.2 i després iniciem Patroni.

Actualització de PostgreSQL

Per actualitzar ràpidament la versió de PostgreSQL, utilitzeu l'opció -k, en què es creen enllaços durs al disc i no cal copiar les teves dades. En bases de 300-400 GB, l'actualització triga 1 segon.

Tenim molts fragments, de manera que l'actualització s'ha de fer automàticament. Per fer-ho, vam escriure un llibre de jugades d'Ansible que gestiona tot el procés d'actualització per nosaltres:

/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='

És important tenir en compte aquí que abans d'iniciar l'actualització, l'has de realitzar amb el paràmetre --comprovarper assegurar-vos que podeu actualitzar. El nostre script també fa la substitució de les configuracions durant la durada de l'actualització. El nostre guió es va completar en 30 segons, que és un resultat excel·lent.

Llançar Patroni

Per resoldre el segon problema, només cal mirar la configuració de Patroni. El repositori oficial té un exemple de configuració amb initdb, que s'encarrega d'inicialitzar una nova base de dades quan inicieu Patroni per primera vegada. Però com que ja tenim una base de dades preparada, simplement hem eliminat aquesta secció de la configuració.

Quan vam començar a instal·lar Patroni en un clúster PostgreSQL ja existent i a executar-lo, vam trobar un nou problema: tots dos servidors van començar com a líders. Patroni no sap res sobre l'estat inicial del clúster i intenta iniciar els dos servidors com a dos clústers separats amb el mateix nom. Per resoldre aquest problema, heu de suprimir el directori amb les dades de l'esclau:

rm -rf /var/lib/postgresql/

Això només s'ha de fer amb l'esclau!

Quan es connecta una rèplica neta, Patroni crea un líder de còpia de seguretat base i la restaura a la rèplica, i després es posa al dia amb l'estat actual d'acord amb els registres de wal.

Una altra dificultat que hem trobat és que tots els clústers PostgreSQL s'anomenen principals per defecte. Quan cada clúster no sap res de l'altre, això és normal. Però quan voleu utilitzar Patroni, tots els clústers han de tenir un nom únic. La solució és canviar el nom del clúster a la configuració de PostgreSQL.

prova de càrrega

Hem posat en marxa una prova que simula l'experiència de l'usuari als taulers. Quan la càrrega va assolir el nostre valor mitjà diari, vam repetir exactament la mateixa prova, vam desactivar una instància amb el líder de PostgreSQL. La migració automàtica per error va funcionar com esperàvem: Patroni va canviar el líder, Consul-template va actualitzar la configuració de PgBouncer i va enviar una ordre per tornar a carregar. Segons els nostres gràfics a Grafana, era evident que hi ha retards de 20-30 segons i una petita quantitat d'errors dels servidors associats a la connexió a la base de dades. Aquesta és una situació normal, aquests valors són acceptables per a la nostra failover i són definitivament millors que el temps d'inactivitat del servei.

Portant Patroni a la producció

Com a resultat, vam elaborar el següent pla:

  • Desplegueu la plantilla Consul als servidors de PgBouncer i inicieu-los;
  • Actualitzacions de PostgreSQL a la versió 11.2;
  • Canvia el nom del clúster;
  • Inici del Clúster Patroni.

Al mateix temps, el nostre esquema ens permet fer el primer punt gairebé en qualsevol moment, podem treure cada PgBouncer de la feina al seu torn i desplegar i executar-hi una plantilla cònsol. Així ho vam fer.

Per a un desplegament ràpid, hem utilitzat Ansible, ja que ja hem provat tots els llibres de joc en un entorn de prova i el temps d'execució de l'script complet va ser d'1,5 a 2 minuts per a cada fragment. Podríem desplegar-ho tot al seu torn a cada fragment sense aturar el nostre servei, però hauríem d'apagar cada PostgreSQL durant uns quants minuts. En aquest cas, els usuaris les dades dels quals es troben en aquest fragment no podrien funcionar completament en aquest moment, i això és inacceptable per a nosaltres.

La sortida a aquesta situació va ser el manteniment previst, que es fa cada 3 mesos. Aquesta és una finestra per al treball programat, quan tanquem completament el nostre servei i actualitzem les nostres instàncies de base de dades. Faltava una setmana per a la següent finestra, i vam decidir esperar i preparar-nos més. Durant el temps d'espera, també ens vam assegurar: per a cada fragment PostgreSQL, vam generar una rèplica de recanvi en cas de no conservar les dades més recents i vam afegir una nova instància per a cada fragment, que hauria de convertir-se en una nova rèplica al clúster Patroni. per no executar una ordre per esborrar dades. Tot això va ajudar a minimitzar el risc d'error.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Vam reiniciar el nostre servei, tot va funcionar com calia, els usuaris van continuar treballant, però als gràfics vam notar una càrrega anormalment alta als servidors de Cònsol.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Per què no ho vam veure a l'entorn de prova? Aquest problema il·lustra molt bé que cal seguir el principi d'Infraestructura com a codi i refinar tota la infraestructura, des dels entorns de prova fins a la producció. En cas contrari, és molt fàcil trobar el problema que tenim. Què va passar? Consul va aparèixer primer en producció, i després en entorns de prova, com a resultat, en entorns de prova, la versió de Consul era superior a la de producció. Només en una de les versions, es va resoldre una fuita de CPU quan es treballava amb la plantilla consul. Per tant, simplement hem actualitzat Cònsol, solucionant així el problema.

Reinicieu el clúster Patroni

Tanmateix, vam tenir un nou problema, que ni tan sols sospitava. Quan actualitzem Consul, simplement eliminem el node Consul del clúster mitjançant l'ordre consul leave → Patroni es connecta a un altre servidor Consul → tot funciona. Però quan vam arribar a l'última instància del clúster Cònsol i li vam enviar l'ordre de sortida del cònsol, tots els clústers de Patroni simplement es van reiniciar i als registres vam veure l'error següent:

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>

El clúster Patroni no ha pogut recuperar informació sobre el seu clúster i s'ha reiniciat.

Per trobar una solució, ens vam posar en contacte amb els autors de Patroni mitjançant un problema a github. Van suggerir millores als nostres fitxers de configuració:

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

Vam poder replicar el problema en un entorn de prova i vam provar aquestes opcions allà, però malauradament no van funcionar.

El problema encara continua sense resoldre. Tenim previst provar les solucions següents:

  • Utilitzeu Consul-agent a cada instància del clúster de Patroni;
  • Solucioneu el problema al codi.

Entenem on s'ha produït l'error: probablement el problema és l'ús del temps d'espera predeterminat, que no es substitueix mitjançant el fitxer de configuració. Quan l'últim servidor Consul s'elimina del clúster, tot el clúster Consul es penja durant més d'un segon, per això, Patroni no pot obtenir l'estat del clúster i reinicia completament tot el clúster.

Afortunadament, no hem trobat més errors.

Resultats de l'ús de Patroni

Després de l'èxit del llançament de Patroni, hem afegit una rèplica addicional a cada clúster. Ara, a cada clúster hi ha una semblança de quòrum: un líder i dues rèpliques, per a una xarxa de seguretat en cas de trencament del cervell quan es canvia.
Clúster de failover PostgreSQL + Patroni. Experiència en la implementació

Patroni fa més de tres mesos que treballa en la producció. Durant aquest temps, ja ha aconseguit ajudar-nos. Recentment, el líder d'un dels clústers va morir a AWS, la failover automàtica va funcionar i els usuaris van continuar treballant. Patroni va complir la seva tasca principal.

Un petit resum de l'ús de Patroni:

  • Facilitat de canvis de configuració. N'hi ha prou amb canviar la configuració en una instància i s'aixecarà a tot el clúster. Si cal reiniciar per aplicar la nova configuració, Patroni us ho farà saber. Patroni pot reiniciar tot el clúster amb una sola ordre, que també és molt convenient.
  • La migració automàtica per error funciona i ja ha aconseguit ajudar-nos.
  • Actualització de PostgreSQL sense temps d'inactivitat de l'aplicació. Primer heu d'actualitzar les rèpliques a la nova versió, després canviar el líder al clúster Patroni i actualitzar el líder antic. En aquest cas, es produeixen les proves necessàries de migració automàtica per error.

Font: www.habr.com

Afegeix comentari