---
cuppy_hub: gcp
cuppy_path: examples/estimate-a-three-tier-migration.md
cuppy_section: docs
cuppy_doc: J0P2-B8GE
cuppy_doc_uuid: d1bee8de-c8b7-476e-b4ed-46cdcb7e37a1
cuppy_permalink: https://cuppy.io/gcp/d/J0P2-B8GE
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/gcp/docs/examples/estimate-a-three-tier-migration
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 88b87f230fd40fcb8f922d3892d967a23736ed229e2d8efab67245ab42691c1d
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Worked example — a three-tier application

**The estate is illustrative.** Every Google Cloud property cited is sourced from the documents in this hub;
the workload's own numbers are placeholders, labelled as such, so the *shape* of the arithmetic is the lesson
rather than the totals.

## The estate, as inventoried

| Component | As found |
|---|---|
| web tier | 4 VMs behind a load balancer, one region |
| app tier | 4 VMs, same region |
| database | **SQL Server**, one primary with a standby |
| object storage | a bucket of documents, "most of it old" |
| queue | one queue, one consumer whose only job is writing events to the warehouse |
| warehouse | a cluster-shaped data warehouse |
| observability | vendor-SDK tracing, 40 dashboards, 60 alerting policies |

## Step 1 — measure (the eight inputs)

| Input | Found |
|---|---|
| utilisation | web tier averages **~15%** CPU; app tier **~35%**. *Placeholder, and the most important number here* |
| dependencies | app → database, app → queue, web → app. **Nothing crosses into the warehouse synchronously** |
| bytes to move | database *and* bucket; the bucket dominates |
| identities, keys | one shared service principal, **one long-lived key**, 6 secrets |
| observability volume | logs GB/day, metric cardinality, trace volume |
| dashboards / policies | **40 / 60** |
| permissions actually used | a small subset of what is granted |
| request profile | queries/day, bytes scanned, messages/day and **average message size** |

**What step 1 already decides:** the web tier at 15% utilisation is the single largest cost line in the
estimate and the single largest saving. Nothing else in this document moves as much money.

## Step 2 — map the names

Compute Engine for the VMs; Cloud SQL or AlloyDB for the database; Cloud Storage for the bucket; Pub/Sub for
the queue; BigQuery for the warehouse; Cloud Logging, Monitoring and Trace for observability. One global VPC
with a regional subnet.

Then stop trusting the map, per `guides/parity-and-migration-method.md` phase 2.

## Step 3 — the differences that apply, per family

| Family | The row that applies here | What it implies |
|---|---|---|
| **compute** | utilisation is 15–35%; **custom machine types exist only for N and E series** | right-size onto a general-purpose series where a custom shape is possible, and round **up** elsewhere. The biggest line |
| **databases** | **SQL Server → Cloud SQL for PostgreSQL** and **→ AlloyDB** are documented heterogeneous paths | two options: same-engine (simple, keeps the licence) or **cross-engine** (drops the licence, and the schema, procedures and data-access code become the project). Price **both** |
| **storage** | `ARCHIVE` commits a **365-day** minimum; the cold classes carry retrieval fees | "most of it old" is only cheap if nobody reads it. Get the access pattern before choosing a class, and **re-tune any lifecycle rule carried in** |
| **messaging** | a Pub/Sub **BigQuery subscription** writes messages in with no code; subscriptions are **at-least-once** by default | the consumer is **deleted** — code, deploy, monitoring and on-call. And whatever consumer survives must be **idempotent** |
| **data** | a warehouse node count does not convert | the inputs are **bytes stored** and **bytes scanned per query per day**, or slot demand. Nothing else is honest |
| **networking** | a Google Cloud **VPC is global** | one network, one subnet. No transit object to reproduce |
| **identity** | the **one long-lived key** | workload identity federation deletes it. The migration is the cheapest moment to do that |
| **observability** | **OpenTelemetry is the recommended instrumentation** | re-instrument **on the source first**; 40 dashboards and 60 policies are **re-created**, and 100 is the effort number |

**And one correctness item, not a cost item:** nothing here runs as a function, so the Lambda-concurrency risk
in `guides/parity/compute.md` §2 does not apply. **Checking that it does not apply is part of the method** —
the risk is silent when it does.

## Step 4 — waves, from the dependency graph

| Wave | Contents | Why |
|---|---|---|
| 1 | the **bucket** | nothing synchronous depends on it. Move it early, mirror new writes by event |
| 2 | **app tier + database + queue** | they talk to each other. Splitting them pays egress on every transaction |
| 3 | **web tier** | depends only on the app tier |
| 4 | the **warehouse** | nothing calls it synchronously |

Pre-flight each wave's **destination quotas**. A wave that fails half-done leaves the source drained and the
target incomplete.

## Step 5 — the three costs no calculator contains

1. **source-side egress** on the bucket and the database — billed by the source cloud, over the whole mirroring
   window;
2. **the phase-3 work:** schema conversion (if cross-engine), consumer idempotency, re-instrumentation, 100
   dashboards and policies, role mapping from the permissions actually used;
3. **the destination's commitments** — a cold storage class's minimum duration, started on arrival.

## What the estimate says — and refuses to say

**Says:**

- a **compute range**, from measured utilisation, with the rounding named per machine family;
- **two database options**, priced separately, with the cross-engine one carrying an explicit application-work
  line;
- **one component deleted** (the queue consumer) and **one key deleted**;
- **one network** instead of several, and no transit object;
- a **wave sequence** derived from measured dependencies;
- an **egress figure** in bytes, with the window it is charged over.

**Refuses to say:**

- whether the SQL and stored procedures port — that is per schema, and it is assessment work;
- whether a Google Cloud machine shape performs like the source's — no published table supports that claim;
- whether the application behaves the same afterwards — the observability baseline is what makes that
  measurable rather than arguable;
- **whether to migrate at all.** This hub documents and never acts.

## Then

Execute **wave 1**, re-run step 1 against the result, and re-estimate. One executed wave replaces more
assumptions than any further analysis.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 91 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/gcp/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
