2

I'm using the Firebase Admin SDK inside a Firebase Functions to list the content of a directory on Cloud Storage. However my function takes quite often more than 5+ seconds to answer. At first I thought this was all due to cold starts of the functions itself and I've tried numerous things to prevent cold starts of the function such as:

  • Set minInstances to 1 or more
  • Use cronjob to call function every minute (without authentication so that it doesn't actually use the admin sdk but still keeps the function warm)
  • Split functions into separate files to reduce cold start time (https://stackoverflow.com/a/47985480/13370504)
  • Try moving function closer to cloud storage (us-central1 to europe-west3)

However none of the above worked and I still get slow response times when the function is called for the first time. After adding some logging inside the function it turns out, that the Admin SDK is taking a over a second to get a single value from Realtime Database and it's taking usually over 5 seconds to run the getFiles command on Cloud Storage (The directory usually only contains a single file).

For example for the code below I'm getting the following console outputs:

listVideos: coldstart true
listVideos: duration 1: 0ms
listVideos: duration 2: 1302ms (realtime database)
listVideos: duration 3: 6505ms (getFiles on cloud storage)
listVideos: coldstart false
listVideos: duration 1: 0ms
listVideos: duration 2: 96ms (realtime database)
listVideos: duration 3: 199ms (getFiles on cloud storage)

My function looks like this:

import * as admin from "firebase-admin";

admin.initializeApp();

let coldStart = true;
exports.listVideos = functions.region("europe-west1").runWith({
  memory: "128MB",
  minInstances: 1,
}).https.onCall(async (data, context) => {
  console.log("coldstart", coldStart);
  coldStart = false;
  const t1 = new Date().getTime();
  if (context.auth) {
    const authUid = context.auth.uid;

    console.log(`duration 1: ${new Date().getTime() - t1}ms`);
    const level = (await admin.database().ref(`users/${authUid}/`).once("value")).val();
    console.log(`duration 2: ${new Date().getTime() - t1}ms (realtime database)`);

    const [files] = await admin.storage().bucket()
        .getFiles({
          prefix: `${authUid}/video`,
        });
    console.log(`duration 3: ${new Date().getTime() - t1}ms (getFiles on cloud storage)`);
    return {status: "success", files: files};
  } else {
    return {status: "error - not authenticated"};
  }
});

I know I can't expect 0ms latency but for a simple getFiles call I'd expect something under 1 second just like it is when the sdk is "warm" (considering that my whole bucket has less than 1000 files and the directory that I'd listing has only 1 file in it)

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
David Luhmer
  • 75
  • 1
  • 4
  • You have not described the data in the GCS bucket. So the GCS file size can affect the amount of time. Also, remember this is a download, not a list, so it is all relative to the size of the object. Then the other latency you complained about is the 1 second time to call the lookup to the RTDB. This again is dependent on the region. If possible I would like you to test these outside the CF on a VM in the same region as the RTDB (remember for RTDB, this is set by the firebase project creation and cannot be changed) and measure the response time? – Priyashree Bhadra Dec 17 '21 at 14:41
  • If the result is the same, then it is more to do with where you have deployed and the type of call you are making. Then repeat the test in the sameway for the Firestore get(). You should record the start and end time for the object, plus the size and add a calculation for the bps to show throughput. A side test would be to see if it is still slow if you are only listing the object. – Priyashree Bhadra Dec 17 '21 at 14:42
  • 1
    @PriyashreeBhadra My files are somewhere between 10-400mb in size. From what I got from the docs [ref](https://googleapis.dev/nodejs/storage/latest/Bucket.html#getFiles) the files are not downloaded (only metadata) so the file size should not matter. I ran the application using the firebase function emulator locally and got the following cold start times: `coldstart true duration 1: 0ms duration 2: 239ms duration 3: 381ms`. Interestingly enough I found this issue on GitHub which I think is the root cause of my problem: https://github.com/googleapis/nodejs-storage/issues/951 – David Luhmer Dec 22 '21 at 06:04
  • @PriyashreeBhadra Just to clarify: I ran the functions locally using the firebase emulator but connected to the realtime database and cloud storage in the datacenter in Belgium (europe-west1). – David Luhmer Dec 22 '21 at 06:06

1 Answers1

1

Cloud Storage isn't a database and is not optimized for queries (getFiles is effectively a query against the entire bucket using the prefix of the names of objects). It's optimized for storing and retrieving blobs of data at massive scale when you already know the name of the object.

If you want to list files quickly, consider storing the metadata of the files in a database that is optimized for the type of query that you want to perform, and link those records to your storage as needed. This is a fairly common practice in GCP projects.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thank you Doug! Do you have an explanation as to why the first getFiles query is always around 5+ seconds while subsequent calls are under 200ms? Does Cloud Storage perform some caching after the first call or is it just the Admin SDK taking a lot of time to initialize? Even the Realtime Database takes significantly more time on cold starts for a simple query (over one second). – David Luhmer Dec 17 '21 at 14:35
  • 2
    You can expect that there is some overhead in SDK initialization (which is probably lazy) and establishing a socket to the service. The GCP SDKs are all open source if you want to see what they do, build them yourself, and even add your own internal benchmarks. – Doug Stevenson Dec 17 '21 at 14:39