Decision Frameworks
Topic 52

Decision Frameworks

DecisionsFrameworks

After eleven chapters and fifty-one services, you have a vocabulary. This topic is about the skill that distinguishes a senior engineer from a junior one with the same vocabulary: given a problem, picking the right service in under a minute with the right justification. Not because you remember every detail of every service, but because you have internalized a small number of decision trees that take you to the right answer fast.

The decision trees that follow are not the only way to think about service selection, but they are durable. The exact products change generation to generation; the questions — does this need ACID across regions, does this need a POSIX filesystem, is this fan-out or one-to-one — do not. Get those questions right and the answers fall out.

The Compute Decision Tree

Asked in order, these questions choose the right compute service for almost every workload:

  1. Is this a stateless HTTP service that fits in a container?Cloud Run. The right default for new web services. Scales to zero, pay per request, no infrastructure to manage.
  2. Is it a single event-driven function with no need for a long-running container?Cloud Functions (gen 2 / Cloud Run functions). Lighter operational surface than Cloud Run for the simplest case.
  3. Does the team run many containerized services that need orchestration, complex networking, or specific Kubernetes features?GKE, ideally Autopilot. Reserve for workloads where Cloud Run is not enough.
  4. Does it need an actual OS — GPUs, custom kernels, specific OS-level configuration, legacy applications that cannot be containerized?Compute Engine. The escape hatch when managed services do not fit.
  5. Is this an existing App Engine application running well?App Engine. Keep it. For new projects, prefer Cloud Run.

The tree resolves in this order on purpose. Always start with Cloud Run as the candidate; only walk further down the tree when a real requirement rules it out. The default mistake here is reaching for GKE or Compute Engine first out of comfort with old patterns.

Stateless HTTP service that fits in a containerCloud Run
Single event-driven function, no long-running containerCloud Functions
Many services needing orchestration or specific Kubernetes featuresGKE
Needs a real OS — GPUs, custom kernel, un-containerizable legacyCompute Engine
Existing App Engine app already running wellApp Engine

The Database Decision Tree

Databases have more dimensions than compute does. The right service depends on the data model, the consistency requirement, and the scale axis.

  1. Is this analytical (large scans, aggregations, infrequent writes)?BigQuery. Not transactional, not for user-facing low-latency reads.
  2. Does it need ACID transactions across global regions with horizontal write scaling?Cloud Spanner. A specific class of problem with a specific solution.
  3. Is it relational (PostgreSQL, MySQL, SQL Server) at regional scale?Cloud SQL as the default. Move to AlloyDB only when a measured Cloud SQL performance limit is hit.
  4. Is it document-structured, mobile-friendly with real-time sync needs?Firestore Native mode.
  5. Is it high-throughput time series, IoT telemetry, operational analytics with known key access patterns?Bigtable. The wide-column NoSQL workhorse.
  6. Is this a cache or session store needing sub-millisecond latency?Memorystore. Redis (or Valkey) for the access pattern; never as a primary data store.

The frequent mistake here is the inverse of the compute tree: starting with the most powerful option (Spanner because "we'll need global scale eventually") instead of the simplest one that fits today (Cloud SQL until measured limits force the migration).

Analytical — large scans, aggregations, infrequent writesBigQuery
ACID transactions across global regions, horizontal writesSpanner
Relational (Postgres/MySQL/SQL Server) at regional scaleCloud SQL
Document-structured, mobile, real-time syncFirestore
High-throughput time series, IoT, known key patternsBigtable
Cache or session store, sub-millisecond latencyMemorystore

The Storage Decision Tree

Three storage services with different shapes:

  1. Object access by key over HTTP, files of any size, globally accessible?Cloud Storage. The default for almost everything: build artifacts, media, backups, data lake ingest, static site hosting.
  2. Block device attached to a VM, filesystem mounted in OS, single-VM access (or read-only multi-attach)?Persistent Disk. The boot disk and the database disk.
  3. POSIX shared filesystem with simultaneous read-write access across multiple VMs?Filestore. The narrowest niche of the three — only when the workload genuinely needs POSIX shared semantics that Cloud Storage cannot deliver.
Object access by key over HTTP, any size, globally accessibleCloud Storage
Block device on one VM, OS filesystem mountedPersistent Disk
POSIX filesystem shared read-write across many VMsFilestore

The Messaging Decision Tree

The decision tree from Chapter 5, applied:

  1. Broadcast fan-out — one event, many independent consumers?Pub/Sub.
  2. Rate-limited HTTP call to one specific endpoint with retry?Cloud Tasks.
  3. Recurring schedule that triggers other work?Cloud Scheduler for recurring cron, or Cloud Tasks with schedule_time for one-off deferred work.
  4. Multi-step workflow with conditional logic, error handling, and rollback?Cloud Workflows.
  5. Reacting to GCP events from many sources, especially audit logs?Eventarc.

The most common error in this tree is conflating Pub/Sub and Cloud Tasks. Re-read the Cloud Tasks page if the choice is not obvious.

One event, many independent consumers (fan-out)Pub/Sub
Rate-limited delivery to one target (HTTP or App Engine), with retriesCloud Tasks
Recurring schedule (cron) that triggers workCloud Scheduler
Multi-step workflow with conditionals and error handlingCloud Workflows
Reacting to events from Google Cloud, SaaS, or custom sourcesEventarc

The Identity Decision Tree

Identity has three distinct dimensions:

  1. End users of a B2C app (customers, mobile app users)?Firebase Authentication, or Identity Platform for enterprise-feature variants.
  2. Internal employees, contractors, GCP operators?Cloud Identity with SAML SSO to your corporate IdP — or skip Google accounts entirely with Workforce Identity Federation.
  3. Workloads outside GCP that need to call GCP services?Workload Identity Federation. Almost never service account keys.
End users of a B2C app (customers, mobile users)Firebase Auth
Internal employees, contractors, GCP operatorsCloud Identity
Workloads outside GCP that must call GCP APIsWorkload Identity Fed.

Decision Anti-Patterns

A few patterns of bad decisions that recur across teams:

  • Choosing for resume — picking GKE because it looks impressive on a CV when Cloud Run handles the workload. The team carries operational overhead forever for one nice line on a profile.
  • Premature optimization for scale — Spanner for an app that will never need global writes. The cost is real and immediate; the benefit is hypothetical and distant.
  • Familiarity bias — every former AWS engineer reaching for EC2-equivalent because that is what they know, instead of Cloud Run. The mental model carries over; the operational discipline does not need to.
  • "It might not scale" — over-engineering with no measurement. Without numbers, the decision is fear-based. Decisions should be load-based.
  • The latest service reflex — picking the newest GCP service because it was announced last quarter. New services lack operational maturity; respect that.
Decision Anti-Patterns
  • Over-engineering for hypothetical scale that does not yet exist and may never exist. Build for the load you have, plus one order of magnitude — not three.
  • Choosing services because they are new, novel, or look good on a CV. Operational maturity matters; the boring tech is often the right tech.
  • Re-implementing in code what a managed service already does. The "we have special requirements" claim is wrong far more often than it is right.
  • Stuck on legacy because change feels risky, even when the new service is dramatically cheaper or simpler. Risk has direction — staying still has a risk too.
  • "We always use X" as a substitute for re-deciding when the workload's shape changes. Every choice should be re-decidable when load patterns shift.
  • Familiarity bias — reaching for EC2-equivalents because that is what the team knows from another cloud. Map the question, not the service.
Best Practices
  • Measure first, optimize second. A decision based on actual load is durable; a decision based on guess is mostly wrong.
  • Start with the simplest service that fits today. Migrate when the next requirement actually demands a different one.
  • Document why each service was chosen. The architecture decision record (ADR) is what protects the next engineer from undoing your work for the wrong reason.
  • Revisit decisions when load patterns change. The right answer six months ago may be wrong now; that is normal, not a failure.
  • Respect the "boring tech" principle for systems that need to be stable. Newness has operational cost.
  • When in doubt, default to the right side of each tree — Cloud Run, Cloud SQL, Cloud Storage, Pub/Sub, Cloud Identity, Firestore. These are the right answers far more often than the more powerful alternatives.
Comparable services AWS Well-Architected Framework + decision guidesAzure Cloud Adoption Framework + Architecture Center

Knowledge Check

A team is starting a new stateless HTTP API in Go. They expect modest traffic with significant idle periods. Which compute service is the right default?

  • Cloud Run — stateless containerized HTTP service that scales to zero is exactly its sweet spot
  • GKE Autopilot — managed Kubernetes is the modern default for any new HTTP service, even a single one
  • Compute Engine with a managed instance group — the most flexible compute primitive available
  • App Engine Flexible — the best fit for HTTP services on the native Go runtime

A workload needs ACID transactions across multiple regions with horizontal write scaling for a fintech ledger. Which database is the right fit?

  • Cloud SQL with cross-region read replicas — replicas extend ACID across regions
  • BigQuery — built for global-scale data with regional replication
  • Cloud Spanner — the specific class of problem it was designed for
  • Firestore in Native mode — multi-region strong consistency is its default

An app needs to call a third-party API rate-limited at 10 requests per second, with retry on transient failure. Which service?

  • Pub/Sub — publish the events to a topic and let the downstream subscriber consumers enforce the 10-requests-per-second rate limit themselves
  • Cloud Tasks — one task per call, dispatch rate matches the third-party limit, automatic retry on transient failure
  • Cloud Workflows — orchestrate the calls with explicit retry blocks defined on each step
  • Eventarc — schedule and dispatch each individual call as a triggered event

A team wants to use Spanner from the start because "we might go global eventually". The app currently has 1000 daily users, all in one region. What is wrong with this decision?

  • Spanner is the right choice here; moving to Cloud SQL would be a strict downgrade for this team
  • Premature optimization for scale that does not exist — Cloud SQL fits the current load at a fraction of the cost and complexity, with a documented migration path if and when real scale appears
  • Spanner cannot start in a single-region configuration at all; it forces an expensive multi-region setup from the very first day of use, even for an app whose users all happen to sit in one region today
  • Spanner does not support the SQL dialect the team already relies on; they should adopt AlloyDB instead

An external partner workload running on AWS needs to call GCP APIs. What is the right authentication path?

  • Create a Cloud Identity human user account for the AWS workload and have it sign in through OAuth
  • Generate a long-lived service account key, store the downloaded JSON key file in AWS Secrets Manager, and load it from the workload on each call
  • Workload Identity Federation — configure GCP to trust the AWS STS identity, exchange it for short-lived GCP tokens, no key file ever exists
  • Firebase Auth — federate the AWS workload's machine identity through Firebase Authentication tokens

A team needs a shared filesystem accessible to multiple Cloud Run instances simultaneously for reading and writing user-uploaded files. What is the right storage choice?

  • Filestore — its POSIX shared filesystem fits the multi-instance read-write requirement perfectly here
  • Persistent Disk with read-only multi-attach — let every concurrent Cloud Run instance see and read from the same shared block volume simultaneously for serving the user-uploaded files back out again
  • Cloud Storage — object storage over HTTP is the right shape for Cloud Run file uploads; Filestore does not natively mount to Cloud Run, and POSIX semantics are not actually required for uploads
  • Bigtable — its low-latency wide-column NoSQL store is the right alternative to a shared filesystem for holding the user-uploaded file blobs

When should you walk further down the compute decision tree from Cloud Run?

  • Whenever the workload's sustained traffic exceeds roughly 10 requests per second at peak
  • When a real requirement rules Cloud Run out — complex Kubernetes features needed (GKE), GPUs or specific OS-level configuration (Compute Engine), or an existing App Engine app that is running well (App Engine)
  • When the team already has prior Kubernetes operating experience from past projects and would simply prefer to keep using GKE so that the team's hard-won operational familiarity does not go to waste on a new platform
  • When the application happens to be written in a programming language that Cloud Run does not support

You got correct