Conflicts v5

Conflict detection

List of conflict types

PGD recognizes the following conflict types, which can be used as the conflict_type parameter:

Conflict typeDescription
insert_existsAn incoming insert conflicts with an existing row by way of a primary key or a unique key/index.
update_differingAn incoming update's key row differs from a local row. This can happen only when using row version conflict detection.
update_origin_changeAn incoming update is modifying a row that was last changed by a different node.
update_missingAn incoming update is trying to modify a row that doesn't exist.
update_recently_deletedAn incoming update is trying to modify a row that was recently deleted.
update_pkey_existsAn incoming update has modified the PRIMARY KEY to a value that already exists on the node that's applying the change.
multiple_unique_conflictsThe incoming row conflicts with multiple UNIQUE constraints/indexes in the target table.
delete_recently_updatedAn incoming delete with an older commit timestamp than the most recent update of the row on the current node or when using row version conflict detection.
delete_missingAn incoming delete is trying to remove a row that doesn't exist.
target_column_missingThe target table is missing one or more columns present in the incoming row.
source_column_missingThe incoming row is missing one or more columns that are present in the target table.
target_table_missingThe target table is missing.
apply_error_ddlAn error was thrown by Postgres when applying a replicated DDL command.

Conflict resolution

Most conflicts can be resolved automatically. PGD defaults to a last-update-wins mechanism or, more accurately, the update_if_newer conflict resolver. This mechanism retains the most recently inserted or changed row of the two conflicting ones based on the same commit timestamps used for conflict detection. The behavior in certain corner-case scenarios depends on the settings used for bdr.create_node_group and alternatively for bdr.alter_node_group.

PGD lets you override the default behavior of conflict resolution by using the following function.

List of conflict resolvers

Several conflict resolvers are available in PGD, with differing coverages of the conflict types they can handle:

ResolverDescription
errorThrows an error and stops replication.
skipSkips processing the remote change and continues replication with the next change. Can be used for insert_exists, update_differing, update_origin_change, update_missing, update_recently_deleted, update_pkey_exists, delete_recently_updated, delete_missing, target_table_missing, target_column_missing, and source_column_missing conflict types.
skip_if_recently_droppedSkips the remote change if it's for a table that doesn't exist downstream because it was recently (within one day) dropped on the downstream. Throw an error otherwise. Can be used for the target_table_missing conflict type.
This conflict resolver can pose challenges if a table with the same name is re-created shortly after it's dropped. In that case, one of the nodes might see the DMLs on the re-created table before it sees the DDL to re-create the table. It then incorrectly skips the remote data, assuming that the table is recently dropped, and causes data loss. We recommend that when using this resolver, you don't reuse the object names immediately after they're dropped.
skip_transactionSkips the whole transaction that generated the conflict.
update_if_newerUpdates if the remote row was committed later (as determined by the wall clock of the originating node) than the conflicting local row. If the timestamps are same, the node id is used as a tie-breaker to ensure that same row is picked on all nodes (higher nodeid wins). Can be used for insert_exists, update_differing, update_origin_change, and update_pkey_exists conflict types.
updateAlways performs the replicated action. Can be used for insert_exists (turns the INSERT into UPDATE), update_differing, update_origin_change, update_pkey_exists, and delete_recently_updated (performs the delete).
insert_or_skipTries to build a new row from available information sent by the origin and INSERT it. If there isn't enough information available to build a full row, skips the change. Can be used for update_missing and update_recently_deleted conflict types.
insert_or_errorTries to build new row from available information sent by origin and insert it. If there isn't enough information available to build full row, throws an error and stops the replication. If there isn't enough information available to build full row, throws an error and stops the replication. Can be used for update_missing and update_recently_deleted conflict types.
ignoreIgnores any missing target column and continues processing.Can be used for the target_column_missing conflict type.
ignore_if_nullIgnores a missing target column if the extra column in the remote row contains a NULL value. Otherwise, throws an error and stops replication. Can be used for the target_column_missing conflict type.
use_default_valueFills the missing column value with the default (including NULL if that's the column default) and continues processing. Any error while processing the default or violation of constraints (that is, NULL default on NOT NULL column) stops replication. Can be used for the source_column_missing conflict type.

The insert_exists, update_differing, update_origin_change, update_missing, multiple_unique_conflicts, update_recently_deleted, update_pkey_exists, delete_recently_updated, and delete_missing conflict types can also be resolved by user-defined logic using Conflict triggers.

This matrix shows the conflict types each conflict resolver can handle.

insert_existsupdate_differingupdate_origin_changeupdate_missingupdate_recently_deletedupdate_pkey_existsdelete_recently_updateddelete_missingtarget_column_missingsource_column_missingtarget_table_missingmultiple_unique_conflicts
errorXXXXXXXXXXXX
skipXXXXXXXXXXXX
skip_if_recently_droppedX
update_if_newerXXXX
updateXXXXXX
insert_or_skipXX
insert_or_errorXX
ignoreX
ignore_if_nullX
use_default_valueX
conflict_triggerXXXXXXXXX

Default conflict resolvers

Conflict typeResolver
insert_existsupdate_if_newer
update_differingupdate_if_newer
update_origin_changeupdate_if_newer
update_missinginsert_or_skip
update_recently_deletedskip
update_pkey_existsupdate_if_newer
multiple_unique_conflictserror
delete_recently_updatedskip
delete_missingskip
target_column_missingignore_if_null
source_column_missinguse_default_value
target_table_missing (see note)skip_if_recently_dropped
apply_error_ddlerror
target_table_missing

This conflict type isn't detected on community Postgresql. If the target table is missing, it causes an error and halts replication. EDB Postgres servers detect and handle missing target tables and can invoke the resolver.

List of conflict resolutions

The conflict resolution represents the kind of resolution chosen by the conflict resolver and corresponds to the specific action that was taken to resolve the conflict.

The following conflict resolutions are currently supported for the conflict_resolution parameter:

ResolutionDescription
apply_remoteThe remote (incoming) row was applied.
skipProcessing of the row was skipped (no change was made locally).
mergeA new row was created, merging information from remote and local row.
userUser code (a conflict trigger) produced the row that was written to the target table.

Conflict logging

To ease diagnosing and handling multi-master conflicts, PGD, by default, logs every conflict into the bdr.conflict_history table. You can change this behavior with more granularity using bdr.alter_node_set_log_config.