A zero-downtime-oriented migration toolkit for moving Azure SQL Database
from Hyperscale to General Purpose Serverless.
The original idea was to create a Hyperscale read replica and convert that replica to General Purpose Serverless. Azure SQL does not support that path: a replica must remain in the same service tier as its primary.
This repository implements the viable design instead:
- Build an independent GP Serverless target.
- Deploy the complete schema before moving data.
- Perform a consistent initial load.
- Replay ongoing inserts, updates, and deletes through CDC.
- Validate data and performance continuously.
- Cut over through controlled application routing.
- Retain Hyperscale temporarily as the rollback environment.
Important
Terraform provisions the target infrastructure. It does not convert a Hyperscale replica to General Purpose and it does not, by itself, provide a zero-downtime data migration.
flowchart LR
APP["Backend / write routing"] --> HS["Existing Hyperscale primary"]
HS -->|"Initial load"| GP["GP Serverless primary"]
HS -->|"CDC: insert / update / delete"| GP
GP -. "Optional active geo-replication" .-> EU["GP geo-secondary · Europe"]
GP -. "Optional active geo-replication" .-> JP["GP geo-secondary · Japan"]
TF["Terraform"] --> GP
TF --> EU
TF --> JP
GP --> OBS["Azure Monitor / Log Analytics / Grafana"]
EU --> OBS
JP --> OBS
The geo-secondaries are intentionally disabled during the first load and performance proof. They are enabled only after the independent GP target has passed validation.
Azure SQL Database Source and target |
Data Factory CDC candidate |
Private Link Private connectivity |
Azure Monitor Telemetry and gates |
Terraform Infrastructure as code |
| Area | Delivered capability |
|---|---|
| Target databases | Configurable GP Serverless databases for MediaDB, CMDB, and additional databases |
| Regional topology | Optional GP geo-secondaries for a three-copy design |
| Networking | Private endpoints, Private DNS links, and narrowly scoped optional firewall rules |
| Security | TLS 1.2 minimum, public access disabled by default, and write-only bootstrap credentials |
| Resilience | Zone-redundancy switch, PITR retention, geo-replica guardrails, and deletion protection |
| Observability | Azure SQL diagnostics routed to a new or existing Log Analytics workspace |
| Discovery | Read-only sizing, feature, object, workload, and CDC-readiness reports |
| Migration gates | Source cutover checks, target integrity checks, and schema inventory scripts |
| Operations | Phased runbook, decision log, rollback rules, and repository validation script |
- Terraform 1.8 or newer.
- Azure CLI authenticated to the target subscription.
- AzureRM provider 4.81.x.
sqlcmdfor source discovery and migration-gate scripts.- An existing protected Azure Storage container for production Terraform state.
- Required Azure resource providers already registered:
Microsoft.Sql,Microsoft.Network,Microsoft.OperationalInsights, andMicrosoft.Insights.
cp terraform.tfvars.example terraform.tfvars
export TF_VAR_sql_administrator_password='use-a-secret-injection-mechanism'Replace every placeholder in terraform.tfvars. Do not commit this file.
For local PoC validation:
terraform initFor protected remote state:
cp backend.tf.example backend.tf
cp backend.hcl.example backend.hcl
# Replace the backend placeholders first.
terraform init -backend-config=backend.hcl./migration/scripts/check-repository.sh
terraform plan -out transition.tfplanAlways review the saved plan before applying it. The existing Hyperscale databases are intentionally outside this Terraform state.
./migration/scripts/collect-readiness.sh \
--server source-server.database.windows.net \
--database MediaDB \
--entraRun the report for every source database before selecting the final GP capacity or CDC implementation.
| Phase | Purpose | Exit signal |
|---|---|---|
| 0 | Source discovery and workload baseline | Size, change rate, peak utilization, schema risks, and topology are known |
| 1 | Provision isolated GP target | Network, security, backup, and monitoring checks pass |
| 2 | Deploy schema and database objects | Schema inventory and permissions match |
| 3 | Initial data load | All selected tables loaded with zero unexplained rejects |
| 4 | CDC catch-up | Durable checkpoints work and replication lag is within threshold |
| 5 | Performance proof | Agreed p95/p99 latency and throughput targets pass |
| 6 | Create final regional topology | Geo-replication and planned failover are tested |
| 7 | Dress rehearsal | Cutover, rollback, ownership, and communications are timed and approved |
| 8 | Production cutover | Final watermark is applied and service SLOs remain healthy |
| 9 | Observation and closure | Restore tested, rollback window closed, Hyperscale decommission approved |
The full delivery tracker is in MIGRATION_PLAN.md. The operator runbook is in migration/README.md.
- No source ownership: Terraform never manages the existing Hyperscale resources.
- Private by default: public SQL access is disabled unless explicitly enabled.
- No password in state: AzureRM's write-only password field is used.
- No silent auto-pause: auto-pause must be disabled whenever geo-replication is enabled.
- No accidental database deletion: target databases use
prevent_destroy. - No unreviewed cutover: SQL scripts report readiness but never switch traffic or stop writes.
- No false rollback promise: rollback semantics must be defined before GP accepts production writes.
| Variable | Default | Meaning |
|---|---|---|
database_names |
MediaDB, CMDB |
Independent GP migration targets |
serverless_sku_name |
GP_S_Gen5_8 |
Maximum Serverless capacity encoded in the SKU |
serverless_min_capacity |
2 |
Minimum warm vCore capacity |
auto_pause_delay_in_minutes |
-1 |
Auto-pause disabled during migration |
max_database_size_gb |
256 |
Per-database GP storage limit |
zone_redundant |
true |
In-region zone resilience |
deploy_geo_replicas |
false |
Creates the optional regional copies when enabled |
public_network_access_enabled |
false |
Keeps Azure SQL off the public network |
See terraform.tfvars.example for the complete configuration surface.
.
├── main.tf # SQL servers, databases, replicas and networking
├── monitoring.tf # Log Analytics and database diagnostics
├── variables.tf # Typed configuration and validation
├── terraform.tfvars.example # Example environment values
├── backend.tf.example # Optional remote-state backend declaration
├── backend.hcl.example # Optional remote-state settings
├── MIGRATION_PLAN.md # Delivery phases, decisions and safety rules
└── migration/
├── README.md # Operator runbook
├── scripts/
│ ├── check-repository.sh
│ └── collect-readiness.sh
└── sql/
├── 00_source_readiness.sql
├── 01_enable_cdc.sql
├── 02_cdc_status.sql
├── 03_inventory.sql
├── 04_cutover_gate.sql
└── 05_target_gate.sql
The infrastructure and discovery foundation is implemented and validated. Actual Azure deployment and source CDC enablement remain gated on real environment inputs and explicit approval.
Validated locally:
- Standalone GP target: 7 additions, 0 changes, 0 deletions.
- Private networking topology: 10 additions, 0 changes, 0 deletions.
- Three-copy regional topology: 17 additions, 0 changes, 0 deletions.
- Terraform formatting, validation, Bash syntax, and negative network-guard tests.
- Azure SQL Database documentation
- Hyperscale reverse migration
- Azure SQL Serverless
- Active geo-replication
- Official Azure architecture icons
- Terraform documentation
Microsoft Azure product icons are used unchanged from the official Azure Architecture Icon collection for documentation purposes. Microsoft Azure and Azure SQL Database are trademarks of Microsoft. Terraform and the Terraform logo are trademarks of HashiCorp. Their use here identifies and links to the respective products and does not imply endorsement.