[Перевод] PostgreSQL и настройки согласованности записи для каждого конкретного соединения

Перевод статьи подготовлен специально для студентов курса «Базы Данных». Интересно развиваться в данном направлении? Приглашаем вас на День Открытых Дверей, где мы подробно рассказываем о программе, особенностях онлайн-формата, компетенциях и карьерных перспективах, которые ждут выпускников после обучения.

gfiyteycddmeefan9z5h1nxdgu0.png

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

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


Знакомьтесь, компромисс

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


Компромисс 1: Производительность

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


Компромисс 2: Согласованность

Результат в случае сбоя в работе лидера в этих двух подходах тоже будет разным. Если работа выполняется асинхронно, то при возникновении такой ошибки, не все записи будут зафиксированы репликами. Сколько будет потеряно? Зависит от самого приложения и эффективности репликации. Репликация Compose помешает реплике стать лидером в случае, если количество информации в ней на 1 Мб меньше, чем в лидере, то есть потенциально может быть потеряно до 1 Мб записей при асинхронной работе.

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

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


Компромисс 3: Сбои

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


По одному соединению на транзакцию?

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

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


Обеспечение контроля на практике

По умолчанию PostgreSQL обеспечивает согласованность. Это контролируется параметром сервера synchronous_commit. По умолчанию он в положении on, но у него есть три других варианта: local, remote_write или off.

При установке параметра в off останавливаются все синхронные коммиты, даже в локальной системе. Параметр в local определяет синхронный режим для локальной системы, но записи в реплики производятся асинхронно. Remote_write заходит еще дальше: записи в реплики производятся асинхронно, но возвращаются, когда реплика приняла запись, но не записала ее на диск.

Рассматривая имеющийся диапазон опций, мы выбираем поведение и, помня о том, что on — это синхронные записи, мы выберем local для асинхронных коммитов по сети, при этом оставив локальные коммиты синхронными.

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

SET SESSION synchronous_commit TO ON;  
// Your writes go here

Все последующие записи в сессии будут подтверждать операции записи для реплик, прежде чем возвращать положительный результат подключенному клиенту. Если, конечно, вы не измените настройку synchronous_commit снова. Можно опустить часть SESSION в команде, поскольку она будет в значении по умолчанию.

Второй способ хорош, когда вы просто хотите убедиться, что получаете синхронную репликацию для одной транзакции. Во многих базах данных поколения «NoSQL» понятия транзакций не существует, но оно существует в PostgreSQL. В этом случае вы запускаете транзакцию, а затем устанавливаете synchronous_commit в on перед выполнением записи для транзакции. COMMIT зафиксирует транзакцию, используя любое значение параметра synchronous_commit, которое было установлено в тот момент, хотя лучше всего задавать переменную заранее, чтобы убедиться, что другие разработчики понимают, что записи не являются асинхронными.

BEGIN;  
SET LOCAL synchronous_commit TO ON;  
// Your writes go here
COMMIT;  

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


Настройка PostgreSQL

До этого мы представляли себе систему PostgreSQL с synchronous_commit, установленным в local. Чтобы это было реально на стороне сервера, вам нужно будет установить два параметра конфигурации сервера. Еще один параметр synchronous_standby_names будет вступать в свои права, когда synchronous_commit будет в on. Он определяет какие реплики имеют право на синхронные коммиты, и мы установим его в *, что будет означать задействование всех реплик. Эти значения обычно настраиваются в файле конфигурации путем добавления:

synchronous_commit = local  
synchronous_standby_names='*'

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

Если вы следили за развитием проекта Governor, вы могли заметить некоторые недавние изменения (1, 2), которые позволили пользователям Governor тестировать эти параметры и контролировать их согласованность.


Еще пара слов…

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

yu-pqnp2g5_ipnofxejmnmkc_uq.png

Этот параметр может быть изменен в любое время. Поведение для любой транзакции определяется настройкой, действующей при коммите. Поэтому возможно и полезно, чтобы для некоторых транзакций коммиты совершались синхронно, а для других — асинхронно. Например, чтобы заставить одну multistatement транзакцию делать коммиты асинхронно, когда значение параметра по умолчанию противоположно, задайте SET LOCAL synchronous_commit TO OFF в транзакции.

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

© Habrahabr.ru