BDR is an active/active or multi-master DBMS. If used asynchronously, writes to the same or related row(s) from multiple different nodes can result in data conflicts when using standard data types.
Conflicts aren't ERRORs - they are events that can be detected and resolved automatically as they occur by BDR, in most cases. Resolution depends upon the nature of the application and the meaning of the data, so it is important that BDR provides the application a range of choices as to how to resolve conflicts.
By default, conflicts are resolved at row level. That is, when changes from two nodes conflict, we pick either the local or remote tuple and discard the other one. For example, we may compare commit timestamps for the two conflicting changes, and keep the newer one. This ensures that all nodes converge to the same result, and establishes commit-order-like semantics on the whole cluster.
This chapter covers row-level conflicts with standard data types in detail.
Conflict handling is configurable, as described later in this chapter. Conflicts can be detected and handled differently for each table using conflict triggers, described in the Stream Triggers chapter.
Column-level conflict detection and resolution is available with BDR, described in the CLCD chapter.
If you wish to avoid conflicts, you can use these features in BDR.
- Conflict-free data types (CRDTs) - described in the CRDT chapter.
- Eager replication - described in the Eager Replication chapter.
By default, all conflicts are logged to bdr.conflict_history. If conflicts
are possible then table owners should monitor for them, analyze to see how they
can be avoided or plans made to handle them regularly as an application task.
The LiveCompare tool is also available to scan regularly for divergence.
Some clustering systems use distributed lock mechanisms to prevent concurrent access to data. These can perform reasonably when servers are very close, but cannot support geographically distributed applications where very low latency is critical for acceptable performance.
Distributed locking is essentially a pessimistic approach, whereas BDR advocates an optimistic approach: avoid conflicts where possible, but allow some types of conflict to occur and resolve them when they arise.
Upgrade Notes
All the SQL visible interfaces are in the bdr schema.
All the previously deprecated interfaces in the bdr_conflicts or
bdr_crdt schema were removed and will not work on 3.7+ nodes or in
groups that contain at least one 3.7+ node.
Please use the ones in bdr schema that are already present in all BDR versions.
How conflicts happen
Inter-node conflicts arise as a result of sequences of events that could not happen if all the involved transactions happened concurrently on the same node. Because the nodes only exchange changes after the transactions commit, each transaction is individually valid on the node it committed on, but would not be valid if applied on another node that did other conflicting work at the same time.
Since BDR replication essentially replays the transaction on the other nodes, the replay operation can fail if there is a conflict between a transaction being applied and a transaction that was committed on the receiving node.
The reason most conflicts can't happen when all transactions run on a single
node is that PostgreSQL has inter-transaction communication mechanisms
to prevent it - UNIQUE indexes, SEQUENCEs, row and relation locking,
SERIALIZABLE dependency tracking, etc. All of these mechanisms are ways
to communicate between ongoing transactions to prevent undesirable concurrency
issues.
BDR does not have a distributed transaction manager or lock manager. That's part of why it performs well with latency and network partitions. As a result, transactions on different nodes execute entirely independently from each other, when using the default, lazy replication. Less independence between nodes can avoid conflicts altogether, which is why BDR also offers eager replication for when this is important.
Types of conflict
PRIMARY KEY or UNIQUE Conflicts
The most common conflicts are row conflicts, where two operations affect a row with the same key in ways they could not do on a single node. BDR can detect most of those and will apply the update_if_newer conflict resolver.
Row conflicts include:
INSERTvsINSERTUPDATEvsUPDATEUPDATEvsDELETEINSERTvsUPDATEINSERTvsDELETEDELETEvsDELETE
The view bdr.node_conflict_resolvers provides information on how
conflict resolution is currently configured for all known conflict types.
INSERT/INSERT Conflicts
The most common conflict, INSERT/INSERT, arises where INSERTs on two
different nodes create a tuple with the same PRIMARY KEY values (or if no
PRIMARY KEY exists, the same values for a single UNIQUE constraint ).
BDR handles this by retaining the most recently inserted tuple of the two, according to the originating host's timestamps, unless overridden by a user-defined conflict handler.
This conflict will generate the insert_exists conflict type, which is by
default resolved by choosing the newer (based on commit time) row and keeping
only that one (update_if_newer resolver). Other resolvers can be configured -
see Conflict Resolution for details.
To resolve this conflict type, you can also use column-level conflict resolution and user-defined conflict triggers.
This type of conflict can be effectively eliminated by use of Global Sequences.
INSERTs that Violate Multiple UNIQUE Constraints
An INSERT/INSERT conflict can violate more than one UNIQUE constraint
(of which one might be the PRIMARY KEY). If a new row violates more than
one UNIQUE constraint and that results in a conflict against more than one
other row, then the apply of the replication change will produce a
multiple_unique_conflicts conflict.
In case of such a conflict, some rows must be removed in order for replication
to continue. Depending on the resolver setting for multiple_unique_conflicts,
the apply process will either exit with error, skip the incoming row, or delete
some of the rows automatically. The automatic deletion will always try to
preserve the row with the correct PRIMARY KEY and delete the others.
Warning
In case of multiple rows conflicting this way, if the result of conflict resolution is to proceed with the insert operation, some of the data will always be deleted!
It's also possible to define a different behaviour using a conflict trigger.
UPDATE/UPDATE Conflicts
Where two concurrent UPDATEs on different nodes change the same tuple
(but not its PRIMARY KEY), an UPDATE/UPDATE conflict can occur on replay.
These can generate different conflict kinds based on the configuration and
situation. If the table is configured with [Row Version Conflict Detection],
then the original (key) row is compared with the local row;
if they are different, the update_differing conflict is generated.
When using [Origin Conflict Detection],
the origin of the row is checked (the origin is the node that the current
local row came from); if that has changed, the update_origin_change conflict
is generated. In all other cases, the UPDATE is normally applied without
a conflict being generated.
Both of these conflicts are resolved same way as insert_exists, as described
above.
UPDATE Conflicts on the PRIMARY KEY
BDR cannot currently perform conflict resolution where the PRIMARY KEY
is changed by an UPDATE operation. It is permissible to update the primary
key, but you must ensure that no conflict with existing values is possible.
Conflicts on the update of the primary key are Divergent Conflicts and require manual operator intervention.
Updating a PK is possible in PostgreSQL, but there are issues in both PostgreSQL and BDR.
Let's create a very simple example schema to explain:
CREATE TABLE pktest (pk integer primary key, val integer); INSERT INTO pktest VALUES (1,1);
Updating the Primary Key column is possible, so this SQL succeeds:
UPDATE pktest SET pk=2 WHERE pk=1;
...but if we have multiple rows in the table, e.g.: