Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-81028

Incorrect $listCatalog behavior in presence of a concurrent collection rename in v7.0

    XMLWordPrintableJSON

Details

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: Major - P3 Major - P3
    • 7.1.1, 7.2.0-rc0, 7.0.4, 6.0.13
    • 7.0.0
    • None
    • None
    • Storage Execution EMEA
    • Fully Compatible
    • ALL
    • v7.1, v7.0, v6.0, v5.0, v4.4
    • Hide

      To reproduce outside of Mongosync:

      • Launch and connect to v7.0 replica set (I used mlaunch)

      m 7.0.0
      mlaunch --replicaset --nodes 3
      mongosh mongodb://localhost:27017,localhost:27018,localhost:27019

      - You will need 2 mongosh for this repro.

      • Create collection and index

      use test
      db.createCollection("coll")
      db.coll.createIndex({x:1})

      • On mongosh 1, run the aggregation pipeline to retrieve the index in an infinite loop (replace collectionUUID with actual value)

      stages1 = [{ "$group": Unknown macro: { "_id"}} }, { "$unwind":{ "path": "$catalogEntry" }}, { "$unwind":{ "path": "$catalogEntry.md.indexes" }}, { "$unset": "catalogEntry.md.indexes.spec.ns" }, { "$match":{ "catalogEntry.md.indexes.ready": true }}, { "$set": { "catalogEntry.md.indexes.spec.sparse": { "$cond": { "if":Unknown macro: { "$eq"}, "then": "$$REMOVE", "else": { "$toBool": "$catalogEntry.md.indexes.spec.sparse" } } }, "catalogEntry.md.indexes.spec.expireAfterSeconds": { "$cond": { "if":Unknown macro: { "$eq"}, "then": "$$REMOVE", "else": { "$toInt": "$catalogEntry.md.indexes.spec.expireAfterSeconds" } } }, "catalogEntry.md.indexes.spec.bits": { "$cond": { "if":Unknown macro: { "$eq"}, "then": "$$REMOVE", "else": { "$toInt": "$catalogEntry.md.indexes.spec.bits" } } } } }, { "$group": { "_id": "$catalogEntry.md.indexes.spec.name", "shards":{ "$push": "$catalogEntry.shard" }, "specs": { "$push": { "$objectToArray": { "$ifNull": [ "$catalogEntry.md.indexes.spec", {}] } } }, "allShards": { "$first": "$allShards" } } }]
      

      stages2 = { "$project": { "numShards":{ "$size": "$allShards" }, "missingFromShards": { "$setDifference": [ "$allShards", "$shards"] }, "spec": { "$arrayToObject":{ "$first": "$specs" }}, "inconsistentOptions": { "$setDifference": [ { "$reduce": { "input": "$specs", "initialValue":{ "$arrayElemAt": [ "$specs", 0] }, "in": { "$setUnion": [ "$$value", "$$this"] } } }, { "$reduce": { "input": "$specs", "initialValue":{ "$arrayElemAt": [ "$specs", 0] }, "in": { "$setIntersection": [ "$$value", "$$this"] } } }] } } }
      

      res = db.runCommand({"aggregate": "coll", "collectionUUID": new UUID("85c8ab7b-d107-4768-913e-19f0b107eb2a"), "readConcern": {"level": "majority", "afterClusterTime": Timestamp({ t: 1693905823, i: 20 })}, cursor: {}, pipeline: [\{"$listCatalog": {}}].concat(stages1).concat([\{"$match": {_id: "x_1"} }]).concat([stages2])}).cursor.firstBatch.length
      

      while (res) { res = db.runCommand({"aggregate": "coll", "collectionUUID": new UUID("85c8ab7b-d107-4768-913e-19f0b107eb2a"), "readConcern": {"level": "majority", "afterClusterTime": Timestamp({ t: 1693905823, i: 20 })}, cursor: {}, pipeline: [\{"$listCatalog": {}}].concat(stages1).concat([\{"$match": {_id: "x_1"} }]).concat([stages2])}).cursor.firstBatch.length }
      

       - On mongosh 2, rename the collection

      use test
      db.coll.renameCollection("coll2")

      The infinite loop in mongosh 1 will exit. In v6.0.8, the infinite loop exits with a CollectionUUIDMismatch error as expected, but v.7.0.0, the infinite loop exits because the server returns an empty list of index.

       

      Show
      To reproduce outside of Mongosync: Launch and connect to v7.0 replica set (I used mlaunch) m 7.0 . 0 mlaunch --replicaset --nodes 3 mongosh mongodb: //localhost:27017,localhost:27018,localhost:27019 - You will need 2 mongosh for this repro. Create collection and index use test db.createCollection( "coll" ) db.coll.createIndex({x: 1 }) On mongosh 1, run the aggregation pipeline to retrieve the index in an infinite loop (replace collectionUUID with actual value) stages1 = [{ "$group" : Unknown macro: { "_id" }} }, { "$unwind" :{ "path" : "$catalogEntry" }}, { "$unwind" :{ "path" : "$catalogEntry.md.indexes" }}, { "$unset" : "catalogEntry.md.indexes.spec.ns" }, { "$match" :{ "catalogEntry.md.indexes.ready" : true }}, { "$set" : { "catalogEntry.md.indexes.spec.sparse" : { "$cond" : { "if" :Unknown macro: { "$eq" }, "then" : "$$REMOVE" , "else" : { "$toBool" : "$catalogEntry.md.indexes.spec.sparse" } } }, "catalogEntry.md.indexes.spec.expireAfterSeconds" : { "$cond" : { "if" :Unknown macro: { "$eq" }, "then" : "$$REMOVE" , "else" : { "$toInt" : "$catalogEntry.md.indexes.spec.expireAfterSeconds" } } }, "catalogEntry.md.indexes.spec.bits" : { "$cond" : { "if" :Unknown macro: { "$eq" }, "then" : "$$REMOVE" , "else" : { "$toInt" : "$catalogEntry.md.indexes.spec.bits" } } } } }, { "$group" : { "_id" : "$catalogEntry.md.indexes.spec.name" , "shards" :{ "$push" : "$catalogEntry.shard" }, "specs" : { "$push" : { "$objectToArray" : { "$ifNull" : [ "$catalogEntry.md.indexes.spec" , {}] } } }, "allShards" : { "$first" : "$allShards" } } }] stages2 = { "$project" : { "numShards" :{ "$size" : "$allShards" }, "missingFromShards" : { "$setDifference" : [ "$allShards" , "$shards" ] }, "spec" : { "$arrayToObject" :{ "$first" : "$specs" }}, "inconsistentOptions" : { "$setDifference" : [ { "$reduce" : { "input" : "$specs" , "initialValue" :{ "$arrayElemAt" : [ "$specs" , 0 ] }, "in" : { "$setUnion" : [ "$$value" , "$$this" ] } } }, { "$reduce" : { "input" : "$specs" , "initialValue" :{ "$arrayElemAt" : [ "$specs" , 0 ] }, "in" : { "$setIntersection" : [ "$$value" , "$$this" ] } } }] } } } res = db.runCommand({ "aggregate" : "coll" , "collectionUUID" : new UUID( "85c8ab7b-d107-4768-913e-19f0b107eb2a" ), "readConcern" : { "level" : "majority" , "afterClusterTime" : Timestamp({ t: 1693905823 , i: 20 })}, cursor: {}, pipeline: [\{ "$listCatalog" : {}}].concat(stages1).concat([\{ "$match" : {_id: "x_1" } }]).concat([stages2])}).cursor.firstBatch.length while (res) { res = db.runCommand({ "aggregate" : "coll" , "collectionUUID" : new UUID( "85c8ab7b-d107-4768-913e-19f0b107eb2a" ), "readConcern" : { "level" : "majority" , "afterClusterTime" : Timestamp({ t: 1693905823 , i: 20 })}, cursor: {}, pipeline: [\{ "$listCatalog" : {}}].concat(stages1).concat([\{ "$match" : {_id: "x_1" } }]).concat([stages2])}).cursor.firstBatch.length }  - On mongosh 2, rename the collection use test db.coll.renameCollection( "coll2" ) The infinite loop in mongosh 1 will exit. In v6.0.8, the infinite loop exits with a CollectionUUIDMismatch error as expected, but v.7.0.0, the infinite loop exits because the server returns an empty list of index.  
    • Execution EMEA Team 2023-10-16, Execution EMEA Team 2023-10-30

    Description

      Mongosync uses $listCatalog to list indexes belonging to collections as follows:

      stages1 = [{ "$group": { "_id": null, "catalogEntry": { "$push": "$$ROOT" }, "allShards": { "$addToSet": "$shard" } } }, { "$unwind": { "path": "$catalogEntry" } }, { "$unwind": { "path": "$catalogEntry.md.indexes" } }, { "$unset": "catalogEntry.md.indexes.spec.ns" }, { "$match": { "catalogEntry.md.indexes.ready": true } }, { "$set": { "catalogEntry.md.indexes.spec.sparse": { "$cond": { "if": { "$eq": ["missing", { "$type": "$catalogEntry.md.indexes.spec.sparse" }] }, "then": "$$REMOVE", "else": { "$toBool": "$catalogEntry.md.indexes.spec.sparse" } } }, "catalogEntry.md.indexes.spec.expireAfterSeconds": { "$cond": { "if": { "$eq": ["missing", { "$type": "$catalogEntry.md.indexes.spec.expireAfterSeconds" }] }, "then": "$$REMOVE", "else": { "$toInt": "$catalogEntry.md.indexes.spec.expireAfterSeconds" } } }, "catalogEntry.md.indexes.spec.bits": { "$cond": { "if": { "$eq": ["missing", { "$type": "$catalogEntry.md.indexes.spec.bits" }] }, "then": "$$REMOVE", "else": { "$toInt": "$catalogEntry.md.indexes.spec.bits" } } } } }, { "$group": { "_id": "$catalogEntry.md.indexes.spec.name", "shards": { "$push": "$catalogEntry.shard" }, "specs": { "$push": { "$objectToArray": { "$ifNull": [ "$catalogEntry.md.indexes.spec", {}] } } }, "allShards": { "$first": "$allShards" } } }]

      stages2 = { "$project": { "numShards": { "$size": "$allShards" }, "missingFromShards": { "$setDifference": [ "$allShards", "$shards"] }, "spec": { "$arrayToObject": { "$first": "$specs" } }, "inconsistentOptions": { "$setDifference": [ { "$reduce": { "input": "$specs", "initialValue": { "$arrayElemAt": [ "$specs", 0] }, "in": { "$setUnion": [ "$$value", "$$this"] } } }, { "$reduce": { "input": "$specs", "initialValue": { "$arrayElemAt": [ "$specs", 0] }, "in": { "$setIntersection": [ "$$value", "$$this"] } } }] } } }res = 

      db.runCommand({"aggregate": "coll", "collectionUUID": new UUID("85c8ab7b-d107-4768-913e-19f0b107eb2a"), "readConcern": {"level": "majority", "afterClusterTime": Timestamp({ t: 1693905823, i: 20 })}, cursor: {}, pipeline: [{"$listCatalog": {}}].concat(stages1).concat([{"$match": {_id: "x_1"} }]).concat([stages2])})

      If the collection gets renamed while the aggregation pipeline is running, Mongosync expects to get a CollectionUUIDMismatch error for the server. That is the behavior for v6.0.8.

      In v7.0, the result of the aggregation pipeline is actually empty, which causes Mongosync to incorrectly believe that there is no index on the collection.

      Attachments

        Activity

          People

            jordi.olivares-provencio@mongodb.com Jordi Olivares Provencio
            craven.huynh@mongodb.com Craven Huynh
            Votes:
            0 Vote for this issue
            Watchers:
            8 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: