Skip to content

Gen 2 function deploys can exhaust regional CPU/memory because release concurrency is fixed at 40 #11001

Description

@EthanSK

[REQUIRED] Environment info

firebase-tools: 15.25.1. I also checked 15.28.2, the latest release at the time of filing, and the relevant concurrency remains unchanged.

Platform: Ubuntu (GitHub Actions), Node.js 22

[REQUIRED] Test case

A Firebase project with approximately 70 Gen 2 functions in one region. Many functions share a bundle, so a shared change causes most of them to require new revisions in one deploy.

The project's regional Cloud Run quota is 20 vCPU / 40 GiB.

[REQUIRED] Steps to reproduce

  1. Change shared function code so many Gen 2 functions need an update.
  2. Run:
firebase deploy --only functions --project <project> --non-interactive --force
  1. Observe many Gen 2 update operations being released concurrently.

The Gen 2 update path uses functionExecutor, while the release executor has a fixed concurrency of 40:

  • const fnsToUpdateSafe = await prompts.promptForUnsafeMigration(fnsToUpdate, options);
    const safeEndpoints = new Set(fnsToUpdateSafe.map((eu) => eu.endpoint));
    for (const changes of allRegionalChanges) {
    changes.endpointsToUpdate = changes.endpointsToUpdate.filter((eu) =>
    safeEndpoints.has(eu.endpoint),
    );
    }
    const throttlerOptions = {
    retries: 30,
    backoff: 20000,
    concurrency: 40,
    maxBackoff: 100000,
    };
    // N.B. THIS IS TEMPORARY
    // This will limit concurrent deploys of run functions to two while zip deploy capacity
    // is low.
    const runThrottlerOptions = {
    ...throttlerOptions,
    concurrency: 2,
    };
    const projectNumber = options.projectNumber || (await getProjectNumber(context.projectId));
  • async updateV2Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise<void> {
    const storageSource = this.sources[endpoint.codebase!]?.storage;
    if (!storageSource) {
    logger.debug("Precondition failed. Cannot update a GCFv2 function without storage");
    throw new Error("Precondition failed");
    }
    const apiFunction = gcfV2.functionFromEndpoint({ ...endpoint, source: { storageSource } });
    // N.B. As of GCFv2 private preview the API chokes on any update call that
    // includes the pub/sub topic even if that topic is unchanged.
    // We know that the user hasn't changed the topic between deploys because
    // of checkForInvalidChangeOfTrigger().
    if (apiFunction.eventTrigger?.pubsubTopic) {
    delete apiFunction.eventTrigger.pubsubTopic;
    }
    const resultFunction = await this.functionExecutor
    .run(
    async () => {
    if (experiments.isEnabled("functionsv2deployoptimizations")) {
    apiFunction.buildConfig.sourceToken = await scraper.getToken();
    }
    const op: { name: string } = await gcfV2.updateFunction(apiFunction);
    return await poller.pollOperation<gcfV2.OutputCloudFunction>({
    ...gcfV2PollerOptions,
    pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`,

firebase deploy --help does not expose a supported way to lower this concurrency.

[REQUIRED] Expected behavior

A bulk functions deploy should schedule Gen 2 updates so temporary revision health checks do not exhaust the project's regional CPU/memory quota.

At minimum, the CLI should expose a supported concurrency setting so CI can choose a value appropriate for the project's quota and function CPU/memory sizes. A quota-aware default would be even better.

[REQUIRED] Actual behavior

One deploy produced 56 errors like these, many within the same second:

Could not create or update Cloud Run service <service>, Container Healthcheck failed.
Quota exceeded for total allowable CPU per project per region.

The deployment failed even though the services themselves were healthy and deployable individually.

Workarounds tried:

  • CPU-aware batches still failed because old/new revision overlap and deployment health checks consume regional quota.
  • Fixed 15-minute waits made the deployment take more than 90 minutes and still eventually hit the memory quota.
  • Pre-updating existing Cloud Run services with gcloud run services update --no-deploy-health-check allowed the same bulk Firebase deployment to complete, but this removes eager startup validation and cannot prepare a service that does not exist yet.

This is specifically about aggregate CPU/memory consumed by concurrent Gen 2 revision deployment health checks. It is separate from Cloud Functions API write-rate quota errors and from a function's configured maxInstances being individually too high.

Related older reports cover retries/rate limits or the CLI exit status, but not a supported limit for this Gen 2 release concurrency:

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions