How can I change Firebase Cloud Function region? - firebase

My Firebase Cloud Function looks like:
exports.sendRequest = functions.firestore
.document('Links/{p_id}/Accepted/{uid}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
return null;
})
It is being deployed it to
us-central1
How can I change its location to
asia-east2 ?

This is covered in the documentation for best practices for changing a region:
If you are changing the specified regions for a function that's
handling production traffic, you can prevent event loss by performing
these steps in order:
Rename the function, and change its region or regions as desired.
Deploy the renamed function, which results in temporarily running the same code in both sets of regions.
Delete the previous function.
This procedure is pretty straightforward.

Related

Different Google Cloud Function Call For Different Storage?

I'm currently working on a project where there are two different storage buckets (one in US central and another in S. Korea).
It would be more efficient both cost-wise and speed-wise to locate the functions for updating storage usage in same location as storage.
But, now I want to manage the function at two different location. This function is meant to update the Firestore whenever new image is uploaded in a storage.
This is the code that I thought would work (but actually don't)
exports.incrementUsedSize = functions.region('us-central1').storage.object().onFinalize
exports.incrementUsedSizeKr = functions.region('asia-northeast3').storage.object().onFinalize
But both of these are called whenever storage at US or S. Korea is updated.
Is there a way to make these functions work at two locations without interfering each other?
I mean, is there a way to restrict the scope of the function to the storage at specific location?
As per the documentation:
To set the region where a function runs, set the region parameter in the function
definition as shown:
exports.incrementUsedSize = functions
.region('us-central1')
.storage
.object()
.onFinalize((object) => {
// ...
});
The .region() sets the location for your cloud function and has nothing to do with the bucket.
However, as you say the buckets are different and not a single "dual-region" bucket, you should be able to deploy this way:
gcloud functions deploy incrementUsedSize \
--runtime nodejs10 \
--trigger-resource YOUR_TRIGGER_BUCKET_NAME \
--trigger-event google.storage.object.finalize
by specifying the trigger bucket you should be able to restrict the invocation trigger. More details in the documentation here
If your question refers to Firebase Cloud Function then you can specify the bucket name as mentioned here:
exports.incrementUsedSize = functions.storage.bucket('bucketName').object().onFinalize(async (object) => {
// ...
});

Firebase cloud functions: How to wait for a document to be created and update it afterwards

Here is the situation:
I have collections 'lists', 'stats', and 'posts'.
From frontend, there is a scenario where the user uploads a content. The frontend function creates a document under 'lists', and after the document is created, it creates another document under 'posts'.
I have a CF that listens to creation of a document under 'lists' and create a new document under 'stats'.
I have a CF that listens to creation of a document under 'posts' and update the document created under 'stats'.
The intended order of things to happen is 2->3->4. However, apparently, step 4 is triggered before step 3, and so there is no relevant document under 'stats' to update, thus throwing an error.
Is there a way to make the function wait for the document creation under 'stats' and update only after it is created? I thought about using setTimeout() for the function in step 4, but guess there might be a better way.
Below is the code that I am using for steps 3 and 4. Can someone advise? Thanks!
//This listens to a creation of a document under 'lists' and creates a new document
//with the same document ID under 'stats'.
exports.statsCreate = functions.firestore
.document('lists/{listid}').onCreate((snap,context)=>{
const listidpath=snap.ref.path;
const pathfinder=listidpath.split('/');
const listid=pathfinder[pathfinder.length-1];
return db.collection('stats').doc(listid).set({
postcount:0,
})
})
//This listens to a creation of a document under 'posts' and updates the corresponding
// document under 'stats'. There is a field under 'posts' with the list ID to make this possible.
// How do I make sure the update operation happens only after the document is actually there?
exports.statsUpdate = functions.firestore
.document('posts/{postid}').onCreate((snap,context)=>{
const data=snap.data();
return db.collection('stats').doc(data.listid).update({
postcount:admin.firestore.FieldValue.increment(1)
})
})
I can see at least two "easy" solutions:
Solution #1: In your front end, set a listener to the to-be-created stat document (with onSnapshot()), and only create the post document when the stat one has been created. Note however that this solution will not work if the user does not have read access right to the posts collection.
Solution #2: Use the "retry on failure" option for background Cloud Functions. Within your statsUpdate Cloud Function you intentionally throw an exception if the stat doc is not found => The CF will be retried until the stat doc is created.
A third solution would be to use a Callable Cloud Function, called from your front-end. This Callable Cloud Function would write the three docs in the following order: list, stat and post. Then the statsUpdate Cloud Function would be triggered in the background (or you could include its business logic in the Callable Cloud Function as well).
One of the drawbacks of this solution is that the Cloud Function may encounter some cold start effect. In this case, from an end-user perspective, the process may take more time than the abonne solutions. However note that you can specify a minimum number of container instances to be kept warm and ready to serve requests.
PS: Note that in the statsCreate CF, you don't need to extract the listid with:
const listidpath=snap.ref.path;
const pathfinder=listidpath.split('/');
const listid=pathfinder[pathfinder.length-1];
Just do:
const listid = context.params.listid;
The context parameter provides information about the Cloud Function's execution.

Setting deploy region in firebase configuration file

Is there a way of defining which region to use when deploying a function to firebase using either the firebase.json or the .firebaserc files? The documentation around this doesn't seem to be clear.
Deploying the firebase functions using GitHub Actions and want to avoid adding the region in the code itself if possible.
Suggestions?
It's not possible using the configurations you mention. The region must be defined synchronously in code using the provided API. You could perhaps pull in an external JSON file using fs.readFileSync() at the global scope of index.js, parse its contents, and apply them to the function builder. (Please note that you have to do this synchronously - you can't use a method that returns a promise.)
I've target this problem using native functions config.
Example:
firebase functions:config:set functions.region=southamerica-east1
Then in functions declaration I'd as follows:
const { region } = functions.config().functions;
exports.myFunction = functions.region(region).https.onCall((data) => {
// do something
});
That way, for each time I need a different region, it would only need a new config set.

Firebase function document.create and user.create triggers firing multiple times

I'm trying to keep track of the number of documents in collections and the number of users in my Firebase project. I set up some .create triggers to update a stats document using increment, but sometimes the .create functions trigger multiple times for a single creation event. This happens with both Firestore documents and new users. Any ideas?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firestore = require('#google-cloud/firestore')
admin.initializeApp();
const db = admin.firestore()
/* for counting documents created */
exports.countDoc = functions.firestore
.document('collection/{docId}')
.onCreate((change, context) => {
const docId = context.params.docId
db.doc('stats/doc').update({
'docsCreated': firestore.FieldValue.increment(1)
})
return true;
});
/* for counting users created */
exports.countUsers = functions.auth.user().onCreate((user) => {
db.doc('stats/doc').update({
'usersCreated': firestore.FieldValue.increment(1)
})
return true;
});
Thanks!
There is some advice on how to achieve your functions' idempotency.
There are FieldValue.arrayUnion() & FieldValue.arrayRemove() functions which safely remove and add elements to an array, without duplicates or errors if the element being deleted is nonexistent.
You can make array fields in your documents called 'users' and 'docs' and add there data with FieldValue.arrayUnion() by triggered functions. With that approach you can retrieve the actual sizes on the client side by getting users & docs fields and calling .size() on it.
You should expect that a background trigger could possibly be executed multiple times per event. This should be very rare, but not impossible. It's part of the guarantee that Cloud Functions gives you for "at-least-once execution". Since the internal infrastructure is entirely asynchronous with respect to the execution of your code on a dedicated server instance, that infrastructure might not receive the signal that your function finished successfully. In that case, it triggers the function again in order to ensure delivery.
It's recommended that you write your function to be idempotent in order to handle this situation, if it's important for your app. This is not always a very simple thing to implement correctly, and could also add a lot of weight to your code. There are also many ways to do this for different sorts of scenarios. But the choice is yours.
Read more about it in the documentation for execution guarantees.

Firebase cloud function use current value and not value when function was initially called

Not sure if this is even possible with firebase cloud functions.
Let's assume, I want to trigger a cloud function onCreate on all documents in a specific collection.
After creation, the cloud function should add another document in a different collection.
Passing a value from the manually created document.
Sure, that works!:
export const createAutomaticInvoice = functions.firestore.document('users/{userId}/lessons/{lesson}').onCreate((snap, context) => {
let db = admin.firestore();
let info = snap.ref.data()
db.collection('toAdd').add({
info: info
})
})
But if I create a document within users/{userId}/lessons/ and change the value of info directly afterwards, before the cloud function is triggered, the cloud function takes the old value of info as supposed to the one it was changed to.
Is this expected behaviour? For me it is definetely not as I would assume that it takes the values at runtime.
How can I make my example work as expected?
This is the expected behavior - the function is going to execute as soon as possible after that document is created. The snapshot is always going to contain the contents of the document as it was originally created. It's not going to wait around to see if that document changes at some point in the future, and it's not going to try to query that document in case it might have changed.
If you want to handle updates to a document, you should also be using an onUpdate trigger to know if that happens.

Resources