Google Cloud Run Limitations: When Serverless Complexity Outweighs the Cost Savings

We start using Google Cloud Run for the right reasons: it is cheap, it abstracts the nightmare of managing a Kubernetes cluster, and it speaks Docker natively. For a simple API, it is unbeatable. However, as requirements evolve toward high availability and large file processing, the “simplicity” of serverless begins to look like a distributed monolith with too many moving parts. If you are fighting the platform to implement basic features like file uploads, you have likely outgrown the service.


Managing High Cold Starts with Large Docker Images

A 1GB Docker image is a heavy lift for any serverless environment. Cloud Run scales to zero to save costs, but pulling a 1GB image results in unacceptable cold starts. Manual scaling with a minimum instance count solves the latency issue but effectively turns Cloud Run into a more expensive, restricted version of a Virtual Machine.

If you are paying to keep instances alive 24/7 to avoid cold starts, the “pay only for what you use” argument loses its teeth. You are now paying for reserved resources plus the overhead of the Cloud Run abstraction layer.


Bypassing the 32MB Request Limit with Signed URLs

Cloud Run imposes a 32MB limit on request bodies. When your business logic requires 1GB uploads, you are forced to use Google Cloud Storage (GCS) signed URLs. This architectural shift forces your frontend to handle multiple requests—one to fetch the URL and another to perform the PUT request—increasing the surface area for client-side failures.

async function uploadFile(file) {
  const response = await fetch('/api/get-signed-url', {
    method: 'POST',
    body: JSON.stringify({ fileName: file.name })
  });
  const { url } = await response.json();

  return fetch(url, {
    method: 'PUT',
    body: file,
    headers: { 'Content-Type': file.type }
  });
}

While this offloads the heavy lifting from your backend, it requires the frontend to be “cloud-aware,” breaking the clean separation of concerns we usually strive for.


The Reliability Gap in Event-Driven Architectures

Once the file is in GCS, you need to process it. Relying on the frontend to call an upload-confirm endpoint is a recipe for data inconsistency. Users close tabs, lose connection, or experience crashes immediately after the upload finishes but before the confirmation is sent.

The robust solution is using Eventarc or GCS Pub/Sub triggers to notify your backend. However, this introduces:

  • A Pub/Sub topic to manage.
  • A push subscription pointing to your backend webhook.
  • Logic to handle retries and idempotency in your backend.

Your simple CRUD application has now become an asynchronous, event-driven system requiring distributed tracing to debug a single file upload.


The HTTP/2 and Networking Overhead

Attempting to move network logic like HTTP/2 streaming into the application layer to bypass limits is a common pitfall. Infrastructure concerns should stay at the infrastructure level; once you start writing application code to handle low-level protocol nuances, you are accumulating technical debt that will be painful to maintain. Cloud Run’s ingress is designed to be opinionated; fighting that opinion usually leads to brittle code.


Is Cloud Run Still the Right Choice?

If your architecture requires manual scaling to survive cold starts, signed URLs to bypass payload limits, and a Pub/Sub chain to ensure data integrity, Cloud Run is no longer providing the “simplicity” you are paying for.

For this specific profile—large images and heavy data ingestion—you might be better served by:

  • GKE Autopilot: You get the benefits (and complexity) of Kubernetes without the management overhead, and it handles large pods and streaming better.
  • Compute Engine with Managed Instance Groups (MIGs): Old school, but predictable. You can stream 1GB files directly to your process and avoid the complexity of signed URLs and webhooks. But be aware that’s being deprecated and soonly unavailable.

The moment your “simple” serverless setup requires five extra GCP services to function, the abstraction has failed.


Discover more from The Dev World – Sergio Lema

Subscribe to get the latest posts sent to your email.


Comments

Leave a comment