I used the following commands to create two sparsebundles, with the only difference being that one is 100GB and the other is 200GB.
hdiutil create -size 100g -fs APFS -volname test1 -type SPARSEBUNDLE -encryption test1.sparsebundle
hdiutil create -size 200g -fs APFS -volname test2 -type SPARSEBUNDLE -encryption test2.sparsebundle
Then, I created a 5GB random file in each of their mounted volumes:
dd if=/dev/urandom of=/Volumes/test1/random_5gb_file bs=1m count=5120
dd if=/dev/urandom of=/Volumes/test2/random_5gb_file bs=1m count=5120
Afterward, I deleted them:
rm /Volumes/test1/random_5gb_file
rm /Volumes/test2/random_5gb_file
Then, after waiting a while, I unmounted and remounted them.
I noticed that the 100GB test1.sparsebundle automatically reclaimed the space, while the 200GB test2.sparsebundle still retained 5.4GB of usage.
Later, I used:
hdiutil compact test2.sparsebundle
But no space was reclaimed.
Now, I want to know what the difference is between the 100GB and 200GB sparsebundles, and how I can reclaim the space in the 200GB test2.sparsebundle.
Now, I want to know what the difference is between the 100GB and 200GB sparsebundles.
So, the first thing to understand here is what's going on behind the scenes. By design, Disk Images and the file system are largely "invisible" to each other. APFS thinks it's running on a standard mass storage device, and the Disk image system has no idea what file system is running on it. With that context, there are two other critical details:
-
On the DiskImage side, what the "sparsebundle" format actually does is track what block the file system has written to, only allocate storage when the file system actually asks for it, and using its own mapping table to track what blocks have been written to.
-
On the vast majority of file systems (including APFS), deleting a file doesn't actually modify ANY of that file’s data blocks. All delete actually does is destroy the catalog records that told the file system "what the file was" and mark data blocks the file used as "available" for reuse. Those blocks will eventually be overwritten, but they aren't actually modified.
With that context, the real mystery here is actually this:
I noticed that the 100GB test1.sparsebundle automatically reclaimed the space,
and how this works at all:
hdiutil compact test2.sparsebundle
There’s an answer to that in the man page as well as a hint to why it didn't work:
compact image [options]
scans the bands of a sparse (SPARSE or SPARSEBUNDLE) disk
image containing an APFS or HFS+ filesystem, removing those
parts of the image which are no longer being used by the
filesystem. Depending on the location of files in the hosted
filesystem, compact may or may not shrink the image. For
SPARSEBUNDLE images, completely unused band files are simply
removed.
What's actually going here is that hdiutil is asking the file system to return a structure that describes its unused data runs. That also explains why it would fail— the file system isn't maintaining a block-by-block accounting of every individual data block, it's using its own structure to track usage. hdiutil can only reclaim that APFS the blocks APFS SAYS are free, which isn't necessarily the same as what's actually being used/not used.
and how I can reclaim the space in the 200GB test2.sparsebundle.
In terms of the volume you're actively using, I'm not sure you can. I suspect the space will free up automatically under normal usage, but I don't think there is any way to force it to clear without any other activity.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware