Высокая доступность, балансировка нагрузки и репликация

Серверы баз данных могут работать вместе, что позволяет быстро переключаться на второй сервер в случае отказа первого (высокая доступность), или позволяет нескольким компьютерам обрабатывать одни и те же данные (балансировка нагрузки). В идеале серверы баз данных могут работать вместе незаметно для пользователя. Веб-серверы, обрабатывающие статические страницы, можно достаточно легко объединить, просто распределив веб-запросы на несколько машин. Собственно, серверы баз данных только для чтения тоже можно относительно легко объединить. К сожалению, большинство серверов баз данных получают смешанные запросы на чтение/запись, а серверы чтения/записи объединить гораздо сложнее. Это связано с тем, что данные только для чтения нужно разместить на каждом сервере только один раз, а запись на любой из сервером должна распространяться на все серверы, чтобы будущие запросы на чтение к этим серверам возвращали согласованные результаты.

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

Некоторые решения справляются с синхронизацией, позволяя модифицировать данные только одному серверу. Серверы, способные модифицировать данные, называются серверами чтения/записи, главными или основными серверами. Серверы, отслеживающие изменения на основном, называются резервными или вторичными серверами. Резервный сервер, к которому нельзя подключаться, пока он не будет повышен до основного, называется сервером теплого резерва, а тот, который может принимать подключения и обрабатывать запросы только на чтение, называется сервером горячего резерва.

Некоторые решения являются синхронными, то есть транзакция, модифицирующая данные, не считается зафиксированной, пока все серверы ее не зафиксируют. Это гарантирует, что при сбое не потеряются никакие данные и что все серверы с балансировкой нагрузки вернуть согласованные результаты независимо от того, к какому серверу был запрос. Асинхронное решение, наоборот, допускает некоторую задержку между временем фиксации и передачей транзакции другим серверам, допуская возможность, что некоторые транзакции могут быть потеряны при переключении на резервный сервер и что серверы с балансировкой нагрузки могут вернуть слегка устаревшие результаты. Асинхронная передача используется, когда синхронная будет чересчур медленной.

Также решения можно классифицировать по степени их детализации. Некоторые решения работают только со всем сервером баз данных, тогда как другие допускают управление на уровне таблиц или баз данных.

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

Далее в этой главе кратко обрисовываются различные решения по реализации отказоустойчивости, репликации и балансировки нагрузки.



Сравнение различных решений

Отказоустойчивость на разделяемых дисках

Отказоустойчивость на разделяемых дисках позволяет избежать издержек синхронизации, поскольку имеется только одна копия базы данных. Она применяет единственный дисковый массив, который совместно используется несколькими серверами. Если основной сервер баз данных отказывает, резервный сервер может подключиться и запустить базу данных, как будто та восстановилась после сбоя. Это обеспечивает быстрое переключение при отказе без потери данных.

Функциональность разделяемого оборудования распространена в сетевых устройствах хранения. Также возможно использование сетевой файловой системы, хотя следует позаботиться о том, чтобы поведение системы полностью соответствовало POSIX (см. подраздел NFS). Единственное значительное ограничение этого метода заключается в том, что при отказе или повреждении разделяемого дискового массива и основной, и резервный серверы перестанут функционировать. Другая сложность состоит в том, что резервный сервер никогда не должен обращаться к разделяемому хранилищу, пока работает основной.

Репликация на уровне файловой системы (блочного устройства)

Модифицированной версией функциональности разделяемого оборудования является репликация на уровне файловой системы, где все изменения в файловой системе отражаются в файловой системе, находящейся на другом компьютере. Единственное ограничение заключается в том, что отражение должно происходить способом, гарантирующим, что на резервном сервере имеется согласованная копия файловой системы — в частности, записями на резервном сервере должны происходить в том же порядке, что и на основном. DRBD является популярным решением с репликацией файловой системы для Linux.

Доставка журналов упреждающей записи

Серверы теплого и горячего резерва могут поддерживаться в актуальном состоянии путем чтения потока записей журнала упреждающей записи (WAL). При сбое на главном сервере резервный содержит почти все данные с него и может быстро быть преобразован в новый основной сервер баз данных. Этот процесс может быть синхронным или асинхронным и может быть выполнен только на уровне всего сервера баз данных.

Резервный сервер можно реализовать путем использования передачи файлов журналов (см. раздел Доставка журналов на резервные серверы), или потоковой репликации (см. подраздел Потоковая репликация), или их комбинацией. Информацию о горячем резерве см. в разделе Горячий резерв.

Логическая репликация

Логическая репликация позволяет серверу баз данных передавать поток модификаций данных другому серверу. Механизм логической репликации QHB формирует поток модификаций данных из WAL. Логическая репликация позволяет воспроизводить изменения данных на уровне таблиц. Кроме того, сервер, публикующий свои изменения, может также подписываться на изменения от другого сервера, позволяя данным передаваться в нескольких направлениях. Более подробную информацию о логической репликации см. в главе Логическая репликация. Через интерфейс логического декодирования (глава Логическое декодирование) подобную функциональность могут предоставлять также и сторонние расширения.

Репликация основной-резервный на основе триггеров

Обычно с установкой репликации на основе триггеров запросы, модифицирующие данные, отправляются на выбранный основной сервер. Действуя на уровне таблиц, основной сервер асинхронно (как правило) передает изменения данных резервным серверам. Резервные сервера могут выполнять запросы одновременно с основным и допускать некоторые изменения локальных данных или операции записи. Эта форма репликации часто применяется для снижения нагрузки, связанной с крупными аналитическими запросами или запросами к хранилищу данных.

Примером такого типа репликации является Slony-I, с детализацией на уровне таблиц и поддержкой нескольких резервных серверов. Поскольку она обновляет резервный сервер асинхронно (пакетами), существует вероятность потери данных во время переключения при отказе.

Репликация на основе SQL в ПО промежуточного слоя

При репликации на основе SQL в промежуточном слоя программа перехватывает каждый запрос SQL и передает его одному или всем серверам. Каждый сервер работает независимо. Запросы на чтение-запись должны передаваться всем серверам, чтобы каждый из них получил все изменения. Но запросы только на чтение можно передавать только одному серверу, позволяя распределить нагрузку чтения между всеми серверами.

Если запросы просто рассылаются без модификаций, функции вроде random(), CURRENT_TIMESTAMP и последовательности могут получить различные значения на разных серверах. Это связано с тем, что каждый сервер работает независимо, а вместо фактических изменений данных рассылаются запросы SQL. Если это неприемлемо, то ПО промежуточного слоя либо приложение должны получить подобные значения из одного источника, а затем использовать их в запросах на запись. Также следует позаботиться о том, чтобы все транзакции фиксировались или прерывались на всех серверах, возможно, с помощью двухфазной фиксации (см. PREPARE TRANSACTION и COMMIT PREPARED). Примерами репликации этого типа являются Pgpool-II и Continuent Tungsten.

Асинхронная репликация с несколькими главными серверами

Для серверов, которые подключаются нерегулярно или через медленные каналы связи, как, например, ноутбуки или удаленные серверы, поддержание согласованности данных между ними представляет сложную задачу. При использовании асинхронной репликации с несколькими главными серверами каждый сервер работает независимо и периодически связывается с другими серверами, чтобы определить конфликтующие транзакции. Эти конфликты могут разрешиться пользователями или правилами разрешения конфликтов. Примером репликации такого типа является Bucardo.

Синхронная репликация с несколькими главными серверами

При синхронной репликации с несколькими главными серверами каждый сервер может принимать запросы на запись, а модифицированные данные передаются с исходного сервера всем остальным, прежде чем каждая транзакция будет зафиксирована. Интенсивная запись может вызвать излишние блокировки, приводящие к снижению производительности. Запросы на чтение могут передаваться на любой сервер. Для снижения издержек при обмене данными некоторые реализации используют разделяемые диски. Синхронная репликация с несколькими главными серверами лучше всего подходит для сред, где преобладают операции чтения, хотя ее значительное преимущество состоит в том, что любой сервер может принимать запросы на запись — нет необходимости разделять рабочую нагрузку между основным и резервными серверами, а поскольку изменения данных передаются от одного сервера другому, отсутствует проблема с недетерминированными функциями вроде random().

QHB не предоставляет репликацию такого типа, хотя для ее реализации в коде приложения или в ПО промежуточного слоя можно использовать двухфазную фиксацию QHB (PREPARE TRANSACTION и COMMIT PREPARED).

В Таблице 1 кратко перечислены возможности различных решений.

Таблица 1. Матрица свойств высокой доступности, балансировки нагрузки и репликации

СвойствоРаздел. дискРепл. файл. сист.Доставка WALЛогическ. репл.Триггерн. репл.Репл. SQL в пром. слоеАсинхр. репл. с н. гл.Синхр. репл. с н. гл.
Изв. примерыNASDRBDвстр. поток. репл.встр. логич. репл., pglogicalLondiste, Slonypgpool-IIBucardo
Метод коммуник.раздел. дискидисков. блокиWALлогич. декодир.строки табл.SQLстроки табл.строки табл. и блокир. строк
Не треб. спец. оборуд.
Допуск. неск. осн. серверов
Нет издержек на осн.
Нет ожидания при неск. серверахбез синхр.без синхр.
Отказ осн. не приводит к потере данныхс синхр.с синхр.
Сервер реплики приним. запр. только на чтен.с горячим резервом
Детал. репликац. на ур. таблиц
Не треб. разрешен. конфликтов

Существует также несколько решений, которые не подпадают под указанные выше категории:

Партиционирование данных

При партиционировании данных таблицы расщепляются на наборы данных. Каждый набор может быть модифицирован только на одном сервере. К примеру, данные могут быть партиционированы по офисам, например, Лондон и Париж, с сервером в каждом офисе. Если требуются запросы с комбинированными данными по Лондону и Парижу, приложение может запросить оба сервера, или может быть применена репликация основной/резервный для хранения на каждом сервере данных из другого офиса в виде копии только для чтения.

Выполнение параллельных запросов на нескольких серверах

Многие из описанных выше решений допускают обработку нескольких запросов несколькими серверами, но ни одно из них не разрешает одному запросу использовать несколько серверов для ускорения вычислений. Это решение позволяет нескольким серверам параллельно обрабатывать один запрос. Обычно это достигается путем разделения данных между серверами, чтобы каждый сервер выполнил свою часть запроса и вернул результаты центральному серверу, где они объединяются и возвращаются пользователю. Это решение можно реализовать с помощью набора средств PL/Proxy.



Доставка журналов на резервные серверы

Для создания кластерной конфигурации высокой доступности (HA) с одним или несколькими резервными серверами, готовыми взять под контроль операции в случае краха основного сервера, может использоваться постоянное архивирование. Эта способность широко известна как теплый резерв или доставка журналов.

Основной и резервный серверы работают вместе для обеспечения этой возможности, хотя эти серверы связаны лишь косвенно. Основной сервер работает в режиме постоянного архивирования, тогда как каждый резервный сервер работает в режиме постоянного восстановления, читая файлы WAL от основного. Для включения этой возможности нет необходимости вносить изменения в таблицы базы данных, поэтому издержки на ее администрирование ниже, чем у ряда других решений репликации. Кроме того, эта конфигурация относительно слабо влияет на производительность основного сервера.

Прямая передача записей WAL с одного сервера баз данных на другой обычно описывается как доставка журналов. QHB реализует доставку журналов на уровне файлов путем передачи записей WAL по одному файлу (сегменту WAL) за раз. Файлы WAL (16 МБ) можно легко и незатратно доставить на любое расстояние, будь то соседняя системы, другая системы в местной сети или система на другом конце света. Пропускная способность, требуемая для этой методики зависит от скорости записи транзакций на основном сервере. Доставка журналов на уровне записей более фрагментарна и передает поток изменений WAL последовательно через сетевое соединение (см. подраздел Потоковая репликация).

Следует отметить, что доставка журналов асинхронна, т. е. записи WAL доставляются после фиксации транзакции. В результате образуется временное окно потенциальной потери данных при критическом отказе основного сервера; еще не доставленные транзакции будут утеряны. Размер этого окна при доставке журналов на уровне файлов можно ограничить с помощью параметра archive_timeout, в котором можно установить значение всего в несколько секунд. Однако такое низкое значение потребует значительно увеличить пропускную способность, необходимую для доставки файлов. Потоковая репликация (см. подраздел Потоковая репликация) позволяет сделать окно потенциальной потери данных гораздо меньше.

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


Планирование

Обычно разумно создавать основной и резервный серверы так, чтобы они были максимально похожи, по крайней мере в том, что касается сервера баз данных. В частности, пути, связанные с табличными пространствами, будут передаваться без изменений, поэтому при использовании этой функциональности и на основном, и на резервных серверах должны быть одинаковые пути монтирования для табличных пространств. Учитывайте, что если CREATE TABLESPACE выполняется на основном сервере, любая новая необходимая для нее точка монтирования должна быть создана на основном и всех резервных серверах еще до выполнения этой команды. Аппаратной части необязательно быть полностью одинаковой, но опыт показывает, что сопровождать на протяжении существования приложения и системы две идентичные системы легче, чем две разные. В любом случае архитектура аппаратной части должна быть одинаковой — доставка журналов, скажем, с 32-битной системы на 64-битную не будет работать.

В целом, доставка журналов между серверами с различными главными версиями QHB невозможна. Политика главной группы разработки QHB состоит в том, чтобы не вносить изменения в дисковые форматы при обновлениях дополнительной версии, так что, скорее всего, основной и резервные серверы с разными дополнительными версиями будут работать успешно. Однако формально это не поддерживается, поэтому рекомендуется, насколько это возможно, поддерживать на основном и резервных серверах одинаковую версию. При обновлении дополнительной версии безопаснее всего будет сначала обновить резервные серверы — новая дополнительная версия с большей вероятностью сможет прочитать файлы WAL из предыдущей дополнительной версии, чем наоборот.


Работа резервного сервера

Сервер переходит в режим резерва, если при его запуске в каталоге данных существует файл standby.signal.

В режиме резерва сервер непрерывно применяет файлы WAL, полученные от основного сервера. Резервный сервер может читать файлы WAL из архива WAL (см. restore_command) или напрямую с основного сервера через соединение TCP (потоковая репликация). Также резервный сервер попытается восстановить все файлы WAL, найденные в его кластере в каталоге pg_wal. Обычно это происходит после перезапуска резервного сервера, когда он снова воспроизводит файлы WAL, полученные от основного перед перезапуском, но можно в любой момент вручную скопировать файлы в pg_wal, чтобы они воспроизвелись.

При запуске резервный сервер начинает восстанавливать все файлы WAL, доступные в архивном хранилище, вызвав команду restore_command. По достижении конца доступных там файлов WAL или при сбое restore_command сервер пытается восстановить все файлы WAL, доступные в каталоге pg_wal. Если это не удается и была сконфигурирована потоковая репликация, резервный сервер пытается подключиться к основному, и начать закачивать поток WAL с последней рабочей записи, найденной в архиве или pg_wal. Если же и это не удается, или потоковая репликация не была сконфигурирована, или подключение позднее прервалось, резервный сервер возвращается к шагу 1 и снова пытается восстановить файл из архива. Этот цикл попыток получения файлов из архива, pg_wal и посредством потоковой репликации продолжается до остановки сервера или переключения его режима, вызванного триггерным файлом.

Режим резерва завершается и сервер переключается в обычный режим работы, когда выполняется команда qhb_ctl promote, вызывается функция pg_promote() или обнаруживается триггерный файл (promote_trigger_file). Перед переключением будут восстановлены все файлы WAL, непосредственно доступные из архива или pg_wal, но попыток подключения к основному серверу больше не будет.


Подготовка основного сервера для работы с резервными

Настройка непрерывного архивирования на основном сервере в архивный каталог, доступный с резервного, описана в разделе Непрерывное архивирование и восстановление на момент времени (PITR). Местоположение архива должно быть доступно с резервного сервера даже при отключении основного, т. е. он должен быть размещен на самом резервном или другом доверенном сервере, но не на основном.

Если вы хотите использовать потоковую репликацию, настройте на основном сервере аутентификацию, чтобы разрешить подключения с резервных серверов; то есть создайте роль и обеспечьте подходящую запись или записи в qhb_hba.conf, а в поле базы данных установите значение replication. Также убедитесь, что в параметре max_wal_senders в файле конфигурации основного сервера установлено достаточно большое значение. Если будут применяться слоты репликации, убедитесь, что значение параметра max_replication_slots тоже достаточно большое.

Создайте базовую резервную копию, как описано в подразделе Создание базовой резервной копии, чтобы запускать резервный сервер.


Настройка резервного сервера

Для настройки резервного сервера восстановите базовую резервную копию, сделанную с основного сервера (см. подраздел Восстановление из непрерывной архивной копии). Затем создайте файл standby.signal в каталоге данных кластера резервного сервера. Задайте в параметре restore_command простую команду для копирования файлов из архива WAL. Если вы планируете иметь несколько резервных серверов в целях высокой доступности, убедитесь, что в параметре recovery_target_timeline установлено latest (значение по умолчанию), чтобы резервный сервер переходил на новую временную шкалу, возникающую при переключении на другой резервный сервер после отказа.

Примечание
Если файла не существует, команда из restore_command должна немедленно завершиться; при необходимости сервер ее повторит.

Если вы желаете использовать потоковую репликацию, задайте в параметре primary_conninfo строку подключения для libpq, включая имя хоста (или IP-адрес) и все дополнительные сведения, необходимые для подключения к основному серверу. Если основному серверу для аутентификации нужен пароль, его тоже следует задать в primary_conninfo.

Если вы настраиваете резервный сервер в целях высокой доступности, настройте на нем архивирование WAL, подключения и аутентификацию, как на основном сервере, поскольку после переключения из-за отказа этот резервный сервер будет работать как основной.

Если вы используете архив WAL, его размер можно уменьшить с помощью команды из archive_cleanup_command, которая будет удалять файлы, больше не нужные резервному серверу. Утилита qhb_archivecleanup разработана специально для использования с командой archive_cleanup_command в типичных конфигурациях с одним резервным сервером. Однако обратите внимание, что если вы используете для резервного копирования архив, следует сохранять файлы, необходимые для восстановления, как минимум с последней базовой резервной копии, даже если они больше не нужны резервному серверу.

Простой пример конфигурации:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'qhb_archivecleanup /path/to/archive %r'

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


Потоковая репликация

Потоковая репликация позволяет резервному серверу отставать меньше, чем это возможно при доставке журналов на уровне файлов. Резервный сервер подключается к ведущему, который передает ему поток записей WAL по мере их генерирования, не дожидаясь окончания заполнения файла WAL.

По умолчанию потоковая репликация асинхронна (см. подраздел Синхронная репликация), то есть имеется небольшая задержка между фиксацией транзакции на основном сервере и проявлением этих изменений на резервном. Тем не менее эта задержка гораздо меньше, чем при доставке файлов журналов, обычно менее одной секунды при условии, что резервный сервер достаточно мощный, чтобы справляться с нагрузкой. При потоковой репликации нет необходимости корректировать значение archive_timeout для уменьшения окна потенциальной потери данных.

Если вы используете потоковую репликацию без непрерывного архивирования файлов, сервер может переработать старые сегменты WAL до того, как их получит резервный сервер. Если это произойдет, резервному серверу потребуется повторная инициализация из новой базовой резервной копии. Этого можно избежать, установив в параметре wal_keep_size достаточно большое значение, чтобы сегменты WAL точно не перерабатывались слишком рано, или сконфигурировав слот репликации для резервного. Если вы установили архив WAL, который доступен с резервного сервера, в этих решениях нет необходимости, поскольку резервный сервер всегда может воспользоваться архивом, чтобы сократить отставание (при условии, что в том сохраняется достаточно сегментов).

Чтобы использовать потоковую репликацию, настройте резервный сервер для доставки журналов на уровне файлов, как описано в разделе Доставка журналов на резервные серверы. Чтобы переключить его в режим потоковой репликации, нужно установить в параметре primary_conninfo строку подключения, указывающую на основной сервер. Настройте listen_addresses и параметры аутентификации (см qhb_hba.conf) на основном сервере так, чтобы резервный мог подключиться к его псевдобазе данных replication (см. подраздел Аутентификация).

В системах, поддерживающих параметр сокета keepalive, настройка параметров tcp_keepalives_idle, tcp_keepalives_interval и tcp_keepalives_count помогает основному серверу вовремя заметить прерванное соединение.

Установите максимальное число одновременных подключений резервных серверов (подробную информацию см. в описании параметра max_wal_senders).

При запуске резервного сервера с корректно установленным primary_conninfo он подключится к основному серверу после воспроизведения всех файлов WAL, доступных в архиве. При успешной установке соединения вы увидите на резервном сервере процесс walreceiver, а на основном — соответствующий процесс walsender.

Аутентификация

Крайне важно установить права доступа для репликации так, чтобы поток WAL могли читать только доверенные пользователи, поскольку из него легко извлечь конфиденциальную информацию. Резервные серверы должны аутентифицироваться на основном от имени пользователя с правом REPLICATION или от имени суперпользователя. Рекомендуется создавать для репликации выделенного пользователя с правами REPLICATION и LOGIN. Хотя право REPLICATION дает очень широкие полномочия, оно не позволяет пользователю модифицировать данные в основной системе, как это делает право SUPERUSER.

Аутентификация клиента для репликации управляется записью в qhb_hba.conf с заданным в поле database значением replication. Например, если резервный сервер запущен на хосте с IP-адресом 192.168.1.100 и учетная запись для репликации foo, администратор может добавить в файл qhb_hba.conf основного сервера следующую строку:

# Разрешить пользователю "foo" с хоста 192.168.1.100 подключаться к этому основному
# серверу в качестве резервного сервера репликации, если пользователем был передан
# корректный пароль.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

Имя хоста и номер порта для основного сервера, имя подключающегося пользователя и пароль указываются в primary_conninfo. Кроме того, пароль можно задать в файле ~/.qhbpass на резервном сервере (укажите в поле database значение replication). Например, если основной сервер запущен на хосте с IP-адресом 192.168.1.50, порт 5432, учетная запись для репликации foo с паролем foopass, администратор может добавить в файл qhb.conf резервного сервера следующую строку:

# Резервный сервер подключается к основному, запущенному на хосте 192.168.1.50
# и порте 5432, от имени пользователя "foo" с паролем "foopass".
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

Мониторинг потоковой репликации

Важным индикатором работоспособности потоковой репликации является количество записей WAL, сгенерированных на основном сервере, но еще не примененных на резервном. Эту задержку можно рассчитать путем сравнения позиции текущей записи WAL на основном сервере с последней позицией WAL, полученной на резервном сервере. Данные позиции можно получить с помощью функции pg_current_wal_lsn на основном и функции pg_last_wal_receive_lsn на резервном сервере соответственно (дополнительную информацию см. в таблицах Функции для управления резервным копированием и Функции для получения информации о восстановлении). Последняя позиция WAL, полученная на резервном сервере, также отображается в текущем статусе процесса- приемника WAL, который выводится с помощью команды ps (дополнительную информацию см. в разделе Стандартные инструменты Unix).

Получить список процессов-передатчиков WAL возможно посредством представления pg_stat_replication. Значительные различия между pg_current_wal_lsn и полем представления sent_lsn могут указывать на большую нагрузку на основном сервере, в то время как разница между sent_lsn и pg_last_wal_receive_lsn на резервном сервере может указывать на сетевую задержку или большую нагрузку на резервном сервере.

На сервере горячего резерва статус процесса-приемника WAL можно получить через представление pg_stat_wal_receiver. Значительные различия между pg_last_wal_replay_lsn и полем представления flushed_lsn указывают на то, что получение WAL происходит быстрее, чем его воспроизведение.


Слоты репликации

Слоты репликации предоставляют автоматический способ сделать так, чтобы основной сервер не удалял сегменты WAL, пока их не получат все резервные серверы, и чтобы основной сервер не удалял строки, которые могут вызвать конфликт восстановления даже при отключении резервного сервера.

Вместо применения слотов репликации для предотвращения удаления старых сегментов WAL можно воспользоваться параметром wal_keep_size либо сохранять сегменты в архиве с помощью команды из archive_command. Однако эти методы зачастую приводят к тому, что хранится больше сегментов WAL, чем необходимо, тогда как в слотах репликации хранится лишь то количество сегментов, которое точно понадобится. С другой стороны, в слотах репликации может храниться столько сегментов WAL, что они заполнят все пространство, выделенное для каталога pg_wal; объем файлов WAL, сохраняемых слотами репликации, ограничивается параметром max_slot_wal_keep_size.

Схожим образом параметры hot_standby_feedback и vacuum_defer_cleanup_age обеспечивают защиту актуальных строк от удаления при очистке, но первый параметр не защищает в тот период времени, когда резервный сервер не подключен, а в последнем для получения адекватной защиты часто требуется задавать большое значение. Слоты репликации устраняют эти недостатки.

Запросы и манипуляции слотов репликации

Каждый слот репликации имеет имя, которое может содержать строчные буквы, цифры и символы подчеркивания.

Существующие слоты репликации и их статус можно просмотреть в представлении pg_replication_slots.

Слоты можно создавать и удалять либо через протокол потоковой репликации (см. раздел Протокол потоковой репликации), либо с помощью функций SQL (см. подраздел Функции для управления репликацией).

Пример конфигурации

Слот репликации можно создать следующим образом:

qhb=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
  slot_name  | lsn
-------------+-----
 node_a_slot |

qhb=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
  slot_name  | slot_type | active
-------------+-----------+--------
 node_a_slot | physical  | f
(1 row)

Чтобы сконфигурировать резервный сервер на использование этого слота, следует указать его имя в параметре конфигурации primary_slot_name этого сервера. Вот простой пример:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
primary_slot_name = 'node_a_slot'

Каскадная репликация

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

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

Каскадный резервный сервер отправляет не только записи WAL, полученные от основного, но и те, что были восстановлены из архива. Так что даже если вышестоящее подключение разорвется, потоковая репликация на нижестоящих серверах будет продолжаться, пока доступны новые записи WAL.

В настоящее время каскадная репликация асинхронна. Параметры синхронной репликации (см. подраздел Синхронная репликация) на данный момент не влияют на каскадную репликацию.

Обратная связь горячего резерва распространяется от нижестоящих серверов к вышестоящим независимо от организации каскада.

Если вышестоящий резервный сервер становится новым основным, нижестоящие серверы продолжат получать поток от нового основного, если в параметре recovery_target_timeline установлено latest (по умолчанию).

Для использования каскадной репликации настройте каскадный резервный сервер, чтобы он мог принимать подключения репликации (то есть установите параметры max_wal_senders и hot_standby и сконфигурируйте аутентификацию по узлу). Кроме того, вам может понадобиться установить на нижестоящем резервном сервере значение параметра primary_conninfo, указывающее на каскадный резервный сервер.


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

По умолчанию потоковая репликация в QHB асинхронна. Если на основном сервере происходит сбой, то некоторые зафиксированные транзакции могут не скопироваться на резервный, приводя к потере данных. Объем потерянных данных пропорционален задержке репликации на момент переключения после отказа.

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

При запросе на синхронную репликацию каждая фиксация пишущей транзакции будет ожидать получения подтверждения, что эта фиксация была записана в журнал упреждающей записи на диске как на основном, так и на резервном сервере. При этом потеря данных может произойти только в том случае, если сбой произойдет одновременно на основном и резервном серверах. Это обеспечивает более высокий уровень прочности, но только если системный администратор благоразумно подходит к размещению и управлению этими двумя серверами. Ожидание подтверждения повышает уверенность пользователя в том, что изменения не будут потеряны во время отказа сервера, но также и увеличивает время отклика для запрашивающей транзакции. Минимальное время ожидания равно времени на передачу и подтверждение данных между основным и резервным серверами.

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

Синхронным резервным сервером может быть резервный сервер при физической репликации или подписчик при логической репликации. Также это может быть любой другой потребитель потока физической или логической репликации WAL, который знает, как отправлять подходящие ответные сообщения. Помимо встроенных систем физической и логической репликации сюда входят специальные программы, например qhb_receivewal и qhb_recvlogical, а также некоторые сторонние системы репликации и настраиваемые программы. Подробную информацию о поддержке синхронной репликации с их использованием можно найти в соответствующей документации.

Базовая конфигурация

При сконфигурированной потоковой репликации для настройки синхронной репликации требуется всего один дополнительный шаг: в параметре synchronous_standby_names необходимо установить непустое значение. Также в параметре synchronous_commit нужно установить on, но поскольку это значение по умолчанию, обычно никаких изменений не требуется. (См. подразделы Параметры и Главный сервер.) При такой конфигурации каждая фиксация будет ожидать подтверждения, что резервный сервер сохранил запись о фиксации в надежное хранилище. Значение synchronous_commit могут устанавливать для себя отдельные пользователи, поэтому оно может быть настроено в файле конфигурации для конкретных пользователей или баз данных либо динамически изменено приложениями для управления гарантией прочности на уровне отдельных транзакций.

После сохранения записи о фиксации на диск на основном сервере эта запись WAL передается на резервный сервер. Тот передает подтверждающие сообщения каждый раз, как очередной блок данных WAL сохраняется на диск, если только в его параметре wal_receiver_status_interval не установлен ноль. В случае, когда в synchronous_commit установлен режим remote_apply, резервный сервер передает подтверждающие сообщения после воспроизведения записи фиксации, делающего транзакцию видимой. Если резервный сервер выбран в качестве синхронного, то в соответствии со значением параметра synchronous_standby_names на основном сервере сообщения от этого резервного сервера вместе с сообщениями от других синхронных резервных серверов будут рассматриваться для определения момента освобождения транзакций, ожидающих подтверждения получения записи фиксации. Эти параметры позволяют администратору указывать, какие резервные серверы должны быть синхронными. Обратите внимание, что конфигурация синхронной репликации производится главным образом на основном сервере. Именованные резервные серверы должны быть напрямую подключены к основному; тот ничего не знает о нижестоящих резервных серверах, использующих каскадную репликацию.

При установке в synchronous_commit значения remote_write каждая фиксация будет ожидать подтверждения, что резервный сервер получил запись фиксации и сохранил ее в своей операционной системе, но не самого сброса данных на его диск. Это значение дает более слабую гарантию прочности, чем значение on: резервный сервер может потерять данные в случае сбоя операционной системы, но не в случае сбоя QHB. Однако на практике такой вариант полезен, поскольку он снижает время отклика для транзакции. Потеря данных может произойти только при одновременном отказе основного и резервного серверов с повреждением базы данных на основном сервере.

При установке в synchronous_commit значения remote_apply каждая фиксация будет ожидать, пока текущие синхронные резервные серверы не сообщат, что они воспроизвели транзакцию, сделав ее видимой для пользовательских запросов. В простых случаях это позволяет обеспечить распределение нагрузки и причинную согласованность.

Пользователи прекратят ожидание при запросе на быструю остановку сервера. Однако, как и при использовании асинхронной репликации, сервер не остановится полностью, пока все ожидающие обработки записи WAL не переместятся на текущие подключенные резервные серверы.

Несколько синхронных резервных серверов

Синхронная репликация поддерживает участие одного или нескольких синхронных резервных серверов; транзакции будут ожидать, пока все резервные серверы, считающиеся синхронными, не подтвердят получение своих данных. Количество синхронных резервных сервером, от которых транзакции должны ожидать ответ, задается в synchronous_standby_names. Также в этом параметре задается список имен резервных серверов и метод выбора синхронных резервных серверов из перечисленных в этом списке (FIRST или ANY).

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

Пример значения synchronous_standby_names для нескольких синхронных резервных серверов, выбираемых на основе приоритетов:

synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

В этом примере, если запущены четыре резервных сервера s1, s2, s3 и s4, два из них, s1 и s2, будут выбраны как синхронные резервные, поскольку их имена находятся в начале списка имен резервных серверов. Сервер s3 является потенциальным синхронным и возьмет на себя роль синхронного резервного в случае отказа s1 или s2. Сервер s4 является асинхронным резервным, так как его имени в списке нет.

При методе ANY задается синхронная репликация на основе кворума, а фиксации транзакций вынуждены дожидаться, пока их записи WAL реплицируются как минимум на запрошенное количество синхронных резервных серверов в списке.

Пример значения synchronous_standby_names для нескольких синхронных резервных серверов, выбираемых на основе кворума:

synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

В этом примере, если запущены четыре резервных сервера s1, s2, s3 и s4, фиксации транзакций будут ожидать ответов как минимум от любых двух резервных серверов из s1, s2 и s3. Сервер s4 является асинхронным резервным, так как его имени в списке нет.

Статусы синхронности резервных серверов можно увидеть в представлении pg_stat_replication.

Планирование производительности

Обычно синхронная репликация требует тщательного планирования и размещения резервных серверов для обеспечения приемлемой производительности приложений. Ожидание не потребляет системные ресурсы, но блокировки транзакций будут удерживаться до подтверждения передачи. Как следствие, неосмотрительное применение синхронной репликации снизит производительность приложений базы данных из-за увеличения времени отклика и возросшего числа конфликтов.

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

Например, в рабочей нагрузке приложения 10% изменений могут относиться к важным данным клиентов, тогда как 90% — к менее важным данным, потерю которых бизнес сможет пережить гораздо легче (например, сообщения, которыми обмениваются пользователи).

Если параметры синхронной репликации задаются на уровне приложения (на основном сервере), мы можем предложить синхронную репликацию для большинства важных изменений без замедления основной массы общей рабочей нагрузки. Настройка на уровне приложения является важным и практичным инструментом получения выгод синхронной репликации для приложений с высокой производительностью.

Следует учитывать, что пропускная способность сети должна быть выше скорости генерирования данных WAL.

Планирование высокой доступности

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

Лучшим решением для высокой доступности будет обеспечить требуемое количество синхронных резервных серверов. Этого можно добиться, указав в параметре synchronous_standby_names несколько потенциальных синхронных резервных серверов.

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

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

Когда резервный сервер впервые присоединяется к основному, он еще не будет полностью синхронизированным. Это описывается как режим catchup (наверстывание). Как только отставание резервного сервера от основного впервые сокращается до нуля, система переходит в состояние streaming (потоковая передача) в реальном времени. Сразу после создания резервного сервера наверстывание может быть длительным. При выключении резервного сервера период наверстывания увеличится в соответствии с продолжительностью его нахождения в отключенном состоянии. Резервный сервер может стать синхронным только по достижении им состояния streaming. Это состояние можно увидеть в представлении pg_stat_replication.

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

Если вы действительно не можете обеспечить требуемое количество синхронных резервных серверов, следует уменьшить в параметре synchronous_standby_names количество синхронных серверов, от которых фиксации транзакций должны ожидать ответов, (или вовсе выключить его) и перезагрузить файл конфигурации на основном сервере.

Если основной сервер отделен от оставшихся резервных, следует переключиться на наилучшего кандидата из этих оставшихся.

Если вам нужно повторно создать резервный сервер при наличии ожидающих транзакций, убедитесь, что команды pg_start_backup() и pg_stop_backup() запускаются в сеансе с synchronous_commit = off, иначе для вновь возникшего резервного сервера эти запросы будут бесконечно ожидающими.


Непрерывное архивирование на резервном сервере

Когда на резервном сервере используется непрерывное архивирование WAL, возможно два различных сценария: архив WAL может быть совместно используемым основным и резервным серверами, или резервный сервер может иметь собственный архив WAL. Когда у резервного сервера есть собственный архив WAL, установите в archive_mode значение always, и этот сервер будет вызывать команду архивации для каждого сегмента WAL, полученного им при восстановлении из архива или потоковой репликации. С разделяемым архивом можно поступить схожим образом, но archive_command должна проверять, не существует ли уже архивируемый файл, и не имеет ли этот существующий файл идентичное содержимое. Это требует от archive_command большей осторожности, поскольку она должна позаботиться, чтобы существующий файл с другим содержимым не был перезаписан, но сообщать об успешном выполнении при попытке повторного архивирования идентичного файла. И все это необходимо делать независимо от условий гонки, если два сервера попытаются одновременно заархивировать один и тот же файл.

Если в archive_mode установлено on, то в режиме восстановления или резерва архиватор не включается. При повышении резервного сервера он начнет архивацию после повышения, но не будет архивировать файлы WAL или истории временной шкалы, которые генерировал не он сам. Чтобы заполучить в архив полный набор файлов WAL, следует обеспечить архивацию всех файлов WAL до того, как они достигнут резервного сервера. Это по умолчанию происходит при доставке журналов на уровне файлов, поскольку резервный сервер может восстановить только файлы, которые обнаруживаются в архиве, но при включении потоковой репликации этого не происходит. Когда сервер находится не в режиме резерва, разницы между режимами on и always нет.



Переключение после отказа

При отказе основного сервера резервный должен начать процедуры переключения после отказа.

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

Если основной сервер отказывает и резервный сервер становится новый основным, а затем старый основной сервер перезапускается, вам нужно иметь механизм, информирующий старый ведущий сервер, что тот больше не ведущий. Иногда его называют STONITH (Shoot The Other Node In The Head, «выстрели в голову другому узлу»), и он необходим, чтобы избежать ситуаций, когда обе системы считают себя основными, что приводит к путанице и в итоге к потере данных.

Во многих системах переключения после отказа используются всего две системы: основная и резервная, соединенные неким контрольным механизмом, который постоянно проверяет соединение между ними и работоспособность основной. Также возможно использование третьей системы (называемой следящим сервером) для предотвращения некоторых вариантов недопустимого переключения после отказа, но эта дополнительная сложность может быть оправдана, только если она установлена с достаточной тщательностью и интенсивным тестированием.

QHB не предоставляет системного программного обеспечения, необходимого для определения сбоя на основном сервере и уведомления резервного сервера баз данных. Существует множество подобных инструментов, которые хорошо интегрируются со средствами операционной системы, требуемыми для успешного переключения после отказа, например, для миграции IP-адреса.

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

Таким образом, переключение с основного сервера на резервный может быть быстрым, но требует некоторого времени на повторную подготовку отказоустойчивого кластера. Регулярное переключение с основного сервера на резервный полезно, поскольку позволяет выделять на каждой системе плановое время на отключение для проведения обслуживания. Кроме того, это позволяет проверить механизм переключения после отказа, чтобы убедиться, что он действительно будет работать, когда потребуется. Рекомендуется документировать эти процедуры администрирования.

Чтобы запустить переключение резервного сервера, принимающего журналы, выполните команду qhb_ctl promote, вызовите функцию pg_promote() или создайте триггерный файл с именем и путем, заданным в параметре promote_trigger_file. Если для переключения вы планируете использовать qhb_ctl promote или вызвать pg_promote(), файл из promote_trigger_file не требуется. Если вы устанавливаете резервный сервер как сервер отчетов, который используется только чтобы разгрузить основной сервер, выполняя запросы на чтение, а не для обеспечения высокой доступности, повышать его до основного нет необходимости.



Горячий резерв

Термин «горячий резерв» используется, чтобы описать возможность подключаться к серверу и выполнять запросы только на чтение, пока сервер находится в режиме резерва или восстановления архива. Это полезно как для целей репликации, так и для восстановления из резервной копии до желаемого состояния с высокой точностью. Кроме того, термин «горячий резерв» относится к возможности сервера переходить из режима восстановления к обычной работе, пока пользователи продолжают выполнять запросы и/или держат свои соединения открытыми.

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


Обзор на уровне пользователя

Когда на резервном сервере параметр hot_standby установлен в true, тот начнет принимать подключения, как только восстановление приведет систему в согласованное состояние. Все подобные подключения открываются строго только для чтения; записывать нельзя даже во временные таблицы.

Получение данных резервным сервером с основного занимает некоторое время, так что между основным и резервным серверами будет измеряемая задержка. Поэтому одновременное выполнение одного и того же запроса на основном и резервном серверах может вернуть разные результаты. Можно сказать, что данные на резервном сервере в конце концов согласуются с основным. После того как запись о фиксации транзакции воспроизводится на резервном сервере, изменения, совершенные этой транзакцией, становятся видимыми во всех новых снимках состояния, сделанных на резервном сервере. Снимки могут быть сделаны в начале каждого запроса или в начале каждой транзакции в зависимости от текущего уровня изоляции транзакции. Более подробную информацию см. в разделе Изоляция транзакций.

Транзакции, запущенные во время горячего резерва, могут выполнять следующие команды:

  • Доступ к данным: SELECT, COPY TO

  • Команды для курсора: DECLARE, FETCH, CLOSE

  • Параметры: SHOW, SET, RESET

  • Команды управления транзакциями:

    • BEGIN, END, ABORT, START TRANSACTION
    • SAVEPOINT, RELEASE, ROLLBACK TO SAVEPOINT
    • Блоки EXCEPTION и другие внутренние субтранзакции
  • LOCK TABLE, но только когда выполняется явно в одном из следующих режимов: ACCESS SHARE, ROW SHARE или ROW EXCLUSIVE.

  • Планы и ресурсы: PREPARE, EXECUTE, DEALLOCATE, DISCARD

  • Плагины и расширения: LOAD

  • UNLISTEN

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

  • Язык манипулирования данными (DML): INSERT, UPDATE, DELETE, COPY FROM, TRUNCATE. Обратите внимание, что нет разрешенных действий, которые приводили бы к выполнению триггера во время восстановления. Это ограничение применяется даже к временным таблицам, поскольку строки таблиц нельзя прочитать или записать без присвоения идентификатора транзакции, что в среде горячего резерва в настоящее время невозможно.

  • Язык определения данных (DDL): CREATE, DROP, ALTER, COMMENT. Это ограничение применяется даже к временным таблицам, поскольку выполнение этих операций потребовало бы обновления таблиц системных каталогов.

  • SELECT ... FOR SHARE | UPDATE, поскольку блокировки строк нельзя провести без обновления нижележащих фалов данных.

  • Правила для операторов SELECT, которые генерируют команды DML.

  • LOCK, которая явно требует режим более строгий, чем ROW EXCLUSIVE MODE.

  • LOCK в короткой форме по умолчанию, поскольку та требует ACCESS EXCLUSIVE MODE.

  • Команды управления транзакциями, которые явно устанавливают состояние не только для чтения:

    • BEGIN READ WRITE, START TRANSACTION READ WRITE
    • SET TRANSACTION READ WRITE, SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE
    • SET transaction_read_only = off
  • Команды двухфазной фиксации: PREPARE TRANSACTION, COMMIT PREPARED, ROLLBACK PREPARED, поскольку даже транзакции только для чтения нужно записывать в WAL в фазе подготовки (первая фаза двухфазной фиксации).

  • Обновления последовательностей: nextval(), setval()

  • LISTEN, NOTIFY

При обычной работе транзакциям «только для чтения» разрешено использовать LISTEN и NOTIFY, следовательно, сеансы горячего резерва работают с чуть более строгими ограничениями, чем обычные сеансы с доступом только для чтения. Возможно, в будущих выпусках некоторые из этих ограничений будут ослаблены.

Во время горячего резерва параметр transaction_read_only всегда равен true и не может быть изменен. Но если не пытаться модифицировать базу данных, подключения при горячем резерве работают почти так же, как и любое другое подключение к базе данных. Если происходит переключение после отказа или смена ролей, база данных переходит в обычный режим обработки. Когда сервер меняет режим работы, сеансы остаются подключенными. После выхода из режима горячего резерва можно будет запускать транзакции на чтение и запись (даже в сеансах, начатых во время горячего резерва).

Пользователи могут узнать, активен ли в настоящее время режим горячего резерва для их сеанса, выполнив команду SHOW in_hot_standby. (В ранних версиях сервера параметра in_hot_standby не существует; вместо него работает вариант SHOW transaction_read_only.) Кроме того, ряд функций (таблица Функции для получения информации о восстановлении) позволяет пользователям получить информацию о резервном сервере. Эти инструменты позволяют писать программы, учитывающие текущее состояние базы данных. Также их можно использовать для мониторинга прогресса восстановления или писать сложные программы, восстанавливающие базу данных до конкретного состояния.


Обработка конфликтов запросов

Основной и резервный серверы слабо связаны между собой самым различным образом. Действия на основном сервере отразятся на резервном. В результате существует потенциальная возможность отрицательного взаимодействия или конфликтов между ними. Самый простой для понимания конфликт — производительность: если на основном сервере происходит загрузка значительного объема данных, это создаст соответствующий поток записей WAL на резервном сервере, поэтому запросы на последнем могут начать конкурировать за системные ресурсы, например, за ввод/вывод.

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

  • Эксклюзивные блокировки на основном сервере, установленные как с помощью явных команд LOCK, так и при различных действиях DDL, конфликтуют с доступом к таблицам в запросах на резервном сервере.

  • Удаление табличного пространства на основном сервере конфликтует с запросами на резервном, использующими это табличное пространство для хранения временных рабочих файлов.

  • Удаление базы данных на основном сервере конфликтует с сеансами, подключенными к этой базе данных на резервном сервере.

  • Приложение очистки старых записей из WAL конфликтует с транзакциями на резервном сервере, снимки которых по-прежнему могут «видеть» какие-то из удаляемых строк.

  • Приложение очистки старых записей из WAL конфликтует с запросами, обращающимися к целевой странице на резервном сервере независимо от того, являются ли удаляемые данные видимыми.

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

Пример проблемной ситуации: администратор на основном сервере выполнил команду DROP TABLE для таблицы, которая в данный момент фигурирует в запросе на резервном сервере. Очевидно, что такой запрос не может выполняться дальше, если на резервном сервере применится DROP TABLE. Если бы эта ситуация возникла на основном сервере, DROP TABLE ждала бы завершения другого запроса. Но когда DROP TABLE выполняется на основном сервере, у того отсутствует информация о том, какие запросы выполняются на резервном, поэтому основной сервер не будет ожидать завершения подобных запросов. Записи WAL с изменениями поступят на резервный сервер, пока запрос на нем еще выполняется, вызвав конфликт. Резервный сервер должен либо задержать применение этих записей WAL (и всех, что идут за ними), либо отменить конфликтующий запрос, чтобы можно было применить DROP TABLE.

Если конфликтующий запрос короткий, обычно желательно разрешить ему завершиться, ненадолго задержав применение WAL, но длительная задержка применения WAL, как правило, нежелательна. Поэтому у механизма отмены имеются параметры max_standby_archive_delay и max_standby_streaming_delay, определяющие максимально допустимую задержку применения WAL. Конфликтующие запросы будут отменены, если длятся дольше соответствующего времени задержки применения очередных полученных данных WAL. Два параметра существуют, чтобы можно было задать разные значения задержки для чтения данных WAL из архива (т. е. при изначальном восстановлении из базовой резервной копии или при «наверстывании» резервного сервера в случае значительного отставания) и для чтения данных WAL при потоковой репликации.

На резервном сервере, существующем главным образом для обеспечения высокой доступности, лучше всего установить параметры задержки относительно короткими, чтобы этот сервер не слишком отстал от основного из-за задержек, вызванных запросами резервных серверов. Однако если резервный сервер предназначен для выполнения длительных запросов, то может быть предпочтительнее установить значительную или даже бесконечную задержку. Тем не менее учтите, что если длительный запрос задерживает применение записей WAL, другие сеансы на резервном сервере могут не увидеть последние изменения на основном.

После превышения времени задержки, указанного в max_standby_archive_delay или max_standby_streaming_delay, конфликтующие запросы будут отменены. Обычно это приводит только к ошибке отмены, хотя в случае воспроизведения команды DROP DATABASE прервется весь конфликтующий сеанс. Также, если конфликт произошел из-за блокировки, удерживаемой фиктивной транзакцией, конфликтующий сеанс прерывается (это поведение может измениться в будущем).

Отмененные запросы можно тут же повторить (разумеется, после начала новой транзакции). Поскольку причина отмены запроса зависит от природы воспроизводимых записей WAL, отмененный запрос вполне может быть успешен при повторном выполнении.

Учтите, что параметры задержки сравниваются с временем, прошедшим с момента получения резервным сервером данных WAL. Таким образом, допустимый период отсрочки для любого запроса на резервном сервере никогда не превышает значение параметра задержки и может быть значительно короче, если резервный сервер уже отстал от основного в результате ожидания завершения предыдущих запросов или из-за загрузки большого объема обновлений.

Наиболее частой причиной конфликтов между запросами на резервном сервере и воспроизведением WAL является преждевременная очистка. Обычно QHB допускает очистку старых версий строк, когда ни одной транзакции нет необходимости их видеть, в целях обеспечения корректной видимости данных согласно правилам MVCC. Однако это правило могут применяться только к транзакциям, выполняемым на основном сервере. Поэтому возможет сценарий, когда очистка на основном сервере удалит версии строк, которые все еще видимы для транзакции на резервном сервере.

Опытные пользователи должны обратить внимание, что не только очистка, но и замораживание версий строк может потенциально вызвать конфликт с запросами на резервном сервере. Выполнение команды VACUUM FREEZE вручную, скорее всего, вызовет конфликт даже в таблицах, где нет измененных или удаленных строк.

Пользователи должны ясно понимать, что регулярно и значительно изменяемые таблицы на основном сервере быстро станут причиной отмены более долгих запросов на резервном сервере. В таких случаях можно считать, что установка конечного значения в max_standby_archive_delay или max_standby_streaming_delay схожа с установкой параметра statement_timeout.

Если количество отмененных запросов на резервном сервере оказывается неприемлемым, существует ряд возможностей это исправить. Первый вариант — установить параметр hot_standby_feedback, не дающий команде VACUUM удалять строки, ставшие неактивными недавно, и тем самым предотвратить конфликты очистки. Если вы это сделаете, то следует учесть, что это вызовет задержку очистки неактивных строк на основном сервере, что может привести к нежелательному разбуханию таблицы. Тем не менее ситуация с очисткой будет не хуже, чем если бы запросы с резервного сервера выполнялись непосредственно на основном, и при этом вы все равно получали бы преимущества, как при перекладывании этой нагрузки на резервный сервер. Если соединение с резервными серверами часто прерывается и восстанавливается, возможно, стоит скорректировать период, когда обратная связь через hot_standby_feedback не обеспечивается. Например, подумайте об увеличении значения max_standby_archive_delay, чтобы в периоды разъединения при конфликтах в файлах архива WAL запросы отменялись не сразу. Также следует подумать об увеличении значения max_standby_streaming_delay, чтобы избежать быстрой отмены запросов из-за очередных полученных в потоке записей WAL после восстановления подключения.

Другой вариант — увеличить значение параметра vacuum_defer_cleanup_age на основном сервере, чтобы неактивные строки очищались не так быстро, как обычно. Это даст запросам на резервном сервере больше времени на выполнение, прежде чем они будут отменены, без необходимости устанавливать высокое значение в max_standby_streaming_delay. Однако с таким подходом трудно гарантировать какое-то определенное временное окно для выполнения запросов, поскольку vacuum_defer_cleanup_age измеряется в транзакциях, выполняемых на основном сервере.

Количество отмен запросов и их причины можно посмотреть с помощью системного представления pg_stat_database_conflicts на резервном сервере. Краткую информацию также содержит системное представление pg_stat_database.

Пользователи могут управлять тем, создается ли в журнале сообщение, когда воспроизведение WAL ожидает разрешения конфликта дольше заданного в параметре deadlock_timeout времени. Это делается с помощью параметра log_recovery_conflict_waits.


Обзор на уровне администратора

Если в файле qhb.conf параметр hot_standby равен on (это значение по умолчанию) и присутствует файл standby.signal, сервер будет запущен в режиме горячего резерва. Однако может пройти некоторое время, прежде чем к нему будут разрешены подключения, поскольку этот сервер не будет их принимать, пока не произведет восстановление, достаточное для обеспечения состояния согласованности, с которым могут выполняться запросы. В течение этого периода клиентам, пытающимся к нему подключиться, будет отказано, и они получат сообщение об ошибке. Чтобы убедиться, что сервер готов к работе, можно либо повторять попытки подключения из приложения, либо поискать в журналах сервера эти сообщения:

LOG:  entering standby mode
-- вошел в режим резерва

... потом, еще через некоторое время ...

LOG:  consistent recovery state reached
LOG:  database system is ready to accept read-only connections
-- при восстановлении достигнуто согласованное состояние
-- система баз данных готова принимать подключение только на чтение

На основном сервере информация о согласованности записывается один раз во время каждой контрольной точки. Нельзя включить сервер горячего резерва при прочтении данных WAL, записанных в период, когда на основном сервере параметр wal_level не был установлен в значение replica или logical. Достижение согласованного состояния также может быть отложено, если выполняются оба следующих условия:

  • Пишущая транзакция имеет более 64 субтранзакций

  • Очень долгосрочные пишущие транзакции

Если вы выполняете доставку журналов на уровне файлов («теплый резерв»), возможно, вам придется подождать получения следующего файла WAL, причем время ожидания может достигать значения параметра archive_timeout на основном сервере.

Значения некоторых параметров определяют объем разделяемой памяти для отслеживания идентификаторов транзакций, блокировок и подготовленных транзакций. Эти структуры разделяемой памяти на резервном сервере должны быть не меньше, чем на основном, чтобы гарантировать, что во время восстановления на резервном сервере не закончится разделяемая память. Например, если на основном сервере выполнялась подготовленная транзакция, но резервный сервер не выделил разделяемую память для отслеживания подготовленных транзакций, то без изменения конфигурации резервного сервера восстановление не сможет продолжаться. Это касается следующих параметров:

  • max_connections

  • max_prepared_transactions

  • max_locks_per_transaction

  • max_wal_senders

  • max_worker_processes

Самый простой способ сделать так, чтобы это не стало проблемой, — установить на резервных серверах значения этих параметров равными или превышающими их значения на основном сервере. Поэтому если вы хотите увеличить эти значения, прежде чем вносить изменения на основном сервере, нужно сначала сделать это на всех резервных. И наоборот, если вы хотите уменьшить эти значения, нужно сначала сделать это на основном сервере, а уже потом вносить изменения на всех резервных. Учтите, что когда резервный сервер повышается до основного, он становится новым источником требуемых значений параметров для идущих за ним резервных серверов. Таким образом, чтобы избежать этой проблемы во время переключения узлов или переключения после отказа, рекомендуется устанавливать одинаковые значения этих параметров на всех резервных серверах.

Изменения этих параметров на основном сервере отслеживаются в WAL. Если сервер горячего резерва обрабатывает WAL, указывающий, что текущее значение на основном сервере превышает его собственное, он протоколирует предупреждение и приостанавливает восстановление. Например:

WARNING:  hot standby is not possible because of insufficient parameter settings
-- ПРЕДУПРЕЖДЕНИЕ: горячий резерв невозможет вследствие несоответствующих значений параметров
DETAIL:  max_connections = 80 is a lower setting than on the primary server, where its value was 100.
-- ПОДРОБНОСТИ: Значение max_connections = 80 ниже, чем на основном сервере, где это значение равно 100
LOG:  recovery has paused
-- ЖУРНАЛ: восстановление было приостановлено
DETAIL:  If recovery is unpaused, the server will shut down.
-- ПОДРОБНОСТИ: Если продолжить восстановление, сервер отключится.
HINT:  You can then restart the server after making the necessary configuration changes.
-- СОВЕТ: После внесения в конфигурацию необходимых изменений вы можете перезапустить сервер.

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

Важно, чтобы администратор выбрал подходящие значения для параметров max_standby_archive_delay и max_standby_streaming_delay. Оптимальные значения зависят от приоритетных направлений деятельности. Например, если главным назначением сервера является обеспечение высокой доступности, то желательно установить низкие значения задержки, возможно даже нулевые, хотя это очень агрессивный вариант. Если резервный сервер служит дополнительным сервером для запросов в системе поддержки принятия решений, то может оказаться приемлемой установка значений максимальной задержки в несколько часов или даже -1, что означает бесконечное ожидание завершения запросов.

Вспомогательные биты статуса транзакций, записанные на основном сервере, не протоколируются в WAL, поэтому данные на резервном сервере, скорее всего, снова перезапишут на нем эти биты. Таким образом, резервный сервер все равно будет производить запись на диск, даже если все пользователи только читают данные; сами значения данных не меняются. Пользователь по-прежнему будут записывать временные файлы при сортировке больших объемов и обновлять информационные файлы кэша отношений, поэтому в режиме горячего резерва ни одна часть базы данных на самом деле не работает в режиме «только чтение». Также обратите внимание, что записи в удаленные базы данных с помощью модуля dblink и другие операции вне базы данных с использованием функций PL все равно будут возможны, даже несмотря на то, что локально транзакции будут только читающими.

В режиме восстановления недоступны следующие типы административных команд:

  • Язык определения данных (DDL): например, CREATE INDEX

  • Управление правами и владением: GRANT, REVOKE, REASSIGN

  • Команды обслуживания: ANALYZE, VACUUM, CLUSTER, REINDEX

Опять же, обратите внимание, что некоторые из этих команд в действительности разрешены для транзакций в режиме «только чтение» на основном сервере.

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

На сервере пользователя будут работать функции pg_cancel_backend() и pg_terminate_backend(), но не процесс запуска, осуществляющий восстановление. Представление pg_stat_activity не отображает восстанавливаемые транзакции как активные. В результате представление pg_prepared_xacts всегда пустует во время восстановления. Если вы желаете разобрать сомнительные подготовленные транзакции, просмотрите pg_prepared_xacts на основном сервере и выполните команды для разбора транзакций там либо по окончание восстановления.

В представлении pg_locks, как обычно, отобразятся блокировки, удерживаемые серверной частью. Также pg_locks показывает виртуальные транзакции, обработанные процессом запуска, который владеет всеми блокировками AccessExclusiveLock, удерживаемыми транзакциями, воспроизведенными при восстановлении. Обратите внимание, что процесс запуска не запрашивает блокировки, чтобы внести изменения в базу данных, поэтому блокировки, отличные от AccessExclusiveLock, для процесса запуска в pg_locks не отображаются; их существование просто подразумевается.

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

Команды управления файлами WAL, например, pg_start_backup, pg_switch_wal и т. п., во время восстановления работать не будут.

Динамически загружаемые модули, включая pg_stat_statements, работают.

Рекомендательные блокировки при восстановлении работают как обычно, включая выявление взаимоблокировок. Обратите внимание, что рекомендательные блокировки никогда не протоколируются в WAL, поэтому для рекомендательной блокировки как на основном, так и на резервном сервере невозможен конфликт с воспроизведением WAL. Также невозможно получить рекомендательную блокировку на основном сервере и инициировать похожую рекомендательную блокировку на резервном. Рекомендательные блокировки относятся только к тому серверу, на котором они получены.

Системы репликации на основе триггеров, например Slony, Londiste и Bucardo, вообще не будут запускаться на резервном сервере, хотя будут замечательно выполняться на основном сервере, пока изменения не передаются для применения на резервный сервер. Воспроизведение WAL не основано на триггерах, поэтому WAL нельзя переслать с резервного сервера в любую систему, требующую дополнительных записей в базу данных или полагающуюся на использование триггеров.

Новые OID не могут быть присвоены, хотя некоторые генераторы UUID могут по-прежнему работать, если они не прибегают к записи нового статуса в базу данных.

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

Команда DROP TABLESPACE может выполниться успешно, только если табличное пространство пустое. Некоторые пользователи резервного сервера могут активно использовать табличное пространство посредством своего параметра temp_tablespaces. Если в табличном пространстве имеются временные файлы, все активные запросы отменяются для обеспечения удаления временных файлов, чтобы можно было удалить табличное пространство и продолжить воспроизведение WAL.

Выполнение команды DROP DATABASE или ALTER DATABASE ... SET TABLESPACE на основном сервере сгенерирует запись WAL, которая вызовет принудительное отключение всех пользователей, подключенных к этой базе данных на резервном сервере. Это действие происходит немедленно независимо от значения параметра max_standby_streaming_delay. Обратите внимание, что команда ALTER DATABASE ... RENAME не отключает пользователей, так что обычно проходит незамеченной, хотя в некоторых случаях может вызвать сбой программы, если та каким-то образом зависит от имени базы данных.

Если вы в обычном режиме (не восстановления) выполните DROP USER или DROP ROLE для роли с возможностью подключения, пока этот пользователь еще подключен, то с ним ничего не произойдет — он останется подключенным. Однако переподключиться этот пользователь уже не сможет. Такое же поведение действует в режиме восстановления, так что выполнение DROP USER на основном сервере не отключит этого пользователя от резервного сервера.

Сборщик статистики во время восстановления активен. Все операции сканирования, чтения, блоки, использование индексов и т. д. будут записываться на резервном сервере обычным образом. Воспроизводимые действия не будут дублировать свои эффекты на основном сервере, поэтому воспроизведение добавления данных не увеличит значение столбца Inserts в представлении pg_stat_user_tables. Файлы статистики удаляются в начале восстановления, поэтому статистика на основном сервере и на резервном будет отличаться; это считается особенностью, а не дефектом.

Автоочистка во время восстановления неактивна. Она запустится как обычно в конце восстановления.

Процесс контрольных точек и процесс фоновой записи во время восстановления активны. Процесс контрольных точек будет создавать точки перезапуска (схожие с контрольными точками на основном сервере), а процесс фоновой записи будет выполнять обычные операции по очистке блоков. Сюда могут входить обновления информации о вспомогательных битах, сохраненных на резервном сервере. Команда CHECKPOINT во время восстановления принимается, но создает точку перезапуска, а не новую контрольную точку.


Ссылки на параметры горячего резерва

Различные параметры были упомянуты выше в подразделах Обработка конфликтов запросов и Обзор на уровне администратора.

На основном сервере можно использовать параметры wal_level и vacuum_defer_cleanup_age. Параметры max_standby_archive_delay и max_standby_streaming_delay, установленные на основном сервере, не действуют.

На резервном сервере можно использовать параметры hot_standby, max_standby_archive_delay и max_standby_streaming_delay. Параметр vacuum_defer_cleanup_age не действует, пока сервер остается в режиме резерва, но станет значимым, если резервный сервер станет основным.


Ограничения применимости

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

  • Перед созданием снимка данных требуется полная информация о запущенных транзакциях. Транзакции, использующие большое количество субтранзакций (в настоящее время больше 64) будут задерживать начало подключений только для чтения до завершения самой длительной пишущей транзакции. При возникновении такой ситуации в журнал сервера будут переданы поясняющие сообщения.

  • Допустимые стартовые точки для запросов на резервном сервере генерируются при каждой контрольной точке на основном. Если резервный сервер отключается, пока основной находится в отключенном состоянии, для него может оказаться невозможным вновь войти в режим горячего резерва, пока не запустится основной сервер, чтобы резервный сервер продолжил генерировать стартовые точки в журналах WAL. В большинстве случаев, когда эта ситуация может произойти, он не является проблемой. Обычно если основной сервер отключен и больше не доступен, это является следствием серьезного сбоя, который в любом случае требует, чтобы резервный сервер был преобразован в новый основной. А в ситуациях, когда основной сервер был отключен намеренно, процедура согласования для гарантии благополучного преобразования резервного сервера в новый основной тоже является стандартной.

  • В конце восстановления блокировки AccessExclusiveLock, удерживаемые подготовленными транзакциями, потребуют удвоенное по сравнению с обычным количество блокировок записей таблицы. Если вы планируете выполнить либо большое количество параллельных транзакций, обычно запрашивающих AccessExclusiveLock, либо одну большую транзакцию, запрашивающую много AccessExclusiveLock, рекомендуется установить большое значение max_locks_per_transaction, возможно, в два раза больше значения этого параметра на основном сервере. Это необязательно делать, если вы установили в max_prepared_transactions значение 0.

  • В настоящее время в режиме горячего резерва недоступен уровень изоляции транзакции Serializable. (Подробную информацию см. в подразделах Уровень изоляции Serializable и Обеспечение согласованности с помощью сериализуемых транзакций.) Попытка установить для транзакции уровень сериализуемой изоляции в режиме горячего резерва вызовет ошибку.