Details
-
Question
-
Resolution: Done
-
Major - P3
-
None
-
4.0.2
-
None
Description
First of all, sorry for my english.
I'm trying to implement a simple example of ACID transaction in MongoDB, using NodeJS driver. I also use Mongoose. I'm just experimenting with transaction to understand how it works and to foresee and prevent all possible errors.
I have a collection 'testcollection', and i have a single document in it, like:
var testcollection = new mongoose.Schema({ |
id: Number,
|
name: String,
|
number: Number
|
});
|
This is code:
var mongoDB = require('./libs/db'); |
var _ = require('underscore'); |
|
|
mongoDB.connect("mongodb://testuser:name32@localhost:27018/testbase"); |
|
|
var testcoll = require('./libs/db/models/testcollection'); |
|
|
var _session = mongoose.startSession({readPreference: {mode: "primary"}}); |
(async () => _session = await _session)(); // wait for ClientSession |
|
|
async function transactionOperations(id, session){
|
|
|
async function transactionOperations(id, session){ await testcoll.updateOne({name: "ilya", id: 1}, {$inc: {number: 2}}).session(session); // change document as transaction !!! |
|
|
await testcoll.updateOne({name: "ilya", id: 1}, {$inc: {number: 3}}); |
// this is not transaction!!!
|
}
|
|
|
var performTransaction = function(id){
|
async function commit() {
|
try { |
await _session.commitTransaction();
|
} catch (error) { |
if (error.errorLabels && error.errorLabels.indexOf('UnknownTransactionCommitResult') >= 0) { |
console.warn('UnknownTransactionCommitResult, retrying commit operation.', error); |
await commit();
|
} else { |
console.error('Transaction aborted. Caught exception during transaction.', error); |
}
|
}
|
}
|
|
async function runTransactionWithRetry(txnFunc, _transactionOperations) {
|
try { |
await txnFunc(_transactionOperations);
|
} catch (error) { |
if (error.errorLabels && error.errorLabels.indexOf('TransientTransactionError') >= 0) { |
console.warn('TransientTransactionError, retrying transaction.', error); |
await _session.abortTransaction();
|
await runTransactionWithRetry(txnFunc, _transactionOperations);
|
} else { |
if(_session.inTransaction()){ |
await runTransactionWithRetry(txnFunc, _transactionOperations);
|
} else { |
await _session.abortTransaction();
|
console.error('Transaction aborted. Caught exception during transaction.', error); |
}
|
}
|
}
|
}
|
|
|
async function createTransaction(_transactionOperations) {
|
// wait for ClientSession |
_session = await _session;
|
console.log("START " + id + " TRANSACTION"); |
_session.startTransaction({
|
readConcern: { level: 'snapshot' }, |
writeConcern: { w: 'majority',} |
});
|
// ----- all transaction operations here ----- |
await _transactionOperations(id, _session);
|
// ----- all transaction operations here ----- |
|
|
try { |
await commit();
|
} catch (error) { |
await _session.abortTransaction();
|
console.error('Transaction aborted. Caught exception during transaction.', error); |
}
|
} runTransactionWithRetry(createTransaction, transactionOperations);
|
}
|
In my transaction i'm trying to simulate the situation, when at first i change document as a transaction (increment field 'number' +2), and than change it not as a transaction (increment same field +3 in same doc, but i don't pass session parameter in query function). So it must cuase some write conflict, and throw some error.
When i running this transaction, it waits for a 60 seconds (as i understood, max txn time), and than throw an error:
errorLabels: [ 'TransientTransactionError' ], |
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1540719109 }, |
ok: 0, |
errmsg: 'Transaction 1 has been aborted.', |
code: 251, |
codeName: 'NoSuchTransaction' |
|
It seems, that transaction runs, can't perform update operation because of write conflict and than just waiting for txn timeout. If i'm not mistaking, this is a specific write-conflict error, so why it wait 60 seconds and throws 'NoSuchTransaction' MongoError?
BUT!!! Somitimes (for example, when i add some console.log-s in transaction body) it throws another error, such as:
errorLabels: [ 'TransientTransactionError' ], |
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1540719759 }, |
ok: 0, |
errmsg: 'Unable to acquire lock \'{8613801417341912551: Database, 1696272389700830695}\' within a max lock request timeout of \'5ms\' milliseconds.', |
code: 24, |
codeName: 'LockTimeout' |
Here transaction don't have enough time (5ms) to lock the document. As i understood, transactions in mongo locks document not in the time, when calls session.startTransaction(), but when the operations, that are assosiated with this session and transaction, are executing.
So, what i should do with this problem? How can i handle correctly this write conflicts? Why in one situation i catch one MongoError, and in other situation - another, but both errors is caused by single reason?
Thanks a lot.