7.3. Распределённые транзакции
7.3.1. Видимость и CSN
7.3.1.1. CSN — порядковый номер фиксации
Кластер Shardman использует механизм изоляции снимков для распределённых транзакций. Механизм предоставляет способ синхронизации снимков между различными узлами кластера и способ атомарной фиксации такой транзакции по отношению к другим параллельным глобальным и локальным транзакциям. Эти глобальные транзакции можно координировать, используя предоставленные SQL-функции или посредством postgres_fdw, который прозрачно использует эти функции на удалённых узлах.
Предположим, что каждый узел использует видимость на основе CSN: база данных отслеживает счётчик для каждой фиксации транзакции (CSN
). С таким параметром снимок будет состоять из одного числа — копии текущего CSN
на момент создания снимка. Правила видимости сводятся к проверке того, меньше ли CSN
текущего кортежа, чем CSN
нашего снимка.
Предположим, что CSN
— это текущее физическое время на узле, и назовём его GlobalCSN
. Если физическое время на разных узлах идеально синхронизировано, то такой снимок, полученный на одном узле, можно использовать на других узлах для обеспечения необходимого уровня изоляции транзакций. Но, к сожалению, физическое время никогда не бывает идеально синхронизировано и может смещаться, и это следует учитывать. Кроме того, в распределённой среде нет простого понятия блокировки или атомарной операции, поэтому атомарность фиксации на разных узлах в отношении одновременного получения снимков должна каким-то образом обрабатываться. Эта проблема решается следующим образом:
Для достижения атомарности фиксаций разных узлов вводится промежуточный шаг: при первом запуске транзакция помечается как
InDoubt
на всех узлах, и только после этого каждый узел фиксирует её и ставит метку с заданнымGlobalCSN
. Все обработчики, столкнувшиеся с кортежами транзакцииInDoubt
, должны дождаться её завершения и перепроверить видимость.Когда координатор помечает транзакции как
InDoubt
на других узлах, он собираетProposedGlobalCSN
от каждого участника, которые представляют собой местное время на этих узлах. Затем он выбирает максимальное значение всехProposedGlobalCSN
и фиксирует транзакцию на всех узлах с этим максимальнымGlobalCSN
, даже если это значение больше, чем текущее время на этом узле из-за смещения. Таким образом,GlobalCSN
для данной транзакции будет одинаковым на всех узлах. Каждый узел записывает свой последний сгенерированныйCSN
(last_csn
) и не может генерироватьCSN
>last_csn
. Когда узел фиксирует транзакцию сCSN
>last_csn
,last_csn
настраивается для записи этогоCSN
. Благодаря этому механизму узел не может сгенерироватьCSN
, который меньше, чемCSN
уже совершённых транзакций.Если локальная транзакция импортирует сторонний глобальный снимок с некоторым
GlobalCSN
и текущее время на этом узле меньше, чем входящегоGlobalCSN
, транзакция должна дождаться, когда время этогоGlobalCSN
наступит локально.
Два последних правила обеспечивают защиту от смещения по времени.
7.3.1.2. Задержка фиксации и внешняя согласованность
Соблюдение вышеприведённых правил по-прежнему не гарантирует актуальность снимков, созданных на узлах, которые не участвуют в транзакции. Операция чтения из такого узла может получить устаревшие данные. Вероятность аномалии напрямую зависит от рассогласования системных часов в кластере Shardman.
Особое внимание следует уделить синхронизации системных часов на всех узлах кластера. Значение рассогласования часов должно быть измерено. Если требуется внешняя согласованность, то такое рассогласование можно компенсировать задержкой фиксации. Эта задержка добавляется перед каждой фиксацией в системе, поэтому она увеличивает задержку транзакций. Эта задержка не влияет на транзакции только для чтения. Задержку можно установить при помощи параметра конфигурации csn_commit_delay.
7.3.1.3. Карта CSN
Описанный выше механизм видимости CSN
не является общим способом проверки видимости всех транзакций. Он используется для обеспечения изоляции только распределённых транзакций. В результате каждый узел кластера использует механизм проверки видимости, основанный на xid
и xmin
. Чтобы иметь возможность использовать снимок CSN
, указывающий на прошлое, нужно сохранить старые версии кортежей на всех узлах и, следовательно, отложить их очистку. Для этого каждый узел в кластере Shardman поддерживает сопоставление CSN
и xid
. Карта называется CSNSnapshotXidMap
. Эта карта является циклическим буфером и хранит соответствие между текущими snapshot_csn
и xmin
в разреженном виде: snapshot_csn
округляется до секунд (и здесь используется тот факт, что snapshot_csn
— это просто метка времени), а xmin
хранится в циклическом буфере, где округлённое значение snapshot_csn
действует как смещение из текущего циклического буфера. Размер циклического буфера управляется параметром конфигурации csn_snapshot_defer_time. VACUUM
не может очищать кортежи, у которых xmax
новее, чем самый старый xmin
в CSNSnapshotXidMap
.
Когда приходит снимок CSN
, проверяется, что его snapshot_csn
всё ещё находится в карте, в противном случае приходит сообщение об ошибке «snapshot too old» (снимок слишком старый). Если snapshot_csn
успешно сопоставлен, xmin
серверной части заполняется значением из карты. Таким образом можно учитывать обслуживающие процессы с импортированным снимком CSN
, и старые версии кортежей будут сохранены.
7.3.1.4. Сокращение карты CSN
Для поддержки глобальных транзакций каждый узел хранит старые версии кортежей не менее csn_snapshot_defer_time
секунд. При больших значениях csn_snapshot_defer_time
это отрицательно сказывается на производительности и связано с тем, что узлы сохраняют все версии строк в течение последних csn_snapshot_defer_time
секунд, но в кластере может не быть больше транзакций, которые могут их прочитать. Специальная задача monitor периодически пересчитывает xmin
в кластере и устанавливает для него на всех узлах минимально возможное значение. Она позволяет процедуре очистки удалить версию строки, которая больше не представляет интереса для какой-либо транзакции. Параметр конфигурации shardman.monitor_trim_csnxid_map_interval управляет рабочим процессом. Рабочий процесс активируется каждые monitor_interval
секунд и выполняет следующие операции:
Проверяет, является ли идентификатор группы репликации текущего узла наименьшим среди всех идентификаторов в кластере. Если это условие не выполняется, то работа на текущем узле прекращается. Таким образом, только один узел в кластере может определять глобальный горизонт.
С каждого узла кластера Shardman координатор собирает самый старый снимок
CSN
среди всех активных транзакций на узле.Координатор выбирает наименьшее значение
CSN
и отправляет его каждому узлу. На каждом узле отбрасываются все значенияcsnXidMap
, которые меньше значения этогоCSN
.
7.3.2. Разрешение двухфазных и подготовленных транзакций
Shardman реализует протокол двухфазной фиксации для обеспечения атомарности распределённых транзакций. Во время выполнения распределённой транзакции узел-координатор отправляет команду BEGIN
узлам-участникам для инициирования их локальных транзакций.
Термин «узлы-участники» здесь и далее относится к подмножеству узлов кластера, которые участвуют в выполнении команды транзакции, пока узел занят записью.
Кроме того, на узле-координаторе создаётся локальная транзакция. Это гарантирует наличие соответствующих локальных транзакций на всех узлах, участвующих в распределённой транзакции.
Во время двухфазной фиксации транзакции узел-координатор отправляет команду PREPARE TRANSACTION
узлам-участникам, чтобы инициировать подготовку их локальных транзакций к фиксации. Если подготовка прошла успешно, локальные данные транзакции сохраняются в хранилище на диске, что делает их постоянными. Если все узлы-участники сообщают об успешной подготовке узлу-координатору, узел-координатор фиксирует свою локальную транзакцию. Впоследствии узел-координатор также зафиксирует ранее подготовленные транзакции на узлах-участниках, используя команду COMMIT PREPARED
.
Если во время выполнения команды PREPARE TRANSACTION
на любом из узлов-участников происходит сбой, распределённая транзакция считается прерванной. Затем узел-координатор транслирует команду отмены ранее подготовленных транзакций ROLLBACK PREPARED
. Если локальная транзакция уже была подготовлена, она прерывается. Однако если подготовленной транзакции с указанным именем не было, команда отката просто игнорируется. Впоследствии узел-координатор откатывает свою локальную транзакцию.
После успешной фазы подготовки на каждом из узлов-участников будет объект prepared transaction
. Эти объекты фактически являются файлами на диске и записями в памяти сервера.
Возможно наличие подготовленной транзакция, которая была создана ранее посредством двухфазной операции и никогда не будет завершена. Это может произойти, например, если узел-координатор выходит из строя сразу после этапа подготовки, но до этапа фиксации. Это также может произойти в результате проблем с сетевым подключением. Например, если команда COMMIT PREPARED
, переданная узлом-координатором узлу-участнику завершается ошибкой, локальные транзакции будут зафиксированы на всех узлах-участниках, кроме узла с ошибкой. Локальная транзакция также будет зафиксирована на узле-координаторе. Все участники, кроме одного с ошибкой, считают, что распределённая транзакция завершена. Однако один участник, всё ещё ожидающий команду COMMIT PREPARED
, никогда не получит её, в результате чего подготовленная транзакция никогда не будет завершена.
Подготовленная транзакция потребляет системные ресурсы, такие как память и дисковое пространство. Незавершённая подготовленная транзакция заставляет другие транзакции, которые обращаются к строкам, изменённым этой транзакцией, ожидать завершения распределённой операции. Поэтому необходимо завершить подготовленные транзакции, даже в тех случаях, когда во время фиксации были сбои, чтобы высвободить ресурсы и обеспечить возможность выполнения других транзакций.
Для решения этой проблемы существует механизм разбора подготовленных транзакций, реализованный в составе Shardman monitor. Он реализован как фоновый рабочий процесс, который периодически активируется, действуя как внутреннее задание «crontab». По умолчанию период активации установлен на 5 секунд, но его можно настроить в параметре конфигурации shardman.monitor_dxact_interval
. Рабочий процесс проверяет наличие подготовленных транзакций, которые были созданы ранее, через определённое время, указанное в параметре конфигурации shardman.monitor_dxact_timeout
(который по умолчанию также равен 5 секундам), на том же узле где работает Shardman monitor.
Когда команда PREPARE TRANSACTION
отправляется узлу-участнику, подготовленной транзакции присваивается специальное имя. Это имя содержит полезную информацию, позволяющую идентифицировать узел-координатор и его локальную транзакцию.
Если Shardman monitor обнаруживает устаревшие подготовленные транзакции, он извлекает идентификатор группы репликации координатора и идентификатор локальной транзакции координатора. Затем monitor отправляет запрос координатору
SELECT shardman.xact_status(TransactionId)
который запрашивает текущий статус локальной транзакции координатора. Если запрос завершается ошибкой, например из-за проблем с сетевым подключением, то подготовленная транзакция останется нетронутой до следующего раза, когда monitor будет активирован.
В случае успешного запроса узел-координатор может ответить с одним из следующих статусов:
committed
Локальная транзакция на узле-координаторе успешно завершена. Поэтому Shardman monitor также фиксирует эту подготовленную транзакцию, используя команду
COMMIT PREPARED
.aborted
Локальная транзакция на узле-координаторе была прервана. Поэтому monitor также прерывает эту транзакцию, используя команду
ROLLBACK PREPARED
.unknown
Транзакция с таким идентификатором никогда не существовала на узле-координаторе. Поэтому monitor прерывает эту транзакцию командой
ROLLBACK PREPARED
.active
Локальная транзакция на узле-координаторе всё ещё находится где-то внутри потока
CommitTransaction()
. Поэтому monitor ничего не делает с этой транзакцией и повторит попытку с этой транзакцией при следующей активации.ambiguous
Этот статус может быть возвращён, когда усечение
CLOG
включено на узле-координаторе.CLOG
— это растровое изображение, в котором хранится состояние завершённых локальных транзакций. Когда транзакция фиксируется или прерывается, её статус отмечается вCLOG
. ОднакоCLOG
может быть усечён (собран мусор) процессомVACUUM
, чтобы отбросить статусы старых транзакций, которые не влияют на видимость данных для любой существующей транзакции.Когда
CLOG
усекается, существует вероятность того, что функцияshardman.xact_status()
не сможет однозначно решить, существовала ли транзакция в прошлом (с некоторым статусом) или никогда не существовала. В таких случаях функция возвращает неоднозначный статус. Это может привести к неопределённости фактического статуса транзакции и затруднить разбор подготовленной транзакции.Когда функция
shardman.xact_status()
возвращает статусambiguous
для подготовленной транзакции, узел monitor вносит в журнал предупреждение, указывающее, что статус нельзя определить однозначно. Подготовленная транзакция остаётся нетронутой, и monitor снова попытается использовать эту транзакцию при следующей активации. Важно установить правильное значение параметраmin_clog_size
со значением1024000
(что означает «никогда не усекать CLOG»), чтобы избежать неоднозначности в статусе подготовленных транзакций.
В ситуациях, когда механизм разбора подготовленных транзакций не может разобрать подготовленные транзакции из-за постоянных ошибок или неоднозначного статуса, администратору потребуется вручную вмешаться в разбор этих транзакций. Для этого может потребоваться изучить журналы сервера и выполнить операции отката или фиксации для подготовленной транзакции вручную. Обратите внимание, что оставление подготовленных транзакций неразобранными может приводить к проблемам с потреблением ресурсов и производительностью, поэтому важно устранять такие ситуации как можно скорее.