Bob Ward recently posed an interesting question on Data Exposed: Is automatic index compaction the end of index maintenance?
That's a pretty big question for those of us who've spent yeeeears managing indexes, and Bob didn't just talk about it. He published the scripts to demonstrate what it can do, so I had to try it myself. 😀
Bob's demo was on Azure SQL Hyperscale with 8 vCores. I used something a bit more modest with a free Azure SQL Database with 2 vCores. Otherwise, I followed his experiment and watched automatic compaction recover the space left behind after deleting nearly half a million rows.
It worked. Very well. But the scan after compaction was slower than I expected, and that's where this got interesting.
Side note; I actually covered this feature back in April in Your Nightly Index Rebuild Job May Be On Its Way Out -- but I only talked about it. This time I get to show what it actually does.
Bob's Demo, My Smaller Database
My test used the Azure SQL Database free offer: General Purpose, serverless, Standard-series (Gen5), 2 vCores and 32 GB of storage. The offer includes a monthly allowance of 100,000 vCore seconds and 32 GB of data per database, subject to terms and limits. This is not Bob's 8-vCore Hyperscale environment, and that distinction matters when we get to scan times.
The demo scripts are in microsoft/bobsql, demos/auto_index_compaction. The test creates a million-row table with a GUID clustered key and fixed-width char(100) data, rebuilds the index to establish a dense starting point -- and then deletes roughly half the rows across the table.
For the scan measurements, I used Bob's single-threaded, cold-cache COUNT_BIG test:
CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SET STATISTICS TIME ON; SET STATISTICS IO ON; SELECT COUNT_BIG(*) AS row_count FROM dbo.aic_demo OPTION (MAXDOP 1); SET STATISTICS TIME OFF; SET STATISTICS IO OFF; GO
The workload was intentionally simple: one table, one full scan, and no concurrent application workload. I followed Bob's demonstration with one additional UPDATE to address a compaction timing issue that I'll explain shortly.
Putting the Index to the Test
After setup, the million-row table required 17,005 logical reads for the scan, which completed in 867 ms. With that baseline established, I ran Bob's degradation script. It deletes rows using CHECKSUM(id) % 2 = 0, spreading the deletions throughout the clustered index rather than removing one continuous range.
| Measurement | Baseline | After Delete |
|---|---|---|
| Row count | 1,000,000 | 500,252 |
| Logical reads | 17,005 | 34,058 |
| Elapsed time | 867 ms | 2,143 ms |
Half the rows were gone, yet the scan required twice as many logical reads and took more than twice as long. The degradation script also reported 33,899 pages at just 52.46% density. Bob's README explains the role of Accelerated Database Recovery (ADR) in that the versioning overhead from the deletes can cause previously full pages to split. In my run, that effect was substantial.
Fewer rows, more work, and a slower scan. That's exactly the kind of problem automatic index compaction is intended to address.
Compaction Was On. Nothing Happened.
I enabled automatic index compaction after the delete, then checked its progress. The database reported the feature as ON. The page count didn't move. Ten minutes later, it still hadn't moved.

This wasn't evidence that compaction had failed. It was a timing problem. Automatic compaction works in conjunction with version cleanup, and MSFT's FAQ explains why enabling it after the relevant modifications may leave existing low-density pages untouched. Bob's README also calls attention to the timing.
For a planned large delete, that's worth knowing before you begin. Enabling compaction afterward does not guarantee that it will revisit the pages you've already changed.
But I still wanted to see what compaction could accomplish with those half-empty pages. Since enabling it after the delete hadn't produced any change, I needed to generate new activity while the feature was enabled.
So I updated every remaining row. This was my addition, not part of Bob's original test:
UPDATE dbo.aic_demo SET val = val + 1;
That affected 500,252 rows and generated new modifications for cleanup to process. My final compacted state was reached through a slightly different path than Bob's original demonstration.
Now Compaction Had Something to Do
A few minutes later, after things settled, two consecutive progress checks using Bob's 05-check-progress.sql query returned the same result:
Rows: 500,252 Page count: 8,563 Page density: 98.86% Fragmentation: 96.55%
From 33,899 pages down to 8,563, without rebuilding the index. That's roughly a 75% reduction in allocated index pages from the degraded state. Automatic compaction had done exactly what it was designed to do -- restoring page density and substantially reducing the number of pages.
But -- the fragmentation was still sitting at 96.55%. That's an important distinction because automatic compaction is designed to improve page density, not eliminate fragmentation. Improving page density isn't necessarily going to restore the logical ordering of the index pages. Microsoft says that here, and my results illustrated it very clearly.
So I had an index that was nearly 99% dense, with about a quarter of its previous page count, but still heavily fragmented. What's that going to mean for query performance?
The Scan Was the Surprise
With so many fewer pages to read, I expected the scan to improve, and in one respect, it did. Logical reads dropped from 34,058 in the degraded state to only 8,722 post compaction. But even with that reduction, the scan still took considerably longer to complete.
The first scan during the final stages of compaction took 6,931 ms, and another after the page count settled took 7,045 ms. Both substantially slower than the 2,143 ms scan before compaction, regardless of the large improvement in page density. So, with fragmentation still at 96.55%, I was curious what a rebuild might change.
What If I Rebuild It Anyway?
I ran one final statement:
ALTER INDEX pk_aic_demo ON dbo.aic_demo REBUILD;
When complete, the index had 8,479 pages at 99.84% density, compared with 8,563 pages at 98.86% density after compaction. That's minimal difference, but the fragmentation dropped from 96.55% to 0.00%.
Here I ran the same cold-cache scan again and it completed in 439 ms. A second run took 440 ms. Both reported 8,508 logical reads, with read-ahead reads of 8,503.
That's a very different result from the compacted scans. And unlike the original demonstration, the question wasn't whether compaction could reclaim space. Obviously, it could. The question now was whether the rebuild still provided any gains after the compaction had been done, and the density restored.
All Four States, Side by Side
| State | Logical reads | Read-ahead reads | Elapsed (ms) |
|---|---|---|---|
| Baseline | 17,005 | 16,998 | 867 |
| Degraded | 34,058 | 34,049 | 2,143 |
| Compacted | 8,722 | 32,836 | 7,045 |
| Rebuilt | 8,508 | 8,503 | 440 |
The baseline and degraded states were each measured once. For the compacted and rebuilt states, I ran the scan twice and used the second result in the comparison above.
The rebuilt scan finished about 16 times faster than the compacted scan in this test, even though the two indexes had almost the same page count and density.
Fragmentation may have contributed to that difference. Microsoft documents how gaps left by compaction can reduce the size of read-ahead I/O. But this test wasn't designed to isolate fragmentation from other factors affecting execution time, so I can't really attribute the entire improvement to the rebuild correcting page order.
So Is it the End of Index Maintenance?
Not on the strength of this test. Automatic compaction was very effective at restoring page density. It took an index that was only 52.46% dense and brought it to 98.86%, reducing its page count remarkably without a rebuild. That's a very meaningful benefit, especially for workloads that create low-density pages through recurring deletes and updates.
The rebuild produced a much faster full scan under my test conditions, but that doesn't mean every compacted index needs rebuilding. And it certainly doesn't mean this 2-vCore General Purpose result predicts what Bob would see on Hyperscale. What I believe it does tell us is that reclaiming space and improving scan performance are not always the same thing.
There are two operational details I'd keep in mind. First, automatic compaction isn't a retroactive fix for every low-density page (the timing of modifications matters). Second, automatic compaction does not update statistics, so it doesn't replace statistics maintenance.
For me, the takeaway isn't to disable compaction or keep rebuilding everything on a schedule. It's to let compaction handle what it handles well, and then to use actual workload measurements to decide whether any index maintenance is still needed.
What This Test Doesn't Prove
This was one table and one single-threaded, cold-cache full scan on a 2-vCore General Purpose serverless database. Baseline and degraded scans were each measured once and the compacted and rebuilt scans were measured twice. I did not test seeks, range queries, warm-cache performance, concurrency, or any other service tiers. I also added a full-table UPDATE after missing the initial compaction window, which changes the sequence of work relative to Bob's demo.
Those limits matter. I wouldn't use these numbers to predict a production performance gain, but I would use them as a reason to measure before removing an established maintenance operation.
Cleanup
Bob's 06-cleanup.sql script removes the demo table and turns the feature off:
DROP TABLE IF EXISTS dbo.aic_demo; GO ALTER DATABASE CURRENT SET AUTOMATIC_INDEX_COMPACTION = OFF; GO
If you're testing on the free offer, disconnect SSMS when you're finished, including Object Explorer. Open sessions can prevent serverless auto-pause and consume your included vCore seconds.
More to Read
Bob Ward: The End of Index Maintenance? — Data Exposed
Bob Ward's demo scripts — microsoft/bobsql
Microsoft Learn: Automatic index compaction
Microsoft Tech Community: Stop defragmenting and start living
Azure SQL Database free offer
SQLFingers: Your Nightly Index Rebuild Job May Be On Its Way Out




























