Multi-AZ in non-production is high availability nobody needs, at the price of a second instance: the standby of a development database, the zone-redundant replica of a test server, the geo-redundant storage of a staging bucket, the second region of a prototype. The saving comes from a standby being billed as a full instance at all three providers and from an environment without an availability commitment doing without it. The fix belongs to the team that copied the environment from the production template, because the template is the cause.
Symptom
The bill shows, in accounts or resource groups tagged env=dev or env=test, lines that belong in production only. On AWS these are lineItem/UsageType EUC1-Multi-AZUsage:db.r6g.large instead of InstanceUsage, Aurora clusters with several instances in different zones, and S3 replication with DataTransfer-Regional-Bytes or cross-region lines. On Azure they are compute meters for database servers with high availability enabled, storage accounts with GRS, RA-GRS or GZRS in the MeterName and zone-redundant disks, ZRS, where LRS would do. On Google Cloud Cloud SQL carries the SKUs for regional instances at double the price, and Cloud Storage the SKUs for dual-region and multi-region buckets. What backup and replication cost in principle is in backup and disaster recovery; what non-production environments may cost as a whole is in non-production cloud costs.
How to detect it
The first step is the same for all three: separate the non-production environments by tag, account, subscription or project, as allocation by account, subscription and project describes, and search within them.
AWS
Databases with a standby and clusters with several instances, with their environment tag:
aws rds describe-db-instances --query 'DBInstances[?MultiAZ==`true`].[DBInstanceIdentifier,DBInstanceClass,Engine,TagList[?Key==`env`].Value|[0]]' --output table
aws rds describe-db-clusters --query 'DBClusters[].[DBClusterIdentifier,Engine,length(DBClusterMembers),MultiAZ]' --output table
Buckets with replication rules show with aws s3api get-bucket-replication --bucket <bucket> per bucket, and DynamoDB global tables with aws dynamodb describe-table --table-name <table> --query 'Table.Replicas'. Multi-AZ instances are billed at the Multi-AZ price of the price list, which includes the standby, and write every transaction twice (RDS FAQ, read on 2026-10-21); Trusted Advisor and Compute Optimizer have no check that rates Multi-AZ by environment. In billing data the candidate is a Multi-AZUsage line in an account that the account map or Cost Categories classify as non-production (AWS Cost and Usage Report).
Azure
Servers with high availability and storage accounts with geo-redundancy, with resource group and tags:
az postgres flexible-server list --query "[?highAvailability.mode!='Disabled'].{name:name, rg:resourceGroup, ha:highAvailability.mode, sku:sku.name, env:tags.env}" -o table
az storage account list --query "[?contains(sku.name,'GRS') || contains(sku.name,'GZRS')].{name:name, rg:resourceGroup, sku:sku.name, env:tags.env}" -o table
az sql db list --server <server> --resource-group <rg> --query "[?zoneRedundant==\`true\` || currentSku.tier=='BusinessCritical'].{name:name, tier:currentSku.tier, zr:zoneRedundant}" -o table
On Azure Database for PostgreSQL the standby is billed at the same rate as the primary server, whether it sits in the same or another zone (reliability in Azure Database for PostgreSQL). Azure Advisor does not rate high availability by environment. In the cost export the candidate is a compute meter with double the Quantity per server or a GRS meter in a resource group with a non-production tag (Azure cost exports).
Google Cloud
Regional Cloud SQL instances and buckets in dual-region or multi-region:
gcloud sql instances list --filter="settings.availabilityType=REGIONAL" --format="table(name,settings.tier,settings.availabilityType,region,settings.userLabels.env)"
gcloud storage buckets list --format="table(name,location,locationType,labels.env)"
A Cloud SQL instance with high availability costs twice as much as a standalone instance, CPU, RAM and storage included (high availability in Cloud SQL). The idle instance recommender finds the instance, not its availability tier. In the billing export the candidate is a resource.name with Regional SKUs or a bucket with Multi-Region or Dual-Region SKUs in a project with a non-production label (Google Cloud billing export).
How to fix it
- Decide per environment what it needs: a development environment no high availability, a staging environment the same architecture as production only for the duration of a test, an environment with a contractual availability commitment the same as production. The decision belongs in the cost policy.
- Switch the standby off, as an online change with a short interruption: AWS
aws rds modify-db-instance --db-instance-identifier <instance> --no-multi-az --apply-immediately; Azureaz postgres flexible-server update --resource-group <rg> --name <server> --high-availability Disabled; Google Cloudgcloud sql instances patch <instance> --availability-type=ZONAL. Aurora clusters lose their reader instances withaws rds delete-db-instanceper instance, the writer stays. - Lower the storage redundancy:
az storage account update --name <account> --resource-group <rg> --sku Standard_LRSfor an account without archive data; an S3 replication rule withaws s3api delete-bucket-replication --bucket <bucket>; a bucket on Google Cloud does not change its location, it is replaced by a regional one. - Correct the template: Terraform modules, Bicep files and deployment templates get a variable per environment that switches high availability, redundancy and replication on in production only; cost in the development lifecycle describes where that sits in the review.
None of these calls has a dry run; the list and the release stand in for it. The standby leaves the bill with the change, the storage redundancy with the account's conversion.
Risks and side effects
- A staging environment without high availability does not test the failover; whoever has to test it switches it on for the test and off afterwards, or schedules the test in production in the maintenance window.
- The move to Single-AZ or zonal is an interruption: short on RDS, a restart on Cloud SQL, the removal of the standby without a failover on Azure. A running test run breaks.
- Lowering storage redundancy from GRS to LRS deletes the second copy; data that lives in the storage account as its only backup needs another backup first.
- Geo-redundant backups are a separate setting on databases; an environment whose backups have to sit in a second region for compliance reasons keeps them, standby or not.
- Reservations for the database class cover the standby too; a standby switched off frees reservation hours another instance has to fill (commitment coverage and utilisation).
How to verify the saving
Before: per non-production environment, the Multi-AZUsage lines, the compute meters with a standby, the GRS meters and the Regional SKUs in the last full month. After: the same environments in the first full month after the conversion, with InstanceUsage instead of Multi-AZUsage, a single Quantity, LRS meters and zonal SKUs; the difference is the saving, and non-production's share of high availability cost is the figure for the estate. The difference from the price list is the estimate, the team's release makes it accepted, the conversion implemented, and the following month's invoice realises it in the pattern of estimated, accepted, implemented, verified.
Sources
Read on 2026-10-21. Multi-AZ DB instance deployments for Amazon RDS, Amazon RDS FAQ, Reliability in Azure Database for PostgreSQL, az postgres flexible-server, Azure Storage redundancy, High availability in Cloud SQL, gcloud sql instances patch.