Migrating a 107GB Gmail Mailbox with LAO: What Microsoft's Docs Don't Tell You

Migrating a single Accounts Payable mailbox from Google Workspace to Exchange Online should have been the easy part of a larger phased migration. It wasn’t. The mailbox was 100GB+ with 600,000+ items, which put it past Exchange Online’s standard migration path and into Large Archive Onboarding (LAO) territory. LAO is the only supported mechanism for migrating a mailbox this size from Gmail while splitting content between primary and archive by date or folder. ...

August 12, 2026 · 9 min · Zac Lohrenz

Google Workspace to Microsoft 365 Migration: Two Weeks of Pilot Migration Stories

Two weeks into a Google Workspace to Microsoft 365 pilot migration for a mid-size org. Thirteen mailboxes in the pilot batch. What should have been a straightforward cutover turned into a deep education in Exchange migration edge cases, MRS throttling behavior, and the gap between what Microsoft documents and what actually happens in production. The Setup The architecture: phased cutover with Mimecast coexistence routing, GAM-managed forwarding during the transition window, and batch migration via Exchange Online migration endpoints backed by a GCP service account. A verified alias subdomain (o365.[domain]) serves as the target delivery domain, with workspace.[domain] as the coexistence routing domain pointing back to Google. ...

March 24, 2026 · 8 min · Zac

What a Google Workspace to M365 Migration Actually Looks Like Mid-Flight

Most migration guides describe the happy path. This isn’t that. This is what you actually find when you inherit a migration in progress and start pulling on threads. The Setup Mid-flight GWS to M365 migration. Test batches already running. Domain setup incomplete. Multiple migration endpoints in Exchange Online pointing at a GCP service account that nobody can confirm ownership of. This is a fairly normal state for an org-scale migration that started before all the architecture decisions were locked in. ...

March 11, 2026 · 6 min · Zac Lohrenz

Azure Key Vault: Migrating from Access Policies to RBAC Before the 2027 Deadline

Microsoft sent an email this week that caught my attention: all Azure Key Vault API versions prior to 2026-02-01 retire on February 27, 2027. The new API makes Azure RBAC the default access control model for Key Vaults, and legacy access policies become an explicit opt-in. Time to migrate before the deadline forces your hand. The Change: What Microsoft Is Actually Doing Starting with API version 2026-02-01 (releasing February 2026): ...

February 5, 2026 · 9 min · Zac Lohrenz

Migrating from Azure Cache for Redis to Azure Managed Redis

Understanding the Azure Redis Migration Microsoft recently announced the retirement of Basic, Standard, and Premium tiers of Azure Cache for Redis, with a hard deadline of September 30, 2028. This affects many production workloads, and planning your migration strategy now will save headaches later. Key Dates to Remember October 1, 2026: No new instances of Azure Cache for Redis can be created September 30, 2028: Migration deadline October 1, 2028: All remaining instances will be disabled Pre-Migration Assessment Inventory Your Redis Instances # List all Redis caches across subscriptions az redis list --query "[].{name:name, tier:sku.name, capacity:sku.capacity, location:location}" --output table # Get detailed configuration for specific instance az redis show --name <redis-name> --resource-group <rg-name> Analyze Current Usage Patterns # Export Redis metrics for capacity planning az monitor metrics list \ --resource <redis-resource-id> \ --metric-names "connectedclients" "usedmemory" "serverLoad" "operationsPerSecond" \ --start-time 2024-01-01T00:00:00Z \ --end-time 2024-01-31T23:59:59Z \ --interval PT1H \ --output json > redis-metrics.json Migration Strategy 1. Create Azure Managed Redis Instance # Create new Managed Redis instance az redis enterprise create \ --name <managed-redis-name> \ --resource-group <rg-name> \ --location <location> \ --sku Enterprise_E10 \ --capacity 2 # Create database within the instance az redis enterprise database create \ --cluster-name <managed-redis-name> \ --resource-group <rg-name> \ --client-protocol Encrypted \ --clustering-policy EnterpriseCluster \ --eviction-policy NoEviction \ --modules name=RedisJSON,name=RedisTimeSeries 2. Data Migration Approaches Option A: Redis MIGRATE Command (Small Datasets) import redis import time # Connect to source and target source = redis.Redis( host='source-redis.redis.cache.windows.net', port=6379, password='source_password', ssl=True ) target = redis.Redis( host='target-redis.region.redisenterprise.cache.azure.net', port=10000, password='target_password', ssl=True ) # Migrate keys for key in source.scan_iter(count=100): ttl = source.ttl(key) value = source.dump(key) if value: target.restore(key, ttl if ttl > 0 else 0, value) # Rate limiting to avoid overwhelming the system time.sleep(0.001) Option B: Redis Replication (Large Datasets) # Export data from source redis-cli -h source-redis.redis.cache.windows.net \ -p 6379 \ -a <password> \ --rdb dump.rdb \ --scan # Import to target using redis-cli redis-cli -h target-redis.region.redisenterprise.cache.azure.net \ -p 10000 \ -a <password> \ --pipe < dump.rdb 3. Application Configuration Updates # Update your application configuration # Old configuration redis: host: old-redis.redis.cache.windows.net port: 6379 ssl: true password: ${REDIS_PASSWORD} # New configuration redis: host: new-redis.region.redisenterprise.cache.azure.net port: 10000 ssl: true password: ${MANAGED_REDIS_PASSWORD} # Additional Managed Redis specific settings cluster_enabled: true read_replicas: - host: replica1.region.redisenterprise.cache.azure.net - host: replica2.region.redisenterprise.cache.azure.net Testing and Validation Performance Testing Script import redis import time import statistics def benchmark_redis_instance(connection_params, test_name): r = redis.Redis(**connection_params) # Test SET operations set_times = [] for i in range(1000): start = time.time() r.set(f'test_key_{i}', 'x' * 1024) # 1KB payload set_times.append(time.time() - start) # Test GET operations get_times = [] for i in range(1000): start = time.time() r.get(f'test_key_{i}') get_times.append(time.time() - start) print(f"\ {test_name} Results:") print(f"SET - Avg: {statistics.mean(set_times)*1000:.2f}ms, " f"P99: {sorted(set_times)[int(len(set_times)*0.99)]*1000:.2f}ms") print(f"GET - Avg: {statistics.mean(get_times)*1000:.2f}ms, " f"P99: {sorted(get_times)[int(len(get_times)*0.99)]*1000:.2f}ms") # Benchmark both instances benchmark_redis_instance(old_redis_config, "Azure Cache for Redis") benchmark_redis_instance(new_redis_config, "Azure Managed Redis") Cutover Strategy Blue-Green Deployment Approach # Step 1: Deploy application with dual-write capability # Application writes to both old and new Redis # Step 2: Verify data consistency redis-cli -h old-redis.redis.cache.windows.net --scan --pattern '*' | wc -l redis-cli -h new-redis.redisenterprise.cache.azure.net --scan --pattern '*' | wc -l # Step 3: Switch reads to new Redis (canary deployment) # Monitor error rates and latencies # Step 4: Complete cutover # Update DNS/load balancer to point to new Redis Post-Migration Monitoring # Set up alerts for the new Managed Redis instance az monitor metrics alert create \ --name high-memory-usage \ --resource <managed-redis-id> \ --condition "avg usedmemorypercentage > 80" \ --window-size 5m \ --evaluation-frequency 1m az monitor metrics alert create \ --name high-cpu-usage \ --resource <managed-redis-id> \ --condition "avg cpuusagepercentage > 75" \ --window-size 5m \ --evaluation-frequency 1m Rollback Plan Always maintain your rollback capability: ...

October 10, 2025 · 4 min · Me