Introduction
Azure SQL Managed Instance has always been positioned as the “lift-and-shift friendly” PaaS option for SQL Server. You get high compatibility, automatic patching, built-in HA, and reduced operational overhead. For many workloads, it is the easiest way to modernize without rewriting applications.
But for years, one thing held it back.
Storage performance.
If you needed more I/O, you had to scale the entire instance. More vCores, more memory, more cost, even when CPU was not the bottleneck. Disk latency issues meant bigger SKUs, longer scale times, and higher bills. It worked, but it was clumsy, slow, and inefficient.
The Next-Gen General Purpose Managed Instance finally fixes that.
This is not a minor enhancement. It is a fundamental architectural shift in how storage works in Azure SQL Managed Instance.
What Actually Changed in Next-Gen Managed Instance
The biggest change is invisible at first glance.
Microsoft replaced the legacy storage backend with Azure Elastic SAN.
This means your data and log files are no longer tied to the old page blob-based architecture. Instead, they run on a shared, high-performance SAN designed for consistent low latency and scalable throughput.
Elastic SAN was built specifically to decouple storage performance from compute. That design decision is what makes everything else possible.
This is the foundation for predictable I/O, better concurrency, and most importantly, direct control over throughput.
The IOPS Slider: The Feature DBAs Have Been Waiting For
Next-Gen General Purpose introduces a new capability that changes daily operations.
You can now directly provision IOPS.
In the Azure portal (and via ARM or REST), you can move a slider to add IOPS without touching vCores or memory. The change is applied online, with no failover and no downtime.
This is a massive shift from the old model.
Before:
- Need more I/O? Scale compute
- Scaling takes time
- Costs increase across the board
- Performance tuning is guesswork
Now:
- Identify I/O bottleneck
- Increase IOPS
- Measure results
- Adjust again if needed
This is how performance tuning should work.
How the IOPS Model Works
Microsoft implemented a simple and transparent model:
• You get 3 IOPS per GB of reserved storage for free
• 1 TB storage includes ~3,000 baseline IOPS
• You can provision additional IOPS at low cost
• Changes are applied online
• No instance restart required
Additional IOPS are billed based on regional storage pricing, roughly storage price divided by three. In many regions, 1,000 extra IOPS costs less than $40 per month.
That is cheaper than a single vCore upgrade and far more targeted.
The One Limitation You Need to Know
IOPS are still capped by vCore count.
The current limit is 1,600 IOPS per vCore.
That means:
- 8 vCores = 12,800 max IOPS
- 16 vCores = 25,600 max IOPS
- 32 vCores = 51,200 max IOPS
This is likely a safety guardrail to keep storage and compute balanced, but it does limit extreme I/O-heavy designs. Even so, it is a massive improvement over the old “scale everything” model.
Why This Matters to DBAs
This upgrade gives DBAs something rare in the cloud.
Operational control.
You can now tune storage the same way you did with on-prem SANs:
- Detect bottleneck
- Add throughput
- Validate improvement
- Roll back if needed
All inside a managed service.
This is the closest Managed Instance has ever felt to running real infrastructure, without the operational burden.
Hands-On Workshop: Measuring and Tuning IOPS
This workshop lets you prove the impact yourself.
Step 1: Baseline Your I/O
Run these before changing anything.
Top waits
SELECT *FROM sys.dm_os_wait_statsWHERE wait_type LIKE 'PAGEIOLATCH%' OR wait_type = 'WRITELOG'ORDER BY wait_time_ms DESC;
File latency
SELECT DB_NAME(vfs.database_id) AS database_name, mf.name, mf.type_desc, vfs.num_of_reads, vfs.io_stall_read_ms / NULLIF(vfs.num_of_reads,0) AS avg_read_ms, vfs.num_of_writes, vfs.io_stall_write_ms / NULLIF(vfs.num_of_writes,0) AS avg_write_msFROM sys.dm_io_virtual_file_stats(NULL, NULL) vfsJOIN sys.master_files mfON vfs.database_id = mf.database_idAND vfs.file_id = mf.file_idORDER BY avg_read_ms DESC;
Step 2: Increase IOPS
Portal
- Open your Managed Instance
- Go to Compute + Storage
- Enable Next-Gen General Purpose
- Move the IOPS slider
- Apply changes
No downtime. No reconnects.
Step 3: Measure Again
Run the same scripts and compare:
- Read latency
- Write latency
- Wait stats
- Concurrency stability
You should see lower latency and fewer I/O waits almost immediately.
Step 4: Find the Cost Sweet Spot
Reduce IOPS gradually until performance just meets your SLA.
This gives you maximum efficiency for minimum cost.
This is now possible in Managed Instance for the first time.
Automation: Adjust IOPS with Azure CLI
You can also automate this using ARM REST.
az rest \ --method patch \ --url "https://management.azure.com/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Sql/managedInstances/<mi>?api-version=2024-11-01-preview" \ --body "{ \"properties\": { \"storageIOps\": 8000 } }"
This allows you to script performance changes during load tests or scheduled scaling windows.
ARM Template Snippet (Next-Gen Enabled)
{ "type": "Microsoft.Sql/managedInstances", "apiVersion": "2024-11-01-preview", "name": "mi-nextgen", "location": "eastus", "sku": { "name": "GP_Gen5", "tier": "GeneralPurpose", "capacity": 8 }, "properties": { "storageSizeInGB": 1024, "isGeneralPurposeV2": true, "storageIOps": 8000 }}
Summary
Azure SQL Managed Instance Next-Gen is one of the most important platform upgrades Microsoft has released for SQL Server in the cloud.
• Storage runs on Elastic SAN
• IOPS are decoupled from compute
• Baseline IOPS are free
• Performance tuning is simple and fast
• Costs are predictable
• Changes happen online
For years, DBAs have been forced to overprovision compute to fix storage problems. That era is over.
If you run Managed Instance today and care about performance, Next-Gen General Purpose is not optional. It is the new baseline.
References
Microsoft Learn – Next-Gen General Purpose Managed Instance
https://learn.microsoft.com/en-us/azure/azure-sql/managed-instance/service-tiers-next-gen-general-purpose-use
Azure SQL Team Blog – Next-Gen General Purpose GA
https://techcommunity.microsoft.com/blog/azuresqlblog/generally-available-azure-sql-managed-instance-next-gen-general-purpose
Elastic SAN Performance Overview
https://learn.microsoft.com/en-us/azure/storage/elastic-san/elastic-san-performance
Azure SQL Managed Instance Resource Limits
https://learn.microsoft.com/en-us/azure/azure-sql/managed-instance/resource-limits
Azure SQL Managed Instance Overview
https://learn.microsoft.com/en-us/azure/azure-sql/managed-instance/sql-managed-instance-paas-overview
DECLARE @DoDelete bit = 0; -- 0 = TEST (SELECT), 1 = DELETEIF @DoDelete = 0BEGIN ;WITH d AS ( SELECT fss.function_schedule_status_id, fss.function_id, fss.schedule_status, fss.status_date, fss.status_description, fss.created_on, fss.created_by, fss.last_modified_on, fss.last_modified_by, fss.status_reference_id, ROW_NUMBER() OVER ( PARTITION BY fss.function_id ORDER BY fss.created_on DESC, fss.function_schedule_status_id DESC ) AS rn FROM dbo.function_schedule_status AS fss ) SELECT function_schedule_status_id, function_id, schedule_status, status_date, status_description, created_on, created_by, last_modified_on, last_modified_by, status_reference_id, rn FROM d WHERE rn > 1000 OR created_on < DATEADD(MONTH, -3, GETDATE());ENDELSEBEGIN ;WITH d AS ( SELECT fss.function_schedule_status_id, fss.function_id, fss.created_on, ROW_NUMBER() OVER ( PARTITION BY fss.function_id ORDER BY fss.created_on DESC, fss.function_schedule_status_id DESC ) AS rn FROM dbo.function_schedule_status AS fss ) DELETE fss FROM dbo.function_schedule_status AS fss JOIN d ON d.function_schedule_status_id = fss.function_schedule_status_id WHERE d.rn > 1000 OR d.created_on < DATEADD(MONTH, -3, GETDATE());END
Discover more from SQLYARD
Subscribe to get the latest posts sent to your email.


