Worked example — estimating a three-tier application's move onto Google Cloud
The method applied end to end to one small, recognisable estate: eight VMs, a SQL Server database, an object store, a queue and a dashboard set. What gets measured, which parity rows apply, what the differences imply, and what the estimate says and refuses to say.
markdownexamplev1J0P2-B8GEstage: designtrust: officialfamily: overviewservice: migration-centerinterface: anyworked examplemigration estimatethree-tier applicationright-sizingSQL Server to PostgreSQLegresswavescorrectness riskobservability baseline
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
- source-side egress on the bucket and the database — billed by the source cloud, over the whole mirroring
window;
- the phase-3 work: schema conversion (if cross-engine), consumer idempotency, re-instrumentation, 100
dashboards and policies, role mapping from the permissions actually used;
- 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.