Viewing Conflict Logs
View and access conflict logs from the conflict collection.
When enabling XDCR conflict logging, you must specify the Conflict Log Collection or conflict collection. Conflict logs or conflict records are stored as documents within these collections. As conflict collections are regular collections, you can use RBAC to control access to the conflict logs, which include copies of your application data. You can also programmatically process and view the conflict records and documents.
The following sections have examples of how to access, process, or locate information within conflict log documents using SQL++ queries, an Eventing function, and a KV Range Scan. While these are some common methods, other options exist for viewing or processing conflict log records, such as using Couchbase Kafka Connect to stream these documents to an application for further processing.
After XDCR logs a conflict, it does not modify or remove the conflict records. You’re responsible for managing and deleting them when no longer needed. Each conflict record has 3 documents. You can find the following in the Conflict Record Document (CRD) of the conflict collection:
-
A conflict event record in the CRD
id
with the prefixcrd_
. -
A copy of the source document in the CRD
srcDoc.id
with the prefixsrc_
. -
A copy of the target document in the CRD
tgtDoc.id
with the prefixtgt_
.
In the conflict event record, the field docId
is the ID or key of the conflicting document in the replication.
The documents in the conflict collection that have IDs with prefixes src_
and tgt_
are copies of the versions of docId
that were in conflict.
For more information about the Conflict Record Document, see Conflict Record.
Using SQL++ to Query Conflict Logs
The common way to view conflict logs or conflict record documents is by using SQL++.
The following are a few examples.
In the examples, the conflict logs are stored in the collection conflictlogs.bucket1.app1logs
.
SQL++ Examples
For queries, you can create a custom index or a primary index.
-
Create a primary index as follows.
CREATE PRIMARY INDEX `#primary` ON `conflictlogs`.`bucket1`.`app1logs`
-
Create a custom index as follows.
You can use the
WHERE
clause withSUBSTR(META().id,0,3) = “crd”
by making it an indexed field instead ofWHERE META().id LIKE "crd%"
.For example:
CREATE INDEX `idx_crd_timestamp_docId` ON `conflictlogs`.`bucket1`.`applogs`(SUBSTR(META().id,0,3), `timestamp`,`docId`)
-
When creating custom indexes,
META().id
is already a part of the index key. -
When querying, if you use underscore in comparison strings, make sure you use the escape character, so that it’s used as an exact comparison instead of a wildcard comparison. For example,
WHERE META().id LIKE “crd\\_%”
.
-
docId
that had conflicts between 2 time periodsSELECT META().id, b.docId, b.timestamp
FROM `conflictlogs`.`bucket1`.`app1logs` AS b
WHERE META().id LIKE "crd%" and b.timestamp between "2025-06-04T18:15:16.363Z" AND "2025-06-09T07:26:40.326Z"
SELECT count(*) AS num_conflicts
FROM `conflictlogs`.`bucket1`.`app1logs` AS b
WHERE META().id LIKE "crd%" and b.timestamp between "2025-06-04T18:15:16.363Z" AND "2025-06-09T07:26:40.326Z"
docIds
that had conflicts between 2 time periodsSELECT count (distinct b.docId) as num_different_docIds
FROM `conflictlogs`.`bucket1`.`app1logs` AS b
WHERE META().id LIKE "crd%" and b.timestamp between "2025-06-04T18:15:16.363Z" AND "2025-06-09T07:26:40.326Z"
docIds
and the number of times each docId
had a logged conflict between 2 time periodsThe following query may be used if you suspect that the same few documents are being updated multiple times concurrently in two different clusters, generating conflicts.
SELECT b.docId AS DOCID, COUNT(1) AS num_times_seen
FROM `conflictlogs`.`bucket1`.`app1logs` AS b
WHERE META(b).id LIKE "crd%" AND b.timestamp BETWEEN "2025-06-04T18:15:16.363Z" AND "2025-06-09T07:26:40.326Z"
GROUP BY b.docId
ORDER BY num_times_seen DESC LIMIT 20
The query result is as follows:
[ { "DOCID": "4d9d18e7-5fa2-4eae-b32b-60d3d369da94", "num_times_seen": 2 }, { "DOCID": "ae03ecde-203c-4a3b-a6d0-36b60f627c8a", "num_times_seen": 1 } ]
docId
, you can see selected fields from the source and target documents.Also, the docId
of the copies of the source and target documents in the conflict collection, if you want to retrieve the entire document.
The only fields that you want to see from the documents that were in conflict are country
and note
.
SELECT b.docId, b.srcDoc.id AS srcDocId, b.tgtDoc.id AS tgtDocId,
(SELECT b2.country, b2.note
FROM `conflictlogs`.`bucket1`.`app1logs` AS b2 USE KEYS b.srcDoc.id)[0] AS src_doc_fields,
(SELECT b3.country, b3.note
FROM `conflictlogs`.`bucket1`.`app1logs` AS b3 USE KEYS b.tgtDoc.id)[0] AS tgt_doc_fields
FROM `conflictlogs`.`bucket1`.`app1logs` AS b
WHERE META(b).id LIKE "crd%" AND b.timestamp BETWEEN "2025-06-04T18:15:16.363Z" AND "2025-06-09T07:26:40.326Z"
The query result is as follows:
[ { "docId": "ae03ecde-203c-4a3b-a6d0-36b60f627c8a", "srcDocId": "src_1749454000005461807_e2f416ed317beb4f16a3587656b3d3450e1bec893cabebd3b9bb857d63824da4", "tgtDocId": "tgt_1749454000005461807_e2f416ed317beb4f16a3587656b3d3450e1bec893cabebd3b9bb857d63824da4", "src_doc_fields": { "country": "UK", “note”: “need review” }, "tgt_doc_fields": { "country": "CA", “note”: “ok” } } ]
Using Eventing to Process Conflict Logs
If you do not like the 3 documents format of the conflict record, then you can create a single document from the 3 documents (crd_
, src_
, tgt_
) for easier processing by using Eventing.
Here, it’s assumed that the documents are small.
If the documents are large such that the combined 3 documents are larger than the maximum size limit, you can copy only a subset of the fields from the src_
and tgt_
documents to the custom single document.
Another effect of creating your own conflict document as shown in the Eventing example is that the new document that you create will not have the system extended attribute ({"_xdcr_conflict": true}
).
This system extended attribute is what prevents the conflict documents, created by XDCR, from being replicated.
As a result, the new combined conflict log document will have the docId
(or doc key) of your preference, and if needed, it can be replicated.
Eventing example
This is an Eventing function example to copy the conflict log documents into a single new document.
Eventing bucket alias: src
for conflictlogs._default.bucket1logs
with read only access.
Eventing bucket alias: copy
for conflictlogs._default.bucket1logs_copy
with read/write access.
function OnUpdate(doc, meta, xattrs) {
if (meta.id.startsWith("crd_") === true)
{
log("crd doc created/updated", meta.id);
//get the conflict source and target document id's
var suffix = (meta.id).substring(4);
var srcId = "src_" + suffix;
var tgtId = "tgt_" + suffix;
//read the conflicting source and target documents
var src_doc = src[srcId];
var tgt_doc = src[tgtId];
//add the conflicting source and target documents to the crd doc
doc["src_doc"] = src_doc;
doc["tgt_doc"] = tgt_doc;
//copy the resulting new crd doc to a different collection
copy[meta.id] = doc;
}
}
Resultant document example:
See Conflict Record Document Format to understand the document format. |
{ "timestamp": "2025-05-20T19:25:55.308Z", "id": "crd_1747769155308581666_c0468a42632daabce649b1fb7e174a530946c04b1dde75218290c4885ad11669", "docId": "ASHUNK100", "replId": "1bc375a26b7f822ebe91288c58a6c614/bucket1/bucket1-PN8-oFADADCOe6S6A8YuIw==", "srcDoc": { "id": "src_1747769155308581666_c0468a42632daabce649b1fb7e174a530946c04b1dde75218290c4885ad11669", "nodeId": "10.0.13.10", "bucketUUID": "6cd3a35ba2d337d84d4fcd30c3a46fb9", "clusterUUID": "d976db26a5ce512a243db65a3026073f", "isDeleted": false, "collection": "_default", "scope": "_default", "expiry": 0, "flags": 33554438, "cas": 1747769124557291500, "revSeqno": 4, "xattrs": { "_vv": "{\"cvCas\":\"0x00002a1777524118\",\"src\":\"ntR6bcR9VHcjcPpQ5osEiw\",\"ver\":\"0x00002a1777524118\",\"pv\":[\"000018fe76524118@8DdrZBqHaACpmDeYJIv+kg\"]}", "_sync": "", "_mou": "" } }, "tgtDoc": { "id": "tgt_1747769155308581666_c0468a42632daabce649b1fb7e174a530946c04b1dde75218290c4885ad11669", "nodeId": "10.0.22.102", "bucketUUID": "fada12469f1027ecb078d2c19a2802fb", "clusterUUID": "1bc375a26b7f822ebe91288c58a6c614", "isDeleted": false, "collection": "_default", "scope": "_default", "expiry": 0, "flags": 33554438, "cas": 1747769104359030800, "revSeqno": 4, "xattrs": { "_vv": "{\"cvCas\":\"0x000018fe76524118\",\"src\":\"8DdrZBqHaACpmDeYJIv+kg\",\"ver\":\"0x000018fe76524118\"}", "_sync": "", "_mou": "" } }, "src_doc": { "author": "Unknown", "create_date_utc": "2025-05-20T07:56:26", "res_from": "WEST", "res_name": "Harry West", "res_note": "visitor", "reserved": true, "title": "Ashmole History", "type": "book", "unix_time": "1747768801", "update_date_utc": "2025-05-20T19:20:01" }, "tgt_doc": { "author": "Unknown", "create_date_utc": "2025-05-20T07:56:26", "res_from": "EAST", "res_name": "John East", "res_note": "employee", "reserved": true, "title": "Ashmole History", "type": "book", "unix_time": "1747768801", "update_date_utc": "2025-05-20T19:20:01" } }
Using KV Range Scan
To use only KV, use Couchbase SDK with KV APIs including KV Range Scan.
For example, to get a list of all crd
prefix documents (CRD documents) using the KV Range Scan PrefixScan, use KV collection.get
to get the conflicting src
and tgt
docs.
You can also use KV sub-document API lookupIn
to retrieve only the selected fields.
After processing the documents, you can remove the conflict log documents using the KV collection.remove
.
For more information about KV Range Scan operations and data operations, see Data Operations. For more information about data operations on sub-documents, see Sub-Document Operations
KV Range Scan examples
The following example uses Couchbase Python SDK.
from datetime import timedelta
import json
from couchbase.auth import PasswordAuthenticator
from couchbase.cluster import Cluster
from couchbase.collection import (GetOptions, RemoveOptions)
from couchbase.collection import DeltaValue, SignedInt64
from couchbase.exceptions import (
CouchbaseException,
PathNotFoundException,
SubdocPathMismatchException)
from couchbase.options import ScanOptions
from couchbase.kv_range_scan import PrefixScan
import couchbase.subdocument as SD
# connection info
connection_string = "couchbase://localhost"
username = "username1"
password = "Some!password"
# conflict logs collection location
bucket_name = "conflictlogs"
scope_name = "bucket1"
collection_name = "app1logs"
cluster = Cluster(
connection_string,
authenticator=PasswordAuthenticator(username, password));
cluster.wait_until_ready(timedelta(seconds=5))
cb = cluster.bucket(bucket_name);
logs_coll = cb.scope(scope_name).collection(collection_name)
# Using KV PrefixScan to get list of crd doc ids
# Get crd document ids only
crd_list = logs_coll.scan(PrefixScan('crd'), ScanOptions(ids_only=True))
# For each crd document, get the docId, timestamp, srcDoc.id, tgtDoc.id
# Then, get the src and tgt docs from the conflict log collection
for crd in crd_list:
crdlookup = logs_coll.lookup_in(crd.id,
[ SD.get("docId"),
SD.get("timestamp"),
SD.get("srcDoc.id"),
SD.get("tgtDoc.id") ])
doc_id = crdlookup.content_as[str](0)
timestamp = crdlookup.content_as[str](1)
src_doc_id = crdlookup.content_as[str](2)
tgt_doc_id = crdlookup.content_as[str](3)
# Get the src and tgt documents in conflict
src_doc = logs_coll.get(src_doc_id);
tgt_doc = logs_coll.get(tgt_doc_id);
# Print the info as json
output = { "docId": doc_id, "log_timestamp": timestamp,
"src_doc": src_doc.content_as[dict],
"tgt_doc": tgt_doc.content_as[dict] }
print (json.dumps(output))