Do datastore transactions need rollback when saving or deleting? - google-cloud-datastore

In the example for dataset.runInTransaction (link), there is explicit error handling that occurs on transaction.get(key,callback).
But on other operations, such as transaction.save(entity) or transaction.delete(key), there are no callbacks, so really no way to handle errors. For example:
dataset.runInTransaction(function(transaction, done) {
transaction.save({
key: dataset.key(['Company', 123]),
data: {}
});
transaction.delete(dataset.key(['Company', 456]));
done();
}, function(err, apiResponse) {});
Does this mean there is no need to explicitly rollback the transaction?

I was also trying to look up for the same issue but found the below question helpful.
Please have a look here

I spent a lot of back-and-forth with the gcloud-node contributors on the issue tracker:
https://github.com/GoogleCloudPlatform/gcloud-node/issues/1120
https://github.com/GoogleCloudPlatform/gcloud-node/issues/633
basically all of the edits are done at the same time (once done() is called) so if any fail, the entire transaction will be aborted at that time.
what was confusing is that some operations like transaction.get() do have callbacks. Basically the version of the entity returned by transaction.get() needs to match the version before the .save() or .delete() occurs (which again, takes place when done() is called) if the version doesn't match, the transaction is automatically aborted.
using transaction.rollback() is still helpfull if something in your .get() call doesn't match what your code expects.

Related

Cloud functions are getting called two times, first time with body and second time without

My index.ts has:
exports.foo = functions.https.onCall(async (data, context) => {
console.log('Hello World');
return null;
});
To deploy, I run:
firebase deploy --only functions:foo
To test, I do:
final callable = FirebaseFunctions.instance.httpsCallable('foo');
await callable.call();
First time when the function execution started, my function body runs, but the second time (don't know how it gets invoked), my function body doesn't run. Is this a standard behavior, am I also getting charged for the automatic second invocation?
NOTE: I've read several posts like this, this, this, this etc before asking this question but none of them seemed to work for me.
I've seen and more-or-less logged this; for example, recently some "minimum instances=1" functions seem to start-up and run a few times a day, but the function itself isn't invoked. I also see this at deploy time (I use some custom code that deploys multiple functions at a time).
The way "cold starts" work is they have to run the function files once to FIND and ASSIGN the "functions" within. This part used to be run silently. It would be nifty if Google either returned to NOT logging this, or differentiated it in the logs.
I don't know flutter, but you run it "as if you were in a browser". In addition, pressing a button usually submit something (I mean, it's not a GET request, most of the time).
So, the combination of both PLUS your issue lead me to think to a preflight request. Check the HTTP verb before performing the processing.

How to update the same document with a read from the same collection in an onUpdate function

I'm trying to update the same document which triggered an onUpdate cloud function, with a read value from the same collection.
This is in a kind of chat app made in Flutter, where the previous response to an inquiry is replicated to the document now being updated, for easier showing in the app.
The code does work, however when a user quickly responds to two separate inquiries, they both read the same latest response thus setting the same previousResponse. This must be down to the asynchronous nature of flutter and/or the cloud function, but I can't figure out where to await or if there's a better way to make the function, so it is never triggering the onUpdate for the same user, until a previous trigger is finished.
Last part also sound a bit like a bad idea.
So far I tried sticking the read/update in a transaction, however that only seems to work for the single function call, and not when they're asynchronous.
Also figured I could fix it, by reading the previous response in a transaction on the client, however firebase doesn't allow reading from a collection in a transaction, when not using the server API.
async function setPreviousResponseToInquiry(
senderUid: string,
recipientUid: string,
inquiryId: string) {
return admin.firestore().collection('/inquiries')
.where('recipientUid', '==', recipientUid)
.where('senderUid', '==', senderUid)
.where('responded', '==', true)
.orderBy('modified', 'desc')
.limit(2)
.get().then(snapshot => {
if (!snapshot.empty &&
snapshot.docs.length >= 2) {
return admin.firestore()
.doc(`/inquiries/${inquiryId}`)
.get().then(snap => {
return snap.ref.update({
previousResponse: snapshot.docs[1].data().response
})
})
}
})
}
I see three possible solutions:
Use a transaction on the server, which ensures that the update you write must be based on the version of the data you read. If the value you write depends on the data that trigger the Cloud Function, you may need to re-read that data as part of the transaction.
Don't use Cloud Functions, but run all updates from the client. This allows you to use transactions to prevent the race condition.
If it's no possible to use a transaction, you may have to include a custom version number in both the upstream data (the data that triggers the write), and the fanned out data that you're updating. You can then use security rules to ensure that the downstream data can only be written if its version matches the current upstream data.
I'd consider/try them in the above order, as they gradually get more involved.

Meteor template optimisation

I was looking at some meteor code, and I saw this :
Template.notifications.helpers({
notifications: function() {
return Notifications.find({userId: Meteor.userId(), read: false});
},
notificationCount: function(){
return Notifications.find({userId: Meteor.userId(), read: false}).count();
}
});
So I was wondering, Is this optimized ?
I mean, will the mongo database execute two queries ? The server part ? the client part ? (mini mongo then)
Is it possible to use the previous result in the second function ? I tried with
notificationCount = function(){
this.notifications.length;
....
But it doesn't work, but maybe meteor remembers the previous result and uses it ?
I will definitely, in my template, return a something.find() to have a cursor and afterwards return other variable with, for example : count, or filter it with fields or other stuff so I'm intereted by this question.
Any expert to explain me ? Thanks a lot meteor community :) !
You're not performing 2 queries on the server, which ends up being the most critical place. When you subscribe to data, the data goes to a local database in the browser called MiniMongo. You can run as many queries as you want on the client, the data set (usually and let's hope so) is small and there isn't a noticeable performance penalty.
If you've some performance issues you can save the results of Notifications.find({userId: Meteor.userId(), read: false}) to Session or to another Reactive Dictionary. This will slightly improve the performance because you save the query time of Minimongo: parsing, searching, etc.
On the server, you should be as careful as possible. A bottleneck in the server might mean that your entire application isn't going to be as fast as wanted.
Read more about mini in memory databases: https://www.meteor.com/mini-databases
yes. on the client side, not on the server side.
you can use {{notifications.count}} on template.

How to debug slow meteor methods?

A number of my meteor methods have mysteriously slowed down recently. Whereas they used to be quite snappy, many are taking 10 or so seconds.
Things not causing the slowdown:
Additional functionality, the slowed sections of the codebase haven't been changed significantly
Machine load (cpu load hovering around 30%)
Additional DB load (no new queries added)
transfer time of the return data (returns undefined)
blocking method (I've tried with this.unblock() within the method)
I did debugging by using console.time() / console.timeEnd() on both the server and client side. The server side code takes about .3 seconds to run, but the client doesnt get the callback until about 11 seconds after the meteor.call() ...
This is the server method:
function cancelSomething(somethingId, reason) {
console.time('cancelSomething method');
check(somethingId, String);
check(reason, String);
if (!AuthChecks()))
throw new Meteor.Error(401, 'Not Authorized');
var something = MySomethings.findOne({'_id': somethingId});
if (!something)
throw new Meteor.Error(404, 'Something not found');
var returnVal = SomethingService.cancel(something, reason);
console.timeEnd('cancelSomething method'); // <--- prints "cancelSomething 350ms" or there abouts
return returnVal;
}
clientSide:
console.time('meteorCall');
Meteor.call('cancelSomething', this._id, reason, function(err) {
if (err) {
console.log('an error occurred', err);
}
console.timeEnd('meteorCall'); // <--- prints "meteorCall 11500" or so
});
EDIT:
I have noticed there is some correlation with the quantity of docs within the "somethings" in the db. w/ 500 documents it takes about 1 second to receive the return on the client, with 5000 it takes about 8.5 seconds ...
Interesting. I think this one is hard to answer without knowing more about your app, but I have some suggestions that could help steer you in the right direction.
blocking
If we can assume that the timers are working properly, what could be happening is that the server is unable to begin execution of the method because another method call by the same client is already in progress. Have a look at unblock in the docs. The answer may be as simple as putting a this.unblock at the top of one of your methods.
syncing
If SomethingService.cancel does something which results in a lot of DDP traffic, that could tie up the client for a while and make it unable to execute your callback. For example if it:
modified or created a lot of documents which then had to be synced to the client
indirectly caused an expensive subscription to be rerun (my money is on this one)
Updated Answer
It seems the problem had to to with a call to observe, which makes a lot of sense given your high CPU load. Frankly, it's a little silly that I didn't suggest this because I have already answered a similar question here. If you want to try to keep the observe, here are some additional suggestions:
Use oplog tailing.
Try to narrow the scope of the observe as much as possible using selectors which are currently supported by the oplog observe driver. The supported selectors should improve at or before meteor 1.0.
Limit the fields to only those you need.
Check that oplog tailing is working by adding the facts package.

Firebase in node.js, set() callback is delayed

I have an EC2 instance running a small node script connecting to Firebase. Strangely enough, it happens quite often on a small instance that the set operation gets executed immeditely but the callback function only gets called much later (between 30s to 2 minutes). Do you see any reason why it would happen that way?
console.log('creating');
// Create workspace
rootRef.child('spaces').child(chid).set(req.space, function(error) {
var end = new Date().getTime();
var time = end - start;
console.log('- created', error, time);
});
The bug is directly related to node 0.11 (set() callback is only called the first name in my scenario). Just revert to 0.10.x and it's all fixed!
I've been facing the same issue. the "Set" callback is not being invoked at all. I noticed, however, that if I run a snippet code similar to yours in a standalone file, the callback is invoked very quickly.
It turned out that if you're installing listeners on the same Node you're calling the "set" function on (i.e., on('child_added'), on('child_removed') ... etc) and that Node has a huge number of records, it'll simply take ages.
I removed the listeners ( to test) and the "set" started to invoke the callback very quickly.
I hope this helps!

Resources