Mongodb abortTransaction() not working On version 5.0.8 - mongodb

I have the code block below for a MongoDB transaction:
return new Promise(async (resolve, reject) => {
let client = await this.mongoClient();
const transactionSession = client.startSession();
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
};
const db = client.db("XXX");
try {
const transactionResults = await transactionSession.withTransaction(async () => {
const A = await db.collection("users").updateOne({query}, {$set: {set}}, {transactionSession}).catch(console.error);
const B = await db.collection(this.sessionCollection).updateMany({query}, {$set: {set}}}, {transactionSession}).catch(console.error);
console.log(A, B)
await transactionSession.abortTransaction();
}, transactionOptions);
if (transactionResults) {
console.log("The reservation was successfully created.");
} else {
console.log("The transaction was intentionally aborted.");
return reject({code: 5000});
}
} catch(e){
console.log("The transaction was aborted due to an unexpected error: " + e);
} finally {
await transactionSession.endSession();
}
});
This block code logs "The transaction was intentionally aborted.", but both A and B execute on database!

Related

Making third party API calls inside MongoDB transaction

const client = new MongoClient(uri);
await client.connect();
await client
.db('mydb1')
.collection('foo');
const session = client.startSession();
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' }
};
// Step 3: Use withTransaction to start a transaction, execute the callback, and commit (or abort on error)
// Note: The callback for withTransaction MUST be async and/or return a Promise.
try {
await session.withTransaction(async () => {
const coll1 = client.db('mydb1').collection('foo');
await coll1.insertOne({user_id: 12344, paid: true }, { session });
await calls_third_party_payment_vendor_api_to_process_payment();
}, transactionOptions);
} finally {
await session.endSession();
await client.close();
}
Suppose that calls_third_party_payment_vendo_apir_to_process_payment throws an error or any system fails after await coll1.insertOne({user_id: 12344, paid: true }, { session }); was successfully written such that to cause the payment to never actually process, will the document that was inserted be guaranteed to be removed?

How to read uncomitted updates in atlas functions

I have a cluster with 1 primary and 2 secondary nodes. Also, I have an atlas function witch uses a multi-document transaction. I faced a problem when I do an update and then read this document using .find() I always get not updated version of this document. Here is the function:
exports = async function({ query, headers, body}, response) {
const collection = context.services.get("mongodb-atlas").db("zakhar").collection("w");
// clean up and insert init data
await collection.deleteMany({});
await collection.insertOne({"name": 1, "category": "toy"});
await collection.insertOne({"name": 2, "category": "toy"});
await collection.insertOne({"name": 3, "category": "game"});
await collection.insertOne({"name": 4, "category": "game"});
const session = context.services.get("mongodb-atlas").startSession();
const transactionOptions = {
readPreference: "primary",
readConcern: { level: "local" },
writeConcern: { w: "majority" }
};
try {
await session.withTransaction(async () => {
await updateStatus(session);
}, transactionOptions);
return 'ok';
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
await session.endSession();
}
};
async function updateStatus(session, ){
const collection = context.services.get("mongodb-atlas").db("zakhar").collection("w");
const countBeforeUpdate = await collection.find({"category": "toy"}, {}, { session }).toArray();
console.log("countBeforeUpdate: " + countBeforeUpdate.length);
const query = {"name": 1};
const update = {"$set": {"category": "game"}};
await collection.updateOne(query, update, { session })
.then(result => {
const { matchedCount, modifiedCount } = result;
console.log("matchedCount: " + matchedCount);
console.log("modifiedCount: " + modifiedCount);
});
const countAfterUpdate = await collection.find({"category": "toy"}, {}, { session }).toArray();
console.log("countAfterUpdate: " + countAfterUpdate.length);
}
And it is an output, but I expect countAfterUpdate: 1
"countBeforeUpdate: 2",
"matchedCount: 1",
"modifiedCount: 1",
"countAfterUpdate: 2"
I tried to use all other transaction options(readPreference, readConcern, writeConcern), but nothing changed. Also tried to use session.startTransaction(...) instead of withTransaction()

When to commit a mongo transaction?

below is a code snippet from https://www.mongodb.com/blog/post/quick-start-nodejs--mongodb--how-to-implement-transactions
It works perfectly but here is what I don't understand:
This function didn't call session.commitTransaction(), how does it commit the transaction?
it aborts the transaction by determining if isListingReservedResults is null or not null, but my implementation is to throw an error in the if block and catches the error then calling the session.abortTransaction() (which result in a "MongoError: Cannot call abortTransaction twice"), I wonder Why would this happen since i only call it onece.
async function createReservation(client, userEmail, nameOfListing, reservationDates, reservationDetails) {
const usersCollection = client.db("sample_airbnb").collection("users");
const listingsAndReviewsCollection = client.db("sample_airbnb").collection("listingsAndReviews");
const reservation = createReservationDocument(nameOfListing, reservationDates, reservationDetails);
const session = client.startSession();
try {
const transactionResults = await session.withTransaction(async () => {
const usersUpdateResults = await usersCollection.updateOne(
{ email: userEmail },
{ $addToSet: { reservations: reservation } },
{ session });
const isListingReservedResults = await listingsAndReviewsCollection.findOne(
{ name: nameOfListing, datesReserved: { $in: reservationDates } },
{ session });
if (isListingReservedResults) {
await session.abortTransaction();
return;
// throw new Error('message'); myi mplementaion, throw error here then abort the transaction in catch block
}
const listingsAndReviewsUpdateResults = await listingsAndReviewsCollection.updateOne(
{ name: nameOfListing },
{ $addToSet: { datesReserved: { $each: reservationDates } } },
{ session });
});
if (transactionResults) {
console.log("The reservation was successfully created.");
} else {
console.log("The transaction was intentionally aborted.");
}
} catch(e){
console.log("The transaction was aborted due to an unexpected error: " + e);
// await session.abortTransaction(); result in a "MongoError: Cannot call abortTransaction twice"
} finally {
await session.endSession();
}
}

Cloud Firestore function triggers and transactions - how to return a promise correctly

I have a Cloud Firestore function trigger "onCreate". Depending on the value of a given field, I would like it to either update some documents via a transaction and to copy the created document as a record in Algolia, or to execute a completely different transaction. There are therefore several conditions and I am not sure that I am returning promises correctly, as sometimes the function is not copying the record in Algolia when expected.
I paste a simplified version of the code in case someone can help.
exports.createArticle = functions.firestore.document('articles/{articleId}').onCreate(async (snap, context) => {
const newDocData = snap.data()
if(newDocData) {
const userCreatorId = newDocData.createdBy
const userDocRef = imported.db.collection('users').doc(userCreatorId)
if(newDocData.type === 1) {
newDocData.objectID = newDocData.id
indexAlgolia.saveObject(newDocData)
.then(() => {
console.log('Article saved in Algolia with id:', newDocData.objectID )
})
.catch(err => {
console.log('ERROR while ADDING object inAlgolia:', err)
})
return imported.db.runTransaction(async t => {
// do some work
const userDoc = await t.get(userDocRef)
const userData = userDoc.data()
if (userData && userData.field1 > 0) {
t.update(userDocRef, {field2: true})
}
}).then(result => {
console.log('Transaction success')
}).catch(err => {
console.log('Transaction failure:', err)
})
}
else {
const colOneRef = imported.db.collection('colOne')
colOneRef.where('field2', '==', newDocData.field3).limit(1).get().then(snapshot => {
return imported.db.runTransaction(async t => {
if (snapshot.empty) {
t.update(userDocRef, {field3: false})
}
const decrement = imported.fieldValue.increment(-1)
t.update(userDocRef, {field4: decrement})
}).then(result => {
console.log('Transaction success')
}).catch(err => {
console.log('Transaction failure:', err)
})
}).catch(() => 'Error while querying colOneRef')
}
}
})
When you have multiple async/then calls you canmake them await the result and run them as if they are synchornous but from your code I see that the second doesn't depent on the first one so you can put them in a Promse.all() to make the function finish faster because they will run in parallel. Your code would look like this:
xports.createArticle = functions.firestore
.document("articles/{articleId}")
.onCreate(async (snap, context) => {
const newDocData = snap.data();
if (newDocData) {
const userCreatorId = newDocData.createdBy;
const userDocRef = imported.db.collection("users").doc(userCreatorId);
if (newDocData.type === 1) {
newDocData.objectID = newDocData.id;
const firstPromise = indexAlgolia
.saveObject(newDocData)
.then(() => {
console.log(
"Article saved in Algolia with id:",
newDocData.objectID
);
})
.catch((err) => {
console.log("ERROR while ADDING object inAlgolia:", err);
});
const secondPromise = imported.db
.runTransaction(async (t) => {
// do some work
const userDoc = await t.get(userDocRef);
const userData = userDoc.data();
if (userData && userData.field1 > 0) {
t.update(userDocRef, { field2: true });
}
})
.then((result) => {
console.log("Transaction success");
})
.catch((err) => {
console.log("Transaction failure:", err);
});
return Promise.all([firstPromise, secondPromise]);
} else {
const colOneRef = imported.db.collection("colOne");
return colOneRef
.where("field2", "==", newDocData.field3)
.limit(1)
.get()
.then((snapshot) => {
return imported.db
.runTransaction(async (t) => {
if (snapshot.empty) {
t.update(userDocRef, { field3: false });
}
const decrement = imported.fieldValue.increment(-1);
t.update(userDocRef, { field4: decrement });
})
.then((result) => {
console.log("Transaction success");
})
.catch((err) => {
console.log("Transaction failure:", err);
});
})
.catch(() => "Error while querying colOneRef");
}
return
}
});

Transactions takes too much time (Node + Meteor + MongoDB)

I have implemented transaction in MongoDB as described in this article:
https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677
Utils.js
import { MongoInternals } from 'meteor/mongo';
// utility async function to wrap async raw mongo operations with a transaction
export const runTransactionAsync = async function (asyncRawMongoOperations, errorCode) {
// setup a transaction
const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const session = await client.startSession();
await session.startTransaction();
try {
// running the async operations
let result = await asyncRawMongoOperations(session);
await session.commitTransaction();
// transaction committed - return value to the client
return result;
} catch (err) {
await session.abortTransaction();
console.error(err.message);
// transaction aborted - report error to the client
throw new Meteor.Error(errorCode, err.message);
} finally {
session.endSession();
}
};
Example of using the transactions:
Meteor.methods({
'changeLanguage': async function(poemId, newLanguageId) {
// define the operations we want to run in transaction
const asyncRawMongoOperations = async session => {
const poem = Poems.findOne({ _id: poemId });
const prevLanguage = poem.languageId;
const profile = Profiles.findOne({ userId: this.userId });
await Profiles.rawCollection().update(
{ userId: this.userId },
{
$inc: {
['countLanguages.' + prevLanguage]: -1,
['countLanguages.' + newLanguageId]: 1
},
$addToSet: { languages: newLanguageId }
},
{ session: session }
);
await Poems.rawCollection().update({
'_id': poemId,
'userId': this.userId
},
{
$set: {
languageId: newLanguageId
}
},
{ session: session }
);
return true; // will be the result in the client
};
let result = await runTransactionAsync(asyncRawMongoOperations, 'Error-01');
return result;
}
});
Transactions work correct, but too slow. It can take up to 7 seconds.
MongoDB hosting is MongoDB.Atlas. MongoDB version is 4.0.12.
Where can be the bottleneck in this case?