Dev/test storage accounts paying the GRS geo-replication premium
What does ZopNight detect here?
ZopNight flags dev/test storage accounts running GRS, RAGRS, GZRS, or RAGZRS replication and prices the switch to LRS as the real per-account premium, (GRS rate − LRS rate) × stored GB. Geo-redundant storage costs roughly 2x the LRS rate. Without a positive measured premium the rule abstains entirely.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1324 |
| Category | rightsizing |
| Severity | low |
| Metric | none — pure configuration read |
| Source | storage_replication.go |
Where it applies
Why geo-redundancy is wasted on dev/test data
Geo-redundant storage keeps six copies of every object: three in the primary region and three in an Azure-paired region, asynchronously replicated for regional-disaster survival. Dev/test data rarely deserves that. Build artifacts, test fixtures, and scratch datasets are reproducible by definition: if the region burns down, you regenerate them. Meanwhile the account pays roughly double the LRS rate for a durability guarantee nobody would ever invoke.
Spot geo-redundant dev accounts
az storage account list \ --query "[?contains(sku.name, 'GRS') || contains(sku.name, 'GZRS')].{name:name, sku:sku.name, rg:resourceGroup}" \ -o tableCross-reference the names against your environment conventions; ZopNight does the same match automatically.
Four gates, all from real data
The rule fires only when every signal aligns. The account name must match a dev/test naming pattern, with infrastructure names such as “devops” explicitly excluded from the match. The discoverer-written replication_type metadata (the last underscore token of the storage SKU) must be GRS, RAGRS, GZRS, or RAGZRS; an absent key means the rule abstains rather than guessing. The aggregator must have supplied a positive monthly cost. And a positive geo-replication premium must have been measured for this specific account.
How the dollar figure is computed
The saving is never a percentage of the bill. The aggregator computes the per-account premium as (GRS rate − base LRS rate) × stored GB, using a measured hot-GRS rate of $0.046 per GB-month from its pricing cache. When the GRS rate is missing from the cache or the stored size is unknown, no premium is emitted and the rule stays silent, a deliberate design that prefers reporting nothing over reporting a fabricated number. The premium is also clamped to the account’s actual cost, so the projected optimized cost can never go negative.
Downgrading and its trade-off
Flipping replication from GRS to LRS in the account’s Configuration blade takes effect without downtime, but the move is a genuine durability reduction: a regional outage now means the data is unavailable until the region recovers, and a regional catastrophe means it is gone. For reproducible dev/test data that trade is usually correct. If an account graduates to production later, flip it back; the change is reversible in both directions.
Detection needs only the built-in Reader role via Azure Resource Graph; the cost inputs come through Cost Management Reader.