Reputation: 719
Environment: Windows, mongodb 5.0, single node replicaset.
in mongod.cfg
setParameter:
maxTransactionLockRequestTimeoutMillis: 3000
Create DB and collection test with one object:
objId = ObjectId("613233675a5ea6722c960051");
db.test.insert({_id: objId, item: "A"});
Open 2 mongo shell windows to create 2 concurrent transactions: Window 1:
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll = session.getDatabase(dbName).getCollection("test");
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
coll.updateOne({_id: objId}, { $set: {item: "B"}})
Window 2:
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll = session.getDatabase(dbName).getCollection("test");
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
coll.updateOne({_id: objId}, { $set: {item: "C"}})
Second update fails immediately without waiting for 3 seconds:
WriteCommandError({
"errorLabels" : [
"TransientTransactionError"
],
"ok" : 0,
"errmsg" : "WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.",
"code" : 112,
"codeName" : "WriteConflict",
"$clusterTime" : {
"clusterTime" : Timestamp(1632429457, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1632429457, 1)
})
Am I doing something wrong? Is it a bug in mongodb?
Upvotes: 0
Views: 3185
Reputation: 6046
That is not how maxTransactionLockRequestTimeoutMillis
works, your problem is related to write conflicts and nothing else. Before trying to acquire a lock, MongoDB checks whether other transactions are updating the target document. If it is the case, then it does not need to ask for a lock on the document because it knows in advance that eventually, when both transactions commit, a write conflict is going to occur.
How to solve a write conflict? The transaction is aborted and retried (in case of retryable writes) hoping that it can now acquire a snapshot that includes the changes made by the first transaction, that must have commit in the meanwhile.
So, how to solve it? You really cannot because the snapshot isolation is an optimistic MVCC strategy. If your transactions are going to conflict with each other and you know it, the only thing you can do is to run them sequentially. The general rule is: you cannot have two ongoing transactions that are trying to change the same document.
About the comment:
It is not directly related to original scenario, but if in the window 2 I execute update WITHOUT transaction, it blocks and waits, probably indefinitely for transaction in window 1 to complete.
This is a different type of conflict, called transaction/operation conflict. In this case the non-transactional operation cannot update the document because it is locked by the transaction until it commits. The strategy used by MongoDB here is to retry the non-transactional operation with exponential backoff logic until the MaxTimeMS
timeout elapsed (if set, otherwise there is an internal timeout that for the NodeJS driver is set to 2 minutes for example, but it is an implementation detail of course).
These topics are quite complex, if you want to learn more about them, my advice is to read this paragraph.
Upvotes: 2