
В днешно време високата достъпност на услугите е необходима навсякъде и по всяко време, не само в големи скъпи проекти. Временно недостъпните сайтове с известие „Съжаляваме, в момента се извършва техническо обслужване“ все още съществуват, но обикновено предизвикват снизходителна усмивка. Нека добавим и живота в облака, когато за стартиране на допълнителен сървър е нужно само едно извикване на API, без да се налага да се мисли за „железната“ експлоатация. И вече не остава оправдание защо критичната система не е била изградена надеждно с използването на клъстерни технологии и резервиране.
Ще ви разкажем за решенията, които разглеждахме за осигуряване на надеждност на базите данни в нашите услуги и до какви изводи стигнахме. Плюс демонстрация с далечни изводи.
Легаси в архитектурата за осигуряване на висока достъпност
Още по-добре е видимо в контекста на развитието на различни opensource системи. Старите решения бяха принудени да добавят технологии за висока достъпност с повишаване на търсенето. И качеството им варираше. Решенията от ново поколение поставят високата достъпност в основата на своята архитектура. Например, MongoDB позиционира клъстера като основен вариант за използване. Клъстерът се мащабира хоризонтално, което представлява силно конкурентно предимство за тази СУБД.
Нека се върнем към PostgreSQL. Това е един от най-старите и популярни opensource проекти, чийто първи релиз беше през 95-та година на миналия век. Проектният екип дълго време не смяташе, че високата достъпност е задача, която трябва да бъде решавана от системата. Затова технологията за репликация за създаване на копия на данни стана вградена само в версия 8.2 през 2006 г., но беше файлово ориентирана (log shipping). През 2010 г. с версия 9.0 се появи потокова репликация, която е основата за създаване на най-различни клъстери. Това всъщност учудва хората, които се запознават с PostgreSQL след Enterprise SQL или съвременни NoSQL — стандартното решение от общността е просто двойка master-replica с синхронна или асинхронна репликация. При това, в стоковата версия превключването на мастера се извършва ръчно, а въпросът за превключването на клиентите също се предлага да се решава самостоятелно.
Как решихме да направим надежден PostgreSQL и какво избрахме за това
Въпреки това, PostgreSQL нямаше да бъде толкова популярен, ако нямаше огромно количество проекти и инструменти, които помагат за изграждането на отказоустойчиво решение, което не изисква постоянно внимание. В облака (MCS) от самото си стартиране DBaaS предлагаха индивидуални сървъри PostgreSQL и двойки мастер-реплика с асинхронна репликация.
Разбира се, искахме да опростим живота на всички и да направим инсталацията на PostgreSQL лесно достъпна, която да служи като основа за високодостъпни услуги, без да е необходимо постоянно наблюдение и будене през нощта за превключване. В този сегмент има както стари доказани решения, така и ново поколение утилити, използващи последните разработки.
Днес проблемът с високата достъпност не опира до резервиране (това е очевидно), а до консенсуса — алгоритъм за избор на лидер (Leader election). Най-често големи аварии се случват не заради недостиг на сървъри, а заради проблеми с консенсуса: не се е избрал нов лидер, появили са се двама лидери в различни датацентрове и т.н. Пример — аварията на MySQL-кластера на Github — те написаха .
Математическата основа по този въпрос е много сериозна. От една страна, има , която налага теоретични ограничения върху възможностите за изграждане на HA-решения, а от друга страна — математически доказани алгоритми за определяне на консенсуса, като и . На тази основа съществуват доста популярни DCS (системи за децентрализиран консенсус) — Zookeeper, etcd, Consul. Следователно, ако системата за вземане на решения работи на някакъв свой алгоритъм, написан самостоятелно, с нея трябва да се отнасяме изключително внимателно. След анализ на огромно количество системи ние се спряхме на Patroni — отворена система, основно разработвана от компанията Zalando.
Като лирическо отклонение ще спомена, че също така разгледахме и multi-master решения, т.е. клъстери, които могат да се мащабират хоризонтално за запис. Обаче, по две основни причини решихме да не реализираме такъв клъстер. Първо, подобни решения имат висока сложност и, съответно, повече уязвимости. Ще бъде трудно да се създаде стабилно решение за всички случаи. На второ място, в такъв случай PostgreSQL спира да бъде чист (native), някои функции ще бъдат недостъпни, а при някои приложения могат да възникнат скрити бъгове при работа.
Patroni
И така, как работи Patroni? Разработчиците не са се опитвали да изобретят колелото и предлагат да се използва в основата едно от утвърдените DCS решения. Всички въпроси по синхронизация на конфигурации, избор на лидер и кворум се оставят на него. Избрахме за това etcd.
По-нататък Patroni се занимава с правилното прилагане на всички настройки на PostgreSQL и настройките на репликацията, както и с изпълнението на команди за switchover и failover (тоест — редовно и аварийно превключване на мастер). По-конкретно в облака MCS може да се създаде клъстер от мастер, синхронна реплика и една или няколко асинхронни реплики. Присъствието на синхронна реплика осигурява запазване на данните на минимум 2 сървъра, и именно тази реплика ще бъде основният „кандидат за мастер“.
Тъй като etcd се разгръща на същите сървъри, се препоръчва брой на сървърите от 3 или 5, за оптимално значение на кворума. Такъв клъстер се мащабира хоризонтално за четене (за мащабиране за запис писах по-горе). Все пак е важно да се има предвид, че асинхронните реплики имат склонност към забавяне, особено при високи натоварвания.
Използването на такива реплики за четене (hot standby) е обосновано за задачи по отчетност или аналитика и облекчава мастер-сервера.
Ако искате да създадете такъв клъстер сами, то ще ви е необходимo:
- да подготвите 3 или повече сървъра, да настроите IP адресацията и правилата за firewall между тях;
- да инсталирате пакетите за услугите etcd, Patroni, PostgreSQL;
- да настроите etcd клъстера;
- да настроите услугата patroni за работа с PostgreSQL.
Тоест, в обобщение, трябва правилно да съставите десет конфигурационни файла и никъде да не сбъркате. За това определено си струва да използвате инструмент за управление на конфигурации, като например Ansible. Въпреки това, тук все още липсва високо-достъпен TCP-балансировач. Създаването му е отделна работа.
За тези, на които им е необходим готов клъстър, но не искат да се занимават с всичко това, се опитахме да опростим живота и създадохме готов клъстър на Patroni в нашето облако, който може да бъде тестван безплатно. Освен самия клъстър, ние направихме:
- TCP-балансировач; той винаги сочи към текущия мастер, синхронна или асинхронна реплика, съответно, по различни портове;
- API за превключване на активния мастер на Patroni.
Можете да ги свържете както чрез API на облака MCS, така и через уеб конзолата.
Демо
За тестване на възможностите на PostgreSQL клъстера в облака MCS, нека видим как ще се държи живото приложение при проблеми с СУБД.
Нататък е представен код на приложението, което ще логира изкуствени събития и ще съобщава за това на екрана. В случай на грешки, то ще ги съобщава и ще продължава да работи в цикъл, докато не го спрем с комбинация от Ctrl + C.
from __future__ import print_function
from datetime import datetime
from random import randint
from time import sleep
import psycopg2
def main():
try:
connection = psycopg2.connect(user = "admin",
password = "P@ssw0rd",
host = "89.208.87.38",
port = "5432",
database = "myproddb")
cursor = connection.cursor()
cursor.execute("SELECT version();")
record = cursor.fetchone()
print("Connection opened to", record[0])
cursor.execute(
"INSERT INTO log VALUES ({});".format(randint(1, 10000)))
connection.commit()
cursor.execute("SELECT COUNT(event_id) from log;")
record = cursor.fetchone()
print("Logged a value, overall count: {}".format(record[0]))
except Exception as error:
print ("Error while connecting to PostgreSQL", error)
finally:
if connection:
cursor.close()
connection.close()
print("Connection closed")
if __name__ == '__main__':
try:
while True:
try:
print(datetime.now())
main()
sleep(3)
except Exception as e:
print("Caught error:n", e)
sleep(1)
except KeyboardInterrupt:
print("exit")
На приложението му е необходим PostgreSQL. Нека създадем клъстър в облака MCS, използвайки API. В обикновен терминал, в който в променливата OS_TOKEN е записан токен за достъп до API (може да се получи с командата openstack token issue), въведете командите:
Създаваме клъстър:
котка < pgc10.json
{"cluster":{"name":"postgres10","allow_remote_access":true,"datastore":{"type":"postgresql","version":"10"},"databases":[{"name":"myproddb"}],"users":[{"databases":[{"name":"myproddb"}],"name":"admin","password":"P@ssw0rd"}],"instances":[{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}}]}}
EOF
curl -s -H "X-Auth-Token: $OS_TOKEN"
-H 'Accept: application/json'
-H 'Content-Type: application/json'
-d @pgc10.json https://infra.mail.ru:8779/v1.0/ce2a41bbd1434013b85bdf0ba07c770f/clusters

Когато клъстерът премине в статус ACTIVE, всички полета ще получат актуални стойности — клъстерът е готов.
В GUI:

Нека пробваме да се свържем и да създадем таблица:
psql -h 89.208.87.38 -U admin -d myproddb
Парола за потребител admin:
psql (11.1, сървър 10.7)
Напишете "help" за помощ.
myproddb=> CREATE TABLE log (event_id integer NOT NULL);
CREATE TABLE
myproddb=> INSERT INTO log VALUES (1),(2),(3);
INSERT 0 3
myproddb=> SELECT * FROM log;
event_id
----------
1
2
3
(3 реда)
myproddb=>

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

Планирано превключване на майстора
Сега да тестваме работата на нашето приложение при планирано превключване на майстора:

Наблюдаваме приложението. Виждаме, че работата на приложението наистина прекъсва, но това отнема само няколко секунди, в този конкретен случай, максимум 9.

Спад на машината
Сега да опитаме да симулираме спад на виртуалната машина, текущия майстор. Можеше просто да изключим виртуалната машина през интерфейса Horizon, само че това ще бъде стандартно изключване. Такова превключване ще бъде обработено от всички услуги, включително и Patroni.
Ние обаче се нуждаем от непредсказуемо изключване. Затова помолих нашите администратори за тестови цели да изключат виртуалната машина — текущия майстор — по нештатен начин.

По същото време нашето приложение продължава да работи. Естествено, такова екстремно превключване на майстора не може да премине незабелязано.
2019-03-29 10:45:56.071234
Свързване с PostgreSQL 10.7 на x86_64-pc-linux-gnu, компилирано от gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-бит
Записана стойност, общо количество: 453
Свързването е затворено
2019-03-29 10:45:59.205463
Свързване с PostgreSQL 10.7 на x86_64-pc-linux-gnu, компилирано от gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-бит
Записана стойност, общо количество: 454
Свързването е затворено
2019-03-29 10:46:02.661440
Грешка при свързването с PostgreSQL серерът затвори връзката неочаквано
Това вероятно означава, че сървърът е прекратил работата си необичайно
преди или по време на обработката на заявката.
Хваната грешка:
локалната променлива 'connection' е използвана преди да бъде присвоена
……………………………………………………….. - тук има някакво количество грешки
2019-03-29 10:46:30.930445
Грешка при свързването с PostgreSQL серерът затвори връзката неочаквано
Това вероятно означава, че сървърът е прекратил работата си необичайно
преди или по време на обработката на заявката.
Хваната грешка:
локалната променлива 'connection' е използвана преди да бъде присвоена
2019-03-29 10:46:31.954399
Свързване с PostgreSQL 10.7 на x86_64-pc-linux-gnu, компилирано от gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-бит
Записана стойност, общо количество: 455
Свързването е затворено
2019-03-29 10:46:35.409800
Свързване с PostgreSQL 10.7 на x86_64-pc-linux-gnu, компилирано от gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-бит
Записана стойност, общо количество: 456
Свързването е затворено
^Cизход
Както виждате, приложението успя да продължи работата си след по-малко от 30 секунди. Да, известно количество потребители на услугата ще забележат проблемите. Въпреки това, това е сериозен срив на сървъра, не се случва толкова често. При това, човекът (администраторът) едва ли би успял да реагира толкова бързо, освен ако не е стоял готов на конзолата със скрипт за превключване.
Извод
Според мен, такъв кластер предоставя огромно предимство за администраторите. По същество, сериозните аварии и неизправности на бази данни няма да бъдат забележими за приложението и, съответно, за потребителя. Няма да е необходимо да се ремонтира нещо в бързаме и да се преминава на времени конфигурации, сървъри и т.н. А ако такова решение се използва под формата на готова услуга в облака, няма да е нужно да се губи време за неговата подготовка. Може да се занимавате с нещо по-интересно.
Източник: habr.com
