Azure Storage Geo-Replication Reference
Home > Reference > Storage > Geo-Replication

Comprehensive reference guide for Azure Storage geo-replication, including replication options, failover strategies, disaster recovery, and best practices for cloud-scale analytics data protection.
Table of Contents
Overview
What is Geo-Replication?
Azure Storage geo-replication automatically replicates your data to a secondary region that is hundreds of miles away from the primary region, protecting against regional disasters.
Replication Architecture
┌────────────────────────────────────────────────────────────┐
│ Azure Storage Replication Architecture │
├────────────────────────────────────────────────────────────┤
│ │
│ Primary Region (East US) Secondary Region (West US) │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ │ │ │ │
│ │ Zone 1 Zone 2 │ │ Zone 1 Zone 2 │ │
│ │ ┌────┐ ┌────┐ │ Async │ ┌────┐ ┌────┐ │ │
│ │ │Data│ │Data│ │ ─────> │ │Data│ │Data│ │ │
│ │ └────┘ └────┘ │ Sync │ └────┘ └────┘ │ │
│ │ LRS/ZRS │ │ LRS/ZRS │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Replication Types: │
│ - GRS: Primary (LRS) → Secondary (LRS) │
│ - RA-GRS: Primary (LRS) → Secondary (LRS) + Read Access │
│ - GZRS: Primary (ZRS) → Secondary (LRS) │
│ - RA-GZRS: Primary (ZRS) → Secondary (LRS) + Read Access │
│ │
└────────────────────────────────────────────────────────────┘
Replication Types
Comparison Matrix
| Feature | LRS | ZRS | GRS | RA-GRS | GZRS | RA-GZRS |
| Copies of data | 3 | 3 | 6 | 6 | 6 | 6 |
| Regions | 1 | 1 | 2 | 2 | 2 | 2 |
| Availability zones | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ |
| Read from secondary | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
| Durability (9's) | 11 | 12 | 16 | 16 | 16 | 16 |
| Relative cost | $ | $$ | $$$ | $$$ | $$$$ | $$$$ |
| Use case | Dev/Test | Production | DR | DR + Read | Production DR | Production DR + Read |
Durability and Availability
| Replication Type | Annual Durability | Availability (Read) | Availability (Write) |
| LRS | 99.999999999% (11 9's) | 99.9% | 99.9% |
| ZRS | 99.9999999999% (12 9's) | 99.9% | 99.9% |
| GRS | 99.99999999999999% (16 9's) | 99.9% | 99.9% |
| RA-GRS | 99.99999999999999% (16 9's) | 99.99% | 99.9% |
| GZRS | 99.99999999999999% (16 9's) | 99.9% | 99.9% |
| RA-GZRS | 99.99999999999999% (16 9's) | 99.99% | 99.9% |
Region Pairs
| Primary Region | Secondary Region | Distance |
| East US | West US | ~2,400 miles |
| East US 2 | Central US | ~800 miles |
| West US 2 | West Central US | ~1,000 miles |
| North Europe | West Europe | ~1,000 miles |
| Southeast Asia | East Asia | ~1,600 miles |
| Japan East | Japan West | ~250 miles |
| Australia East | Australia Southeast | ~500 miles |
| UK South | UK West | ~200 miles |
| Canada Central | Canada East | ~500 miles |
| Brazil South | South Central US | ~5,000 miles |
Geo-Redundant Storage (GRS)
Configuration
Azure CLI
# Create storage account with GRS
az storage account create \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--location eastus \
--sku Standard_GRS \
--kind StorageV2 \
--https-only true \
--min-tls-version TLS1_2 \
--allow-blob-public-access false
# Verify replication status
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query '{name:name,sku:sku.name,primaryLocation:primaryLocation,secondaryLocation:secondaryLocation,statusOfPrimary:statusOfPrimary,statusOfSecondary:statusOfSecondary}'
# Check last sync time
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query 'geoReplicationStats.lastSyncTime'
Azure PowerShell
# Create storage account with GRS
New-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001" `
-Location "eastus" `
-SkuName "Standard_GRS" `
-Kind "StorageV2" `
-EnableHttpsTrafficOnly $true `
-MinimumTlsVersion "TLS1_2"
# Get replication statistics
$account = Get-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001"
$account.GeoReplicationStats | Format-List
# Output:
# Status : Live
# LastSyncTime : 12/10/2024 10:30:00 AM
# CanFailover : True
Bicep Template
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'stadatalake001'
location: location
sku: {
name: 'Standard_GRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
}
encryption: {
services: {
blob: {
enabled: true
keyType: 'Account'
}
file: {
enabled: true
keyType: 'Account'
}
}
keySource: 'Microsoft.Storage'
}
}
}
output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blob
output secondaryEndpoint string = storageAccount.properties.secondaryEndpoints.blob
Replication Behavior
| Operation | Primary Region | Secondary Region |
| Write | Synchronous (3 copies) | Asynchronous replication |
| Read | Always from primary | Not available (use RA-GRS) |
| Failover | Becomes secondary | Becomes primary |
| Consistency | Strong | Eventually consistent |
Read-Access Geo-Redundant Storage (RA-GRS)
Configuration
# Create storage account with RA-GRS
az storage account create \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--location eastus \
--sku Standard_RAGRS \
--kind StorageV2
# Get secondary endpoint
SECONDARY_ENDPOINT=$(az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query secondaryEndpoints.blob -o tsv)
echo "Secondary endpoint: $SECONDARY_ENDPOINT"
# Output: https://stadatalake001-secondary.blob.core.windows.net/
Accessing Secondary Region
Azure CLI
# Read from secondary endpoint
az storage blob download \
--account-name stadatalake001 \
--container-name data \
--name file.txt \
--file downloaded-file.txt \
--blob-endpoint "https://stadatalake001-secondary.blob.core.windows.net/"
# List blobs from secondary
az storage blob list \
--account-name stadatalake001 \
--container-name data \
--blob-endpoint "https://stadatalake001-secondary.blob.core.windows.net/" \
--output table
.NET SDK
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
// Configure client for RA-GRS
var options = new BlobClientOptions
{
GeoRedundantSecondaryUri = new Uri("https://stadatalake001-secondary.blob.core.windows.net/"),
Retry = {
Mode = Azure.Core.RetryMode.Exponential,
MaxRetries = 5,
Delay = TimeSpan.FromSeconds(2)
}
};
var blobServiceClient = new BlobServiceClient(
new Uri("https://stadatalake001.blob.core.windows.net/"),
new DefaultAzureCredential(),
options
);
// Read from primary
var containerClient = blobServiceClient.GetBlobContainerClient("data");
var blobClient = containerClient.GetBlobClient("file.txt");
var content = await blobClient.DownloadContentAsync();
// Read from secondary (automatic on retry)
try
{
var contentFromPrimary = await blobClient.DownloadContentAsync();
}
catch (RequestFailedException)
{
// SDK automatically retries from secondary if configured
Console.WriteLine("Primary failed, reading from secondary");
}
Python SDK
from azure.storage.blob import BlobServiceClient
from azure.identity import DefaultAzureCredential
# Create client with RA-GRS support
credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(
account_url="https://stadatalake001.blob.core.windows.net/",
credential=credential
)
# Access primary endpoint
container_client = blob_service_client.get_container_client("data")
blob_client = container_client.get_blob_client("file.txt")
# Download from primary
with open("file.txt", "wb") as file:
blob_data = blob_client.download_blob()
file.write(blob_data.readall())
# Access secondary endpoint
secondary_client = BlobServiceClient(
account_url="https://stadatalake001-secondary.blob.core.windows.net/",
credential=credential
)
secondary_container = secondary_client.get_container_client("data")
secondary_blob = secondary_container.get_blob_client("file.txt")
# Download from secondary
with open("file-from-secondary.txt", "wb") as file:
blob_data = secondary_blob.download_blob()
file.write(blob_data.readall())
Circuit Breaker Pattern
public class GeoRedundantStorageClient
{
private readonly BlobServiceClient primaryClient;
private readonly BlobServiceClient secondaryClient;
private bool usePrimary = true;
private DateTime lastFailureTime = DateTime.MinValue;
private readonly TimeSpan failoverDuration = TimeSpan.FromMinutes(5);
public GeoRedundantStorageClient(string accountName, TokenCredential credential)
{
primaryClient = new BlobServiceClient(
new Uri($"https://{accountName}.blob.core.windows.net/"),
credential
);
secondaryClient = new BlobServiceClient(
new Uri($"https://{accountName}-secondary.blob.core.windows.net/"),
credential
);
}
public async Task<BlobDownloadInfo> DownloadBlobAsync(string containerName, string blobName)
{
// Check if we should try primary again
if (!usePrimary && DateTime.UtcNow - lastFailureTime > failoverDuration)
{
usePrimary = true;
}
var client = usePrimary ? primaryClient : secondaryClient;
try
{
var containerClient = client.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
return await blobClient.DownloadAsync();
}
catch (RequestFailedException ex) when (usePrimary && IsRetryable(ex))
{
// Primary failed, switch to secondary
usePrimary = false;
lastFailureTime = DateTime.UtcNow;
var secondaryContainerClient = secondaryClient.GetBlobContainerClient(containerName);
var secondaryBlobClient = secondaryContainerClient.GetBlobClient(blobName);
return await secondaryBlobClient.DownloadAsync();
}
}
private bool IsRetryable(RequestFailedException ex)
{
return ex.Status >= 500 || ex.Status == 408 || ex.Status == 429;
}
}
Geo-Zone-Redundant Storage (GZRS)
Configuration
# Create storage account with GZRS
az storage account create \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--location eastus \
--sku Standard_GZRS \
--kind StorageV2
# Verify zone redundancy
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query '{sku:sku.name,primaryLocation:primaryLocation,secondaryLocation:secondaryLocation}'
GZRS Benefits
| Feature | GRS | GZRS |
| Zone failure protection | ❌ No | ✅ Yes |
| Region failure protection | ✅ Yes | ✅ Yes |
| Durability | 16 9's | 16 9's |
| Availability (single zone down) | ❌ Degraded | ✅ Full |
| Cost | Lower | Higher |
| Best for | Cost-sensitive DR | Mission-critical workloads |
When to Use GZRS
- Mission-critical production workloads
- High availability requirements (99.99%+)
- Protection against both zone and region failures
- Compliance requirements for data durability
- Low tolerance for data loss
Failover and Recovery
Types of Failover
| Failover Type | Initiated By | Downtime | Data Loss Risk | Cost |
| Customer-managed | Customer | Minutes | Minimal (RPO ~15min) | No additional |
| Microsoft-managed | Microsoft | Hours | Minimal | No additional |
| Planned failover | Customer | Minimal | None | No additional |
Customer-Managed Failover
Prerequisites
- Storage account with GRS or RA-GRS
- No failover in progress
- Primary region unavailable or degraded
Azure CLI - Initiate Failover
# Check if failover is possible
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query '{canFailover:geoReplicationStats.canFailover,status:statusOfPrimary}'
# Initiate account failover
az storage account failover \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--yes
# Monitor failover status
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query '{primaryLocation:primaryLocation,secondaryLocation:secondaryLocation,statusOfPrimary:statusOfPrimary}'
PowerShell - Initiate Failover
# Check failover eligibility
$account = Get-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001"
if ($account.GeoReplicationStats.CanFailover) {
Write-Host "Account is eligible for failover"
# Initiate failover
Invoke-AzStorageAccountFailover `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001" `
-Force
Write-Host "Failover initiated"
} else {
Write-Host "Account is not eligible for failover"
}
Failover Process
┌────────────────────────────────────────────────────────┐
│ Account Failover Process │
├────────────────────────────────────────────────────────┤
│ │
│ Before Failover: │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ East US │ Async │ West US │ │
│ │ (Primary) │ ──────> │ (Secondary) │ │
│ │ Read/Write │ │ Read (RA-GRS)│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ During Failover (10-60 minutes): │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ East US │ │ West US │ │
│ │ (Offline) │ │ (Promoting) │ │
│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ After Failover: │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ East US │ │ West US │ │
│ │ (No longer │ │ (New Primary)│ │
│ │ paired) │ │ Read/Write │ │
│ └──────────────┘ └──────────────┘ │
│ LRS Only │
│ │
│ Note: After failover, account becomes LRS. │
│ Re-enable GRS to establish new pairing. │
│ │
└────────────────────────────────────────────────────────┘
Post-Failover Actions
# After failover completes, re-enable geo-replication
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_GRS
# Verify new configuration
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query '{sku:sku.name,primaryLocation:primaryLocation,secondaryLocation:secondaryLocation}'
# Update application endpoints if needed
# Primary endpoint remains the same, but physical location changed
Failover Testing
from azure.mgmt.storage import StorageManagementClient
from azure.identity import DefaultAzureCredential
import time
class FailoverTester:
"""Test storage account failover procedures."""
def __init__(self, subscription_id: str):
self.credential = DefaultAzureCredential()
self.storage_client = StorageManagementClient(
self.credential, subscription_id
)
def test_failover(self, resource_group: str, account_name: str, dry_run: bool = True):
"""
Test failover procedure.
Args:
resource_group: Resource group name
account_name: Storage account name
dry_run: If True, only validate without performing failover
"""
print(f"Testing failover for account: {account_name}")
# Check current status
account = self.storage_client.storage_accounts.get_properties(
resource_group, account_name
)
print(f"Current primary location: {account.primary_location}")
print(f"Current secondary location: {account.secondary_location}")
print(f"Can failover: {account.geo_replication_stats.can_failover if account.geo_replication_stats else 'N/A'}")
if dry_run:
print("Dry run mode - no failover performed")
return
if not account.geo_replication_stats or not account.geo_replication_stats.can_failover:
print("Account is not eligible for failover")
return
# Initiate failover
print("Initiating failover...")
operation = self.storage_client.storage_accounts.begin_failover(
resource_group, account_name
)
# Wait for completion
result = operation.result()
print("Failover completed")
# Verify new configuration
time.sleep(30) # Wait for metadata to update
updated_account = self.storage_client.storage_accounts.get_properties(
resource_group, account_name
)
print(f"New primary location: {updated_account.primary_location}")
print(f"New SKU: {updated_account.sku.name}")
# Re-enable geo-replication
print("Re-enabling geo-replication...")
self.storage_client.storage_accounts.update(
resource_group,
account_name,
{
"sku": {"name": "Standard_GRS"}
}
)
print("Failover test completed successfully")
# Usage
tester = FailoverTester(subscription_id="your-sub-id")
tester.test_failover(
resource_group="rg-analytics-prod",
account_name="stadatalake001",
dry_run=True # Set to False for actual failover
)
Data Consistency
Last Sync Time (LST)
# Check last successful sync
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query 'geoReplicationStats.{status:status,lastSyncTime:lastSyncTime,canFailover:canFailover}' \
--output table
# Sample output:
# Status LastSyncTime CanFailover
# -------- --------------------------- ------------
# Live 2024-12-10T14:30:00.000000Z True
Understanding RPO
| Replication Type | Typical RPO | Maximum RPO |
| GRS/RA-GRS | <15 minutes | ~1 hour |
| GZRS/RA-GZRS | <15 minutes | ~1 hour |
Monitoring LST
// Azure Monitor query for LST
AzureMetrics
| where ResourceProvider == "MICROSOFT.STORAGE"
| where MetricName == "GeoReplicationDataSyncTime"
| where TimeGenerated > ago(24h)
| summarize AvgSyncTime = avg(Average), MaxSyncTime = max(Maximum) by bin(TimeGenerated, 1h)
| render timechart
// Alert on high sync lag
AzureMetrics
| where ResourceProvider == "MICROSOFT.STORAGE"
| where MetricName == "GeoReplicationDataSyncTime"
| where Average > 900000 // 15 minutes in milliseconds
| project TimeGenerated, Resource, Average
Monitoring and Metrics
Key Metrics
# Monitor availability
az monitor metrics list \
--resource "/subscriptions/{sub-id}/resourceGroups/rg-analytics-prod/providers/Microsoft.Storage/storageAccounts/stadatalake001" \
--metric "Availability" \
--start-time 2024-12-09T00:00:00Z \
--end-time 2024-12-10T00:00:00Z \
--interval PT1H \
--aggregation Average
# Monitor geo-replication health
az monitor metrics list \
--resource "/subscriptions/{sub-id}/resourceGroups/rg-analytics-prod/providers/Microsoft.Storage/storageAccounts/stadatalake001" \
--metric "GeoReplicationDataLag" \
--aggregation Maximum
Diagnostic Logging
# Enable storage analytics logging
az monitor diagnostic-settings create \
--name storage-diagnostics \
--resource "/subscriptions/{sub-id}/resourceGroups/rg-analytics-prod/providers/Microsoft.Storage/storageAccounts/stadatalake001" \
--logs '[
{"category":"StorageRead","enabled":true},
{"category":"StorageWrite","enabled":true},
{"category":"StorageDelete","enabled":true}
]' \
--metrics '[{"category":"Transaction","enabled":true}]' \
--workspace "/subscriptions/{sub-id}/resourceGroups/rg-analytics-prod/providers/Microsoft.OperationalInsights/workspaces/law-analytics-prod"
Cost Considerations
Pricing Comparison
| Replication Type | Relative Cost | Storage Cost Multiplier | Egress Cost |
| LRS | 1x (baseline) | 1.0x | Standard |
| ZRS | 1.25x | 1.25x | Standard |
| GRS | 2x | 2.0x | Higher (cross-region) |
| RA-GRS | 2x | 2.0x | Higher (cross-region + secondary reads) |
| GZRS | 2.5x | 2.5x | Higher (cross-region) |
| RA-GZRS | 2.5x | 2.5x | Higher (cross-region + secondary reads) |
Cost Optimization
# cost-optimization-strategy.yaml
replication_strategy:
production_critical:
type: "RA-GZRS"
justification: "Mission-critical data requiring highest availability"
production_standard:
type: "GRS"
justification: "Standard production workloads"
development:
type: "LRS"
justification: "Non-production, can be recreated"
archival:
type: "GRS"
tier: "Cool"
justification: "Long-term retention with DR"
backup:
type: "LRS"
tier: "Archive"
justification: "Point-in-time backups, local redundancy sufficient"
Migration Between Replication Types
Upgrade Path
# LRS to ZRS (supported in most regions)
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_ZRS
# LRS to GRS
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_GRS
# GRS to RA-GRS
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_RAGRS
# GRS to GZRS (requires conversion)
# Step 1: Convert to ZRS first
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_ZRS
# Step 2: Convert to GZRS
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_GZRS
Downgrade Considerations
# Note: Downgrading from geo-redundant to local may lose secondary copy
# Ensure data is backed up before downgrade
# GRS to LRS
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--sku Standard_LRS
Best Practices
Replication Checklist
Common Mistakes to Avoid
| Mistake | Impact | Solution |
| Using LRS for production | Data loss risk | Use GRS or higher |
| Not testing failover | Unprepared for disasters | Test annually |
| Ignoring LST monitoring | Undetected replication issues | Set up alerts |
| No failover documentation | Slow recovery | Document procedures |
| Assuming zero data loss | Wrong expectations | Understand RPO (~15min) |
Note: Geo-replication features and availability vary by region. Always verify current capabilities and test failover procedures in non-production environments before relying on them for disaster recovery.