I tried reducing costs by limiting Cloud Functions (gen2) warm instances to business hours only

I tried reducing costs by limiting Cloud Functions (gen2) warm instances to business hours only

We achieved approximately 57% cost reduction by enabling min-instances for Cloud Functions (2nd gen) only during business hours using Cloud Scheduler. We will share the pitfalls and solutions we encountered, including workarounds for lack of PATCH support and 409 errors from the Cloud Run API.
2026.08.02

This page has been translated by machine translation. View original

Introduction

I had set min-instances=1 on a chatbot running on Cloud Functions (2nd generation) to avoid cold starts. However, it seemed wasteful to maintain a warm instance at all times when there was almost no usage during nights and weekends.

When I looked into whether I could "keep it warm only during business hours," I found that Cloud Scheduler could handle this. However, there were a few pitfalls I encountered, so I'm sharing them here.

What I Want to Do

Time Period min-instances State
Weekdays 9:00〜18:00 1 Warm (immediate response)
Other times (nights/weekends) 0 Cold (starts on first request)

I'll create two Cloud Scheduler jobs to switch minInstanceCount=1 at the start of business hours and minInstanceCount=0 at the end of business hours.

cloud-scheduler-min-instances-business-hours-architecture

Prerequisites

  • Cloud Functions 2nd generation (Cloud Run based)
  • Region: asia-northeast1
  • Runtime: Python 3.14
  • roles/editor is granted to the default compute service account

Pitfall 1: Cloud Scheduler Does Not Directly Support the PATCH Method

The PATCH method is required to partially update resources with the Cloud Run / Cloud Functions Admin API. However, only DELETE, GET, HEAD, POST, PUT can be specified with Cloud Scheduler's --http-method. PATCH is not available.

ERROR: argument --http-method: Invalid choice: 'patch'.
Valid choices are [delete, get, head, post, put].

Workaround: Use PUT + X-HTTP-Method-Override: PATCH header. This method is also introduced in the Google Cloud official documentation.

--http-method=PUT \
--headers="Content-Type=application/json,X-HTTP-Method-Override=PATCH"

Pitfall 2: The Cloud Run API Returns a 409 Error

At first, I tried using the Cloud Run Admin API (run.googleapis.com).

https://run.googleapis.com/v2/projects/YOUR_PROJECT/locations/REGION/services/YOUR_SERVICE?update_mask=scaling.min_instance_count

However, requests consistently failed with HTTP 409 (Conflict).

{
  "debugInfo": "URL_ERROR-ERROR_OTHER. Original HTTP response code number = 409",
  "status": "ABORTED"
}

Cause: Cloud Functions (2nd generation) runs on Cloud Run, but directly updating a service managed by Cloud Functions via the Cloud Run API causes a conflict in the management layer.

Solution: Use the Cloud Functions v2 API (cloudfunctions.googleapis.com) instead of the Cloud Run API.

https://cloudfunctions.googleapis.com/v2/projects/YOUR_PROJECT/locations/REGION/functions/YOUR_FUNCTION?updateMask=serviceConfig.minInstanceCount

The request body should also be formatted to match the Cloud Functions API:

{"serviceConfig": {"minInstanceCount": 1}}

Creating Cloud Scheduler Jobs

Here are the final commands taking into account the two pitfalls above.

Warm Start Job (Start of Business Hours)

gcloud scheduler jobs create http warm-bot-start \
  --location=asia-northeast1 \
  --schedule="0 9 * * 1-5" \
  --time-zone="Asia/Tokyo" \
  --uri="https://cloudfunctions.googleapis.com/v2/projects/YOUR_PROJECT/locations/REGION/functions/YOUR_FUNCTION?updateMask=serviceConfig.minInstanceCount" \
  --http-method=PUT \
  --headers="Content-Type=application/json,X-HTTP-Method-Override=PATCH" \
  --message-body='{"serviceConfig":{"minInstanceCount":1}}' \
  --oauth-service-account-email=YOUR_SA@developer.gserviceaccount.com

Warm Stop Job (End of Business Hours)

gcloud scheduler jobs create http warm-bot-stop \
  --location=asia-northeast1 \
  --schedule="0 18 * * 1-5" \
  --time-zone="Asia/Tokyo" \
  --uri="https://cloudfunctions.googleapis.com/v2/projects/YOUR_PROJECT/locations/REGION/functions/YOUR_FUNCTION?updateMask=serviceConfig.minInstanceCount" \
  --http-method=PUT \
  --headers="Content-Type=application/json,X-HTTP-Method-Override=PATCH" \
  --message-body='{"serviceConfig":{"minInstanceCount":0}}' \
  --oauth-service-account-email=YOUR_SA@developer.gserviceaccount.com

Key Points:

  • --schedule="0 9 * * 1-5" is Monday–Friday at 9:00, "0 18 * * 1-5" is Monday–Friday at 18:00
  • --time-zone="Asia/Tokyo" specifies JST
  • For --oauth-service-account-email, specify a service account with Cloud Functions update permission (cloudfunctions.functions.update). roles/editor is sufficient

Verification

Run the jobs manually to verify they work.

# Manually trigger warm start
gcloud scheduler jobs run warm-bot-start --location=asia-northeast1

# Check job status (status: {} = success)
gcloud scheduler jobs describe warm-bot-start --location=asia-northeast1 \
  --format="yaml(status,lastAttemptTime)"

Confirm that min-instances has actually changed:

# Check with Cloud Functions API
gcloud functions describe YOUR_FUNCTION --region=asia-northeast1 --gen2 \
  --format="value(serviceConfig.minInstanceCount)"

# Can also be verified with Cloud Run annotations
gcloud run services describe YOUR_FUNCTION --region=asia-northeast1 \
  --format="value(spec.template.metadata.annotations.'autoscaling.knative.dev/minScale')"

If status: {} is returned, it's a success. If status.code: 10 is returned, it's an HTTP 409 error, so check whether the API endpoint is correct.

Cost Comparison

Configuration Warm Hours/Month Estimated Monthly Cost
Always min-instances=1 ~730 hours ~¥8,500〜10,000
Scheduled (Mon–Fri 9–18) ~195 hours ~¥2,300〜2,700

This results in approximately 73% cost reduction.

Cold Start Trade-offs

Since min-instances=0 during off-hours (nights and weekends), a cold start will occur on the first request during those times.

Specifically:

  • Container startup, Python runtime initialization, and dependency package loading will run
  • If dependency packages are heavy (e.g., google-cloud-aiplatform), startup can take more than 30 seconds
  • For services with HTTP response timeouts like Google Chat, a timeout error may be displayed (the processing itself continues and the response is returned with a delay)

Enabling startup-cpu-boost will speed up startup, but it cannot be completely eliminated.

# Enable startup-cpu-boost (first time only)
gcloud run services update YOUR_FUNCTION --region=asia-northeast1 --startup-cpu-boost

If usage during off-hours is low, this trade-off should be acceptable.

Summary

  • Using Cloud Scheduler + Cloud Functions v2 API, I was able to build a mechanism to maintain min-instances=1 only during business hours
  • For services managed by Cloud Functions (gen2), the Cloud Functions v2 API must be used instead of the Cloud Run API (to avoid 409 errors)
  • Since Cloud Scheduler does not support PATCH, work around it with PUT + X-HTTP-Method-Override: PATCH
  • Approximately 73% cost reduction (compared to always-warm). There is a trade-off of cold starts occurring outside business hours

Share this article