Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Во написот ќе ви кажам како пристапивме кон прашањето за толеранција на грешки PostgreSQL, зошто тоа стана важно за нас и што се случи на крајот.

Имаме многу оптоварена услуга: 2,5 милиони корисници ширум светот, 50K+ активни корисници секој ден. Серверите се наоѓаат во Амазон во еден регион на Ирска: 100+ различни сервери постојано работат, од кои речиси 50 се со бази на податоци.

Целиот заден дел е голема монолитна статусна Java апликација која одржува постојана веб-сокет врска со клиентот. Кога неколку корисници работат на иста табла во исто време, сите тие ги гледаат промените во реално време, бидејќи секоја промена ја запишуваме во базата на податоци. Имаме околу 10 илјади барања во секунда до нашите бази на податоци. При врвно оптоварување во Redis, пишуваме 80-100K барања во секунда.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Зошто се префрливме од Redis на PostgreSQL

Првично, нашата услуга работеше со Redis, продавница со клучна вредност која ги складира сите податоци во RAM меморијата на серверот.

Добрите страни на Редис:

  1. Висока брзина на одговор, бидејќи сè е зачувано во меморија;
  2. Леснотија на резервна копија и репликација.

Недостатоци на Redis за нас:

  1. Нема вистински трансакции. Се обидовме да ги симулираме на ниво на нашата апликација. За жал, ова не секогаш функционираше добро и бараше пишување многу сложен код.
  2. Количината на податоци е ограничена со количината на меморија. Како што се зголемува количината на податоци, меморијата ќе расте и, на крајот, ќе наидеме на карактеристиките на избраната инстанца, која во AWS бара прекин на нашата услуга за да го промениме типот на примерокот.
  3. Потребно е постојано да се одржува ниско ниво на латентност, бидејќи. имаме многу голем број барања. Оптималното ниво на доцнење за нас е 17-20 ms. На ниво од 30-40 ms, добиваме долги одговори на барањата од нашата апликација и деградација на услугата. За жал, ова ни се случи во септември 2018 година, кога еден од случаите со Редис поради некоја причина доби латентност 2 пати повеќе од вообичаеното. За да го решиме проблемот, ја прекинавме услугата среде ден поради непланирано одржување и ја заменивме проблематичната инстанца Redis.
  4. Лесно е да се добие недоследност на податоците дури и со мали грешки во кодот и потоа да се троши многу време за пишување код за да се поправат овие податоци.

Ги зедовме предвид лошите страни и сфативме дека треба да преминеме на нешто попогодно, со нормални трансакции и помала зависност од латентност. Спроведе истражување, анализираше многу опции и избра PostgreSQL.

Веќе 1,5 година се преселивме во нова база на податоци и префрливме само мал дел од податоците, па сега работиме истовремено со Redis и PostgreSQL. Повеќе информации за фазите на преместување и префрлување на податоците помеѓу базите на податоци се запишани во статија на мојот колега.

Кога првпат почнавме да се движиме, нашата апликација работеше директно со базата на податоци и пристапи до главниот Redis и PostgreSQL. Кластерот PostgreSQL се состоеше од главен и реплика со асинхрона репликација. Вака изгледаше шемата на базата на податоци:
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Имплементирање на PgBouncer

Додека се движевме, производот исто така се развиваше: бројот на корисници и бројот на сервери кои работеа со PostgreSQL се зголемија и почнавме да ни недостасуваат врски. PostgreSQL создава посебен процес за секоја врска и троши ресурси. Можете да го зголемите бројот на конекции до одредена точка, во спротивно постои шанса да добиете неоптимални перформанси на базата на податоци. Идеалната опција во таква ситуација би била да се избере менаџер за поврзување кој ќе стои пред основата.

Имавме две опции за менаџерот за поврзување: Pgpool и PgBouncer. Но, првиот не го поддржува трансакцискиот начин на работа со базата на податоци, па го избравме PgBouncer.

Ја поставивме следната шема на работа: нашата апликација пристапува до еден PgBouncer, зад кој стојат MastergreSQL Masters, а зад секој господар има по една реплика со асинхрона репликација.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Во исто време, не можевме да ја складираме целата количина на податоци во PostgreSQL и брзината на работа со базата на податоци ни беше важна, па почнавме да ја делиме PostgreSQL на ниво на апликација. Шемата опишана погоре е релативно погодна за ова: кога се додава нов фрагмент PostgreSQL, доволно е да се ажурира конфигурацијата на PgBouncer и апликацијата може веднаш да работи со новиот фрагмент.

Неуспех на PgBouncer

Оваа шема функционираше до моментот кога почина единствениот примерок на PgBouncer. Ние сме во AWS, каде што сите примероци работат на хардвер што периодично умира. Во такви случаи, примерокот едноставно се префрла на нов хардвер и работи повторно. Ова се случи со PgBouncer, но стана недостапно. Резултатот од оваа есен беше недостапноста на нашата услуга 25 минути. AWS препорачува користење на вишок на корисничка страна за вакви ситуации, што во тоа време не беше имплементирано кај нас.

После тоа, сериозно размислувавме за толеранцијата на грешки на кластерите PgBouncer и PostgreSQL, бидејќи слична ситуација може да се случи со кој било пример во нашата AWS сметка.

Ја изградивме шемата за толеранција на грешки PgBouncer на следниов начин: сите апликациски сервери пристапуваат до Network Load Balancer, зад кој има два PgBouncer. Секој PgBouncer го гледа истиот MastergreSQL господар на секој фрагмент. Ако повторно се случи пад на примерот на AWS, целиот сообраќај се пренасочува преку друг PgBouncer. Неуспехот на Network Load Balancer е обезбеден од AWS.

Оваа шема го олеснува додавањето на нови сервери PgBouncer.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Креирајте кластер за неуспешни PostgreSQL

Кога го решававме овој проблем, разгледавме различни опции: само-напишано откажување, repmgr, AWS RDS, Patroni.

Само-напишани скрипти

Тие можат да ја следат работата на главниот и, ако не успее, да ја промовираат репликата на главниот и да ја ажурираат конфигурацијата на PgBouncer.

Предностите на овој пристап се максималната едноставност, бидејќи вие самите пишувате скрипти и разбирате точно како функционираат.

Конс:

  • Можеби мајсторот не умрел, наместо тоа, може да се случи мрежен неуспех. Failover, несвесен за ова, ќе ја промовира репликата на мајсторот, додека стариот мајстор ќе продолжи да работи. Како резултат на тоа, ќе добиеме два сервери во улога на господар и нема да знаеме кој од нив ги има најновите ажурирани податоци. Оваа ситуација се нарекува и сплит-мозок;
  • Останавме без одговор. Во нашата конфигурација, главниот и една реплика, по префрлувањето, репликата се движи нагоре кон главниот и ние веќе немаме реплики, па затоа мора рачно да додадеме нова реплика;
  • Потребно ни е дополнително следење на операцијата за фајловер, додека имаме 12 фрагменти на PostgreSQL, што значи дека треба да следиме 12 кластери. Со зголемување на бројот на парчиња, исто така мора да запомните да го ажурирате фајловерот.

Самонапишаниот фајловер изгледа многу комплициран и бара нетривијална поддршка. Со еден кластер PostgreSQL, ова би била најлесната опција, но не се скалира, па затоа не е погодна за нас.

Repmgr

Менаџер за репликација за кластери PostgreSQL, кој може да управува со работата на кластерот PostgreSQL. Во исто време, нема автоматско откажување надвор од кутијата, така што за работа ќе треба да напишете сопствена „обвивка“ на врвот на готовиот раствор. Така, сè може да испадне уште покомплицирано отколку со само-напишани скрипти, па затоа не го ни пробавме Repmgr.

AWS RDS

Поддржува сè што ни треба, знае како да прави резервни копии и одржува збир на врски. Има автоматско префрлување: кога господарот умира, репликата станува нов господар, а AWS го менува записот dns во новиот главен, додека репликите можат да се наоѓаат во различни AZ.

Недостатоците вклучуваат недостаток на фини прилагодувања. Како пример за фино подесување: нашите примери имаат ограничувања за tcp врски, што, за жал, не може да се направи во 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 е речиси двојно поскап од цената на редовните примероци, што беше главната причина за напуштање на ова решение.

Покровител

Ова е шаблон за python за управување со PostgreSQL со добра документација, автоматско откажување и изворен код на github.

Добрите страни на Patroni:

  • Секој параметар за конфигурација е опишан, јасно е како функционира;
  • Автоматското откажување работи надвор од кутијата;
  • Напишано во python, и бидејќи ние самите пишуваме многу во python, ќе ни биде полесно да се справиме со проблемите и, можеби, дури и да помогнеме во развојот на проектот;
  • Целосно управува со PostgreSQL, ви овозможува да ја промените конфигурацијата на сите јазли на кластерот одеднаш, и ако кластерот треба да се рестартира за да се примени новата конфигурација, тогаш тоа може да се направи повторно со помош на Patroni.

Конс:

  • Од документацијата не е јасно како правилно да се работи со PgBouncer. Иако е тешко да се нарече минус, бидејќи задачата на Patroni е да управува со PostgreSQL, а како ќе одат врските со Patroni е веќе наш проблем;
  • Има неколку примери за имплементација на Patroni на големи количини, додека има многу примери за имплементација од нула.

Како резултат на тоа, го избравме Patroni за да создадеме кластер за неуспех.

Процес на имплементација на Patroni

Пред Patroni, имавме 12 фрагменти на PostgreSQL во конфигурација од еден главен и една реплика со асинхрона репликација. Апликативните сервери пристапуваа до базите на податоци преку Network Load Balancer, зад кој беа две инстанци со PgBouncer, а зад нив беа сите PostgreSQL сервери.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

За да го имплементираме Patroni, требаше да избереме конфигурација на кластер за дистрибуирана складирање. Patroni работи со дистрибуирани системи за складирање на конфигурации како etcd, Zookeeper, Consul. Имаме само полноправно конзулски кластер на пазарот, кој работи заедно со Vault и повеќе не го користиме. Одлична причина да започнете да го користите Consul за наменетата цел.

Како работи Патрони со конзулот

Имаме кластер конзул, кој се состои од три јазли, и кластер Патрони, кој се состои од водач и реплика (во Патрони, господарот се нарекува водач на кластерот, а робовите се нарекуваат реплики). Секој примерок од кластерот Патрони постојано испраќа информации за состојбата на кластерот до конзулот. Затоа, од конзулот секогаш можете да ја дознаете моменталната конфигурација на кластерот Патрони и кој е лидер во моментот.

Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

За да го поврзете Patroni со конзулот, доволно е да ја проучите официјалната документација, која вели дека треба да наведете домаќин во формат http или https, во зависност од тоа како работиме со конзулот и шемата за поврзување, по избор:

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

Изгледа едноставно, но тука започнуваат замките. Со Consul, работиме на безбедна врска преку https и нашата конфигурација за конекција ќе изгледа вака:

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

Но, тоа не функционира. При стартување, Патрони не може да се поврзе со конзулот, бидејќи сепак се обидува да помине преку http.

Изворниот код на Patroni помогна да се справиме со проблемот. Добро е што е напишано во питон. Излегува дека параметарот на домаќинот не е анализиран на кој било начин и протоколот мора да биде наведен во шемата. Вака за нас изгледа работниот конфигурациски блок за работа со Конзул:

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

конзул-шаблон

Значи, го избравме складирањето за конфигурацијата. Сега треба да разбереме како PgBouncer ќе ја префрли својата конфигурација при менување на лидерот во кластерот Patroni. Одговор на ова прашање во документацијата нема, бидејќи. таму, во принцип, работата со PgBouncer не е опишана.

Во потрага по решение, најдовме статија (за жал не се сеќавам на насловот) каде што пишуваше дека Сonsul-шаблонот многу помогнал во спарувањето на PgBouncer и Patroni. Ова не поттикна да истражиме како функционира Consul-template.

Се испостави дека Consul-template постојано ја следи конфигурацијата на кластерот PostgreSQL во Consul. Кога лидерот се менува, тој ја ажурира конфигурацијата на PgBouncer и испраќа команда за повторно вчитување.

Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Голем плус на шаблонот е тоа што се чува како код, па при додавање нов фрагмент, доволно е да се направи нов commit и автоматски да се ажурира шаблонот, поддржувајќи го принципот Infrastructure as code.

Нова архитектура со Патрони

Како резултат на тоа, ја добивме следната шема на работа:
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Сите сервери на апликации пристапуваат до балансерот → има два примероци на PgBouncer зад него → на секој пример, се активира Consul-template, кој го следи статусот на секој кластер Patroni и ја следи релевантноста на конфигурацијата PgBouncer, која испраќа барања до тековниот лидер на секој кластер.

Рачно тестирање

Ја извршивме оваа шема пред да ја лансираме на мала средина за тестирање и ја проверивме работата на автоматското префрлување. Ја отвориле таблата, ја преместиле налепницата и во тој момент го „убиле“ водачот на кластерот. Во AWS, ова е едноставно како исклучување на примерокот преку конзолата.

Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Налепницата се врати за 10-20 секунди, а потоа повторно почна да се движи нормално. Ова значи дека кластерот Patroni работеше правилно: го смени лидерот, ги испрати информациите до Сonsul и Сonsul-template веднаш ги подигна овие информации, ја замени конфигурацијата на PgBouncer и ја испрати командата за повторно вчитување.

Како да преживеете под големо оптоварување и да го одржувате времето на застој минимално?

Сè работи совршено! Но, има нови прашања: како ќе работи под големо оптоварување? Како брзо и безбедно да се воведе сè во производството?

Тестната средина на која спроведуваме тестирање на оптоварување ни помага да одговориме на првото прашање. Тој е целосно идентичен со производството во однос на архитектурата и генерира податоци за тестирање кои се приближно еднакви по обем на производството. Решивме само да „убиеме“ еден од мајсторите на PostgreSQL за време на тестот и да видиме што ќе се случи. Но, пред тоа, важно е да се провери автоматското тркалање, бидејќи на оваа средина имаме неколку PostgreSQL фрагменти, така што ќе добиеме одлично тестирање на скриптите за конфигурација пред производството.

Двете задачи изгледаат амбициозни, но имаме PostgreSQL 9.6. Можеме ли веднаш да се надградиме на 11.2?

Решивме да го направиме тоа во 2 чекори: прво надградете на 11.2, а потоа стартувајте го Patroni.

Ажурирање на PostgreSQL

За брзо ажурирање на верзијата PostgreSQL, користете ја опцијата -k, во кој се креираат тврди врски на дискот и нема потреба од копирање на вашите податоци. На база од 300-400 GB, ажурирањето трае 1 секунда.

Имаме многу парчиња, па ажурирањето треба да се направи автоматски. За да го направите ова, напишавме Ansible Playbook што се справува со целиот процес на ажурирање за нас:

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

Овде е важно да се забележи дека пред да започнете со надградбата, мора да ја извршите со параметарот --проверетеза да бидете сигурни дека можете да надградите. Нашата скрипта прави и замена на конфигурации за времетраењето на надградбата. Нашето сценарио заврши за 30 секунди, што е одличен резултат.

Стартувајте го Patroni

За да го решите вториот проблем, само погледнете ја конфигурацијата Patroni. Официјалното складиште има пример конфигурација со initdb, која е одговорна за иницијализирање на нова база на податоци кога првпат ќе го стартувате Patroni. Но, бидејќи веќе имаме готова база на податоци, едноставно го отстранивме овој дел од конфигурацијата.

Кога почнавме да го инсталираме Patroni на веќе постоечки кластер PostgreSQL и да го работиме, наидовме на нов проблем: двата сервери започнаа како лидер. Патрони не знае ништо за раната состојба на кластерот и се обидува да ги стартува двата сервери како два посебни кластери со исто име. За да го решите овој проблем, треба да го избришете директориумот со податоци на робот:

rm -rf /var/lib/postgresql/

Ова треба да се направи само на робот!

Кога ќе се поврзе чиста реплика, Патрони прави лидер за резервна копија и ја враќа на репликата, а потоа ја достигнува моменталната состојба според логовите.

Друга тешкотија на која наидовме е тоа што сите PostgreSQL кластери се стандардно именувани како главни. Кога секој кластер не знае ништо за другиот, тоа е нормално. Но, кога сакате да користите Patroni, тогаш сите кластери мора да имаат единствено име. Решението е да се смени името на кластерот во конфигурацијата PostgreSQL.

тест за оптоварување

Лансиравме тест кој симулира корисничко искуство на табли. Кога оптоварувањето ја достигна нашата просечна дневна вредност, го повторивме истиот тест, исклучивме еден пример со лидерот PostgreSQL. Автоматското откажување функционираше како што очекувавме: Patroni го смени лидерот, Consul-template ја ажурираше конфигурацијата на PgBouncer и испрати команда за повторно вчитување. Според нашите графикони во Графана, беше јасно дека има доцнења од 20-30 секунди и мала количина на грешки од серверите поврзани со поврзувањето со базата на податоци. Ова е нормална ситуација, таквите вредности се прифатливи за нашиот фајловер и дефинитивно се подобри од времето на прекин на услугата.

Доведување на Patroni во производство

Како резултат, дојдовме до следниот план:

  • Распоредете го шаблонот Consul на серверите на PgBouncer и стартувајте;
  • PostgreSQL ажурирања на верзијата 11.2;
  • Променете го името на кластерот;
  • Започнување на Патрони Кластерот.

Во исто време, нашата шема ни овозможува да ја направиме првата точка речиси во секое време, можеме да го отстраниме секој PgBouncer од работа за возврат и да го распоредиме и извршиме конзул-шаблон на него. Така направивме.

За брзо распоредување, користевме Ansible, бидејќи веќе ги тестиравме сите книги за играње на тест средина, а времето на извршување на целосната скрипта беше од 1,5 до 2 минути за секој дел. Можевме да префрлиме сè за возврат на секој фрагмент без да ја прекинеме нашата услуга, но ќе треба да го исклучиме секој PostgreSQL неколку минути. Во овој случај, корисниците чии податоци се на овој фрагмент не можат целосно да работат во овој момент и тоа е неприфатливо за нас.

Излезот од оваа ситуација беше планираното одржување, кое се одвива на секои 3 месеци. Ова е прозорец за закажана работа, кога целосно ќе ја исклучиме нашата услуга и ќе ги надградиме примерите на нашата база на податоци. Остана уште една недела до следниот прозорец и решивме само да почекаме и да се подготвиме понатаму. За време на чекањето дополнително се обезбедивме: за секој фрагмент PostgreSQL подигнавме резервна реплика во случај на неуспех да ги задржиме најновите податоци и додадовме нов примерок за секој фрагмент, кој треба да стане нова реплика во кластерот Patroni. за да не се изврши команда за бришење податоци . Сето ова помогна да се минимизира ризикот од грешка.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Ја рестартиравме нашата услуга, сè работеше како што треба, корисниците продолжија да работат, но на графиконите забележавме ненормално големо оптоварување на серверите на Consul.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Зошто не го видовме ова во околината за тестирање? Овој проблем многу добро илустрира дека е неопходно да се следи принципот на Инфраструктура како код и да се рафинира целата инфраструктура, од тест околини до производство. Во спротивно, многу е лесно да го добиеме проблемот што го добивме. Што се случи? Конзул прво се појави на производство, а потоа и на тест околини, како резултат на тоа, на тест околини, верзијата на Конзул беше повисока отколку на производство. Само во едно од изданијата, беше решено истекување на процесорот при работа со конзул-шаблон. Затоа, едноставно го ажуриравме конзулот, со што го решивме проблемот.

Рестартирајте го кластерот Patroni

Сепак, добивме нов проблем, за кој не се ни посомневавме. Кога го ажурираме Конзулот, едноставно го отстрануваме јазолот Конзул од кластерот со помош на командата за заминување конзул → Патрони се поврзува со друг сервер конзул → сè работи. Но, кога стигнавме до последниот примерок на кластерот конзул и ја испративме командата конзуларно заминување до него, сите кластери Патрони едноставно се рестартираа, а во дневниците ја видовме следнава грешка:

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>

Кластерот Patroni не можеше да добие информации за својот кластер и се рестартираше.

За да најдеме решение, контактиравме со авторите на Patroni преку проблем на github. Тие предложија подобрувања на нашите конфигурациски датотеки:

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

Можевме да го повториме проблемот на тест средина и ги тестиравме овие опции таму, но за жал тие не работеа.

Проблемот сè уште останува нерешен. Планираме да ги пробаме следните решенија:

  • Користете конзул-агент за секој примерок на кластерот Patroni;
  • Поправете го проблемот во кодот.

Разбираме каде настанала грешката: проблемот е веројатно во користењето на стандардниот истек на време, кој не е отфрлен преку конфигурациската датотека. Кога последниот Consul сервер е отстранет од кластерот, целиот Consul кластер виси повеќе од една секунда, поради тоа, Patroni не може да го добие статусот на кластерот и целосно го рестартира целиот кластер.

За среќа, не наидовме на повеќе грешки.

Резултати од користењето на Patroni

По успешното лансирање на Patroni, додадовме дополнителна реплика во секој кластер. Сега во секој кластер има привид на кворум: еден лидер и две реплики, за безбедносна мрежа во случај на поделен мозок при префрлување.
Кластер за неуспех PostgreSQL + Patroni. Искуство за имплементација

Патрони работи на производство повеќе од три месеци. За тоа време, тој веќе успеа да ни помогне. Неодамна, лидерот на еден од кластерите почина во AWS, автоматското откажување работеше и корисниците продолжија да работат. Патрони ја исполни својата главна задача.

Мало резиме на употребата на Patroni:

  • Лесно се менуваат конфигурациите. Доволно е да ја смените конфигурацијата на еден пример и таа ќе се повлече до целиот кластер. Ако е потребно рестартирање за да се примени новата конфигурација, тогаш Patroni ќе ве извести. Patroni може да го рестартира целиот кластер со една команда, што е исто така многу погодно.
  • Автоматското откажување работи и веќе успеа да ни помогне.
  • Ажурирање на PostgreSQL без прекин на апликацијата. Прво мора да ги ажурирате репликите на новата верзија, потоа да го смените лидерот во кластерот Patroni и да го ажурирате стариот лидер. Во овој случај, се случува неопходното тестирање на автоматско откажување.

Извор: www.habr.com

Додадете коментар