Pull to refresh

Watchdog для репликации в PostgreSQL 9

Reading time4 min
Views3.9K
Приветствую. Хочу поделиться одним самописным костылём, авось кому-нибудь будет полезен.

Коротко о главном


Моделируем ситуацию: есть кластер PostgreSQL-серверов — мастер и n-реплик. Наступает черный день и одна(или несколько) реплик падает. Причины неважны — сдохла железка, уборщица перебила шваброй провод или НЛО временно зохавало серверную. Итог один — если реплика долго лежала, то сама она уже никогда не нагонится.

Причина заключается в самом процессе репликации PostgreSQL. По мере поступления данных, журнал скидывается на диск(по дефолту — кусками по 16Мб). В конфиге мастера мы используем опцию wal_keep_segments, которая указывает на то, сколько последних кусков XLOG-а мы будем хранить. Пусть, параметр будет равен 16ти. Это значит, что в директории pg_xlog одновременно будет находится 16-лог файлов, а при появлении 17го, будет произведена попытка отправить 1й в архив(иными словами — происходит постоянная ротация). И если ваша реплика отстанет, больше чем на эти 16 файлов — при попытке нагнать мастер, в логе вы увидете сообщение о том, что необходимый кусок XLOG-а не может быть получен т.к. на мастере он уже ушел в архив. А всё потому, что дефолтная команда архивации, как правило, выглядит так:

cp %p /var/lib/postgresql/9.0/main/archive/%f

Как можно видеть — никаких проверок на то, не повредит ли кому-нибудь архивация файла.
В сущности — ничего смертельного в этом нет — если необходимый архивный файл не был удалён, нужно просто подсунуть его на реплику в нужное место и файл сам «накатится». Неприятный момент заключается в том, что, в зависимости от нагрузки, копировать, как правило приходится не один файл, а десятки/сотни/тысячи. К тому же за процессом нужно следить т.к. пока вы копируете одни файлы — мастер архивирует новые. В худшем случае можно спастись полной «переналивкой» реплики.
Для того, чтобы не сталкиваться с такими радостями, я написал небольшой скрипт на смеси Perl и Bash. Заранее скажу, что ни в коем случае не претендую на звание лучшего программиста — главным условием была не красота кода, а выполнение необходимых действий. Итак, скрипт лежит здесь, а я пока расскажу, что именно он делает.

Как это работает


master01:~# ps aux|grep postg|grep sender
postgres 26132 0.0 0.0 6518660 3052 ? Ss 20:21 0:01 postgres: wal sender process postgres 192.168.12.1(36254) streaming 153/ED957E68
postgres 26133 0.0 0.0 6518660 3056 ? Ss 20:21 0:01 postgres: wal sender process postgres 192.168.12.2(51907) streaming 153/ED957E68
postgres 26135 0.0 0.0 6518660 3060 ? Ss 20:21 0:01 postgres: wal sender process postgres 192.168.12.3(39404) streaming 153/ED957E68
postgres 29142 0.0 0.0 6518724 3084 ? Ss 20:44 0:01 postgres: wal sender process postgres 192.168.12.4(51897) streaming 153/ED957E68
postgres 29320 0.0 0.0 6518724 3084 ? Ss 20:45 0:01 postgres: wal sender process postgres 192.168.12.5(49234) streaming 153/ED957E68
postgres 29453 0.0 0.0 6518724 3084 ? Ss 20:46 0:01 postgres: wal sender process postgres 192.168.12.6(35519) streaming 153/ED957E68


Как мы видим из этой несложной команды — в списке процессов можно найти:
1. Количество подключенных реплик, с указанием их ip-адресов.
2. Кусок XLOG-а, который на данный момент ими обрабатывается.

А мы в свою очередь, как люди, управляющие этой конструкцией, знаем, сколько у нас реплик и какие у них адреса. Обратим внимание на последнюю часть строк — это позиция XLOG-а для конкретной реплики. Если мы уберем из этого значения слэш и из получившегося отрежем последние 6 символов, то у нас получится 153ED. Это 16-тиричное число.

А еще в списке процессов можно найти такое:

master01:~# ps aux|grep postg|grep arch
postgres 556 0.0 0.0 66184 1668 ? Ss Oct11 0:23 postgres: archiver process last was 0000000100000153000000EB


Последняя часть строки — файл, который был заархивирован. Забираем последние 13 символов, убираем из них 6 нулей, идущих подряд. Получим 153EB.

Преобразуем полученные числа в десятеричные(для наглядности), получим 87021 и 87019. Сравнив эти два числа мы узнаем, что реплики использует лог, который старше последнего заархивированного на 2 шага — следовательно, один фрагмент мы можем смело удалять.

Исходя из этих несложных манипуляций и работает мой скрипт:

1. Администратор складывает в /etc/postgresql/9.0/main/slaves.list список реплик(по одной на строку).
2. Скрипт вписывается в качестве archive_command в конфиг PostgreSQL.
3. При попытке заархивировать кусок XLOG-а происходит следующее:
3.1. Проверяется, соответствует ли количество подключенных реплик, количеству, указанному в конфиге.
3.2. Если все хорошо — смотрим, соответствует ли хосты, указанным в конфиге(сделано на случай, если при расширении кластера мы забудем записать новую реплику в конфиг).
3.3. Вычисляем минимальную позиция XLOG-а среди реплик(ищем самую отстающую).
3.4. Сравниваем числа из имени файла, подлежащего архивации и числом из предыдущего пункта.

Если все условия пройдены — копируем файл в архив и возвращаем 0. Если что-то пошло не так — возвращаем единицу — PostgreSQL сочтёт попытку неудачно и через какое-то время предпримет следующую. За процессом можно следить в /var/log/postgresql/9.0/watchdog.log

Ахтунг! Подразумевается, что в списке реплик будут указаны существующие DNS-записи(сделано для личного удобства), либо имена, указанные локально в /etc/hosts. Чтобы отключить это — нужно закомментировать строки с 47й по 55ю.
Tags:
Hubs:
+23
Comments8

Articles