Not a Debian specific question.  You may possibly want to ask/check
on, e.g. relevant qemu
list or the like.

Though qcow2 is quite flexible, and can be quite efficient, depending
what data is written there,
what snapshots are or may have been there, etc., it may also be rather
to quite inefficient, including even
less efficient than raw.  So, e.g. if compression is used, and the
data can't be compressed, it will take
at least slightly more space than that data itself.  Likewise, if
there are no unallocated blocks, not only no space
savings there, but there's the additional overhead of tracking where
the blocks are, as they may be added
in most any order.

So, let's see how grossly inefficient I can be, and if I can recover
some of that.
# qemu-img create -f qcow2 -o
compression_type=zlib,preallocation=off,size=2G
/var/local/vtest/2GiB.qcow2
Formatting '/var/local/vtest/2GiB.qcow2', fmt=qcow2 cluster_size=65536
extended_l2=off preallocation=off compression_type=zlib
size=2147483648 lazy_refcounts=off refcount_bits=16
# stat -c '%s' /var/local/vtest/2GiB.qcow2
196640
#
$ virsh attach-disk balug /var/local/vtest/2GiB.qcow2 vdc --live
--subdriver qcow2
Disk attached successfully

$
// from the guest VM:
bs=65536
# (seek=0; while dd if=/dev/random of=/dev/vdc bs="$bs" count=1
seek="$seek" status=none; do seek=$(expr "$seek" + 2); done
dd: error writing '/dev/vdc': No space left on device
# (seek=1; while dd if=/dev/random of=/dev/vdc bs="$bs" count=1
seek="$seek" status=none; do seek=$(expr "$seek" + 2); done
dd: /dev/vdc: cannot seek: Invalid argument
#
// I used random data so it (generally) wouldn't compress,
// and filled clusters in alternating order to avoid possible
contiguous mapping efficiencies
// Back on the physical host:
# stat -c '%s' /var/local/vtest/2GiB.qcow2
2148073472
# expr 512 \* 2 \* 1024 \* 1024 \* 2
2147483648
#
// Let's detach it, add a snapshot, reattach, and likewise fill again
$ virsh detach-disk balug vdc
Disk detached successfully

$
# qemu-img snapshot -l /var/local/vtest/2GiB.qcow2
# qemu-img snapshot -c snap01 /var/local/vtest/2GiB.qcow2
# qemu-img snapshot -l /var/local/vtest/2GiB.qcow2
Snapshot list:
ID        TAG               VM SIZE                DATE     VM CLOCK     ICOUNT
1         snap01                0 B 2025-05-20 01:42:06 00:00:00.000          0
#
$ virsh attach-disk balug /var/local/vtest/2GiB.qcow2 vdc --live
--subdriver qcow2
Disk attached successfully

$
// back on the VM guest:
# (seek=0; while dd if=/dev/random of=/dev/vdc bs="$bs" count=1
seek="$seek" status=none; do seek=$(expr "$seek" + 2); done; seek=1;
while dd if=/dev/random of=/dev/vdc bs="$bs" count=1 seek="$seek"
status=none; do seek=$(expr "$seek" + 2); done
dd: error writing '/dev/vdc': No space left on device
dd: /dev/vdc: cannot seek: Invalid argument
#
// So, back to host, let's detach and see what we can free up.
$ virsh detach-disk balug vdc
Disk detached successfully

$
# ls -ons /var/local/vtest/2GiB.qcow2
4199252 -rw------- 1 0 4296015872 May 20 02:08 /var/local/vtest/2GiB.qcow2
# qemu-img snapshot -d snap01 /var/local/vtest/2GiB.qcow2
# ls -ons /var/local/vtest/2GiB.qcow2
2099792 -rw------- 1 0 4296015872 May 20 02:09 /var/local/vtest/2GiB.qcow2
#
// It's sparse file, we got most all that spare space back.
// Now let's see if we can do likewise for data in the image, if we
replace it with something that
// compresses highly well.
$ virsh attach-disk balug /var/local/vtest/2GiB.qcow2 vdc --live
--subdriver qcow2
Disk attached successfully

$
// and back to the VM guest:
# dd if=/dev/zero of=/dev/vdc bs="$bs" status=none; unset bs
dd: error writing '/dev/vdc': No space left on device
#
// And back to host:
$ virsh detach-disk balug vdc
Disk detached successfully

$
# ls -ons /var/local/vtest/2GiB.qcow2
2099792 -rw------- 1 0 4296015872 May 20 02:13 /var/local/vtest/2GiB.qcow2
# fallocate -d /var/local/vtest/2GiB.qcow2; ls -ons /var/local/vtest/2GiB.qcow2
368 -rw------- 1 0 4296015872 May 20 02:16 /var/local/vtest/2GiB.qcow2
#
Well, that nicely and radically shrunk it - not the logical size, but
freed huge numbers of null blocks to make it very sparse.
So, you might try something like that on the filesystem on the VM,
e.g. fill the unallocated space
with large file(s) containing nothing but ASCII NUL characters - can
then remove those files from the
VM's filesystem.  And then with the qcow2 file inactive, see what you
can do with fallocate -d (don't do that
to the file while it's in use by the VM).  I not uncommonly do similar
on VMs to save space on their
filesystem images - basically fill most or all the spare space with
large file(s) of just null(s), then remove
those files, and then with the backing file not in use by the VM, use
fallocate -d
There may be more efficient ways if discard/trim is in use all the way
down and through, but often that's
not the case (one may even specifically not want to do that, for
certain reasons).

On Fri, May 16, 2025 at 5:21 AM Celejar <cele...@gmail.com> wrote:
>
> Hi,
>
> I have a QEMU / KVM VM running Windows that has been running as a guest
> on various Debian hosts for about a decade. The Windows OS has
> undergone various repairs and reinstalls over the years. I was recently
> quite surprised to discover that the VM image size (actual size on
> disk, not apparent size) has somehow grown to about 4x the allocated
> size of the disk:
>
> ~# ls -alsh /var/lib/libvirt/images/win10.qcow2
> 314G -rw------- 1 root root 352G May 15 12:40 
> /var/lib/libvirt/images/win10.qcow2
>
> ~# qemu-img  info /var/lib/libvirt/images/win10.qcow2
> image: /var/lib/libvirt/images/win10.qcow2
> file format: qcow2
> virtual size: 80 GiB (85899345920 bytes)
> disk size: 314 GiB
> cluster_size: 65536
> Format specific information:
>     compat: 1.1
>     compression type: zlib
>     lazy refcounts: true
>     refcount bits: 16
>     corrupt: false
>     extended l2: false
> Child node '/file':
>     filename: /var/lib/libvirt/images/win10.qcow2
>     protocol type: file
>     file length: 352 GiB (377549750272 bytes)
>     disk size: 314 GiB
>
> I've found all kinds of discussions of this type of thing online, but
> no explanation / solution that seems applicable to my situation.
>
> The image contains no snapshots:
>
> ~# qemu-img snapshot -l /var/lib/libvirt/images/win10.qcow2
> ~#
>
> I think TRIM / DISCARD is properly configured. From the VM XML:
>
> <disk type='file' device='disk'>
>       <driver name='qemu' type='qcow2' discard='unmap'/>
>       <source file='/var/lib/libvirt/images/win10.qcow2'/>
>       <target dev='vda' bus='virtio'/>
>       <address type='pci' domain='0x0000' bus='0x04' slot='0x00' 
> function='0x0'/>
> </disk>
>
> TRIM / DISCARD is enabled in the Windows guest, and I've issued manual
> TRIM commands in the guest several times, like so:
>
> https://winaero.com/trim-ssd-windows-10/
>
> I think this did claw back some space, but only on the order of tens of GB.
>
> Can anyone explain what's going on here, and how I can fix this?
>
> --
> Celejar
>

Reply via email to