oxide icon indicating copy to clipboard operation
oxide copied to clipboard

Wrap batch updates on a transaction

Open fcoury opened this issue 3 years ago • 3 comments

When you send an update command with multiple clauses, it should be atomic and if one of the updates don't work, all updates should be rolled back.

fcoury avatar Jul 16 '22 18:07 fcoury

Apparently mongodb doesn't work this way -- seems like the updates are atomic per document. You can see that the update to the last item of this collection wasn't applied at all, the name was not changed, even though the error was on the counter field.

test> db.trx.insertMany([
  {x: 1, name: 'John', counter: 1}, 
  {x: 2, name: 'Alex', counter: 2}, 
  {x: 3, name: 'Kate', counter: 'Str'}
])
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId("62d304384ca23db6dd87d2c4"),
    '1': ObjectId("62d304384ca23db6dd87d2c5"),
    '2': ObjectId("62d304384ca23db6dd87d2c6")
  }
}
test> db.trx.updateMany({}, { $set: {name: 'Old'}, $inc: {counter: 1} })
MongoServerError: Cannot apply $inc to a value of non-numeric type. {_id: ObjectId('62d304384ca23db6dd87d2c6')} has the field 'counter' of non-numeric type string
test> db.trx.find()
[
  {
    _id: ObjectId("62d304384ca23db6dd87d2c4"),
    x: 1,
    name: 'Old',
    counter: 2
  },
  {
    _id: ObjectId("62d304384ca23db6dd87d2c5"),
    x: 2,
    name: 'Old',
    counter: 3
  },
  {
    _id: ObjectId("62d304384ca23db6dd87d2c6"),
    x: 3,
    name: 'Kate',
    counter: 'Str'
  }
]
test> 

fcoury avatar Jul 16 '22 18:07 fcoury

We might need to have a streaming update:

  1. retrieve all the matching rows for the query part of the update
  2. get next row
  3. run all the updates for the row wrapped on a transaction
  4. if an error occurs rollback and continue

fcoury avatar Jul 16 '22 18:07 fcoury

OK, this keeps getting more interesting:

test> db.trx.insertMany([
  {x: 1, name: 'John', counter: 1}, 
  {x: 2, name: 'Alex', counter: 'Str'}, 
  {x: 3, name: 'Kate', counter: 3}
])
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId("62d305c44ca23db6dd87d2c7"),
    '1': ObjectId("62d305c44ca23db6dd87d2c8"),
    '2': ObjectId("62d305c44ca23db6dd87d2c9")
  }
}
test> db.trx.updateMany({}, { $set: {name: 'Old'}, $inc: {counter: 1} })
MongoServerError: Cannot apply $inc to a value of non-numeric type. {_id: ObjectId('62d305c44ca23db6dd87d2c8')} has the field 'counter' of non-numeric type string
test> db.trx.find()
[
  {
    _id: ObjectId("62d305c44ca23db6dd87d2c7"),
    x: 1,
    name: 'Old',
    counter: 2
  },
  {
    _id: ObjectId("62d305c44ca23db6dd87d2c8"),
    x: 2,
    name: 'Alex',
    counter: 'Str'
  },
  {
    _id: ObjectId("62d305c44ca23db6dd87d2c9"),
    x: 3,
    name: 'Kate',
    counter: 3
  }
]

So, here's how this should take place:

  1. retrieve all the matching rows for the query part of the update
  2. get next row
  3. run all the updates for the row wrapped on a transaction
  4. if an error occurs rollback and abort further updates

fcoury avatar Jul 16 '22 18:07 fcoury