0

The data in mongodb is like this

{
    "_id": "1",
    "a": 1,
    "b": 2,
    "c": 3
    "d": 4
}

I can update the document using a single "$set" operator.

db.collection.update({"_id": "1"}, {
        "$set": {
            "a": 100,
            "b": 200,
            "c": 300,
            "d": 400
        }
    })

I can also update the document using multiple "$set" (each field has a "$set").

db.collection.update({"_id": "1"}, [
        {
            "$set": { "a": 100 }
        },
        {
            "$set": { "b": 200 }
        },
        {
            "$set": { "c": 300 }
        },
        {
            "$set": { "d": 400 }
        }
    ])

I would like to know if there are any performance concerns on the mongodb end by using the "multi-set" version. Is it Ok to use the "multi-set" version if, for some cases, I need to.

2
  • 3
    lesser the number of stages in pipeline, the faster i guess. but im not entirely sure if its the case here as well. db.collection.update({ "_id": "1" }, [ { "$set": { "a": 100, "b": 200, "c": 300, "d": 400 } } ]) also an alternative way using the aggregation pipeline in a single stage Commented Dec 15, 2024 at 9:48
  • 2
    Why do you need this? The commands are regular JSON objects, you can compose them dynamically to one stage. Commented Dec 15, 2024 at 12:09

2 Answers 2

1

In MongoDB official doc, there is a pipeline optimization for multiple $match operators.

When a $match immediately follows another $match, the two stages can coalesce into a single $match combining the conditions with an $and.

For your case, I think there will be a trivial performance difference, if not none. The explain plans for them are nearly identical.

explain output for single $set:

{
  "$clusterTime": {
    "clusterTime": Timestamp(1734276869, 3),
    "signature": {
      "hash": BinData(0, "tRv1PQVgmouDxOtttOdSk2z0O1U="),
      "keyId": NumberLong(7394893876424605697)
    }
  },
  "command": {
    "$db": "75f38391413d12711811639fd9b97e4f",
    "filter": {},
    "find": "collection",
    "maxTimeMS": NumberLong(20000)
  },
  "executionStats": {
    "allPlansExecution": [],
    "executionStages": {
      "advanced": 1,
      "direction": "forward",
      "docsExamined": 1,
      "executionTimeMillisEstimate": 0,
      "isEOF": 1,
      "nReturned": 1,
      "needTime": 0,
      "needYield": 0,
      "restoreState": 0,
      "saveState": 0,
      "stage": "COLLSCAN",
      "works": 2
    },
    "executionSuccess": true,
    "executionTimeMillis": 0,
    "nReturned": 1,
    "totalDocsExamined": 1,
    "totalKeysExamined": 0
  },
  "explainVersion": "1",
  "operationTime": Timestamp(1734276869, 3),
  "queryPlanner": {
    "indexFilterSet": false,
    "maxIndexedAndSolutionsReached": false,
    "maxIndexedOrSolutionsReached": false,
    "maxScansToExplodeReached": false,
    "namespace": "75f38391413d12711811639fd9b97e4f.collection",
    "parsedQuery": {},
    "planCacheKey": "5F5FC979",
    "queryHash": "5F5FC979",
    "rejectedPlans": [],
    "winningPlan": {
      "direction": "forward",
      "stage": "COLLSCAN"
    }
  },
  "serverParameters": {
    "internalDocumentSourceGroupMaxMemoryBytes": 104857600,
    "internalDocumentSourceSetWindowFieldsMaxMemoryBytes": 104857600,
    "internalLookupStageIntermediateDocumentMaxSizeBytes": 104857600,
    "internalQueryFacetBufferSizeBytes": 104857600,
    "internalQueryFacetMaxOutputDocSizeBytes": 104857600,
    "internalQueryMaxAddToSetBytes": 104857600,
    "internalQueryMaxBlockingSortMemoryUsageBytes": 104857600,
    "internalQueryProhibitBlockingMergeOnMongoS": 0
  }
}

Mongo Playground

explain output for multiple $set:

{
  "$clusterTime": {
    "clusterTime": Timestamp(1734276224, 3),
    "signature": {
      "hash": BinData(0, "QUsSlU1zBAvtbFAgbv3/fA7RENU="),
      "keyId": NumberLong(7394893876424605697)
    }
  },
  "command": {
    "$db": "3d58f286ab6b7455181163098d794665",
    "filter": {},
    "find": "collection",
    "maxTimeMS": NumberLong(20000)
  },
  "executionStats": {
    "allPlansExecution": [],
    "executionStages": {
      "advanced": 1,
      "direction": "forward",
      "docsExamined": 1,
      "executionTimeMillisEstimate": 0,
      "isEOF": 1,
      "nReturned": 1,
      "needTime": 0,
      "needYield": 0,
      "restoreState": 0,
      "saveState": 0,
      "stage": "COLLSCAN",
      "works": 2
    },
    "executionSuccess": true,
    "executionTimeMillis": 0,
    "nReturned": 1,
    "totalDocsExamined": 1,
    "totalKeysExamined": 0
  },
  "explainVersion": "1",
  "operationTime": Timestamp(1734276224, 3),
  "queryPlanner": {
    "indexFilterSet": false,
    "maxIndexedAndSolutionsReached": false,
    "maxIndexedOrSolutionsReached": false,
    "maxScansToExplodeReached": false,
    "namespace": "3d58f286ab6b7455181163098d794665.collection",
    "parsedQuery": {},
    "planCacheKey": "5F5FC979",
    "queryHash": "5F5FC979",
    "rejectedPlans": [],
    "winningPlan": {
      "direction": "forward",
      "stage": "COLLSCAN"
    }
  },
  "serverParameters": {
    "internalDocumentSourceGroupMaxMemoryBytes": 104857600,
    "internalDocumentSourceSetWindowFieldsMaxMemoryBytes": 104857600,
    "internalLookupStageIntermediateDocumentMaxSizeBytes": 104857600,
    "internalQueryFacetBufferSizeBytes": 104857600,
    "internalQueryFacetMaxOutputDocSizeBytes": 104857600,
    "internalQueryMaxAddToSetBytes": 104857600,
    "internalQueryMaxBlockingSortMemoryUsageBytes": 104857600,
    "internalQueryProhibitBlockingMergeOnMongoS": 0
  }
}

Mongo Playground

Given your query pattern in the example on updating on only 1 document and you are fetching by _id, I don't think there will be any observable performance difference.

There are 2 caveats outside of performance though:

  1. If in your real use case, you have a dependency on the $set operations, you need to break them into different stages

e.g. you $set a to be c * d = 12, and b to be a + 1 = 12 + 1 = 13. You will need to do:

db.collection.update({
  "_id": "1"
},
[
  {
    "$set": {
      "a": {
        "$multiply": [
          "$c",
          "$d"
        ]
      }
    }
  },
  {
    "$set": {
      "b": {
        "$add": [
          "$a",
          1
        ]
      }
    }
  }
])

Mongo Playground

A single $set won't give you expected result of b = 13, but 2 because a is yet to be evaluated as 12(keeping 1 as value)

db.collection.update({
  "_id": "1"
},
[
  {
    "$set": {
      "a": {
        "$multiply": [
          "$c",
          "$d"
        ]
      },
      "b": {
        "$add": [
          "$a",
          1
        ]
      }
    }
  }
])

Mongo Playground

  1. readablility: In real-life and more complex pipelines, you may find breaking up $set statement more readable or manageable. But I will admit this is quite subjective and you may consider the other way round.
Sign up to request clarification or add additional context in comments.

4 Comments

I assume, when your documents are very big and you like to update many documents, then the performance difference becomes perceptible.
@WernfriedDomscheit likely it will be bound by the 16MB document size limit..? Either way, I think this would be a good exercise left for OP to figure out.
Thanks @ray. (that would be a different story if there is dependency between each "$set". Fortunately, it is not my case). My question is actually asking about the difference between letting the mongodb merge multiple "$set" documents and letting the application merge them into a single "$set". (In my use case, each "$set" is similar to what I posted in the question, except that some of the values are a complex dictionary). Based on your answer, seems there is no difference.
@Bruce I just found out that there is an optimization steps for your case in the offical doc. I have updated the answer and please check it out.
-1

Why do you think, someone "needs to do so"? The operators are JSON objects, you can create an update for example like this:

let values = { a: 100 };
values.b = 200;
values["c"] = 300;
values = Object.assign(values, { d: 400 });

db.collection.update({"_id": "1"}, { $set: values })

//or 
const operation = { $set: values };
db.collection.update({"_id": "1"}, operation)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.