You archived or moved hundreds of gigabytes out of a mailbox database, yet the .edb file is exactly the same size. That's by design. In this post I'll explain how white space works in Exchange Server SE and Exchange 2019, how to measure it with AvailableNewMailboxSpace, and the two ways to give disk space back: a new database with online mailbox moves (the right default) and offline defragmentation with eseutil /d (a last resort).
How it works#
A mailbox database is an Extensible Storage Engine (ESE) file made of pages. When items, folders or whole mailboxes go away, their pages are freed inside the file, not handed back to the volume. Background database maintenance makes those pages available for reuse, so the database fills its own white space before it grows again, but the file never shrinks while mounted. Every DAG copy and every full backup carries the same white space. The usual sources:
- Mailbox moves out. A moved mailbox isn't removed from the source straight away; it stays as a soft-deleted mailbox until the database's
MailboxRetentionperiod (30 days by default) ends. - Disabled or removed mailboxes, which are kept for the same retention period.
- Purges: Recoverable Items cleanup after deleted item retention, retention policies and archiving projects.
Measuring it#
Get-MailboxDatabase -Status | Sort-Object Name | Select-Object Name,
@{n='SizeGB';e={[math]::Round($_.DatabaseSize.ToBytes()/1GB,1)}},
@{n='WhiteSpaceGB';e={[math]::Round($_.AvailableNewMailboxSpace.ToBytes()/1GB,1)}}The -Status switch is required; without it both values are empty. Treat AvailableNewMailboxSpace as an indicator rather than an exact figure: it reports free space the database can hand to new or growing mailboxes and doesn't necessarily count every free page. Cross-check it against what the mailboxes actually hold:
Get-MailboxStatistics -Database DB01 |
Group-Object { if ($null -ne $_.DisconnectReason) { "$($_.DisconnectReason)" } else { 'Connected' } } |
Select-Object Name, Count, @{n='GB';e={[math]::Round((($_.Group | ForEach-Object {
$_.TotalItemSize.Value.ToBytes() + $_.TotalDeletedItemSize.Value.ToBytes() }) | Measure-Object -Sum).Sum/1GB,1)}}You get one line each for connected, soft-deleted and disabled mailboxes, including Recoverable Items. If the file is far larger than the total, the gap is white space plus internal overhead; a big soft-deleted line after a migration explains much of it. Run both scripts in the Exchange Management Shell on an Exchange server, because in a plain remote session the sizes come back as text.
Step-by-step: reclaiming the space#
1. Decide whether you need to#
White space costs disk on every copy and lengthens backups and reseeds, but it isn't a performance problem in itself. If the database will grow back into it within months, leave it. Reclaim it when you genuinely need the disk, such as after moving many mailboxes to Exchange Online.
2. Purge soft-deleted mailboxes you no longer need#
Get-MailboxStatistics -Database DB01 | Where-Object DisconnectReason -eq 'SoftDeleted' |
Format-Table DisplayName, MailboxGuid, TotalItemSize -AutoSize
Remove-StoreMailbox -Database DB01 -Identity <MailboxGuid> -MailboxState SoftDeletedOnly remove entries for moves you've confirmed as complete; this is permanent. It doesn't shrink the file either, but it turns those pages into reusable white space.
3. Recommended: move everything to a new database#
Moves are online: users keep working and each mailbox only reconnects at the end of its own move. Create the new database and, in a DAG, its copies:
New-MailboxDatabase -Name DB01B -Server EX01 -EdbFilePath "E:\DB01B\DB01B.edb" -LogFolderPath "F:\DB01B\Logs"
Mount-Database DB01B
Add-MailboxDatabaseCopy -Identity DB01B -MailboxServer EX02 -ActivationPreference 2Exchange warns that the Microsoft Exchange Information Store service should be restarted after a database is added; schedule that restart once the server's active databases are moved elsewhere. Then move user, system and archive mailboxes:
Get-Mailbox -Database DB01 -ResultSize Unlimited | New-MoveRequest -TargetDatabase DB01B -BatchName DB01-rebuild
Get-Mailbox -Database DB01 -Arbitration | New-MoveRequest -TargetDatabase DB01B -BatchName DB01-rebuild
Get-Mailbox -Database DB01 -AuditLog | New-MoveRequest -TargetDatabase DB01B -BatchName DB01-rebuild
Get-Mailbox -Database DB01 -PublicFolder | New-MoveRequest -TargetDatabase DB01B -BatchName DB01-rebuild
# Archives stored in DB01 whose primary mailbox lives in another database
Get-Mailbox -Archive -ResultSize Unlimited |
Where-Object { "$($_.ArchiveDatabase)" -eq 'DB01' -and "$($_.Database)" -ne 'DB01' } |
New-MoveRequest -ArchiveOnly -ArchiveTargetDatabase DB01B -BatchName DB01-rebuildTrack progress with Get-MoveRequest -BatchName DB01-rebuild | Get-MoveRequestStatistics. Moves write a comparable volume of transaction logs on the target, and workload management throttles them to protect DAG replication; if they crawl, see stalled and failed mailbox moves.
When everything has finished, remove the completed move requests, the old database's passive copies (Remove-MailboxDatabaseCopy DB01\EX02) and then the database (Remove-MailboxDatabase DB01). Exchange refuses while any mailbox remains, and it leaves the files on disk, so delete the old .edb and log folder yourself once you're sure.
4. Last resort: offline defragmentation#
Use eseutil /d only when a second database isn't an option (say, a standalone server with no room for another volume) and a long outage is acceptable:
- Take a full backup and confirm it can be restored.
- In a DAG, remove the passive copies first. Microsoft lists an offline defragmentation of any copy among the situations that require reseeding.
- Run
Dismount-Database DB01, then check thateseutil /mh "E:\DB01\DB01.edb"reportsState: Clean Shutdown. - Defragment. By default the temporary database is created in the current folder, so point it at a volume with room for the compacted database plus roughly 10% (the long-standing "110%" rule of thumb):
Command Prompt
eseutil /d "E:\DB01\DB01.edb" /t"G:\Defrag\TEMPDFRG.EDB" - Mount the database and take a new full backup straight away. Eseutil itself recommends this, because restoring an older backup rolls the database back to that point. Then re-add the DAG copies so they seed from scratch.
| Aspect | New database and moves | eseutil /d |
|---|---|---|
| User impact | Online; a brief reconnect per mailbox | Database offline for the whole run |
| Disk needed | A volume for the new database and its logs | About the compacted size plus 10% |
| DAG copies | New copies seed normally | Every other copy must be reseeded |
| Control | Per mailbox: pause, retry or remove a request | All or nothing |
| Side benefits | A fresh database; corrupted items surface per mailbox | None beyond the smaller file |
Verify#
Get-MailboxDatabase DB01B -Status | Format-List Name, DatabaseSize, AvailableNewMailboxSpace
Get-Item "E:\DB01B\DB01B.edb" | Select-Object Name, @{n='GB';e={[math]::Round($_.Length/1GB,1)}}
Get-Volume -DriveLetter E | Format-Table DriveLetter, SizeRemaining, Size -AutoSizeGood looks like a database close to the mailbox total you measured earlier, a small AvailableNewMailboxSpace, more free space on the volume and, in a DAG, every copy reporting Healthy.
Tips & gotchas#
- Don't schedule offline defrags as routine maintenance. Background maintenance already looks after the database; nothing justifies a regular outage, reseed and fresh backup.
- Watch the log volume during moves. If backups aren't truncating logs, the target's log disk fills long before the moves finish.
- Set activation preferences on the new database's copies before production load lands on it; see activation preference and rebalancing.

