29.7. Конфликты#

29.7. Конфликты

29.7. Конфликты #

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

Активируется дополнительное логирование, и статистика конфликтов собирается (отображается в представлении pg_stat_subscription_stats) в следующих случаях возникновения конфликта:

insert_exists #

Вставка строки, нарушающей ограничение уникальности NOT DEFERRABLE. Обратите внимание, что для регистрации сведений об источнике и времени фиксации конфликтующего ключа необходимо включить параметр track_commit_timestamp на подписчике. В этом случае будет возникать ошибка до тех пор, пока конфликт не будет разрешён вручную.

update_origin_differs #

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

update_exists #

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

update_missing #

Строка для обновления не найдена. В этом случае обновление просто будет пропущено.

delete_origin_differs #

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

delete_missing #

Строка, которую требуется удалить, не была найдена. В этом случае удаление просто будет пропущено.

multiple_unique_conflicts #

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

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

Формат журнала для конфликтов логической репликации следующий:

LOG:  conflict detected on relation "schemaname.tablename": conflict=conflict_type
DETAIL:  detailed_explanation.
{detail_values [; ... ]}.

where detail_values is one of:

    Key (column_name [, ...])=(column_value [, ...])
    existing local row [(column_name [, ...])=](column_value [, ...])
    remote row [(column_name [, ...])=](column_value [, ...])
    replica identity {(column_name [, ...])=(column_value [, ...]) | full [(column_name [, ...])=](column_value [, ...])}

Журнал предоставляет следующую информацию:

LOG
  • schemaname.tablename определяет локальное отношение, участвующее в конфликте.

  • conflict_type — это тип возникшего конфликта (например, insert_exists, update_exists).

DETAIL
  • detailed_explanation показывает источник, идентификатор транзакции и время фиксации транзакции, которая изменила существующую локальную строку, если эта информация доступна.

  • Раздел Key показывает значения ключей локальной строки, которая нарушила ограничение уникальности для конфликтов insert_exists, update_exists или multiple_unique_conflicts.

  • Раздел existing local row показывает локальную строку, если её источник отличается от удалённой строки для конфликтов update_origin_differs или delete_origin_differs, либо если значение ключа конфликтует с удалённой строкой для конфликтов insert_exists, update_exists или multiple_unique_conflicts.

  • Раздел remote row показывает новую строку из удалённой операции вставки или обновления, которая вызвала конфликт. Обратите внимание, что для операции обновления значение столбца новой строки будет равно null, если значение не изменилось и имеет формат toasted.

  • Раздел replica identity включает значения ключа идентификации реплики, которые использовались для поиска существующей локальной строки, подлежащей обновлению или удалению. Может включать полное значение строки, если локальное отношение помечено как REPLICA IDENTITY FULL.

  • column_name — это имя столбца. Для разделов existing local row, remote row и replica identity full имена столбцов записываются в журнал только если у пользователя нет прав доступа ко всем столбцам таблицы. Имена столбцов, если они есть, отображаются в том же порядке, что и соответствующие значения столбцов.

  • column_value — это значение столбца. Большие значения столбцов урезаются до 64 байт.

  • Обратите внимание, что в случае конфликта multiple_unique_conflicts генерируются строки detailed_explanation и detail_values, каждая из которых будет содержать информацию по отдельным уникальным ограничениям.

Операции логической репликации выполняются с привилегиями роли, которая владеет подпиской. Ошибки разрешений на целевые таблицы вызовут конфликты репликации, а также включенная строковая защита на уровне строк на целевых таблицах, к которой подписчик имеет доступ, независимо от того, будет ли отклонена INSERT, UPDATE, DELETE или TRUNCATE, которые реплицируются. Ограничение на строковую безопасность может быть снято в будущих версиях Tantor SE.

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

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

ERROR:  conflict detected on relation "public.test": conflict=insert_exists
DETAIL:  Key already exists in unique index "t_pkey", which was modified locally in transaction 740 at 2024-06-26 10:47:04.727375+08.
Key (c)=(1); existing local row (1, 'local'); remote row (1, 'remote').
CONTEXT:  processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/14C0378

В логах сервера можно найти LSN транзакции, содержащей изменение, которое нарушает ограничение, и имя источника данных репликации (LSN 0/14C0378 и источник репликации pg_16395 в приведённом выше случае). Конфликтующую транзакцию можно пропустить с помощью ALTER SUBSCRIPTION ... SKIP , указав LSN ее завершения (т.е. LSN 0/14C0378). В качестве LSN завершения может быть указана LSN, на котором транзакция была зафиксирована или подготовлена на стороне публикации. Кроме того, транзакцию можно также пропустить, вызвав функцию pg_replication_origin_advance(). Перед использованием этой функции подписку необходимо либо временно отключить с помощью ALTER SUBSCRIPTION ... DISABLE, либо использовать подписку с параметром disable_on_error. Затем можно вызвать функцию pg_replication_origin_advance() и передать ей node_name (т.е. pg_16395) и LSN, следующий за LSN завершения (т.е. 0/14C0379). Текущие позиции источников можно посмотреть в системном представлении pg_replication_origin_status. Обратите внимание, что пропуск всей транзакции включает пропуск изменений, которые могли бы не нарушать никаких ограничений. Это может легко привести к несогласованности состояния подписчика. Дополнительные сведения о конфликтующих строках, такие как их источник и время фиксации, можно увидеть в строке DETAIL журнала. Но обратите внимание, что эта информация доступна только при включённом параметре track_commit_timestamp на подписчике. Пользователи могут использовать эту информацию, чтобы решить, сохранять ли локальное изменение или принять удалённое изменение. Например, строка DETAIL в приведённом выше журнале указывает, что существующая строка была изменена локально. Пользователи могут вручную применить удаленное изменение remote-change-win.

Когда streaming режим установлен в parallel, конечный LSN неудачных транзакций может не быть записан. В этом случае может потребоваться изменить режим потоковой передачи на on или off и вызвать те же конфликты снова, чтобы конечный LSN неудачной транзакции был записан в журнал сервера. Для использования конечного LSN, пожалуйста, обратитесь к ALTER SUBSCRIPTION ... SKIP.