Failover Cluster PostgreSQL + Patroni. Опит във внедряването

В статията ще ви разкажа как подходихме към проблема с толерантността към грешки на PostgreSQL, защо това стана важно за нас и какво се случи в крайна сметка.

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

Целият бекенд е голямо монолитно Java приложение с поддържащо състояние, което поддържа постоянна websocket връзка с клиента. Когато няколко потребители работят на една и съща дъска едновременно, всички те виждат промените в реално време, защото ние записваме всяка промяна в базата данни. Имаме около 10 хиляди заявки в секунда към нашите бази данни. При пиково натоварване в Redis пишем 80-100K заявки в секунда.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Защо преминахме от Redis към PostgreSQL

Първоначално нашата услуга работи с Redis, хранилище за ключ-стойност, което съхранява всички данни в RAM паметта на сървъра.

Плюсове на Redis:

  1. Висока скорост на реакция, т.к всичко се съхранява в паметта;
  2. Лесно архивиране и репликация.

Минуси на Redis за нас:

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

Взехме предвид недостатъците и осъзнахме, че трябва да преминем към нещо по-удобно, с нормални транзакции и по-малко зависимост от латентност. Проведохме проучване, анализирахме много опции и избрахме PostgreSQL.

Преминаваме към нова база данни вече 1,5 години и преместихме само малка част от данните, така че сега работим едновременно с Redis и PostgreSQL. Повече информация за етапите на преместване и превключване на данни между бази данни е написана в статията на колегата.

Когато за първи път започнахме да се местим, нашето приложение работеше директно с базата данни и имаше достъп до основния Redis и PostgreSQL. Клъстерът PostgreSQL се състоеше от главен и реплика с асинхронна репликация. Ето как изглеждаше схемата на базата данни:
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Внедряване на PgBouncer

Докато се местехме, продуктът също се развиваше: броят на потребителите и броят на сървърите, които работеха с PostgreSQL, се увеличиха и започнаха да ни липсват връзки. PostgreSQL създава отделен процес за всяка връзка и консумира ресурси. Можете да увеличите броя на връзките до определен момент, в противен случай има шанс да получите неоптимална производителност на базата данни. Идеалният вариант в такава ситуация би бил да изберете мениджър за връзка, който ще стои пред основата.

Имахме две опции за мениджъра на връзки: Pgpool и PgBouncer. Но първият не поддържа транзакционния режим на работа с базата данни, така че избрахме PgBouncer.

Настроили сме следната схема на работа: нашето приложение има достъп до един PgBouncer, зад който стоят PostgreSQL master, а зад всеки master стои една реплика с асинхронна репликация.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

В същото време не можехме да съхраняваме цялото количество данни в PostgreSQL и скоростта на работа с базата данни беше важна за нас, така че започнахме шардинг на PostgreSQL на ниво приложение. Описаната по-горе схема е сравнително удобна за това: при добавяне на нов PostgreSQL шард е достатъчно да актуализирате конфигурацията на PgBouncer и приложението веднага може да работи с новия шард.

PgBouncer отказ

Тази схема работеше до момента, в който единственият екземпляр на PgBouncer умря. Ние сме в AWS, където всички екземпляри работят на хардуер, който умира периодично. В такива случаи инстанцията просто се премества към нов хардуер и работи отново. Това се случи с PgBouncer, но той стана недостъпен. Резултатът от това падане беше липсата на услугата ни за 25 минути. AWS препоръчва използването на резервиране от страна на потребителя за такива ситуации, което не беше приложено в нашата страна по това време.

След това сериозно се замислихме за устойчивостта на грешки на клъстерите PgBouncer и PostgreSQL, защото подобна ситуация може да се случи с всеки екземпляр в нашия AWS акаунт.

Ние изградихме схемата за толерантност към грешки на PgBouncer, както следва: всички сървъри на приложения имат достъп до Network Load Balancer, зад който има два PgBouncers. Всеки PgBouncer разглежда един и същ PostgreSQL master на всеки шард. Ако отново възникне срив на екземпляр на AWS, целият трафик се пренасочва през друг PgBouncer. Преминаването при отказ на балансиращото натоварване на мрежата се предоставя от AWS.

Тази схема улеснява добавянето на нови PgBouncer сървъри.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Създайте PostgreSQL Failover Cluster

При решаването на този проблем обмислихме различни опции: самостоятелно написан отказ, repmgr, AWS RDS, Patroni.

Самостоятелно написани сценарии

Те могат да наблюдават работата на капитана и в случай на повреда да повишат репликата до капитана и да актуализират конфигурацията на PgBouncer.

Предимствата на този подход са максималната простота, защото вие сами пишете скриптове и разбирате как точно работят.

против:

  • Главният може да не е умрял, вместо това може да е възникнала повреда в мрежата. Failover, без да знае за това, ще повиши репликата до главния, докато старият мастер ще продължи да работи. В резултат на това ще получим два сървъра в ролята на master и няма да знаем кой от тях има последните актуални данни. Тази ситуация се нарича още раздвоен мозък;
  • Останахме без отговор. В нашата конфигурация, главният и една реплика, след превключване, репликата се премества нагоре към главния и вече нямаме реплики, така че трябва ръчно да добавим нова реплика;
  • Нуждаем се от допълнителен мониторинг на операцията за преустановяване на отказ, докато имаме 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 сървъри.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

За да внедрим Patroni, трябваше да изберем конфигурация на клъстер за разпределено съхранение. Patroni работи с разпределени системи за съхранение на конфигурации като etcd, Zookeeper, Consul. Просто имаме пълноценен клъстер Consul на пазара, който работи заедно с Vault и вече не го използваме. Чудесна причина да започнете да използвате Consul по предназначение.

Как Patroni работи с Consul

Имаме клъстер Consul, който се състои от три възела, и клъстер Patroni, който се състои от лидер и реплика (в Patroni главният се нарича лидер на клъстера, а подчинените се наричат ​​реплики). Всеки екземпляр на клъстера Patroni постоянно изпраща информация за състоянието на клъстера на Consul. Затова от Consul винаги можете да разберете текущата конфигурация на клъстера Patroni и кой е лидерът в момента.

Failover Cluster PostgreSQL + Patroni. Опит във внедряването

За да свържете Patroni с Consul, достатъчно е да проучите официалната документация, която казва, че трябва да посочите хост във формат http или https, в зависимост от начина, по който работим с Consul, и схемата на свързване, по избор:

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 }}

Но това не работи. При стартиране Patroni не може да се свърже с Consul, защото така или иначе се опитва да премине през http.

Изходният код на Patroni помогна за справяне с проблема. Добре, че е написано на python. Оказва се, че хост параметърът не се анализира по никакъв начин и протоколът трябва да бъде посочен в схемата. Ето как изглежда работещият конфигурационен блок за работа с Consul за нас:

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

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

И така, избрахме хранилището за конфигурацията. Сега трябва да разберем как PgBouncer ще смени конфигурацията си при смяна на лидера в клъстера Patroni. В документацията няма отговор на този въпрос, т.к. там по принцип не е описана работа с PgBouncer.

В търсене на решение намерихме статия (за съжаление не си спомням заглавието), където беше написано, че Сonsul-template помогна много при сдвояването на PgBouncer и Patroni. Това ни накара да проучим как работи Consul-template.

Оказа се, че Consul-template постоянно следи конфигурацията на PostgreSQL клъстера в Consul. Когато лидерът се промени, той актуализира конфигурацията на PgBouncer и изпраща команда за презареждане.

Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Голям плюс на шаблона е, че се съхранява като код, така че при добавяне на нов шард е достатъчно да направите нов комит и да актуализирате шаблона автоматично, поддържайки принципа на инфраструктурата като код.

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

В резултат на това получихме следната схема на работа:
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Всички сървъри на приложения имат достъп до балансира → зад него има два екземпляра на PgBouncer → на всеки екземпляр се стартира Consul-template, който следи състоянието на всеки клъстер Patroni и следи уместността на конфигурацията на PgBouncer, която изпраща заявки до текущия лидер на всеки клъстер.

Ръчно тестване

Изпълнихме тази схема, преди да я стартираме в малка тестова среда и проверихме работата на автоматичното превключване. Те отвориха дъската, преместиха стикера и в този момент „убиха“ лидера на клъстера. В AWS това е толкова просто, колкото изключване на инстанцията чрез конзолата.

Failover Cluster 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 клъстер и да го стартираме, се натъкнахме на нов проблем: и двата сървъра стартираха като лидер. Patroni не знае нищо за ранното състояние на клъстера и се опитва да стартира двата сървъра като два отделни клъстера с едно и също име. За да разрешите този проблем, трябва да изтриете директорията с данни на slave:

rm -rf /var/lib/postgresql/

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

Когато се свърже чиста реплика, Patroni прави базов резервен лидер и я възстановява в репликата, след което наваксва текущото състояние според wal logs.

Друга трудност, която срещнахме, е, че всички PostgreSQL клъстери са наречени главни по подразбиране. Когато всеки клъстер не знае нищо за другия, това е нормално. Но когато искате да използвате Patroni, всички клъстери трябва да имат уникално име. Решението е да промените името на клъстера в конфигурацията на PostgreSQL.

тест за натоварване

Пуснахме тест, който симулира потребителското изживяване на дъски. Когато натоварването достигна средната ни дневна стойност, повторихме точно същия тест, изключихме един екземпляр с лидера на PostgreSQL. Автоматичният преход при срив работи както очаквахме: Patroni промени лидера, Consul-template актуализира конфигурацията на PgBouncer и изпрати команда за презареждане. Според нашите графики в Grafana беше ясно, че има закъснения от 20-30 секунди и малко количество грешки от сървърите, свързани с връзката към базата данни. Това е нормална ситуация, такива стойности са приемливи за нашия failover и определено са по-добри от престоя на услугата.

Въвеждане на Patroni в производство

В резултат на това стигнахме до следния план:

  • Разположете Consul-шаблон на PgBouncer сървъри и стартирайте;
  • Актуализации на PostgreSQL до версия 11.2;
  • Променете името на клъстера;
  • Стартиране на клъстер Patroni.

В същото време нашата схема ни позволява да направим първата точка почти по всяко време, можем да премахнем всеки PgBouncer от работа на свой ред и да внедрим и стартираме консулски шаблон върху него. Така и направихме.

За бързо разгръщане използвахме Ansible, тъй като вече тествахме всички книги с игри в тестова среда и времето за изпълнение на пълния скрипт беше от 1,5 до 2 минути за всеки шард. Можем да внедрим всичко на свой ред във всеки шард, без да спираме нашата услуга, но ще трябва да изключим всеки PostgreSQL за няколко минути. В този случай потребителите, чиито данни са на този шард, не могат да работят напълно в момента и това е неприемливо за нас.

Изходът от тази ситуация беше плановата поддръжка, която се извършва на всеки 3 месеца. Това е прозорец за планирана работа, когато напълно спираме нашата услуга и надграждаме екземплярите на нашата база данни. Оставаше една седмица до следващия прозорец и решихме просто да изчакаме и да се подготвим допълнително. По време на времето за изчакване ние се подсигурихме допълнително: за всеки шард на PostgreSQL създадохме резервна реплика в случай на невъзможност да запазим най-новите данни и добавихме нов екземпляр за всеки шард, който трябва да стане нова реплика в клъстера Patroni, за да не се изпълни команда за изтриване на данни . Всичко това помогна да се сведе до минимум рискът от грешка.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Рестартирахме нашата услуга, всичко работи както трябва, потребителите продължиха да работят, но на графиките забелязахме необичайно високо натоварване на сървърите на Consul.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Защо не видяхме това в тестовата среда? Този проблем илюстрира много добре, че е необходимо да се следва принципът на инфраструктурата като код и да се прецизира цялата инфраструктура, от тестови среди до производство. В противен случай е много лесно да се получи проблемът, който имаме. Какво стана? Consul първо се появи в производствени, а след това в тестови среди, в резултат на това в тестови среди версията на Consul беше по-висока от тази в производствената. Само в една от версиите изтичането на CPU беше разрешено при работа с консулски шаблон. Затова просто актуализирахме Consul, като по този начин решихме проблема.

Рестартирайте клъстера Patroni

Получихме обаче нов проблем, за който дори не подозирахме. Когато актуализираме Consul, ние просто премахваме възела Consul от клъстера, като използваме командата consul leave → Patroni се свързва с друг Consul сървър → всичко работи. Но когато стигнахме до последното копие на клъстера Consul и изпратихме командата за напускане на консула до него, всички клъстери 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

Успяхме да копираме проблема в тестова среда и тествахме тези опции там, но за съжаление те не проработиха.

Проблемът все още остава неразрешен. Планираме да опитаме следните решения:

  • Използвайте Consul-agent на всеки екземпляр на клъстер Patroni;
  • Коригирайте проблема в кода.

Разбираме къде е възникнала грешката: проблемът вероятно е използването на таймаут по подразбиране, който не се отменя чрез конфигурационния файл. Когато последният сървър Consul бъде премахнат от клъстера, целият клъстер Consul виси за повече от секунда, поради това Patroni не може да получи състоянието на клъстера и напълно рестартира целия клъстер.

За щастие не срещнахме повече грешки.

Резултати от използването на Patroni

След успешното стартиране на Patroni добавихме допълнителна реплика във всеки клъстер. Сега във всеки клъстер има подобие на кворум: един лидер и две реплики, за предпазна мрежа в случай на раздвоен мозък при превключване.
Failover Cluster PostgreSQL + Patroni. Опит във внедряването

Patroni работи над продукцията повече от три месеца. През това време той вече успя да ни помогне. Наскоро лидерът на един от клъстерите умря в AWS, автоматичният отказ работи и потребителите продължиха да работят. Patroni изпълни основната си задача.

Кратко резюме на използването на Patroni:

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

Източник: www.habr.com

Добавяне на нов коментар