AzureDBA

Azure SQL MI vs. VM Performance Gap: Migration Estimator Tool

It’s a common and frustrating scenario for teams migrating to Azure SQL PaaS. A workload that performs well on a 16-core Azure SQL VM (IaaS) suddenly requires a 32-core SQL Managed Instance just to achieve similar query performance. This expensive “fix” isn’t a bug—it’s a fundamental mismatch in service tiers.

This deep-dive analysis, updated for October 2025, moves beyond a simple vCore-to-vCore comparison. We will investigate the hidden resource governance for memory, log throughput, and I/O that is built into the Managed Instance model and explains why your General Purpose MI can’t keep up with your IaaS VM. Why Azure SQL MI vs. VM Performance Differs | GigXP.com

Why Azure SQL PaaS Doesn't Perform Like a VM with the Same Specs

The "16-core VM vs. 32-core MI" Problem Explained

By The GigXP Technical Team | Updated: November 3, 2025

It is a common and frustrating story for teams moving to Azure's PaaS database services. You have a workload running perfectly well on a 16-core Azure SQL Virtual Machine (IaaS). You decide to migrate to an Azure SQL Managed Instance (PaaS) to reduce management overhead. You choose a 16-core instance, expecting similar performance. Instead, the application slows to a crawl, and queries time out.

After testing, you find that only a 32-core Managed Instance provides the stability you need. This is a 2x cost increase and it makes no sense. Is the PaaS service simply less powerful?

The answer is no. The problem is that a "vCore" in PaaS is not the same as a "vCore" in IaaS. In PaaS, a vCore is a bundle of resources, and your workload is not hitting a CPU limit. It is hitting three other "hidden" resource walls that are governed by the platform.

The IaaS vs. PaaS Performance Model

Understanding the "why" starts with one concept: resource governance. On your IaaS VM, you control everything. The SQL Server instance can "pull" whatever resources it needs from the hardware. In PaaS, the platform "pushes" (allocates) a specific quota of resources to your instance to ensure stability for everyone. Your instability is the platform succeeding at its job of enforcing those quotas.

IaaS (VM): Unfettered "Pull" Model

Your Control:

  • Operating System
  • SQL Server Engine
  • Host Caching
  • All Resource Allocation

Your SQL Server "pulls" all the RAM, I/O, and CPU it can from the hardware. It is limited only by the VM's physical SKU.

PaaS (MI): Governed "Push" Model

Azure Control (Governance):

  • Memory Ratio
  • Log Throughput
  • I/O Latency & IOPS
  • HA/Backups (Overhead)

Azure "pushes" a metered quota of resources to your instance. When you hit a limit, the platform throttles your workload.

The Four Bottlenecks You Are Hitting

Your workload is not CPU-bound. It is resource-bound. Click the tabs below to see the exact limits you are hitting on a 16-core General Purpose MI.

Bottleneck 1: The ~46 GB Memory Deficit

This is the single biggest source of the performance gap. Your IaaS VM is likely a memory-optimized series (like Edsv5), which gives you an 8:1 memory-to-vCore ratio. The MI General Purpose (Gen5) tier has a fixed, lower ratio of 5.1 GB per vCore.

This creates a massive memory deficit. Your queries, tuned to 128 GB of RAM, suddenly only have 81.6 GB. This causes:

  • Memory Grant Issues: Queries wait for memory, causing high RESOURCE_SEMAPHORE waits.
  • TempDB Spills: The engine writes intermediate results to tempdb instead of processing in-memory, creating a new I/O problem.
  • Buffer Pool Evictions: The buffer pool (your data cache) is smaller, leading to more physical disk reads, which are slow on this tier.

Note that in PaaS, Max Memory is pre-configured and not user-adjustable. Scaling to 32 vCores "fixes" this by incidentally giving you 163.2 GB of RAM (32 * 5.1), which finally exceeds your original 128 GB.

Chart: Total RAM Comparison

Bottleneck 2: The Transaction Log "Wall"

On your IaaS VM, log write speed is only limited by your disk (e.g., 200+ MiB/s). The MI General Purpose tier enforces a hard, instance-level governor on log write throughput to guarantee its service level.

Here is the simple math for the Standard-series (Gen5) tier:

  • 16-core MI Cap: 16 vCores * 4.5 MiB/s per vCore = 72 MiB/s
  • 32-core MI Cap: 32 vCores * 4.5 MiB/s = 144 MiB/s (but hits the instance max) = 96 MiB/s

Your workload (made worse by tempdb spills) is generating more than 72 MiB/s of log writes. The platform throttles it, causing high LOG_RATE_GOVERNOR waits. The 96 MiB/s cap is the absolute maximum for the *entire instance* on this hardware, also known as INSTANCE_LOG_GOVERNOR. Scaling to 32 vCores buys you an extra 24 MiB/s of headroom, which is just enough to stabilize the workload.

Chart: Log Write Throughput Cap

Bottleneck 3: Storage IOPS, Throughput, and Latency

A well-configured IaaS VM has an optimized I/O path. The MI General Purpose tier is designed for cost-effectiveness, not raw I/O performance. It's not just about latency; IOPS and throughput are also governed.

  • IaaS VM: Uses a local ephemeral SSD for tempdb (<1ms latency) and Premium SSDs for data, often with ReadOnly host caching that serves reads from the host's local SSD.
  • MI GP Tier: Uses a local SSD for tempdb (which is good), but all your data and log files are on remote Azure Blob Storage. This has a built-in latency of 5-10ms for every single I/O operation. There is no host caching.

When your memory pressure (Bottleneck 1) forces queries to spill and read from disk, those reads are now 5-10x slower. Furthermore, I/O in the GP tier is governed by file size, not vCores. A larger disk file gets you more IOPS and throughput, up to a maximum. This is another trap: a 16-core MI with small files will have terrible I/O performance, regardless of its vCore count.

Infographic: I/O Read Latency Path

IaaS VM (with Host Cache)
App VM Host Cache Disk < 1ms
PaaS MI (General Purpose)
App Gateway MI Instance Remote Storage 5-10ms

Bottleneck 4: The CPU Hardware Itself

Finally, a "vCore" is not a standardized unit. The underlying physical hardware matters. It is very likely your IaaS VM and PaaS MI are running on different hardware generations.

The Standard-series (Gen5) hardware used by the base General Purpose tier is older and slower than the hardware you typically use for a performance-oriented IaaS VM. This can result in a 30-40% reduction in single-thread CPU performance, even before considering the other bottlenecks.

We've created a dedicated section below to explain this in more detail. Click here to jump to the CPU Hardware Generations section.

How to Find the Bottlenecks: Check Your Wait Stats

Do not guess. You can confirm these exact problems by querying SQL Server's Dynamic Management Views (DMVs). Run the following queries on your 16-core MI *during* the performance test.

Query 1: Look for Top Wait Statistics

This query shows you what your queries are spending the most time waiting on. High values for the waits listed below are your smoking guns.


SELECT TOP 10
    wait_type,
    wait_time_ms / 1000.0 AS wait_time_sec,
    (wait_time_ms * 100.0) / SUM(wait_time_ms) OVER() AS pct_wait_time
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
    -- Filter out common "benign" waits
    'CLR_SEMAPHORE', 'LAZYWRITER_TIMER', 'SQLTRACE_BUFFER_FLUSH',
    'SLEEP_TASK', 'WAITFOR', 'REQUEST_FOR_DEADLOCK_SEARCH'
)
ORDER BY wait_time_ms DESC;
                            
  • High RESOURCE_SEMAPHORE: This is your memory problem. Queries are waiting for available memory grants. This confirms Bottleneck 1.
  • High LOG_RATE_GOVERNOR: This is your log throttling problem. The platform is actively capping your transaction log throughput. This confirms Bottleneck 2.
  • High PAGEIOLATCH_SH / PAGEIOLATCH_EX: This is your I/O problem. Queries are waiting for data to be read from (or written to) disk. This is a symptom of both low memory (Bottleneck 1) and high-latency storage (Bottleneck 3).

The 'TempDB' Factor in General Purpose

A common misconception is that because the GP tier uses a local SSD for `tempdb`, its performance is unlimited. This is not true.

While the local SSD provides excellent latency (like on your IaaS VM), the IOPS and throughput for `tempdb` are *also* governed based on your vCore count. When your memory-starved queries (Bottleneck 1) begin spilling to `tempdb`, they generate a massive amount of I/O. This I/O quickly hits the `tempdb` governance limits, creating a new bottleneck.

Your workload becomes a chain reaction of failure:

  1. Low Memory causes queries to request memory grants.
  2. Wait Stats show RESOURCE_SEMAPHORE.
  3. Queries Spill to `tempdb` to avoid waiting.
  4. TempDB I/O spikes, hitting the local SSD's IOPS/throughput cap.
  5. Log Activity also spikes from the spills, hitting the LOG_RATE_GOVERNOR cap.

The entire instance becomes throttled, not just by one limit, but by three separate resource governors at the same time.

Choosing Your Weapon: A Deeper Look at Service Tiers

The root of the problem is an architectural mismatch. You are comparing an IaaS VM built for performance with a PaaS tier (General Purpose) built for cost-efficiency. Here is the architectural difference.

General Purpose: Decoupled Storage

This tier separates compute (vCores, RAM) from storage (data files). Your data lives on remote Azure Blob Storage, which is cost-effective and durable but has higher latency.

  • Best for: Most business applications, development, and workloads that are not I/O-intensive.
  • Key Trait: 5-10ms storage latency.
  • The Trap: Performance depends on file size, not vCores.

Business Critical: Coupled Storage

This tier bundles compute and storage on the same machine. Your data lives on a super-fast local SSD, much like your IaaS VM. This provides the best performance and lowest latency.

  • Best for: High-performance OLTP, reporting, and I/O-intensive workloads.
  • Key Trait: 1-2ms storage latency.
  • The Benefit: Architecturally identical to a high-performance VM.

CPU Hardware Generations Explained

Not all vCores are created equal. The underlying physical hardware generation dictates the "speed" of your vCore. Your IaaS VM is likely on newer, faster hardware than the base PaaS tier.

Azure SQL Managed Instance offers different "hardware series" that map to these generations. You are likely on the "Standard-series (Gen5)" hardware, which is the oldest and slowest.

Hardware Series Typical Tier CPU Generation Base Speed
Standard-series (Gen5) General Purpose Intel Broadwell / Cascade Lake 2.3 - 2.5 GHz
Premium-series Business Critical Intel Ice Lake 2.8 GHz (3.5 Turbo)
Premium-series (Mem-Opt) Business Critical Intel Ice Lake 2.8 GHz (3.5 Turbo)
IaaS VM (Edsv5) IaaS Intel Ice Lake 3.0 GHz (3.5 Turbo)

This table shows the 16-core GP instance is running on a CPU that is significantly slower (2.5 GHz) than your IaaS VM (3.0-3.5 GHz). This slower clock speed directly impacts query processing time, compounding the memory and I/O problems. The Business Critical tier runs on the same fast "Premium-series" (Ice Lake) hardware as modern IaaS VMs.

Real-World Performance Mapping: IaaS VM to PaaS MI

Stop thinking 1:1 on vCores. The right way to migrate is to match your workload's *profile*. Here is a simple guide for mapping a high-performance IaaS VM to the correct PaaS tier.

Workload Profile Recommended IaaS VM SKU Equivalent PaaS MI Tier
General / Dev / Test Standard_Dsv5 General Purpose
High I/O / OLTP Standard_Ebdsv5 (local SSD) Business Critical
High Memory / Analytics Standard_Edsv5 (8:1 ratio) Business Critical (Mem-Optimized)
Massive Scale (VLDB) (N/A - Complex Cluster) Hyperscale

This table makes the problem obvious. Your 16-core IaaS VM was likely an "E-series" (memory-optimized) VM, which maps directly to the Business Critical tier, not General Purpose. You were testing the wrong tier from the start.

Interactive PaaS SKU Estimator

Use this simple calculator to get a starting recommendation for your IaaS to PaaS migration. Enter your *current* IaaS VM's core specs.

The "Aha!" Moment: Comparing Tiers

The data makes the problem clear. The 32-core GP instance "fixes" the problem by brute-forcing the memory and log rate limits. But this is inefficient. You are buying 16 extra vCores just to get the RAM and I/O that came with your original 16-core VM.

Table 1: The Inefficient Comparison (Your Test Scenario)

Resource 16-core VM (Best Practice) 16-core MI (GP, Gen5) 32-core MI (GP, Gen5)
Total RAM 128 GB 81.6 GB (~46 GB Deficit) 163.2 GB (Solved)
Log Rate Cap 200+ MiB/s (Unthrottled) 72 MiB/s (Throttled) 96 MiB/s (Instance Cap)
Data Storage Local SSD Cache (<1ms) Remote Storage (5-10ms) Remote Storage (5-10ms)
CPU (Example) 3.5 GHz (Ice Lake) 2.5 GHz (Cascade Lake) 2.5 GHz (Cascade Lake)

The Solution: Test the Business Critical Tier

The General Purpose tier is not the architectural equivalent to your high-performance VM. The Business Critical (BC) tier is. Before you pay for a 32-core GP instance, you should test a 16-core Business Critical instance. Here is why it is the correct "apples-to-apples" comparison.

Table 2: The Correct Architectural Comparison

Resource 16-core VM (Best Practice) 16-core MI (Business Critical)
Total RAM 128 GB 112 GB (Premium-series)
Log Rate Cap 200+ MiB/s 192 MiB/s (Instance Cap)
Data Storage Local SSD Cache (<1ms) Local SSDs (1-2ms)
CPU Hardware Ice Lake (e.g., 3.5 GHz) Ice Lake (e.g., 2.8 GHz)

As the table shows, a 16-core Business Critical instance is a much closer match. It uses fast, local SSDs for data (eliminating the 5-10ms latency) and has a log rate cap (192 MiB/s) that is 2.6x higher than the 32-core GP instance's cap. The memory (112 GB) is also much closer to your original 128 GB and may be sufficient. This tier is built for high-performance, I/O-intensive workloads like yours.

What About Hyperscale?

Hyperscale is another option, but it's architecturally different. It's designed for massive scale (up to 100 TB) and extremely fast backups and restores. It has a unique log service and tiered caching system. While its performance is excellent, it may be overkill. Business Critical is the more direct "lift and shift" performance equivalent to a high-spec IaaS VM. Test Business Critical first; if your database is exceptionally large (10s of TBs), then Hyperscale becomes the next logical step to evaluate.

Executive Summary

  • A PaaS vCore is not just a CPU; it is a bundle of metered resources (CPU, RAM, I/O).
  • Your 16-core GP instance is slow because it is starved of memory (~46 GB deficit) and throttled on log writes (72 MiB/s cap).
  • Your problems are compounded by high-latency remote storage and older, slower CPU hardware (Gen5).
  • The 32-core GP "fix" works by incidentally giving you enough memory (163.2 GB) and log headroom (96 MiB/s) to stop the throttling.
  • You are paying for 16 extra vCores you likely do not need.
  • The main recommendation: Before you approve the 32-core General Purpose instance, test your workload on a 16-core Business Critical instance. It is the correct architectural equivalent to your IaaS VM and will likely meet your performance needs more efficiently.

Final Thoughts: It's About Architecture, Not vCores

Moving from IaaS to PaaS is an architectural shift, not a simple hardware swap. The "16-core VM vs. 32-core MI" mystery is a classic case of an architectural mismatch. The problem isn't that Azure SQL PaaS is "slower"; it's that the General Purpose tier is built for a different purpose than your high-performance IaaS VM.

General Purpose is designed for cost-efficiency with decoupled storage, making it perfect for thousands of applications. But your workload, which was tuned for 128 GB of RAM and sub-millisecond I/O, belongs on a tier that provides those resources. That tier is Business Critical.

Stop focusing on vCore count and start focusing on the right architecture. Use the DMV queries to find your specific bottlenecks. We are confident that when you test a 16-core Business Critical instance, your performance problems will disappear, and you will have found the true, cost-efficient PaaS equivalent to your powerful VM.

Frequently Asked Questions

A 16-core Business Critical instance is more expensive than a 32-core General Purpose one. How is that a better deal?

It's about "right-sizing" and efficiency. With the 32-core GP instance, you are paying for 16 extra vCores (and their associated software licenses) that your workload likely doesn't even need. You are buying them *only* to get the memory and log rate that comes with them. The 16-core BC instance "right-sizes" your deployment: you pay for the 16 vCores you actually need, and you get the high-performance storage and memory that are appropriate for that workload. It's the more efficient and architecturally correct solution.

My workload is still slow on Business Critical. Now what?

This is rare if you're coming from a comparable VM, but it means you've found a new bottleneck. First, check your DMVs for waits. If you are no longer I/O or log bound, you might be genuinely CPU-bound. In this case, you may need to scale up to 20 or 24 vCores. Second, check your memory. If you see RESOURCE_SEMAPHORE waits, consider the "Memory-Optimized" hardware series in the BC tier, which provides a much higher 1:8 memory-to-vCore ratio, just like your original VM.

Can't I just optimize my queries to work on the 16-core GP?

You can and should always optimize queries. However, optimization has its limits. If your application's logic fundamentally requires large data scans, sorting large datasets in `tempdb`, or performing complex joins that need a lot of memory, no amount of tuning will fix a 46 GB memory deficit or a 5-10ms storage latency bottleneck. You will be optimizing *around* a platform limit rather than solving the root problem.

© 2025 GigXP.com. All rights reserved.

Disclaimer: The Questions and Answers provided on https://gigxp.com are for general information purposes only. We make no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services, or related graphics contained on the website for any purpose.

What's your reaction?

Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0

More in:Azure

Next Article:

0 %