Can cold start happen multiple times per invocation in Firebase functions? - firebase

I am trying to organize my functions with some business logic and some helper functions that are running only from within my code (not exposed endpoints).
As I am trying to organize it, some helper functions would depend on admin or other imports, so in both business.js and common.js I would have
const admin = require("firebase-admin");
admin.initializeApp();
Lets say business.js function calls some function from common.js, and they both use admin.firestore() - how does cold start work in this case? Would both of these functions pay for the cold start time - would common.childCalledFunction pay for cold start even if admin is initialized for called function business.parentCalledFunction? Is cold start once per invocation or per each function? If per function, what would be preferred solution to common functions like fetch and validate from Firestore?
I have found many articles online about cold start but never actually answer to this particular case, or maybe I just misread it.
Also, my knowledge of javascript is limited.

See note at end.
When using require(), you pay the cold start cost of loading the code whenever require() is first used for a particular library which involves fetching the code (and its dependencies) from the disk and evaluating it. Further to this, there is another cold start when you call the initialization method of these libraries.
The Firebase SDKs use the named singleton pattern. When you first invoke
initializeApp(name) (where name defaults to "[DEFAULT]" when omitted) you initialize an instance of the SDK's core functionality for that name. Care must be taken around initializeApp(name) as you can only initialize each named app once without it throwing a "App named X already exists" error. Additionally each component of the Firebase SDK gets initialized the first time you call it for a given app using getFirestore(app) or firestore(app) (where app defaults to the default app when omitted) in the case of Firestore. Unlike initializeApp() these "get component" initializers can be called as many times as you want.
So in your case, calling initializeApp() at the top of both common.js and business.js would actually crash your application. Instead, you should move the initializeApp() into it's own firebase.js file so it is only evaluated once, but can be used in multiple places.
This would mean swapping out these lines:
// business.js / common.js
const admin = require("firebase-admin");
admin.initializeApp();
to:
// business.js / common.js
const admin = require('./firebase.js')
// firebase.js
const admin = require("firebase-admin");
admin.initializeApp();
module.exports = admin;
project/
functions/
lib/
business.js
common.js
firebase.js
functions/
feat1.js
feat2.js
index.js
...
...
As you are new to JavaScript, take the time to learn the new versions of the JavaScript SDKs, known as the Modular SDKs, as these are what will be using going forward in new versions of Firebase. This will require using a compiler step on your code before production, but it does offer the ability to trim off the fat components of the SDK you aren't actually using. It also involves learning the new names for functions such as getFirestore() instead of admin.firestore() and using doc(firestore, path) instead of firestore.doc(path). You should also use ES6 module imports instead of require().

Related

Deploying Cloud Firestore Trigger function from GCP Console

I have a function that is fired on a onUpdate trigger from my cloud firestore database.
The function not being called at all when I change my database.
I did not deploy the function using firestore CLI, instead I deployed it using the GCP Console.
Here is the function:
exports.NotificationListener = functions
.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const userId = context.params.userId.toString();
const eventId = context.eventId;
console.log('EventId:' + eventId);
console.log('Change in:' + userId);
return 200;
});
Here is the deployment information from the GCP console (showing the trigger):
Finally, here is the Cloud Firestore schema:
I want to monitor any changes to any "USER" in the collection: "/user", hence I am using "user/{userId}".
Why is this function not being called when I change the database ?
EDIT 1
A little information about my environment:
I have my entire project core in a TypeScript file. I have over 40 HTTPS triggered functions that are currently online.
I add a new function in my TS file, then I do a npm run build to compile and get the JS file.
Finally, I go to Google Cloud Console and create a function and choose "Zip Upload" and upload the compiled JS file (obviously, along with the required JSON files for getting Database URL, Authentication etc.)
This approach works perfectly fine, at least for HTTP triggered firestore functions.
Now I repeated the same steps as above for the onUpdate trigger and just instead of choosing HTTP trigger, I chose Cloud Firestore trigger. The trigger information can be found above in the screenshot.
onUpdate is not being fired on DB changes.
EDIT 2
My event trigger function NotificationListener is showing up in the firebase console functions list along with my other 40 HTTPS functions. But it is not being called.
#doug-stevenson, your answer seems to have disappeared, I am not sure why.
Anyway, I found the reason why it wasn't working.
My firebase database was in project "Placeholder 1" and my GCP functions were in project "Placeholder 2".
Now, I was able to update the "Placeholder 1" DB from GCP functions (in "Placeholder 2") using firabse-functions API because I set the DatabaseURL to "Placeholder 1".
But, just setting the DatabaseURL to the desired database doesn't work if you want to LISTEN to the database for changes. You actually need to have the function in the same project otherwise it is not able to subscribe and listen for events.
I think it's a little inconsistent that you can read/write to a DB from different projects, but to listen for events, function needs to be in same project.
Or maybe I am missing something fundamental that caused this confusion for me.

Firebase One-time Functions

I'm sure these are common scenarios, but after researching some hours, I couldn't really find what the common practice is. Maybe someone with more experience in Firebase can point me to the right direction.
I have two particular scenarios:
1. Code that runs once
Example 1: adding new data to all users in firestore, which is needed for a new feature
Example 2: start duplicating data into existing documents
I currently write the code in a cloud function, and run it on a firestore event (onUpdate of a "hidden" document) and then I immediately delete the function if everything goes well.
I also increase the timeout and memory for this function, as the idea is to potentially update millions of documents.
2. Manually trigger a function from the firebase console (or command line)
Example: Give a user admin privileges in the app (function that sets custom claims and firestore data). We don't have time to implement a back-office, so doing this from the firebase web portal/console would be ideal, specifying the user id.
My current solution is to use a https function, and run it from the GCP portal (on the function's "Testing" tab, being able to pass a json). BUT the function can be triggered publicly, which I don't really like...
What are the common practices for these scenarios?
To expand on my comment: if you want to create a node script to run one-off code, you just write your JS code like for any cloud function but simply run it immediately. Something like this.
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
db.collection('users')
.where('myField', '==', true)
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
// update doc
});
});
If you save this as script.js and execute it with node script.js you’ll be pointed towards downloading a JSON file with credentials. Follow the instructions and you can then run the script again and now you’re running your own code on Firestore, from the command line.
For administrative type operations, you're better off just running them on your desktop or some other server you control. Cloud Functions is not well suited for long running operations, or things that must just happen once on demand.
Case 1 really should be managed by a standalone program or script that you can monitor by running it on your desktop.
Case 2 can be done a number of ways, such as building your own admin web site. But you might find it easiest to mirror the contents of a document to custom claims using a Firestore trigger. Read this: https://medium.com/firebase-developers/patterns-for-security-with-firebase-supercharged-custom-claims-with-firestore-and-cloud-functions-bb8f46b24e11

trigger function on firebase deploy functions

Does anyone know if there is an easy way to trigger a function everytime i re-deploy some funciont to firebase?
I have an specific firabase functions which i define inside GCP (this way when i do "firebase deploy" it doesnt re-deploy, unnisntal or touch in any form my current function)
but sometimes i might update this function manually on GCP and i would like to trigger a inner function of its code everytime it happens... is it possible?
ex:
exports.decrementAction = (req, res) => {/*do stuff*/res.status(200).send("ok")};
function auxiliary(){
//to be called on re-deploy
}
Unfortunately, there isn't an easy way for you to trigger a function within a code that is being redeployed. Since this code is only being deployed at the moment, this wouldn't be possible to be done automatically.
The alternative would be to have this function separately from the "root" function in the moment of deploying and use triggers to run this other Cloud Function, when the first is redeployed. This way, it would be possible to run it based in the deployment of the other.
You can get more information on the triggers available for Cloud Functions here: Calling Cloud Functions. With them, you should be able to configure the timing for the execution.
Besides that, it might be worth it to raise a Feature Request for Google's to verify the possibility of adding this in future releases.
Let me know if the information clarified!
I think there exists a manner.
With Pub/Sub you can catch logs from Stackdriver (docs). Those services allow you to store only the logs related to the deployment of a Cloud Function.
The store could be, for instance, Cloud Firestore. As you should know, there is available a trigger for Cloud Firestore events.
Finally, every time an event log related to a function's deployment is generated, it will be stored and triggers a function attached to that event. In the function, you can parse or filter the logs.

How can I initialize firebase app in a serverless model? What is the concurrent app limit?

I created a serverless function that performs that Firebase Token Validation.
Everything works as intended. Except, I have I get errors on subsequent calls to initialize my app that the default app already exists (same container). This raises some questions.
If my serverless infrastructure was to spin up multiple concurrent containers, each working to initialize the app. Would this also cause this error? That the app is initiailized elsewhere? Or is this error isolated to local instances?
If its the latter, If I provide a named app based on the container it is spun up in, is there a firebase limit to the maximum number of apps that can be initialized at once?
This is how I am initializing the app now:
cred = credentials.Certificate(SERVICE)
firebase_admin.initialize_app(cred)
I could do this but am not sure about firebase app limits or concurrent initializations (cant find any specifics in docs):
cred = credentials.Certificate(SERVICE)
firebase_admin.initialize_app(cred, 'APP-NAME-[CONTAINERID]')
Or, should I just re-write this using my own JWT Decoder and grabbing the public keys from google?
And here is the full error:
Error occurred setting firebase credentials: The default Firebase app already exists. This means you called initialize_app() more than once without providing an app name as the second argument. In most cases you only need to call initialize_app() once. But if you do want to initialize multiple apps, pass a second argument to initialize_app() to give each app a unique name.
UPDATE: AWS Lambda, Python.
I am going to test out with the following, to prevent re-initializing the app within the same container on warm function executions and move forward with the assumption that there are no API limits on performing auth.validate_id_token() and that this won't conflict with concurrent container executions. Ill report back if it tests out differently.
try:
firebase_admin.get_app()
logger.info('firebase already intialized.')
except ValueError as e:
logger.info('firebase not initialized. initialize.')
cred = credentials.Certificate(SERVICE)
firebase_admin.initialize_app(cred)
I will probably still migrate to another JWT validation to reduce function size (since I already have a jwt library for my own app use) and migrate away from relying on Firebase API to decode it.
If you get an error when initializing the admin SDK that says the default app already exists, that just means you're trying to init the admin SDK twice in the same process. Obviously, don't do that. If you init once and only once per process, you will never see this error.
You will have to take some care to only call the init method once per server instance. It's not clear exactly what you're doing from the code you've shown. I don't know about python, but with node, you can init once in a global context without problems. If you need to init during a function execution, you should have some flag to check that ensures the default Firebase app hasn't already been initialized, and init only conditionally based on that flag.

How to carry out anything other than clients.openWindow upon onnotificationclick?

I have successfully used onnotificationclick to handle actions and so far the only thing I can do is clients.openWindow().
I tried using importScripts() to include firestore JS SDK and write something to firestore, doesn't work.
I tried tried using importScripts() to include functions JS SDK and invoke functions as such: https://firebase.google.com/docs/functions/callable, doesn't work.
Chrome throws "Illegal invokation". I believe because the JS SDK uses something not available in service worker (e.g. XHR)?
Can I use fetch() to interact with firestore REST endpoint or invoke cloud functions?
FYI I am trying to implement a Mute or Archive action that requires invoking some cloud function or updating firestore DB.

Resources