Bug 2010067 - Downloading prellocated disk created preallocated image
Summary: Downloading prellocated disk created preallocated image
Keywords:
Status: CLOSED CURRENTRELEASE
Alias: None
Product: ovirt-imageio
Classification: oVirt
Component: Client
Version: 2.2.0
Hardware: Unspecified
OS: Unspecified
medium
medium
Target Milestone: ovirt-4.5.0
: 2.3.0
Assignee: Nir Soffer
QA Contact: Ilia Markelov
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2021-10-03 12:05 UTC by Nir Soffer
Modified: 2022-04-20 06:33 UTC (History)
6 users (show)

Fixed In Version: ovirt-imageio-2.3.0-1
Doc Type: Bug Fix
Doc Text:
Cause: Previoulsy, when downloading preallocated disk the downloaded image is fully allocated instead of sparse. Consequence: This was inefficient when the image should be transferred to another machine, or uploaded back to storage. Fix: downloaded image is now saved as sparse
Clone Of:
Environment:
Last Closed: 2022-04-20 06:33:59 UTC
oVirt Team: Storage
Embargoed:
sbonazzo: ovirt-4.5+


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Red Hat Issue Tracker RHV-43743 0 None None None 2021-10-03 12:06:46 UTC
oVirt gerrit 116921 0 master MERGED client: Sparsify downloaded image 2021-10-05 20:25:20 UTC

Description Nir Soffer 2021-10-03 12:05:26 UTC
Description of problem:

When downloading preallocated disk the downloaded image is fully allocated
instead of sparse. This is inefficient when the image should be transferred
to another machine, or uploaded back to storage.

This also affects full and incremental backup, increasing the size of the
backup, and slowing down restore. Backup vendors that do zero detection 
and deduplication should not be affected.

When copying images using "qemu-img convert", images are sparsified during
the copy, creating smaller images.

Example download with 10g preallocated image:

$ ./download_disk.py -c engine-dev 5154931f-a988-4b3a-b248-6c9466163ff7 /data/scratch/nsoffer/download.qcow2
[   0.0 ] Connecting...
[   0.2 ] Creating image transfer...
[   1.9 ] Transfer ID: 596d4685-c15c-451e-a8f9-a96662db7203
[   1.9 ] Transfer host name: host4
[   1.9 ] Downloading disk...
[ 100.00% ] 10.00 GiB, 27.39 seconds, 373.93 MiB/s                             
[  29.3 ] Finalizing image transfer...
[  35.5 ] Download completed successfully

Querying the image show that is is fully allocated:

$ qemu-img info /data/scratch/nsoffer/download.qcow2 
image: /data/scratch/nsoffer/download.qcow2
file format: qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 10 GiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false


The original image is empty, so it should downloaded as a very small qcow2
image, with all extents detected as "zero": true, but we get "zero": false.

$ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | head
[{ "start": 0, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 12910592},
{ "start": 4194304, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 29687808},
{ "start": 8388608, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 50659328},
{ "start": 12582912, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 63242240},
{ "start": 16777216, "length": 8388608, "depth": 0, "present": true, "zero": false, "data": true, "offset": 92602368},
{ "start": 25165824, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 117768192},
{ "start": 29360128, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 126156800},
{ "start": 33554432, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 147128320},
{ "start": 37748736, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 155516928},
{ "start": 41943040, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 201654272},

$ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | wc -l
2105

$ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | grep '"data": true' | wc -l
2105

Uploading the image back to storage is slow, since we have to upload 10 GiB
of zeroes:

$ ./upload_disk.py -c engine-dev --sd-name alpine-iscsi-01 --disk-format raw /data/scratch/nsoffer/download.qcow2
[   0.0 ] Checking image...
[   0.0 ] Image format: qcow2
[   0.0 ] Disk format: raw
[   0.0 ] Disk content type: data
[   0.0 ] Disk provisioned size: 10737418240
[   0.0 ] Disk initial size: 10737418240
[   0.0 ] Disk name: download.raw
[   0.0 ] Disk backup: False
[   0.0 ] Connecting...
[   0.0 ] Creating disk...
[  15.8 ] Disk ID: 40f672eb-48b3-4002-87f1-1473369e032e
[  15.8 ] Creating image transfer...
[  17.5 ] Transfer ID: e2886b1a-0edd-4843-9bcb-9b8060f2e098
[  17.5 ] Transfer host name: host4
[  17.5 ] Uploading image...
[ 100.00% ] 10.00 GiB, 36.87 seconds, 277.75 MiB/s                             
[  54.4 ] Finalizing image transfer...
[  61.6 ] Upload completed successfully


If we use --detect-zeroes=unmap in qemu-nbd:

$ ./download_disk.py -c engine-dev 5154931f-a988-4b3a-b248-6c9466163ff7 /data/scratch/nsoffer/download.qcow2
[   0.0 ] Connecting...
[   0.2 ] Creating image transfer...
[   1.9 ] Transfer ID: 1c1b0f5f-1219-497d-99d3-b006ffcfcb7c
[   1.9 ] Transfer host name: host4
[   1.9 ] Downloading disk...
[ 100.00% ] 10.00 GiB, 22.84 seconds, 448.37 MiB/s                             
[  24.7 ] Finalizing image transfer...
[  25.8 ] Download completed successfully

The download is faster, since writing zeroes is much faster than writing
actual data (writing zero clusters in qcow2 image).

The image contains only metadata for the zeroed areas:

$ qemu-img info /data/scratch/nsoffer/download.qcow2 
image: /data/scratch/nsoffer/download.qcow2
file format: qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 1.5 MiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false

And querying the image show that it is completely zero:

$ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 
[{ "start": 0, "length": 10737418240, "depth": 0, "present": true, "zero": true, "data": false}]

Uploading the downloaded image is 4.5 times faster since we don't have to
upload any data, only send fast zero requests:

$ ./upload_disk.py -c engine-dev --sd-name alpine-iscsi-01 --disk-format raw /data/scratch/nsoffer/download.qcow2
[   0.0 ] Checking image...
[   0.0 ] Image format: qcow2
[   0.0 ] Disk format: raw
[   0.0 ] Disk content type: data
[   0.0 ] Disk provisioned size: 10737418240
[   0.0 ] Disk initial size: 10737418240
[   0.0 ] Disk name: download.raw
[   0.0 ] Disk backup: False
[   0.0 ] Connecting...
[   0.0 ] Creating disk...
[   8.6 ] Disk ID: 059de1c6-78c0-45eb-90c0-7fc03930f8e9
[   8.6 ] Creating image transfer...
[  10.3 ] Transfer ID: 1b0c4dba-fa9b-47e1-b467-f8e3e48acf76
[  10.3 ] Transfer host name: host4
[  10.3 ] Uploading image...
[ 100.00% ] 10.00 GiB, 7.66 seconds, 1.31 GiB/s                                
[  18.0 ] Finalizing image transfer...
[  24.1 ] Upload completed successfully


Version-Release number of selected component (if applicable):
2.3.0-0

How reproducible:
Always

Steps to Reproduce:
1. Download empty raw preallocated disk

Actual results:
Zeroed areas are stored as data.

Expected results:
Zeroed areas are stored as zero cluster (qcow2) or unallocated areas (raw).

Testing notes:

Should be tested with:
- empty image (best case)
- image full with non-zero data (worst case)
- common images - 50% full image (common case)

Can be tested with any setup to evaluate the functionality. To test performance
it should be tested with real server and storage.

The easiest way to test this is to add a raw preallocated disk to a VM, and
download the disk.

To ensure that new empty disk contain zeros, zero it:

    blkdiscard --zeroout /dev/vdb

To test partly utilized disk, write data directly to the disk:

    dd if=/dev/zero bs=1M count=5120 | tr "\0" "\1" > /dev/vdb
    sync

Comment 1 Nir Soffer 2021-10-03 14:39:32 UTC
Example functional test:

1. Add 2g preallocated disk with alias=empty uuid=7be36ade-6b39-4da2-ad1d-1a2a74381d1f
2. Add 2g preallocated disk with alias=half uuid=6f09ea12-d8a6-48a3-8c05-5ecd94b34403
3. Add 2g preallocated disk with alias=full uuid=baf0e5df-ba31-42d5-902a-94735b83e911
4. Start the VM
5. Zero the tests disks

    for disk_id in 7be36ade-6b39-4da2-ad1d-1a2a74381d1f 6f09ea12-d8a6-48a3-8c05-5ecd94b34403 baf0e5df-ba31-42d5-902a-94735b83e911; do
        blkdiscard --zeroout /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_$disk_id
    done

6. Write 1g data to disk "half"

    dd if=/dev/zero bs=1M count=1024 | tr "\0" "\1" > /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_6f09ea12-d8a6-48a3-8c05-5ecd94b34403

7. Write 2g of data to disk "full"

    dd if=/dev/zero bs=1M count=2048 | tr "\0" "\1" > /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_baf0e5df-ba31-42d5-902a-94735b83e911

8. Shut down the VM

9. Download disk "empty"

    $ ./download_disk.py -c engine-dev 7be36ade-6b39-4da2-ad1d-1a2a74381d1f empty.qcow2

10. Download disk "half"

    $ ./download_disk.py -c engine-dev 6f09ea12-d8a6-48a3-8c05-5ecd94b34403 /data/scratch/nsoffer/half.qcow2

11. Download disk "full"

    $ ./download_disk.py -c engine-dev baf0e5df-ba31-42d5-902a-94735b83e911 /data/scratch/nsoffer/full.qcow2

12. Check downloaded images size

    $ qemu-img info empty.qcow2 | grep 'disk size'
    disk size: 512 KiB

    $ qemu-img info half.qcow2 | grep 'disk size'
    disk size: 1 GiB

    $ qemu-img info full.qcow2 | grep 'disk size'
    disk size: 2 GiB

Comment 3 Sandro Bonazzola 2022-03-16 16:11:32 UTC
$ git tag --contains 70e9fb26d8acdd1dbd72e0de85d063ba56b67e19
v2.3.0
v2.4.1

Comment 7 Ilia Markelov 2022-04-11 12:30:19 UTC
Verified.

Zeroed areas are not stored as data, extends are detected as "zero": true, and image size is as big as it should be while testing all best/common/worst cases.

Version 4.5.0-0.237.el8ev
ovirt-imageio-client-2.4.1-1.el8ev.x86_64

Comment 8 Sandro Bonazzola 2022-04-20 06:33:59 UTC
This bugzilla is included in oVirt 4.5.0 release, published on April 20th 2022.

Since the problem described in this bug report should be resolved in oVirt 4.5.0 release, it has been closed with a resolution of CURRENT RELEASE.

If the solution does not work for you, please open a new bug report.


Note You need to log in before you can comment on or make changes to this bug.