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.
29.7. Conflicts #
Logical replication behaves similarly to normal DML operations in that the data will be updated even if it was changed locally on the subscriber node. If incoming data violates any constraints the replication will stop. This is referred to as a conflict. When replicating UPDATE or DELETE operations, missing data is also considered as a conflict, but does not result in an error and such operations will simply be skipped.
Additional logging is triggered, and the conflict statistics are collected (displayed in the pg_stat_subscription_stats view) in the following conflict cases:
insert_exists#Inserting a row that violates a
NOT DEFERRABLEunique constraint. Note that to log the origin and commit timestamp details of the conflicting key,track_commit_timestampshould be enabled on the subscriber. In this case, an error will be raised until the conflict is resolved manually.update_origin_differs#Updating a row that was previously modified by another origin. Note that this conflict can only be detected when
track_commit_timestampis enabled on the subscriber. Currently, the update is always applied regardless of the origin of the local row.update_exists#The updated value of a row violates a
NOT DEFERRABLEunique constraint. Note that to log the origin and commit timestamp details of the conflicting key,track_commit_timestampshould be enabled on the subscriber. In this case, an error will be raised until the conflict is resolved manually. Note that when updating a partitioned table, if the updated row value satisfies another partition constraint resulting in the row being inserted into a new partition, theinsert_existsconflict may arise if the new row violates aNOT DEFERRABLEunique constraint.update_missing#The row to be updated was not found. The update will simply be skipped in this scenario.
delete_origin_differs#Deleting a row that was previously modified by another origin. Note that this conflict can only be detected when
track_commit_timestampis enabled on the subscriber. Currently, the delete is always applied regardless of the origin of the local row.delete_missing#The row to be deleted was not found. The delete will simply be skipped in this scenario.
multiple_unique_conflicts#Inserting or updating a row violates multiple
NOT DEFERRABLEunique constraints. Note that to log the origin and commit timestamp details of conflicting keys, ensure thattrack_commit_timestampis enabled on the subscriber. In this case, an error will be raised until the conflict is resolved manually.
Note that there are other conflict scenarios, such as exclusion constraint violations. Currently, we do not provide additional details for them in the log.
The log format for logical replication conflicts is as follows:
LOG: conflict detected on relation "schemaname.tablename": conflict=conflict_typeDETAIL:detailed_explanation. {detail_values[; ... ]}. wheredetail_valuesis 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[, ...])}
The log provides the following information:
LOGschemaname.tablenameidentifies the local relation involved in the conflict.conflict_typeis the type of conflict that occurred (e.g.,insert_exists,update_exists).
DETAILdetailed_explanationincludes the origin, transaction ID, and commit timestamp of the transaction that modified the existing local row, if available.The
Keysection includes the key values of the local row that violated a unique constraint forinsert_exists,update_existsormultiple_unique_conflictsconflicts.The
existing local rowsection includes the local row if its origin differs from the remote row forupdate_origin_differsordelete_origin_differsconflicts, or if the key value conflicts with the remote row forinsert_exists,update_existsormultiple_unique_conflictsconflicts.The
remote rowsection includes the new row from the remote insert or update operation that caused the conflict. Note that for an update operation, the column value of the new row will be null if the value is unchanged and toasted.The
replica identitysection includes the replica identity key values that were used to search for the existing local row to be updated or deleted. This may include the full row value if the local relation is marked withREPLICA IDENTITY FULL.column_nameis the column name. Forexisting local row,remote row, andreplica identity fullcases, column names are logged only if the user lacks the privilege to access all columns of the table. If column names are present, they appear in the same order as the corresponding column values.column_valueis the column value. The large column values are truncated to 64 bytes.Note that in case of
multiple_unique_conflictsconflict, multipledetailed_explanationanddetail_valueslines will be generated, each detailing the conflict information associated with distinct unique constraints.
Logical replication operations are performed with the privileges of the role which owns the subscription. Permissions failures on target tables will cause replication conflicts, as will enabled row-level security on target tables that the subscription owner is subject to, without regard to whether any policy would ordinarily reject the INSERT, UPDATE, DELETE or TRUNCATE which is being replicated. This restriction on row-level security may be lifted in a future version of PostgreSQL.
A conflict that produces an error will stop the replication; it must be resolved manually by the user. Details about the conflict can be found in the subscriber's server log.
The resolution can be done either by changing data or permissions on the subscriber so that it does not conflict with the incoming change or by skipping the transaction that conflicts with the existing data. When a conflict produces an error, the replication won't proceed, and the logical replication worker will emit the following kind of message to the subscriber's server log:
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
The LSN of the transaction that contains the change violating the constraint and the replication origin name can be found from the server log (LSN 0/14C0378 and replication origin pg_16395 in the above case). The transaction that produced the conflict can be skipped by using ALTER SUBSCRIPTION ... SKIP with the finish LSN (i.e., LSN 0/14C0378). The finish LSN could be an LSN at which the transaction is committed or prepared on the publisher. Alternatively, the transaction can also be skipped by calling the pg_replication_origin_advance() function. Before using this function, the subscription needs to be disabled temporarily either by ALTER SUBSCRIPTION ... DISABLE or, the subscription can be used with the disable_on_error option. Then, you can use pg_replication_origin_advance() function with the node_name (i.e., pg_16395) and the next LSN of the finish LSN (i.e., 0/14C0379). The current position of origins can be seen in the pg_replication_origin_status system view. Please note that skipping the whole transaction includes skipping changes that might not violate any constraint. This can easily make the subscriber inconsistent. The additional details regarding conflicting rows, such as their origin and commit timestamp can be seen in the DETAIL line of the log. But note that this information is only available when track_commit_timestamp is enabled on the subscriber. Users can use this information to decide whether to retain the local change or adopt the remote alteration. For instance, the DETAIL line in the above log indicates that the existing row was modified locally. Users can manually perform a remote-change-win.
When the streaming mode is parallel, the finish LSN of failed transactions may not be logged. In that case, it may be necessary to change the streaming mode to on or off and cause the same conflicts again so the finish LSN of the failed transaction will be written to the server log. For the usage of finish LSN, please refer to ALTER SUBSCRIPTION ... SKIP.