Strange behaviour of firebase transaction - firebase

My firebase looks like this:
This is test code (coffee script):
Firebase = require 'firebase'
ref = new Firebase 'https://my_firebase.firebaseio.com/items'
ref.once 'child_added', (snapshot) ->
childRef = snapshot.ref()
console.log "child_added", childRef.toString(), snapshot.val()
childRef.transaction(
(data) ->
console.log 'transaction on data', data
return if !data or data.my_key isnt 'my_val'
data.my_key = 'new_val'
return data
,
(err, commited, snapshot) ->
if err
console.error 'error', err
return
console.log 'commited? '+commited
console.log 'server data', snapshot.val()
,
false
)
And output:
child_added https://my_firebase.firebaseio.com/items/item1 { my_key: 'my_val' }
transaction on data null
commited? false
server data null
Same happens when third parameter of transaction(...) is true.
To make this code work, I have to change ref.once 'child_added', (snapshot) -> to ref.on 'child_added', (snapshot) -> (once to on). After this change output is:
child_added https://my_firebase.firebaseio.com/items/item1 { my_key: 'my_val' }
transaction on data { my_key: 'my_val' }
commited? true
server data { my_key: 'new_val' }
It seems that for some reason when I am using once data are not synced properly and local snapshot is not updated and transaction "thinks" that there is no data under the ref. Is it a bug or I am doing something wrong? I know about transactions that updateFunction can be called more than one time, and about third parameter (I have tried true and false options for it) but still I can't understand why transaction does not work when using once to obtain a child.

The transaction should eventually succeed, and run on the correct state of the data, but will initially run in an "uncached" state, meaning it will run against the client's local copy of the data (likely to be null), try to commit the change to the server (which will fail), and then re-try the transaction.
This is normal, and expected. If, however, the transaction does not ever succeed, I would recommend reaching out to the support folks at support#firebase.com to continue troubleshooting the problem.

Related

Firestore Native Client SDK cold start? (React Native Firebase)

In short: Is there some kind of cold start when connecting to Firestore directly from Client SDK
Hey. I'm using Firestore client sdk in Andoid and IOS application through #react-native-firebase.
Everything works perfectly but I have noticed weird behavior I haven't found explanation.
I have made logging to see how long it takes from user login to retrieve uid corresponding data from Firestore and this time has been ~0.4-0.6s. This is basically the whole onAuthStateChanged workflow.
let userLoggedIn: Date;
let userDataReceived: Date;
auth().onAuthStateChanged(async (user) => {
userLoggedIn = new Date();
const eventsRetrieved = async (data: UserInformation) => {
userDataReceived = new Date();
getDataDuration = `Get data duration: ${(
(userDataReceived.getTime() - userLoggedIn.getTime()) /
1000
).toString()}s`;
console.log(getDataDuration)
// function to check user role and to advance timing logs
onUserDataReceived(data);
};
const errorRetrieved = () => {
signOut();
authStateChanged(false);
};
let unSub: (() => void) | undefined;
if (user && user.uid) {
const userListener = () => {
return firestore()
.collection('Users')
.doc(user.uid)
.onSnapshot((querySnapshot) => {
if (querySnapshot && querySnapshot.exists) {
const data = querySnapshot.data() as UserInformation;
data.id = querySnapshot.id;
eventsRetrieved(data);
} else errorRetrieved();
});
};
unSub = userListener();
} else {
if (typeof unSub === 'function') unSub();
authStateChanged(false);
}
});
Now the problem. When I open the application ~30-50 minutes after last open the time to retrieve uid corresponding data from Firestore will be ~3-9s. What is this time and why does it happen? And after I open the application right after this time will be low again ~0.4-0-6s.
I have been experiencing this behavior for weeks. It is hard to debug as it happens only on build application (not in local environments) and only between +30min interval.
Points to notice
The listener query (which I'm using in this case, I have used also simple getDoc function) is really simple and focused on single document and all project configuration works well. Only in this time interval, which seems just like cold start, the long data retrieval duration occurs.
Firestore Rules should not be slowing the query as subsequent request are fast. Rules for 'Users' collection are as follows in pseudo code:
function checkCustomer(){
let data =
get(/databases/$(database)/documents/Users/$(request.auth.uid)).data;
return (resource.data.customerID == data.customerID);
}
match /Users/{id}{
allow read:if
checkUserRole() // Checks user is logged in and has certain customClaim
&& idComparison(request.auth.uid, id) // Checks user uid is same as document id
&& checkCustomer() // User can read user data only if data is under same customer
}
Device cache doesn't seem to affect the issue as application's cache can be cleaned and the "cold start" still occurs
Firestore can be called from another environment or just another mobile device and this "cold start" will occur to devices individually (meaning that it doesn't help if another device opened the application just before). Unlike if using Cloud Run with min instances, and if fired from any environment the next calls right after will be fast regardless the environment (web or mobile).
EDIT
I have tested this also by changing listener to simple getDoc call. Same behavior still happens on a build application. Replacing listener with:
await firestore()
.collection('Users')
.doc(user.uid)
.get()
.then(async document => {
if (document.exists) {
const data = document.data() as UserInformation;
if (data) data.id = document.id;
eventsRetrieved(data);
}
});
EDIT2
Testing further there has been now 3-15s "cold start" on first Firestore getDoc. Also in some cases the timing between app open has been only 10 minutes so the minimum 30 min benchmark does not apply anymore. I'm going to send dm to Firebase bug report team to see things further.
Since you're using React Native, I assume that the documents in the snapshot are being stored in the local cache by the Firestore SDK (as the local cache is enabled by default on native clients). And since you use an onSnapshot listener it will actually re-retrieve the results from the server if the same listener is still active after 30 minutes. From the documentation on :
If offline persistence is enabled and the listener is disconnected for more than 30 minutes (for example, if the user goes offline), you will be charged for reads as if you had issued a brand-new query.
The wording here is slightly different, but given the 30m mark you mention, I do expect that this is what you're affected by.
In the end I didn't find straight answer why this cold start appeared. I ended up changing native Client SDK to web Client SDK which works correctly first data fetch time being ~0.6s (always 0.5-1s). Package change fixed the issue for me while functions to fetch data are almost completely identical.

Hook logic in between stream : Rxjs Firebase Firestore

this.store
.collection(collectioName)
.onSnapshot((data) => {
});
We can listen to a document with the onSnapshot() method. Each time the contents change, another call updates the document snapshot.
I am looking for hook/rxjs operator that we can use in between, when the data is about to change/emitted and data received by stream.
It will be helpful to
show loading spinner when we get new data or
disable form or table
when we received new update
Something like
this.store
.collection(collectioName)
.onSnapshot()
.pipe(
aboutToInitiate(() => { // start spinner },
dataReceived(() => { // stop spinner, stream received }
).subscribe(() => {
// Or we can stop spinner here, but where exactly we hook the logic to start spinner
);
Note: When creating/updating ( triggered manually ) we can start
spinner and stop on success.
But when the stream return an update that is triggered by server or real time update,
when we are subscribed at that time we need to show spinner or indicator
that we are about to receive some data and data updated/received.
So in short a hook in between subscription that automatically start spinner when about to receive data and stop automatically when data receives.
We can use tap operator but that will fire after data received not when it's initiating the process of getting update.
The onSnapshot() is listening to data changes in realtime. That means you can have a spinner only on initialisation. Every change after that happens immediately your device receives it from the backend or changed from your device.
You could start a Spinner before you initialize the onSnapshot()
//START Spinner
const unsub= this.store
.collection(collectioName)
.onSnapshot((data) => {
//STOP Spinner
});
//Stop listener
unsub()
With the unsub you can stop listening to realtime changes.
One thing you should consider is if you have offline capabilities enabled. In that case you could listen to both changes: data written do device cache and data written to backend.
You can do that by enabling the metatadat changeds to your listener like here:
db.collection("cities").doc("SF")
.onSnapshot({
// Listen for document metadata changes
includeMetadataChanges: true
}, (doc) => {
// ...
});
You can detect then if the data is writen only to the device or to the server:
db.collection("cities").doc("SF")
.onSnapshot((doc) => {
var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
console.log(source, " data: ", doc.data());
});
You can find more about it here.
You could us that metada to determin when the data is written localy (then start the spinner) and when it's written to the server (stop the spinner)

Firebase function return value while async action is ongoing [duplicate]

I'm using Firebase Functions with https triggers, and I was wondering how long after sending the response to the client, the functions keeps executing. I want to send a response to the client and then perform another operation (send a mail).
Currently I'm doing this as following:
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
doSomeAsyncJob()
.then(() => {
res.send("Ok");
})
.then(() => {
emailSender.sendEmail();
})
.catch(...);
});
The above code is working for me, but I'm suspecting that the code only works because sending the mail has finished before the res.send has completed, so I was wondering how exactly the termination process is working to make sure the code will not break.
You should expect that the HTTP function terminates the moment after you send the response. Any other behavior is some combination of luck or a race condition. Don't write code that depends on luck.
If you need to send a response to the client before the work is fully complete, you will need to kick off a second function to continue where the HTTP function left off. It's common to use a pub/sub function to do with. Have the HTTP function send a pub/sub message to another function, then terminate the HTTP function (by sending a response) only after the message is sent.
If the expected response is not pegged to the outcome of the execution, then you can use
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
res.write('SUCCESS')
return doSomeAsyncJob()
.then(() => {
emailSender.sendEmail();
})
.then(() => {
res.end();
})
.catch(...);
});
This sends back a response a soon as a request is received, but keeps the function running until res.end() is called
Your client can end the connection as soon as a response is received back, but the cloud function will keep running in the background
Not sure if this helps, but it might be a workaround where the client needs a response within a very limited time, considering that executing pub/sub requires some extra processing on its own and takes time to execute
TL;DR
While https functions will terminate shortly after res.send(), it is not guaranteed that 0 lines of code after res.send() will be executed.
I think a fuller answer has 2 components:
as Doug pointed out, do not put any additional code you expect to be executed after res.send()
cloud functions will terminate shortly after res.send(), but don't expect that exactly 0 lines of code will be executed
I ran into a situation where for a db maintenance script, if no records met my criteria, I said goodbye with res.send() and had additional logic after it. I was expecting that piece not to be run, since I've already terminated the request.
Example producing unexpected results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
})
In the above example, I was expecting res.send() to terminate the function immediately - this is not so, the second console.log may also be hit - along with any other code you may have. This is not guaranteed, however, so execution may abruptly stop at some point.
Example producing correct results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
else {
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
}
})
In this version, you will see exactly 1 line of console.logs - as I was originally intending.

Trouble returning a valid result while integrating Stripe API into my Meteor app

Here's the rundown:
I'm trying to run Stripe API on my Meteor app asynchronously
Long story short, everything works (i.e. subscription and charge is/are created normally and shows up in my Stripe dashboard)
When errors occur, the errors throw normally and show on client via user friendly alerts
I have a problem when there is a success and customer subscription is created, the result is not present in client and instead always returns as an error, despite it being a successful process
Here's what my method looks like on the server:
createCustomer: function(token, email, plan){
try{
let createCustomer = Meteor.wrapAsync(stripe.customers.create);
let result = createCustomer({
source: token,
email: email,
plan: plan
});
let subscription = {
customer: result.id,
sub: result.subscriptions.data[0].id,
plan: result.subscriptions.data[0].plan.name
};
Meteor.users.update({_id: Meteor.userId()}, {$set: subscription});
} catch(error){
if(error.code === "incorrect_cvc"){
throw new Meteor.Error("incorrect_cvc", error.message);
}
// More of such errors follows
}
}
Here's what it looks like on the client:
Stripe.card.createToken({
number: number,
cvc: cvc,
exp_month: exp,
exp_year: exp_year,
address_zip: zip,
address_country: country
}, function(status, response){
if(response.error){
console.log("Make sure all fields are filled before submitting order.");
} else{
let token = response.id;
Meteor.call("createCustomer", token, email, plan, function(error, result){
if(result){
console.log("Congratulations, everything worked!");
} else{
if(error.error === "incorrect_cvc"){
console.log("oops, the CSV is incorrect");
}
// More of such errors follow..
}
})
}
});
So, everything works in terms of when there is a real error, it throws fine on server + client. When user uses card, the charges are created and subscription is always created. HOWEVER, when there is a success and everything clicking fine, I still receive an error on client via callback and the result is never true or triggered. No idea why.
Not 100% up on Meteor, but it looks to me like your createCustomer method doesn't actually return anything, so the result from your (err, result) might never have anything in it?
As was mentioned in the comments, you might want to separate out the steps and wrap each in its own try-catch set so you can better isolate the issue.
Also, I feel like you could probably generalize your server-side error code to something like:
throw new Meteor.Error(error.error, error.message);
And I might even be tempted to do something like this, at least during testing/development - that way you can actually console.log() the original error in the browser:
throw new Meteor.Error(error.error, error.message, JSON.stringify(error));

Meteor collection updated on server, not reflecting on client

I am trying to create a Meteor app that stores content in a Meteor collection to be passed between the server and the client to display a success message after an asynchronous api call through the twit package.
However, I am running into an issue where when I update the collection on the server and the updates are not reflected on the client. My code is as follows:
/lib
Alerts = new Meteor.Collection("alerts");
/client
Template.suggestionForm.events({
"submit form": function (e) {
return Meteor.call('submitMessage', message);
}
});
Meteor.subscribe('alerts');
Meteor.startup(function() {
Tracker.autorun(function() {
console.log(Alerts.find());
})
});
/server
Fiber = Npm.require('fibers')
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
Meteor.publish("alerts", function(){
Alerts.find();
});
Meteor.methods({
submitMessage: function(message) {
this.unblock();
Twit.post('statuses/update', { 'status': message }, function(err, data, response) {
Fiber(
Alerts.remove({});
Alerts.insert({response: err});
).run();
}));
}
});
When I submit the form the function calls just fine and updates the collection, however the Tracker.autorun() does not run. Any ideas why this is happening or how I can make the client listen for changes in collections would be super helpful. Thank you!
Remember to return the resulting cursor in the publish():
Meteor.publish("alerts", function(){
return Alerts.find();
});
Reference: http://docs.meteor.com/#/full/meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents to each subscribed client. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
and
Alternatively, a publish function can directly control its published record set by calling the functions added (to add a new document to the published record set), changed (to change or clear some fields on a document already in the published record set), and removed (to remove documents from the published record set). These methods are provided by this in your publish function.
If a publish function does not return a cursor or array of cursors, it is assumed to be using the low-level added/changed/removed interface, and it must also call ready once the initial record set is complete.

Resources