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 prefix crd_.

    • A copy of the source document in the CRD srcDoc.id with the prefix src_.

    • A copy of the target document in the CRD tgtDoc.id with the prefix tgt_.

    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 with SUBSTR(META().id,0,3) = “crd” by making it an indexed field instead of WHERE 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\\_%”.

    Query example 1: List all docId that had conflicts between 2 time periods
    SELECT 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"
    Query example 2: Number of conflicts logged between 2 time periods
    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"
    Query example 3: Number of different docIds that had conflicts between 2 time periods
    SELECT 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"
    Query example 4: List all docIds and the number of times each docId had a logged conflict between 2 time periods

    The 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
      }
    ]
    Query example 5: In the following example, for each 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))