firebasegoogle-cloud-platformgoogle-cloud-functionscold-start

How dependencies affect Firebase Functions cold start?


Background: I have about 20 Firebase Cloud Functions and they are stored under the default reporoot/functions/src/ folder, which was created when I first init the Firebase project. Each Cloud Function could have its own unique dependencies and all are specified inside one single reporoot/functions/package.json

Simplified version of the scenario:

  1. In the package.json, I specify dependencies LibA, LibB and devDpendencies LibX
  2. In reporoot/functions/src/myFeatureSet1/feature1.ts, I have a CloudFunction1 by import functionA from LibA
  3. In reporoot/functions/src/myFeatureSet2/feature2.ts, I have a CloudFunction2 by import functionB from LibB

Questions:

  1. If I deploy CloudFunction1 to Firebase, will LibA, LibB and LibX be packaged together in the same deployment zip? Or only LibA will be included in the package to be deployed because that is the only library referred by CloudFunction1 in reporoot/functions/src/myFeatureSet1/feature1.ts?
  2. Will the number of dependencies impact the cold start time of all functions developed with the same package.json under reporoot/functions/src/? Or the deployed Cloud Function will have only what it needs without extra dependencies included?
  3. Similar to the Question 2 above, how about devDependencies? Will the number of devDependencies impact the cold start time of all functions developed with the same package.json under reporoot/functions/src/? I think deploying the Cloud Function associates npm i --production and hence will not include devDependencies. Is that true?
  4. If the answer for Q2/Q3 above is Yes, how can I split the dependencies among different functions so that each function packages with only the required dependencies? I read https://firebase.google.com/docs/functions/organize-functions#managing_multiple_source_packages_monorepo and it shows different package.json files for different codebase. But following that leads to the following error when I start using the emulator: functions: Failed to load function definition from source: FirebaseError: Error parsing triggers: Cannot find module 'axios'. Note: One of my Cloud Function uses axios and I put it inside one package.json
  5. Will there be any code start time difference if I import functionA from LibA VS import * from LibA?

Solution

  • The following assumes your index.ts is similar to:

    export functionA from LibA
    export functionB from LibB
    

    and each function is similar to:

    export default functions.https.onRequest(/* ... */);
    
    1. If I deploy CloudFunction1 to Firebase, will LibA, LibB and LibX be packaged together in the same deployment zip?

    As LibA and LibB are true dependencies, they will be installed when deployed. As LibX is in devDependencies, it will be skipped.

    However, if the source code of LibA, LibB and LibX are all inside the reporoot/functions folder, they will all get deployed, but not necessarily executed (that would depend on your code).

    1. Will the number of dependencies impact the cold start time of all functions developed with the same package.json under reporoot/functions/src/? Or the deployed Cloud Function will have only what it needs without extra dependencies included?

    By default, dependencies will affect the cold start of all functions.

    There are some ways you can structure your code to mitigate this, as covered in this article, this thread or by using a library like better-firebase-functions.

    You could also compile your code into many bundles, deploying a single bundle of each function, containing only that function and its dependencies. You'd do this using deploy scripts similar to those covered in this thread. This strategy can get messy if not properly thought out.

    1. Similar to the Question 2 above, how about devDependencies?

    As deployed functions are considered a production environment, you are correct in that devDependencies will not be installed in the production environment.

    1. How can I split the dependencies among different functions so that each function packages with only the required dependencies?

    One of the best approaches to achieve this is to export each function in its own functionX.ts file (preferably with an accurate name). You can then compile these as standalone bundles, invoke them using lazy loading or group them together with other similar functions. See the resources linked above for further details.

    If axios is used by only one function, either spin that function off into its own file/bundle or import it using (await import('axios')).default when you need it. The error you get suggests that one of your libraries tries to import the axios library at the top of the file, but when that library gets imported by a non-axios function (where it's omitted from the package.json file), it causes a deploy error.

    I would personally use either node-fetch-native, node-fetch or gaxios (a node-fetch-based variant of the axios APIs, used internally to the Firebase libs) instead.

    1. Will there be any code start time difference if I import functionA from LibA VS import * from LibA?

    Based on just that line, it's hard to tell. It would depend on the contents of your library and the build tool you are using. The default compiler will just convert your TypeScript to plain JavaScript regardless of whether its used or not. Whereas if your build tool supports "tree shaking", it will only compile what it needs ignoring the unnecessary "leaves" (unused code). Popular tree shaking tools include Webpack and Rollup.