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 "имя_схему.имя_таблицы": conflict=типа_конфликта
DETAIL:  детальная_информация.
{информация_о_значениях [; ... ]}.

где информация_о_значениях является одним из следующего:

    Key (имя_столбца [, ...])=(значение_столбца [, ...])
    existing local row [(имя_столбца [, ...])=](значение_столбца [, ...])
    remote row [(>имя_столбца [, ...])=](значение_столбца [, ...])
    replica identity {(>имя_столбца [, ...])=(значение_столбца [, ...]) | full [(c>имя_столбца [, ...])=](значение_столбца [, ...])}

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

LOG
  • имя_схемы.имя_таблицы указывает на локальное конфликтующее отношение.

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

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

  • Раздел 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, если оно не изменилось и имеет формат TOAST.

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

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

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

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

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

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

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

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 в журнале указывает на то, что существующая строка была изменена локально. Применить удалённое изменение пользователи могут вручную.

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