CLOUD SPANNER → STACKIT · ARCHITECTURE
Google Cloud Spanner migration strategies
StackToIT classifies Spanner as a redesign and writes no Terraform for it, because horizontal scale-out with external consistency has no STACKIT counterpart. The real question is how much of that guarantee your workload uses. This page sets four options side by side, shows what a run records for a Spanner estate and gives the questions that decide between PostgreSQL Flex, a distributed SQL database you operate, leaving Spanner in place, or splitting the workload.
What the run does with a Spanner estate
A demo estate written for this page and assessed by StackToIT on 4 November 2026: one instance, a GoogleSQL database with two interleaved tables and a PostgreSQL-dialect database, plus a reader grant. Left: the instance and the first database from the input. Right: three verbatim fragments of the unit in assessment.json (its id, class and target, its reason), then two console lines.
resource "google_spanner_instance" "ledger" {
name = "ledger"
config = "regional-europe-west3"
display_name = "Ledger"
processing_units = 1000
}
resource "google_spanner_database" "accounts" {
instance = google_spanner_instance.ledger.name
name = "accounts"
database_dialect = "GOOGLE_STANDARD_SQL"
deletion_protection = true
version_retention_period = "3d"
ddl = [
"CREATE TABLE Accounts (AccountId STRING(36) NOT NULL, Owner STRING(128), Balance NUMERIC) PRIMARY KEY (AccountId)",
"CREATE TABLE Entries (AccountId STRING(36) NOT NULL, EntryId STRING(36) NOT NULL, Amount NUMERIC, CreatedAt TIMESTAMP OPTIONS (allow_commit_timestamp=true)) PRIMARY KEY (AccountId, EntryId), INTERLEAVE IN PARENT Accounts ON DELETE CASCADE",
]
}"id":"google_spanner_instance.ledger"
"class":"REDESIGN","confidence":"MEDIUM","target":{"service":"PostgreSQL Flex (single region) or a self-managed distributed SQL database — data-model redesign","resources":["stackit_postgresflex_instance"]
"reason":"Spanner's horizontal scale-out with external consistency (TrueTime) has no STACKIT counterpart. Workloads that fit one region move to PostgreSQL Flex (Spanner's PostgreSQL dialect eases this); truly global ones need a distributed SQL database operated on SKE."Source 4 resources · 1 unit · visibility: Complete
Migration 0 drafted · 1 manual (of 1 units the draft considered)Four resources, one unit, no draft
The instance, both databases and the grant form one unit. The run drafts nothing for it: zero units drafted, one left for the architect, and terraform/ holds only the provider file, the variables file and a README that lists the unit under the work left for the architect.
The reason is the decision
The class is REDESIGN at medium confidence. The stated reason is that horizontal scale-out with external consistency, built on TrueTime, has no STACKIT counterpart. Workloads that fit one region move to PostgreSQL Flex, and truly global ones need a distributed SQL database operated on SKE.
Data movement is undecided
The data-movement file lists the unit as decision required with the mechanism unknown. The size of the data, its change rate, the downtime and the recovery point are not in the source, and the run states that it never moves data.
Regional or multi-region: the run does not tell them apart
The demo was assessed twice, once with a regional configuration and once, without the reader grant, with the three-continent multi-region configuration nam-eur-asia1 and three nodes. The Google documentation lists that configuration as multi-region, with read-write, read-only and witness replicas across continents.
name = "ledger"
config = "regional-europe-west3"
display_name = "Ledger"
processing_units = 1000 name = "ledger"
config = "nam-eur-asia1"
display_name = "Ledger"
num_nodes = 3"id":"google_spanner_instance.ledger"
"class":"REDESIGN","confidence":"MEDIUM","target":{"service":"PostgreSQL Flex (single region) or a self-managed distributed SQL database — data-model redesign","resources":["stackit_postgresflex_instance"]
"reason":"Spanner's horizontal scale-out with external consistency (TrueTime) has no STACKIT counterpart. Workloads that fit one region move to PostgreSQL Flex (Spanner's PostgreSQL dialect eases this); truly global ones need a distributed SQL database operated on SKE."Same output, different workloads
Class, target service and reason were identical in both runs. The configuration is listed as a critical setting for residency, and the run does not choose an option from it. Reading the instance configuration is your first design step.
What the choice rests on
Spanner states that external consistency is its strictest guarantee, applied by default or with serializable isolation, and that TrueTime timestamps let clients read consistently across an entire database, even across regions, without blocking writes. Whether your application relies on that across regions or only within one is the fact that separates the options.
Four options compared
| Option | Where it fits | What you take on |
|---|---|---|
| PostgreSQL Flex, single region | Workloads that fit one region; the PostgreSQL dialect of Spanner eases the move | Rewrite queries and schema; one region; a replica set of 3 nodes and daily backups with point-in-time recovery |
| Distributed SQL database on SKE | Workloads that need a distributed database with SQL, in STACKIT | You operate it: upgrades, backups, capacity, failure handling; the run records no such managed service in the STACKIT catalogue it checked |
| Leave Spanner in place | A datastore whose consistency model the application cannot give up | A dependency on Google Cloud stays; network path, identity and latency between clouds become part of the design |
| Split the workload | Data that is regional by nature, such as per-country ledgers | Routing and cross-region reads move into the application; general practice, not an engine result |
PostgreSQL Flex facts from the STACKIT architecture page. The run names the first two options; the last two are design choices for your architect.
What the run leaves for you to decide
The run leaves nine critical semantics of the demo unit open, which touch twelve critical settings. The report groups the settings in six kinds, verbatim in the pane below; these are the decisions behind them.
Data model
The choice between the two targets and the redesign of the data model that follows it. Nothing here is drafted.
Dialect and DDL
GoogleSQL or PostgreSQL dialect fixes the SQL syntax the application and the schema scripts use. The DDL is re-created by a person, because the targets have no schema resource in the provider.
Version retention
The point-in-time window of the Spanner database, here three days, becomes the backup and recovery design of whatever replaces it.
Deletion guard
deletion_protection is Terraform's own guard on the Spanner database. No STACKIT counterpart is stated, so decide it with the chosen target.
Access
The reader grant names a user and a role. The new target has its own access model, so a person translates the grant.
Names and residency
Database names are checked against the applications that use them, and the instance configuration against your residency requirement.
The open settings and a schema sketch
Left: the grouped critical settings from engineering/architect-report.md. Right: a hand-written sketch of what the two interleaved tables turn into on PostgreSQL. It is not engine output; the run lists the DDL as a setting for a person.
- identity access (2; REVIEW 2): `google_spanner_database_iam_member.reader.member`, `google_spanner_database_iam_member.reader.role`
- backup retention (1; REVIEW 1): `google_spanner_database.accounts.version_retention_period`
- deletion protection (2; REVIEW 2): `google_spanner_database.accounts.deletion_protection`, `google_spanner_database.audit.deletion_protection`
- residency compliance (1; REVIEW 1): `config`
- workload compatibility (5; REVIEW 5): `name`, `google_spanner_database.accounts.database_dialect`, `google_spanner_database.accounts.name`, `google_spanner_database.audit.database_dialect`, `google_spanner_database.audit.name`
- data semantics (1; REVIEW 1): `google_spanner_database.accounts.ddl`-- Sketch written by hand, not engine output.
-- Spanner DDL (input): INTERLEAVE IN PARENT Accounts ON DELETE CASCADE
CREATE TABLE accounts (
account_id varchar(36) PRIMARY KEY,
owner varchar(128),
balance numeric
);
CREATE TABLE entries (
account_id varchar(36) NOT NULL REFERENCES accounts ON DELETE CASCADE,
entry_id varchar(36) NOT NULL,
amount numeric,
created_at timestamptz, -- was TIMESTAMP with allow_commit_timestamp
PRIMARY KEY (account_id, entry_id)
);Interleaving is physical, a foreign key is logical
Spanner interleaving stores child rows next to the parent row, and INTERLEAVE IN PARENT enforces the relationship. A foreign key does not co-locate rows. On PostgreSQL the relationship survives as a constraint, and the locality benefit is a different question.
Keys were designed for Spanner
Spanner splits data by key range and the documentation advises hashed, reordered or random keys to avoid hotspots. Those choices shaped your keys; a primary key in PostgreSQL has different trade-offs, so review the keys together with the schema.
How to decide, in order
Measure what you actually use
List which transactions span regions and what staleness they tolerate. If everything resolves inside one region, the single-region option is open.
State the availability and recovery targets
Write the recovery point and downtime the business accepts. PostgreSQL Flex offers a 3-node replica set, daily backups and point-in-time recovery from the transaction logs; STACKIT states that recovery goes to a cloned instance, not into the existing one. Compare that with your target before you choose.
Cost the operating model
A distributed database on SKE moves backups, upgrades and failure handling to your team. Count the people and the on-call load, not only the instances.
Plan the data move
Spanner can export a database as Avro files through Dataflow into a Cloud Storage bucket, optionally as of an earlier timestamp. The load into the target, and any catch-up of changes made after the export, is tooling you write or choose; the run picks no mechanism.
Pilot one database
Move one low-risk database first, with the chosen target and a rollback to Spanner. Keep Spanner running until the new database has carried real load.
Questions architects ask
Why is there no Terraform for Spanner?
The unit is a redesign and the run drafts nothing for it. The draft folder holds the provider file, variables and a README that lists the instance as left for the architect. A target resource appears only after you choose the target.
Does the PostgreSQL dialect of Spanner make PostgreSQL Flex a drop-in?
No. The dialect eases the move, but Google states that some PostgreSQL language features, such as extensions, user-defined data types and stored procedures, are not supported, and that the dialect adds Spanner-specific extensions such as interleaved tables and query hints. Code written for Spanner can rely on those, so it still needs review against standard PostgreSQL.
Can we keep Spanner and run the rest on STACKIT?
Yes, as a design choice. The estate would then depend on Google Cloud for that datastore, and the path between clouds, the identity used to reach it and the latency become part of the design. The run does not model that option.
Does STACKIT offer a globally distributed relational database?
The run records that the STACKIT catalogue it checked lists none, which is why the second target is a database you operate on SKE. Check the current STACKIT catalogue when you decide.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.