Вводная


Предположим, что мы настроили асинхронную репликацию на двух хостах с PostgreSQL. С какой проблемой могут столкнуться потенциальные пользователи? На мой взгляд самой очевидной является отставание реплики от ведущего мастера. Не страшно, если юзер (или его клиентское приложение) открыло BEGIN. В этот момент может разорваться соединение, и все незакомиченные изменения будут откачены.
Но возможна ситуация, когда ведущая СУБД выдаст пользователю COMMIT, резко упадёт, а на слейв не успеет дойти подтверждение транзакции. Тогда при восстановлении реплики эти данные попадут под ROLLBACK (так как на самом деле на ведомую ноду передаются не конечные данные, а вся последовательность действий), а пользователь будет ошибочно считать, что его данные были успешно сохранены.

Синхронная репликация


Решим эту проблему в два счёта! Переходим в PGDATA (по умолчанию для Windows это %ProgramFiles%\PostgreSQL\9.4\bin) и запускаем утилиту подключения к СУБД:

psql.exe -h <ip-мастера> -U postgres

Указываем IP или DNS реплик, запись с которыми будет синхронизирована. Можно использовать параметр "*", так как в случае использования физической репликации количество подчинённых хостов будет ограничено выделенными для них слотами:


ALTER SYSTEM SET synchronous_standby_names = '10.10.1.2, 10.10.1.3'; 

Задаём мастеру уровень синхронизации:


ALTER SYSTEM SET synchronous_commit = 'on';

Все изменения попадут в файл %PGDATA%\postgresql.auto.conf и перекочуют в основной postgresql.conf после перезапуска службы СУБД. Но нам не терпится активировать изменения и мы пересчитываем конфигурацию встроенной функцией, которая возвращает «t» в случае успеха:


SELECT pg_reload_conf(); # 

Примечание: указанные выше настройки на работающие реплики внести нельзя — за эти проверки большое спасибо предусмотрительному сообществу разработчиков postgres. Не все параметры, например max_replication_slot, можно активировать без перезапуска СУБД (некоторые вообще приходится менять на этапе компиляции исходников)).

Проверим, что на слейвах продолжает работать репликация:


SELECT (slot_name, active) from pg_replication_slots; 

Что мы в итоге получили?


Технически теперь COMMIT транзакции первым оказывается на реплике). Появляется возможность читать данные со слейва, разгружая основной сервер. В качестве бонуса — селекты пользователей будут вносить изменения только на ноде, к которой было обращение.

При больших нагрузках скорость работы клиентов снизиться: здесь скажется задержка ведомой нод при подтверждении записи транзакций. «Потери» минимизируются тюнингом постгреса и грамотной планировкой работы с БД. Например, при записи данных с низким приоритетом есть возможность задать параметр сессии:


SET synchronous_commit = 'local'; 
Параметр принимает заданное значения до окончания соединения. Значение off, на мой взгляд, используется только для откровенного мусора.

Отказ слейва.

В случае выхода из строя подчинённой ноды ведущий сервер будет бесконечно долго ждать подтверждения записи проведённой транзакции. Выходов из этой ситуации три:

  1. Научить пользователей\приложение в случае повисания транзакции отменять её и переводить свои сессии в режим локальной записи.

  2. Подключить к мастеру вторую слейв-ноду, создав для неё второй физический слот, так как в PostgreSQL подтверждение записи транзакций достаточно от одного подчиненного хоста. В случае если требуется уложиться в схему из двух физических машин с операционками linux-like, то можно второй слейв поставить на компьютер мастерa, указав другой порт работы службы. На Windows поставить 2 постгреса через инсталлятор не получится — установщик корректно предлагает открыть 5433 порт (5432 уже занят первой СУБД), создаёт уникальное имя для второй службы, но стартует её с ошибкой.

  3. Написать свою программу, которая будет отслеживать состояние передачи данных с мастера на слейв-хост, о чём я и расскажу во второй части статьи.

PostgreSAM


Программа представляет собой однопоточную утилиту, которая локально проверяет через пользовательский интерфейс postgres состояние репликации данных. Из-за упрощения логики работы перед запуском достаточно указать только пароль учётной записи postgres.


В случае если мастер теряет соединение со слейвом, утилита выключает на ведущем сервере параметр синхронизации данных и пересчитывает конфигурацию. На экран выводиться сообщение о внесённых изменениях. Когда связь со слейвом будет восстановлена, можно нажать клавишу «ОК» — PostgreSAM переконфигурирует ведущий сервер в исходное состояние. Утилита так же работает на слейве: сообщение о недоступности мастера будет выводиться, но изменений в работу ноды внесено не будет. На работе пользователей перезапуск конфигурации никак себя не проявляет: соединение не обрывается, а «повисшие» транзакции успешно завершаются.
Для связи с СУБД используется библиотека Npgsql — .NET провайдер для PostgreSQL. Для её корректной работы так же требуются библиотека Mono.Security — так как программка не требует установки, то обе библиотеки с исполняемым файлом есть в архиве. Если вы планируете использовать эту библиотеку в своем проекте, то необходимо добавить её в сборку и объявить:

using Npgsql;

Теперь нам доступен класс NpgsqlConnection с конструктором, принимающим по умолчанию строковый параметр:

"Server=<ip_или_dns>;User Id=<пользователь>;Password=<пароль>;Database=<база>;Timeout=<секунды>;"

Если пропустить Database, Timeout, то будет использоваться БД, идентичная имени пользователя, и таймаут в 20 секунд. После этого появляется возможность вызывать встроенную в класс функцию Open — откроется соединение с нужной нам БД. Далее используется класс NpgsqlCommand:

NpgsqlCommand <имя_объекта> = new NpgsqlCommand(<команда_SQL>, <имя_объявленного_NpgsqlConnection>);

В случае ошибки установления соединения или неправильного выполнения переданной на СУБД команды создаётся исключение, которое рекомендую отлавливать комбинацией try-catch. Данные выполненных запросов считываются при помощи связки объектов классов NpgsqlCommand (создавая объект, необходимо указать SQL-запрос и строку подключения), а так же NpgsqlDataReader:

NpgsqlDataReader <имя_переменной> = <созданный_объект_NpgsqlCommand>.ExecuteReader();

Для созданной переменной предусмотрена функция Read(). К плюсам Npgsql я бы отнёс предусмотренную поддержку работы по SSL-соединению, а так же возможность передачи запросов и получения ответов в виде plain-текст. В минусы сразу попадают exeption'ы — хотя в NpgsqlCommand есть CheckConnectionState, всё-равно в случае ошибки соединения генерируется InvalidOperationException.

В конце выполнения команд СУБД необходимо закрывать соединение функцией класса NpgsqlConnection — Close().
Поделиться с друзьями
-->

Комментарии (4)


  1. unnforgiven
    08.09.2016 12:52

    ОУ виндавс POSTGRES — ты серьезно?


    1. Louter
      08.09.2016 13:13
      +1

      Сначала так же подумал, но потом вспомнил заказчиков, сертефикаторов и т.д. У автора в статье упоминается .NET, так что видимо там всё виндовое. Ну нет у них репликации на уровне ФС, ну в принципе пофиг)


  1. Louter
    08.09.2016 13:11

    Думаю можно ещё так:
    Для уменьшения задержки при синхронной репликации можно установить значение synchronous_commit в remote_write. В таком случае будет ожидаться не запись на диск, а ответ от резервного сервера о получении данных.
    Так же использовать её следует для реально важных данных и включать её следует на уровне приложения, а не конфигурационно для всего сервера.

    Минус: при больших нагрузках скорость работы клиентов снизиться: здесь скажется задержка ведомых нод при подтверждении записи транзакций.

    Не ведомых, а ведомой, СУБД для синхронной репликации в версии 9.4 использует первую доступную ноду. В случае её сбоя другую доступную, но всегда одну.

    Всё-таки синхронную репликацию лучше делать частью каскадной репликации с горячим резервом, т.к. как ни крути, а накладной расход на ожидание уменьшает отклик, а если приложение постоянно мелкими запросами меняет данные, то отклик увеличится на порядок.

    Про потерю данных в асинхронном режиме: она возможна, на время недоступности ведущего сервера, это есть, хотя потоковая репликация уменьшает это время, всё равно остаётся риск. Но она сильно быстрее работает, а по восстановлении ведущего сервера комиты дозапишутся, если ведомые сервера работали в режиме только для чтения. Если же горячая замена, то по идее без конфликтов допишется, но конфликты могут начаться просто с id записей и oid-ами.

    Поэтому грамотный баланс синхронной и асинхронной репликации дадут и возможность горячего резерва с рабочими данными, и бэкап, и хорошие нервы, но делать его хорошо бы на уровне и приложения, и БД, и сетевом, и хардварном))


    1. postgrez4ik
      09.09.2016 08:01

      спасибо — поправил)