Azure waste

High availability on a non-production MySQL server

A MySQL flexible server running a paid standby while tagged non-production.
The agent reports this
Why it happens

Configuring high availability on an Azure Database for MySQL flexible server provisions a standby replica, and Microsoft states plainly that you pay for the provisioned compute and storage for both the primary and the secondary. There is no separate high-availability meter, so the standby bills on the same compute meter as the primary and turning it on doubles the compute line. Both modes provision one, and the documentation calls SameZone local-redundant. Unlike the PostgreSQL equivalent this remedy is one-way: disabling high availability is an online operation that does not interrupt connections, but zone-redundant high availability can only be enabled when the server is created, so putting it back means building a new server and migrating onto it. Zone-redundant high availability is also unavailable on the Burstable tier, so a server carrying it is on General Purpose or Memory Optimized.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.dbformysql/flexibleservers'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
// mode is Disabled, ZoneRedundant or SameZone. Unlike the PostgreSQL spec, MySQL declares no
// default for it, so this treats an absent value as off without claiming a documented default.
// BOTH paid modes provision a standby. Microsoft's documentation calls SameZone "local-redundant".
| extend haMode = tostring(properties.highAvailability.mode)
| where haMode in~ ('ZoneRedundant', 'SameZone')
// Deliberately no dollar arithmetic. The compute rate is read live by the agent so it cannot go
// stale here, and the standby costs one server's compute.
| project name, resourceGroup, location, haMode,
          documentedName = iff(haMode =~ 'ZoneRedundant', 'zone-redundant', 'local-redundant'),
          sku = tostring(sku.name), tier = tostring(sku.tier),
          haState = tostring(properties.highAvailability.state), environment = cfopEnv

Microsoft states it plainly for MySQL: when you configure high availability, the service provisions and manages a standby replica, and you pay for the provisioned compute and storage for both the primary and the secondary. Verified against the retail API, there is no high-availability meter: 67 flexible-server items in East US, none naming HA, so the standby is a second server on the same compute meter and turning HA on doubles the compute line. This is the sibling of the PostgreSQL check and four of its facts differ, which is why it is a separate rule. The remedy is one-way: disabling high availability is an online operation that does not interrupt connections, but zone-redundant high availability can only be enabled when the server is created, so putting it back means building a new server and migrating onto it. PostgreSQL toggles it freely. MySQL's specification also declares no default for the mode where PostgreSQL declares Disabled, and Microsoft's MySQL documentation calls SameZone local-redundant and never uses the ARM value, so this query projects both names. Zone-redundant HA is unavailable on the Burstable tier, so a server carrying it is on General Purpose or Memory Optimized. This is a structural test, so what it returns is what the check reports.

Applies to microsoft.dbformysql/flexibleservers
What the agent does about it

A MySQL flexible server running a paid standby while tagged non-production.

It will not fix this one

It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.