You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have searched existing issues to ensure the bug has not already been reported
Mongoose version
8.2.3
Node.js version
20.x
MongoDB server version
Atlas 7.x
Typescript version (if applicable)
5.2.2
Description
I didn't want to hijack #14268, but it's probably related, even though our scenario is even simpler, and potentially quite common.
We just discovered this in PROD because our data wasn't updated despite a seemingly successful update in case of transaction errors. Unfortunately, Atlas is hitting us with these retriable transaction errors on a regular basis, so this is a common scenario for us.
Basically, what we're doing on a failed update is a simple rollback (if possible), create a new session and run the update again: new session -> doc.save() -> rollback -> new session -> doc.save() -> commit (doesn't update anything)
Here's my minimal repro with just one document (not) being updated:
exportasyncfunctionrunTest(){constChildSchema=newmongoose.Schema({foo: Number});constChildModel=mongoose.model("Child",ChildSchema);awaitChildModel.deleteMany({});// create new entity// note: fetching as part of the transaction is not viable - those are complex business flows that take too long, and fetching takes place elsewhereconstchild=awaitChildModel.create({foo: 1})// modify the entitychild.foo=2;// start transaction #1letsession=awaitmongoose.startSession();awaitsession.startTransaction();// trigger the updateawaitchild.save({session});// abort transaction #1awaitsession.abortTransaction();awaitsession.endSession()// change tracking is goneconsole.log("modified paths",child.modifiedPaths());// []// retry with transaction #2session=awaitmongoose.startSession();awaitsession.startTransaction();// trigger the update (doesn't do anything since change tracking wasn't rolled back)awaitchild.save({session});// commit the transactionawaitsession.commitTransaction();awaitsession.endSession()// fetch a fresh instance - won't reflect the changes.constfreshCopy=awaitChildModel.findOne({});console.log(freshCopy.foo);// 1}
If we commit without retries, everything works fine. Also, as a dirty hack in this artificial test, i could just invoke child.markModified("foo"); before running save() again, but that's not applicable for us.
I guess we could also cache the modified paths for each entity, and restore them after a rollback. But that also sounds like a brittle workaround for something I would expect from the framework.
@vkarpov15 mentioned the transaction helper, but this is not an option for us, as we are using document.save, and are passing around the wrapped session object across repositories and complex business flows. But if transaction helper can properly restore tracking states, is there anything we could invoke in case of a transaction error in order to restore the tracked changes?
Also, I wasn't sure whether this is also related of #13973 that was fixed in 7.x?
Steps to Reproduce
change a field
start a transaction
run a doc.save()
roll back the transaction
start a new transaction
run a doc.save()
commit the new transaction
-> change is not persisted
Expected Behavior
Change tracking should reset if a transaction fails or is being rolled back in order to enable retries, which are a common MongoDB necessity.
Temporary Workaround
I implemented a simple helper that integrates with our transaction wrapper. This seems to solve the issue for us for now, but I only see that as a temporary solution. Given how simple it is, I hope this can be solved on the framework level.
import{IEntity}from"./Entities/IEntity";import{Document}from"mongoose";import{ArrayUtil}from"../Utils/ArrayUtil";/** * Tracks modifications on updated entities in order to restore * change tracking after transaction rollbacks. * This is a workaround for https://github.com/Automattic/mongoose/issues/14460 */exportclassEntityChangeTracker{privatechangeMap=newMap<string,{entity: IEntity&Document,modifiedPaths: string[]}>();/** * Registers a set of entities and caches their tracked changes for * restoration in case they get lost during a transaction rollback. */trackChanges(entities: (IEntity&Document)[]){entities.forEach(entity=>{this.changeMap.set(entity.id,{ entity,modifiedPaths: entity.modifiedPaths({includeChildren: true})});});}/** * Restores cached tracked changes for each registered entity. */restoreTrackedChanges(){for(consttrackedEntityofArrayUtil.fromMap(this.changeMap)){const{ entity, modifiedPaths }=trackedEntity;modifiedPaths.forEach(c=>entity.markModified(c));}}}
The text was updated successfully, but these errors were encountered:
Prerequisites
Mongoose version
8.2.3
Node.js version
20.x
MongoDB server version
Atlas 7.x
Typescript version (if applicable)
5.2.2
Description
I didn't want to hijack #14268, but it's probably related, even though our scenario is even simpler, and potentially quite common.
We just discovered this in PROD because our data wasn't updated despite a seemingly successful update in case of transaction errors. Unfortunately, Atlas is hitting us with these retriable transaction errors on a regular basis, so this is a common scenario for us.
Basically, what we're doing on a failed update is a simple rollback (if possible), create a new session and run the update again:
new session -> doc.save() -> rollback -> new session -> doc.save() -> commit (doesn't update anything)
Here's my minimal repro with just one document (not) being updated:
If we commit without retries, everything works fine. Also, as a dirty hack in this artificial test, i could just invoke
child.markModified("foo");
before runningsave()
again, but that's not applicable for us.I guess we could also cache the modified paths for each entity, and restore them after a rollback. But that also sounds like a brittle workaround for something I would expect from the framework.
@vkarpov15 mentioned the transaction helper, but this is not an option for us, as we are using
document.save
, and are passing around the wrappedsession
object across repositories and complex business flows. But if transaction helper can properly restore tracking states, is there anything we could invoke in case of a transaction error in order to restore the tracked changes?Also, I wasn't sure whether this is also related of #13973 that was fixed in 7.x?
Steps to Reproduce
change a field
start a transaction
run a doc.save()
roll back the transaction
start a new transaction
run a doc.save()
commit the new transaction
-> change is not persisted
Expected Behavior
Change tracking should reset if a transaction fails or is being rolled back in order to enable retries, which are a common MongoDB necessity.
Temporary Workaround
I implemented a simple helper that integrates with our transaction wrapper. This seems to solve the issue for us for now, but I only see that as a temporary solution. Given how simple it is, I hope this can be solved on the framework level.
The text was updated successfully, but these errors were encountered: