For incrementing an int value in my database , I first get that value by using a listener , increment it by 1 and then set the new value to database. This works but I want to know if there is an easier way of doing this. This way seems like too much work.
Update: since early 2020 there actually is a server-side operation to increment values in the Realtime Database. See dfeverx's answer on this, or my own Q&A comparing performance between transactions and increments: How quickly can you atomically increment a value on the Firebase Realtime Database?.
There is no server-side way to increment values (or do other calculations) in the Firebase Database.
Your approach is one way of doing this, but it has the chance of leading to a race condition.
the value in the database is 12
client 1 reads the value 12
client 2 read the value 12
client 1 writes its incremented value 13
client 2 writes its incremented value 13
The result is now likely incorrect (it depends on your use-case).
In that case, one way to make it work is to use Firebase transactions, which combine the reading and writing into a single function.
Transactions are covered in the Firebase Database documentation. I highly recommend reading it. A few hours spent there will prevent many problems down the road. From the docs:
postRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Long value = mutableData.getValue(Long.class);
if (value == null) {
mutableData.setValue(0);
}
else {
mutableData.setValue(value + 1);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
Log.d(TAG, "transaction:onComplete:" + databaseError);
}
});
From firebase JavaScript SDK v7.14.0 you can use ServerValue.increment()
to increment value in firebase
Method 1
var userRef= firebase.database().ref("user/user_id_123");
userRef.push({
likes: firebase.database.ServerValue.increment(1)
});
Method 2
firebase.database()
.ref('user')
.child('user_id_123')
.child('likes')
.set(firebase.database.ServerValue.increment(1))
You can also pass float or negative value to the increment function
You can find the reference here: https://firebase.google.com/docs/reference/js/v8/firebase.database.ServerValue
Related
Token for email verification is created with User registration and needs to be deleted from database within 24 hours with crons job help. In a delete function using query builder, token gets deleted only if date value is manually provided in form of string: {delDate: "2021-02-08T17:59:48.485Z" }. Here, all tokens with date before or equal 2021-02-08 get deleted, and is working fine. But thats a static input, manually put in hard code!
Since this must be a dynamic input, I set up variable 'delTime',which stores current date minus 24 hrs in it, but it seems .where condition will not take a variable as value, and will not delete, as: {delDate: deltime}. In fact, 'delDate' consoles exactly the info I need, but it will only work in form of string.
There is a ton of content online teaching how to delete stuff with a static value in typeorm querybuilder, but so hard to find with dynamic values....
How else can I make this work in a dynamic way ?
async delete(req: Request, res: Response){
try {
const tokenRepository = getRepository(Token);
var delTime = new Date();
delTime.setDate( delTime.getDate() - 1 );
console.log(delTime) //consoles 24 hors ago
await tokenRepository
.createQueryBuilder()
.delete()
.from(Token)
.where("tokenDate <= :deleteTime", { deleteTime: delTime})//value dynamically stored in variable does not delete
//.where("tokenDate <= :deleteTime", { deleteTime: "2021-02-08T18:01:10.489Z"})//static hard code value deletes
//.where("tokenDate <= :delTime", { delTime})//Variable put this way will not work either...
.execute();
} catch (error) {
res.status(404).send("Tokens not found");
return;
}
res.status(200).send('Tokens deleted successfuly');
}
Your parameterized query looks correct.
Switch on TypeOrm Query Logging to see the generated SQL, maybe you will be able to see what's going wrong. Paste the generated SQL into the SQL query console to debug.
Alternatively you can write your delete query without parameters and let Sqlite calculate current date -1 day:
.where("tokenDate <= DateTime('Now', '-1 Day'"); // for Sqlite
Note 1: Sqlite DateTime('Now') uses UTC time, so your tokenDate should use UTC also.
Note 2: This syntax is specific to Sqlite, you can do the same in other databases but the syntax is different, e.g. Microsoft SQL Server:
.where("tokenDate <= DATEADD(day, -1, SYSUTCDATETIME()); // for MS SQL Server
I've one firebase database instance and I would like to add a counter to a certain node.
Everytime the users run an specific action I would like to increment the node value. How to do that without getting synchronization problems? How to use google functions to do that?
Ex.:
database{
node {
counter : 0
}
}
At certain time 3 different users read the value on counter, and try to increment it. As they read at exact same time all of them read "0" and incremented to "1", but the desired value at end of execution should be "3" since it was read 3 times
==================update===================
#renaud pointed to use transactions to keep synchronization on of the saved data, but i have another scenario where i need the synchronization done on read side also:
ex.
the user read the actual value, acording to it does a different action and finishing by incrementing one...
in a sql like enviorement i would write a procedure for doing that, because doesn't matter what user will do with the info i will finish always by incrementing one
If i did understand #renaud answer right, in that scenario 4 different users reading the database at same time would get 0 as current value, then on transaction update the final stored value would be 4, but on client side each of them just read 0
You have to use a Transaction in this case, see https://firebase.google.com/docs/database/web/read-and-write#save_data_as_transactions and also https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
A Transaction will "ensure there are no conflicts with other clients writing to the same location at the same time."
In a Cloud Function you could write your code along the following lines, for example:
....
const counterRef = admin
.database()
.ref('/node/counter');
return counterRef
.transaction(current_value => {
return (current_value || 0) + 1;
})
.then(counterValue => {
if (counterValue.committed) {
//For example update another node in the database
const updates = {};
updates['/nbrOfActionsExecuted'] = counterValue.snapshot.val();
return admin
.database()
.ref()
.update(updates);
}
})
or simply the following if you just want to update the counter (Since a transaction returns a Promise, as explained in the second link referred to above):
exports.testTransaction = functions.database.ref('/path').onWrite((change, context) => {
const counterRef = admin
.database()
.ref('/node/counter');
return counterRef
.transaction(current_value => {
return (current_value || 0) + 1;
});
});
Note that, in this second case, I have used a Realtime Database trigger as an example of trigger.
I am working on using the Firebase database in a Unity project. I read that you want to structure your database as flat as possible for preformance so for a leaderboard I am structuring it like this
users
uid
name
uid
name
leaderboard
uid
score
uid
score
I want a class that can get this data and trigger a callback when it is done. I run this in my constructor.
root.Child("leaderboard").OrderByChild("score").LimitToLast(_leaderboardCount).ValueChanged += onValueChanged;
To trigger the callback with the data I wrote this.
void onValueChanged(object sender, ValueChangedEventArgs args)
{
int index = 0;
List<string> names = new List<string>();
foreach (DataSnapshot snapshot in args.Snapshot.Children)
{
root.Child("players").Child(snapshot.Key).GetValueAsync().ContinueWith(task =>
{
if (task.Result.Exists)
{
names.Add(task.Result.Child("name").Value.ToString());
index++;
if (index == args.Snapshot.ChildrenCount)
{
_callback(names);
}
}
});
}
}
I am wondering if there is a better way to do this that I am missing? I'm worried if the tasks finish out of order my leaderboard will be jumbled. Can I get the names and scores in one snapshot?
Thanks!
You can only load:
a single complete node
a subset of all child nodes matching a certain condition under a single node
It seems that what you are trying to do is get a subset of the child nodes from multiple roots, which isn't possible. It actually looks like you're trying to do a join, which on Firebase is something you have to do with client-side code.
Note that loading the subsequent nodes is often a lot more efficient than developers expect, since Firebase can usually pipeline the requests (everything goes over a single web socket connection). For more on this, see http://stackoverflow.com/questions/35931526/speed-up-fetching-posts-for-my-social-network-app-by-using-query-instead-of-obse/35932786#35932786.
I have a firebase functions which get triggered on onCreate for path /activites/{uid}/{workId}. This function then do some processing with the values and then updates the results (number value) to /users/{uid}/jobs.
// value read from the snapshot function gives
// processing on value
// .
// .
// .
db.ref('/users/{uid}/jobs').transaction(currentVal => {
return (currentVal || 0) + workIdValue;
})
My issue of inconsistent writes appears when let say function is triggered 40 times in span of 1 second per uid i.e. for the same path. This makes data inconsistent since each execution is having different currentValue from path /users/{uid}/jobs
I tired a solution of reading the value of the jobs from db to achieve consistency and then initiate transaction to update value but the result is still the same.
Other solution which I didn't tested is to use a lock mechanism by placing a boolean at the node /users/{uid}/jobs/locked and when trying to do update first check if boolean is false (meaning no other execution is modifying data) then set boolean true update the value and then release the lock setting value false.
Is there any better approach to solve this inconsistency?
So I have a cloud function that is triggered each time a transaction is liked/unliked. This function increments/decrements the likesCount. I've used firestore transactions to achieve the same. I think the problem is the Code inside the Transaction block is getting executed multiple times, which may be correct as per the documentation.
But my Likes count are being updated incorrectly at certain times.
return firestore.runTransaction(function (transaction) {
return transaction.get(transRef).then(function (transDoc) {
let currentLikesCount = transDoc.get("likesCount");
if (event.data && !event.data.previous) {
newLikesCount = currentLikesCount == 0 || isNaN(currentLikesCount) ? 1 : transDoc.get("likesCount") + 1;
} else {
newLikesCount = currentLikesCount == 0 || isNaN(currentLikesCount) ? 0 : transDoc.get("likesCount") - 1;
}
transaction.update(transRef, { likesCount: newLikesCount });
});
});
Anyone had similar experience
Guys finally found out the cause for this unexpected behaviour.
Firestore isn't suitable for maintaining counters if your application is going to be traffic intensive. They have mentioned it in their documentation. The solution they suggest is to use a Distributed counter.
Many realtime apps have documents that act as counters. For example,
you might count 'likes' on a post, or 'favorites' of a specific item.
In Cloud Firestore, you can only update a single document about once
per second, which might be too low for some high-traffic applications.
https://cloud.google.com/firestore/docs/solutions/counters
I wasn't convinced with that approach as it's too complex for a simple use case, which is when I stumbled across the following blog
https://medium.com/evenbit/on-collision-course-with-cloud-firestore-7af26242bc2d
These guys used a combination of Firestore + Firebase thereby eliminating their weaknesses.
Cloud Firestore is sitting conveniently close to the Firebase Realtime
Database, and the two are easily available to use, mix and match
within an application. You can freely choose to store data in both
places for your project, if that serves your needs.
So, why not use the Realtime database for one of its strengths: to
manage fast data streams from distributed clients. Which is the one
problem that arises when trying to aggregate and count data in the
Firestore.
Its not correct to say that Firestore is an upgrade to the Realtime database (as it is advertised) but a different database with different purposes and both can and should coexist in a large scale application. That's my thought.
It might have something to do with what you're returning from the function, as you have
return transaction.get(transRef).then(function (transDoc) { ... })
And then another return inside that callback, but no return inside the inner-most nested callback. So it might not be executing the transaction.update. Try removing the first two return keywords and add one before transaction.update:
firestore.runTransaction(function (transaction) {
transaction.get(transRef).then(function (transDoc) {
let currentLikesCount = transDoc.get("likesCount");
if (event.data && !event.data.previous) {
newLikesCount = currentLikesCount == 0 || isNaN(currentLikesCount) ? 1 : transDoc.get("likesCount") + 1;
} else {
newLikesCount = currentLikesCount == 0 || isNaN(currentLikesCount) ? 0 : transDoc.get("likesCount") - 1;
}
return transaction.update(transRef, { likesCount: newLikesCount });
});
});
Timeouts
First of all, check your Cloud Functions logs to see if you get any timeout messages.
Function execution took 60087 ms, finished with status: 'timeout'
If so, sort out your function so that it returns a Promise.resolve(). And shows
Function execution took 344 ms, finished with status: 'ok'
Idempotency
Secondly, write your data so that the function is idempotent. When your function runs, write a value to the document that you are reading. You can then check if that value exists before running the function again.
See this example for ensuring that functions are only run once.