* [PATCH docs/manager/qemu-server 0/3] partially fix #1989: add QEMU qcow2 cache options
@ 2026-06-05 15:35 Erik Fastermann
2026-06-05 15:35 ` [PATCH qemu-server 1/3] partially fix #1989: disk: add " Erik Fastermann
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Erik Fastermann @ 2026-06-05 15:35 UTC (permalink / raw)
To: pve-devel; +Cc: Erik Fastermann
Add multiple options to configure the qcow2 L2/refcount cache. This
can provide significant performance gains in some cases.
qemu-server:
Erik Fastermann (1):
partially fix #1989: disk: add qcow2 cache options
src/PVE/QemuServer.pm | 71 ++++++++++++++++++-
src/PVE/QemuServer/Blockdev.pm | 44 ++++++++++++
src/PVE/QemuServer/Drive.pm | 122 ++++++++++++++++++++++++++++++++
src/PVE/QemuServer/QemuImage.pm | 24 +++++++
4 files changed, 260 insertions(+), 1 deletion(-)
pve-manager:
Erik Fastermann (1):
partially fix #1989: ui: qemu: disk: add qcow2 cache size config
www/manager6/Parser.js | 2 +-
www/manager6/qemu/HDEdit.js | 67 +++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 1 deletion(-)
pve-docs:
Erik Fastermann (1):
partially fix #1989: qm: document qcow2 cache size config
qm.adoc | 10 ++++++++++
1 file changed, 10 insertions(+)
Summary over all repositories:
7 files changed, 338 insertions(+), 2 deletions(-)
--
Generated by murpp 0.11.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH qemu-server 1/3] partially fix #1989: disk: add qcow2 cache options 2026-06-05 15:35 [PATCH docs/manager/qemu-server 0/3] partially fix #1989: add QEMU qcow2 cache options Erik Fastermann @ 2026-06-05 15:35 ` Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak 2026-06-05 15:35 ` [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config Erik Fastermann 2026-06-05 15:35 ` [PATCH pve-docs 3/3] partially fix #1989: qm: document " Erik Fastermann 2 siblings, 1 reply; 7+ messages in thread From: Erik Fastermann @ 2026-06-05 15:35 UTC (permalink / raw) To: pve-devel; +Cc: Erik Fastermann Add multiple options to configure the qcow2 L2/refcount cache. This can provide significant performance gains in some cases. For a detailed explanation of the options see the QEMU docs [0]. Additionally the cache size can be configured based on the size of the disk image automatically. Both blockdev and the older drive commandline are supported, which makes the feature accessible to all QEMU machine versions supported by PVE. Options which only apply to disk creation (cluster_size, refcount_bits) are not considered in this patch. [0] https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> --- src/PVE/QemuServer.pm | 71 ++++++++++++++++++- src/PVE/QemuServer/Blockdev.pm | 44 ++++++++++++ src/PVE/QemuServer/Drive.pm | 122 ++++++++++++++++++++++++++++++++ src/PVE/QemuServer/QemuImage.pm | 24 +++++++ 4 files changed, 260 insertions(+), 1 deletion(-) diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 55e9f520..211927db 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -78,6 +78,7 @@ use PVE::QemuServer::Drive qw( parse_drive print_drive storage_allows_io_uring_default + qcow2_cache_size_by_disk_size ); use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos); use PVE::QemuServer::Machine; @@ -1294,6 +1295,49 @@ sub print_drive_commandline_full { $opts .= ",auto-remove=on"; } + for my $qcow2_cache_option ( + 'qcow2-cache-size', + 'qcow2-l2-cache-size', + 'qcow2-l2-cache-entry-size', + 'qcow2-refcount-cache-size', + 'qcow2-cache-clean-interval', + ) { + if (defined($drive->{$qcow2_cache_option}) && $format ne 'qcow2') { + log_warn("$drive_id: $qcow2_cache_option requires disk format qcow2"); + } + } + + if ($format eq 'qcow2') { + if ( + defined($drive->{'qcow2-cache-size-based-on-disk'}) + && $drive->{'qcow2-cache-size-based-on-disk'} + ) { + my $cache_size = qcow2_cache_size_by_disk_size($storecfg, $drive); + $opts .= ",cache-size=" . int($cache_size); + } + + if (defined($drive->{'qcow2-cache-size'})) { + $opts .= ",cache-size=" . int($drive->{'qcow2-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-l2-cache-size'})) { + $opts .= ",l2-cache-size=" . int($drive->{'qcow2-l2-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-l2-cache-entry-size'})) { + $opts .= ",l2-cache-entry-size=" . int($drive->{'qcow2-l2-cache-entry-size'}); + } + + if (defined($drive->{'qcow2-refcount-cache-size'})) { + $opts .= + ",refcount-cache-size=" . int($drive->{'qcow2-refcount-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-cache-clean-interval'})) { + $opts .= ",cache-clean-interval=" . int($drive->{'qcow2-cache-clean-interval'}); + } + } + # my $file_param = $live_restore_name ? "file.file.filename" : "file"; my $file_param = "file"; if ($live_restore_name) { @@ -5217,6 +5266,26 @@ sub vmconfig_update_disk { || safe_string_ne($drive->{ssd}, $old_drive->{ssd}) || safe_string_ne($drive->{vendor}, $old_drive->{vendor}) || safe_string_ne($drive->{ro}, $old_drive->{ro}) + || safe_string_ne( + $drive->{'qcow2-cache-size'}, + $old_drive->{'qcow2-cache-size'}, + ) + || safe_num_ne( + $drive->{'qcow2-l2-cache-size'}, + $old_drive->{'qcow2-l2-cache-size'}, + ) + || safe_num_ne( + $drive->{'qcow2-l2-cache-entry-size'}, + $old_drive->{'qcow2-l2-cache-entry-size'}, + ) + || safe_num_ne( + $drive->{'qcow2-refcount-cache-size'}, + $old_drive->{'qcow2-refcount-cache-size'}, + ) + || safe_num_ne( + $drive->{'qcow2-cache-clean-interval'}, + $old_drive->{'qcow2-cache-clean-interval'}, + ) ) { die "skip\n"; } diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm index 101c747c..0dd5067d 100644 --- a/src/PVE/QemuServer/Blockdev.pm +++ b/src/PVE/QemuServer/Blockdev.pm @@ -9,6 +9,7 @@ use File::stat; use JSON; use PVE::JSONSchema qw(json_bool); +use PVE::RESTEnvironment qw(log_warn); use PVE::Storage; use PVE::QemuServer::Drive qw(drive_is_cdrom); @@ -403,6 +404,49 @@ sub generate_format_blockdev { $blockdev->{'discard-no-unref'} = JSON::true if $format eq 'qcow2'; } + for my $qcow2_cache_option ( + 'qcow2-cache-size', + 'qcow2-l2-cache-size', + 'qcow2-l2-cache-entry-size', + 'qcow2-refcount-cache-size', + 'qcow2-cache-clean-interval', + ) { + if (defined($drive->{$qcow2_cache_option}) && $format ne 'qcow2') { + log_warn("$drive_id: $qcow2_cache_option requires disk format qcow2"); + } + } + + if ($format eq 'qcow2') { + if ( + defined($drive->{'qcow2-cache-size-based-on-disk'}) + && $drive->{'qcow2-cache-size-based-on-disk'} + ) { + my $cache_size = qcow2_cache_size_by_disk_size($storecfg, $drive); + $blockdev->{'cache-size'} = int($cache_size); + } + + if (defined($drive->{'qcow2-cache-size'})) { + $blockdev->{'cache-size'} = int($drive->{'qcow2-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-l2-cache-size'})) { + $blockdev->{'l2-cache-size'} = int($drive->{'qcow2-l2-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-l2-cache-entry-size'})) { + $blockdev->{'l2-cache-entry-size'} = int($drive->{'qcow2-l2-cache-entry-size'}); + } + + if (defined($drive->{'qcow2-refcount-cache-size'})) { + $blockdev->{'refcount-cache-size'} = + int($drive->{'qcow2-refcount-cache-size'}) * 1024 * 1024; + } + + if (defined($drive->{'qcow2-cache-clean-interval'})) { + $blockdev->{'cache-clean-interval'} = int($drive->{'qcow2-cache-clean-interval'}); + } + } + return $blockdev; } diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm index b80b7dbb..e123947e 100644 --- a/src/PVE/QemuServer/Drive.pm +++ b/src/PVE/QemuServer/Drive.pm @@ -7,6 +7,7 @@ use Storable qw(dclone); use IO::File; use List::Util qw(first); +use POSIX; use PVE::RESTEnvironment qw(log_warn); use PVE::Storage; @@ -26,6 +27,7 @@ our @EXPORT_OK = qw( parse_drive print_drive storage_allows_io_uring_default + qcow2_cache_size_by_disk_size ); my $DROPPED_PROPERTIES = ['cyls', 'heads', 'secs', 'trans']; @@ -256,6 +258,46 @@ my %drivedesc_base = ( optional => 1, default => 0, }, + 'qcow2-cache-size' => { + type => 'integer', + minimum => 1, + maximum => 10 * 1024, + description => 'Cache size for qcow2 disks in MiB', + optional => 1, + }, + 'qcow2-cache-size-based-on-disk' => { + type => 'boolean', + description => + 'Automatically pick a qcow2 cache size based on the configured disk size', + optional => 1, + }, + 'qcow2-l2-cache-size' => { + type => 'integer', + minimum => 1, + maximum => 10 * 1024, + description => 'L2 cache size for qcow2 disks in MiB', + optional => 1, + }, + 'qcow2-l2-cache-entry-size' => { + type => 'integer', + minimum => 512, + maximum => 2 * 1024 * 1024, + description => 'L2 cache entry size for qcow2 disks in bytes', + optional => 1, + }, + 'qcow2-refcount-cache-size' => { + type => 'integer', + minimum => 1, + maximum => 10 * 1024, + description => 'Refcount cache size for qcow2 disks in MiB', + optional => 1, + }, + 'qcow2-cache-clean-interval' => { + type => 'integer', + minimum => 0, + description => 'Cache clean interval for qcow2 disks in seconds', + optional => 1, + }, ); my %iothread_fmt = ( @@ -838,6 +880,52 @@ sub parse_drive { } } + my $cache_option_count = + defined($res->{'qcow2-cache-size'}) + + defined($res->{'qcow2-l2-cache-size'}) + + defined($res->{'qcow2-refcount-cache-size'}); + + if ($cache_option_count > 2) { + warn "at most two of qcow2-cache-size, qcow2-l2-cache-size, qcow2-refcount-cache-size" + . " can be set simultaneously\n"; + ++$error; + } + + if ( + defined($res->{'qcow2-l2-cache-entry-size'}) + && !is_power_of_two($res->{'qcow2-l2-cache-entry-size'}) + ) { + warn "qcow2-l2-cache-entry-size must be a power of two\n"; + ++$error; + } + + if ( + defined($res->{'qcow2-cache-size-based-on-disk'}) + && $res->{'qcow2-cache-size-based-on-disk'} + ) { + if ( + defined($res->{'qcow2-cache-size'}) + || defined($res->{'qcow2-l2-cache-size'}) + || defined($res->{'qcow2-refcount-cache-size'}) + ) { + warn "qcow2-cache-size-based-on-disk is not compatible with cache-size," + . " l2-cache-size or refcount-cache-size being set\n"; + ++$error; + } + } + + if (defined($res->{'qcow2-cache-size'})) { + if (($res->{'qcow2-l2-cache-size'} // 0) >= $res->{'qcow2-cache-size'}) { + warn "qcow2-l2-cache-size is larger than or equal to qcow2-cache-size\n"; + ++$error; + } + + if (($res->{'qcow2-refcount-cache-size'} // 0) >= $res->{'qcow2-cache-size'}) { + warn "qcow2-refcount-cache-size is larger than or equal to qcow2-cache-size\n"; + ++$error; + } + } + return if $error; return if $res->{mbps_rd} && $res->{mbps}; @@ -857,6 +945,11 @@ sub parse_drive { return $res; } +sub is_power_of_two { + my ($n) = @_; + return $n > 0 && (($n & ($n - 1)) == 0); +} + sub print_drive { my ($drive, $with_alloc) = @_; my $skip = ['index', 'interface']; @@ -1179,4 +1272,33 @@ sub drive_qmp_peer { return drive_uses_qsd_fuse($storecfg, $drive) ? qsd_qmp_peer($vmid) : vm_qmp_peer($vmid); } +sub qcow2_cache_size_by_disk_size { + # The calculation is adapted from here: + # https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt#L97-98 + # As this combines the refcount and L2 cache sizes, QEMU is free to + # choose a different value for each of them, which means the L2 cache + # is not necessarily 4 times bigger than the refcount cache with the + # default cluster_size, refcount_bits and no extended L2 entries. + + my ($storecfg, $drive) = @_; + + my $img_info = PVE::QemuServer::QemuImage::info($storecfg, $drive->{file}); + my $cluster_size = $img_info->{'cluster-size'} // 65536; + my $refcount_bits = $img_info->{'format-specific'}->{data}->{'refcount-bits'} // 16; + my $has_extended_l2 = $img_info->{'format-specific'}->{data}->{'extended-l2'} // 0; + + my $l2_multiplier = $has_extended_l2 ? 16 : 8; + my $l2_cache_size = ($drive->{size} * $l2_multiplier) / $cluster_size; + + $l2_cache_size = 2 * $cluster_size if $l2_cache_size < (2 * $cluster_size); + $l2_cache_size = ceil($l2_cache_size / $cluster_size) * $cluster_size; + + my $refcount_cache_size = ($drive->{size} * $refcount_bits) / (8 * $cluster_size); + + $refcount_cache_size = 4 * $cluster_size if $refcount_cache_size < (4 * $cluster_size); + $refcount_cache_size = ceil($refcount_cache_size / $cluster_size) * $cluster_size; + + return $l2_cache_size + $refcount_cache_size; +} + 1; diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm index 71be3abb..9c9db4e8 100644 --- a/src/PVE/QemuServer/QemuImage.pm +++ b/src/PVE/QemuServer/QemuImage.pm @@ -15,6 +15,30 @@ use PVE::QemuServer::Blockdev; use PVE::QemuServer::Drive qw(checked_volume_format); use PVE::QemuServer::Helpers; +sub info { + my ($storecfg, $volid) = @_; + + my $disk_path = PVE::Storage::abs_filesystem_path($storecfg, $volid, 1); + my $cmd = ['/usr/bin/qemu-img', 'info', '--output=json', $disk_path]; + + my $res = eval { + my $output = ''; + PVE::Tools::run_command( + $cmd, + outfunc => sub { + my $line = shift; + $output .= $line; + }, + ); + decode_json($output); + }; + + my $err = $@; + die "qemu-img info failed: $err" if $err; + + return $res; +} + sub convert_iscsi_path { my ($path) = @_; -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH qemu-server 1/3] partially fix #1989: disk: add qcow2 cache options 2026-06-05 15:35 ` [PATCH qemu-server 1/3] partially fix #1989: disk: add " Erik Fastermann @ 2026-07-16 8:25 ` Dominik Csapak 0 siblings, 0 replies; 7+ messages in thread From: Dominik Csapak @ 2026-07-16 8:25 UTC (permalink / raw) To: Erik Fastermann, pve-devel a few comments inline personally i would have liked the size=0 -> auto approach more than a separate option, but i guess this is personal preference... On 6/5/26 5:35 PM, Erik Fastermann wrote: > Add multiple options to configure the qcow2 L2/refcount cache. This > can provide significant performance gains in some cases. > > For a detailed explanation of the options see the QEMU docs [0]. > Additionally the cache size can be configured based on the size of the > disk image automatically. > > Both blockdev and the older drive commandline are supported, which makes > the feature accessible to all QEMU machine versions supported by PVE. > Options which only apply to disk creation (cluster_size, refcount_bits) > are not considered in this patch. > > [0] https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt > > Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> > --- > src/PVE/QemuServer.pm | 71 ++++++++++++++++++- > src/PVE/QemuServer/Blockdev.pm | 44 ++++++++++++ > src/PVE/QemuServer/Drive.pm | 122 ++++++++++++++++++++++++++++++++ > src/PVE/QemuServer/QemuImage.pm | 24 +++++++ > 4 files changed, 260 insertions(+), 1 deletion(-) > > diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm > index 55e9f520..211927db 100644 > --- a/src/PVE/QemuServer.pm > +++ b/src/PVE/QemuServer.pm > @@ -78,6 +78,7 @@ use PVE::QemuServer::Drive qw( > parse_drive > print_drive > storage_allows_io_uring_default > + qcow2_cache_size_by_disk_size > ); > use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos); > use PVE::QemuServer::Machine; > @@ -1294,6 +1295,49 @@ sub print_drive_commandline_full { > $opts .= ",auto-remove=on"; > } > > + for my $qcow2_cache_option ( > + 'qcow2-cache-size', > + 'qcow2-l2-cache-size', > + 'qcow2-l2-cache-entry-size', > + 'qcow2-refcount-cache-size', > + 'qcow2-cache-clean-interval', what about the auto-size option? > + ) { > + if (defined($drive->{$qcow2_cache_option}) && $format ne 'qcow2') { > + log_warn("$drive_id: $qcow2_cache_option requires disk format qcow2"); > + } > + } nit: this could avoid some checks by pulling the format comparison out: if ($format ne 'qcow2') { # log warning loop here } else { # assignments here } > + > + if ($format eq 'qcow2') { > + if ( > + defined($drive->{'qcow2-cache-size-based-on-disk'}) > + && $drive->{'qcow2-cache-size-based-on-disk'} > + ) { > + my $cache_size = qcow2_cache_size_by_disk_size($storecfg, $drive); > + $opts .= ",cache-size=" . int($cache_size); > + } > + > + if (defined($drive->{'qcow2-cache-size'})) { > + $opts .= ",cache-size=" . int($drive->{'qcow2-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-l2-cache-size'})) { > + $opts .= ",l2-cache-size=" . int($drive->{'qcow2-l2-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-l2-cache-entry-size'})) { > + $opts .= ",l2-cache-entry-size=" . int($drive->{'qcow2-l2-cache-entry-size'}); > + } > + > + if (defined($drive->{'qcow2-refcount-cache-size'})) { > + $opts .= > + ",refcount-cache-size=" . int($drive->{'qcow2-refcount-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-cache-clean-interval'})) { > + $opts .= ",cache-clean-interval=" . int($drive->{'qcow2-cache-clean-interval'}); > + } > + } > + > # my $file_param = $live_restore_name ? "file.file.filename" : "file"; > my $file_param = "file"; > if ($live_restore_name) { > @@ -5217,6 +5266,26 @@ sub vmconfig_update_disk { > || safe_string_ne($drive->{ssd}, $old_drive->{ssd}) > || safe_string_ne($drive->{vendor}, $old_drive->{vendor}) > || safe_string_ne($drive->{ro}, $old_drive->{ro}) > + || safe_string_ne( > + $drive->{'qcow2-cache-size'}, > + $old_drive->{'qcow2-cache-size'}, > + ) > + || safe_num_ne( > + $drive->{'qcow2-l2-cache-size'}, > + $old_drive->{'qcow2-l2-cache-size'}, > + ) > + || safe_num_ne( > + $drive->{'qcow2-l2-cache-entry-size'}, > + $old_drive->{'qcow2-l2-cache-entry-size'}, > + ) > + || safe_num_ne( > + $drive->{'qcow2-refcount-cache-size'}, > + $old_drive->{'qcow2-refcount-cache-size'}, > + ) > + || safe_num_ne( > + $drive->{'qcow2-cache-clean-interval'}, > + $old_drive->{'qcow2-cache-clean-interval'}, > + ) what about the 'cache-size-based-on-disk' option here? even if it's not necessary to check, some kind of comment would be good to have so we know it was not accidentally missed > ) { > die "skip\n"; > } > diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm > index 101c747c..0dd5067d 100644 > --- a/src/PVE/QemuServer/Blockdev.pm > +++ b/src/PVE/QemuServer/Blockdev.pm > @@ -9,6 +9,7 @@ use File::stat; > use JSON; > > use PVE::JSONSchema qw(json_bool); > +use PVE::RESTEnvironment qw(log_warn); > use PVE::Storage; > > use PVE::QemuServer::Drive qw(drive_is_cdrom); > @@ -403,6 +404,49 @@ sub generate_format_blockdev { > $blockdev->{'discard-no-unref'} = JSON::true if $format eq 'qcow2'; > } > > + for my $qcow2_cache_option ( > + 'qcow2-cache-size', > + 'qcow2-l2-cache-size', > + 'qcow2-l2-cache-entry-size', > + 'qcow2-refcount-cache-size', > + 'qcow2-cache-clean-interval', > + ) { > + if (defined($drive->{$qcow2_cache_option}) && $format ne 'qcow2') { > + log_warn("$drive_id: $qcow2_cache_option requires disk format qcow2"); > + } > + } > + > + if ($format eq 'qcow2') { > + if ( > + defined($drive->{'qcow2-cache-size-based-on-disk'}) > + && $drive->{'qcow2-cache-size-based-on-disk'} > + ) { > + my $cache_size = qcow2_cache_size_by_disk_size($storecfg, $drive); > + $blockdev->{'cache-size'} = int($cache_size); > + } > + > + if (defined($drive->{'qcow2-cache-size'})) { > + $blockdev->{'cache-size'} = int($drive->{'qcow2-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-l2-cache-size'})) { > + $blockdev->{'l2-cache-size'} = int($drive->{'qcow2-l2-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-l2-cache-entry-size'})) { > + $blockdev->{'l2-cache-entry-size'} = int($drive->{'qcow2-l2-cache-entry-size'}); > + } > + > + if (defined($drive->{'qcow2-refcount-cache-size'})) { > + $blockdev->{'refcount-cache-size'} = > + int($drive->{'qcow2-refcount-cache-size'}) * 1024 * 1024; > + } > + > + if (defined($drive->{'qcow2-cache-clean-interval'})) { > + $blockdev->{'cache-clean-interval'} = int($drive->{'qcow2-cache-clean-interval'}); > + } > + } > + these checks/calculations are the same as above, just the assignment is different. Maybe we can factor that out and reuse it? > return $blockdev; > } > > diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm > index b80b7dbb..e123947e 100644 > --- a/src/PVE/QemuServer/Drive.pm > +++ b/src/PVE/QemuServer/Drive.pm > @@ -7,6 +7,7 @@ use Storable qw(dclone); > > use IO::File; > use List::Util qw(first); > +use POSIX; > > use PVE::RESTEnvironment qw(log_warn); > use PVE::Storage; > @@ -26,6 +27,7 @@ our @EXPORT_OK = qw( > parse_drive > print_drive > storage_allows_io_uring_default > + qcow2_cache_size_by_disk_size > ); > > my $DROPPED_PROPERTIES = ['cyls', 'heads', 'secs', 'trans']; > @@ -256,6 +258,46 @@ my %drivedesc_base = ( > optional => 1, > default => 0, > }, > + 'qcow2-cache-size' => { > + type => 'integer', > + minimum => 1, > + maximum => 10 * 1024, > + description => 'Cache size for qcow2 disks in MiB', > + optional => 1, i think it would be good to document the default qemu uses (if that does not change too often), so a user can get a hint what that is without having to read qemu documentation > + }, > + 'qcow2-cache-size-based-on-disk' => { > + type => 'boolean', > + description => > + 'Automatically pick a qcow2 cache size based on the configured disk size', > + optional => 1, > + }, > + 'qcow2-l2-cache-size' => { > + type => 'integer', > + minimum => 1, > + maximum => 10 * 1024, > + description => 'L2 cache size for qcow2 disks in MiB', > + optional => 1, > + }, > + 'qcow2-l2-cache-entry-size' => { > + type => 'integer', > + minimum => 512, > + maximum => 2 * 1024 * 1024, > + description => 'L2 cache entry size for qcow2 disks in bytes', > + optional => 1, > + }, > + 'qcow2-refcount-cache-size' => { > + type => 'integer', > + minimum => 1, > + maximum => 10 * 1024, > + description => 'Refcount cache size for qcow2 disks in MiB', > + optional => 1, > + }, > + 'qcow2-cache-clean-interval' => { > + type => 'integer', > + minimum => 0, > + description => 'Cache clean interval for qcow2 disks in seconds', > + optional => 1, > + }, > ); > > my %iothread_fmt = ( > @@ -838,6 +880,52 @@ sub parse_drive { > } > } > > + my $cache_option_count = > + defined($res->{'qcow2-cache-size'}) + > + defined($res->{'qcow2-l2-cache-size'}) + > + defined($res->{'qcow2-refcount-cache-size'}); > + > + if ($cache_option_count > 2) { > + warn "at most two of qcow2-cache-size, qcow2-l2-cache-size, qcow2-refcount-cache-size" > + . " can be set simultaneously\n"; > + ++$error; > + } i guess this behavior (that cache-size is the overall limit of l2 and refcount cache) should be documented on our side (e.g. api description) > + > + if ( > + defined($res->{'qcow2-l2-cache-entry-size'}) > + && !is_power_of_two($res->{'qcow2-l2-cache-entry-size'}) > + ) { > + warn "qcow2-l2-cache-entry-size must be a power of two\n"; > + ++$error; > + } > + same for this, e.g. an additional sentence in the description: "(Must be a power of two)" would be nice > + if ( > + defined($res->{'qcow2-cache-size-based-on-disk'}) > + && $res->{'qcow2-cache-size-based-on-disk'} > + ) { > + if ( > + defined($res->{'qcow2-cache-size'}) > + || defined($res->{'qcow2-l2-cache-size'}) > + || defined($res->{'qcow2-refcount-cache-size'}) > + ) { > + warn "qcow2-cache-size-based-on-disk is not compatible with cache-size," > + . " l2-cache-size or refcount-cache-size being set\n"; > + ++$error; > + } > + } same with this > + > + if (defined($res->{'qcow2-cache-size'})) { > + if (($res->{'qcow2-l2-cache-size'} // 0) >= $res->{'qcow2-cache-size'}) { > + warn "qcow2-l2-cache-size is larger than or equal to qcow2-cache-size\n"; > + ++$error; > + } > + > + if (($res->{'qcow2-refcount-cache-size'} // 0) >= $res->{'qcow2-cache-size'}) { > + warn "qcow2-refcount-cache-size is larger than or equal to qcow2-cache-size\n"; > + ++$error; > + } > + } > + and this. Basically the api description should give a good hint on what values to use. As a user, it's always cumbersome to have to test which values are allowed by experimenting. if the api description states the limits and constraints, it's much better (IMO)." > return if $error; > > return if $res->{mbps_rd} && $res->{mbps}; > @@ -857,6 +945,11 @@ sub parse_drive { > return $res; > } > > +sub is_power_of_two { > + my ($n) = @_; > + return $n > 0 && (($n & ($n - 1)) == 0); > +} > + > sub print_drive { > my ($drive, $with_alloc) = @_; > my $skip = ['index', 'interface']; > @@ -1179,4 +1272,33 @@ sub drive_qmp_peer { > return drive_uses_qsd_fuse($storecfg, $drive) ? qsd_qmp_peer($vmid) : vm_qmp_peer($vmid); > } > > +sub qcow2_cache_size_by_disk_size { > + # The calculation is adapted from here: > + # https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt#L97-98 > + # As this combines the refcount and L2 cache sizes, QEMU is free to > + # choose a different value for each of them, which means the L2 cache > + # is not necessarily 4 times bigger than the refcount cache with the > + # default cluster_size, refcount_bits and no extended L2 entries. > + > + my ($storecfg, $drive) = @_; > + > + my $img_info = PVE::QemuServer::QemuImage::info($storecfg, $drive->{file}); > + my $cluster_size = $img_info->{'cluster-size'} // 65536; > + my $refcount_bits = $img_info->{'format-specific'}->{data}->{'refcount-bits'} // 16; > + my $has_extended_l2 = $img_info->{'format-specific'}->{data}->{'extended-l2'} // 0; > + > + my $l2_multiplier = $has_extended_l2 ? 16 : 8; > + my $l2_cache_size = ($drive->{size} * $l2_multiplier) / $cluster_size; > + > + $l2_cache_size = 2 * $cluster_size if $l2_cache_size < (2 * $cluster_size); > + $l2_cache_size = ceil($l2_cache_size / $cluster_size) * $cluster_size; > + > + my $refcount_cache_size = ($drive->{size} * $refcount_bits) / (8 * $cluster_size); > + > + $refcount_cache_size = 4 * $cluster_size if $refcount_cache_size < (4 * $cluster_size); > + $refcount_cache_size = ceil($refcount_cache_size / $cluster_size) * $cluster_size; > + > + return $l2_cache_size + $refcount_cache_size; > +} > + > 1; > diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm > index 71be3abb..9c9db4e8 100644 > --- a/src/PVE/QemuServer/QemuImage.pm > +++ b/src/PVE/QemuServer/QemuImage.pm > @@ -15,6 +15,30 @@ use PVE::QemuServer::Blockdev; > use PVE::QemuServer::Drive qw(checked_volume_format); > use PVE::QemuServer::Helpers; > > +sub info { > + my ($storecfg, $volid) = @_; > + > + my $disk_path = PVE::Storage::abs_filesystem_path($storecfg, $volid, 1); > + my $cmd = ['/usr/bin/qemu-img', 'info', '--output=json', $disk_path]; > + > + my $res = eval { > + my $output = ''; > + PVE::Tools::run_command( > + $cmd, > + outfunc => sub { > + my $line = shift; > + $output .= $line; > + }, > + ); > + decode_json($output); > + }; > + > + my $err = $@; > + die "qemu-img info failed: $err" if $err; > + > + return $res; > +} > + there is already `PVE::Storage::Common::qemu_img_info`, please reuse that. > sub convert_iscsi_path { > my ($path) = @_; > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config 2026-06-05 15:35 [PATCH docs/manager/qemu-server 0/3] partially fix #1989: add QEMU qcow2 cache options Erik Fastermann 2026-06-05 15:35 ` [PATCH qemu-server 1/3] partially fix #1989: disk: add " Erik Fastermann @ 2026-06-05 15:35 ` Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak 2026-06-05 15:35 ` [PATCH pve-docs 3/3] partially fix #1989: qm: document " Erik Fastermann 2 siblings, 1 reply; 7+ messages in thread From: Erik Fastermann @ 2026-06-05 15:35 UTC (permalink / raw) To: pve-devel; +Cc: Erik Fastermann Add the option to control the size of the qcow2 cache in the vm disk create and edit dialogs. This can provide significant performance gains in some vm configurations. For a detailed explanation see the QEMU docs [0]. Currently only the cache-size is configurable in the frontend, either directly or based on the size of the disk. [0] https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> --- www/manager6/Parser.js | 2 +- www/manager6/qemu/HDEdit.js | 67 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/www/manager6/Parser.js b/www/manager6/Parser.js index 36df8e9d..f8d2bdad 100644 --- a/www/manager6/Parser.js +++ b/www/manager6/Parser.js @@ -207,7 +207,7 @@ Ext.define('PVE.Parser', { if (!p || p.match(/^\s*$/)) { return undefined; // continue } - let match = p.match(/^([a-z_]+)=(\S+)$/); + let match = p.match(/^([0-9a-z-]+)=(\S+)$/); if (!match) { if (!p.match(/[=]/)) { res.file = p; diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js index 1bb2bfda..3716a0b0 100644 --- a/www/manager6/qemu/HDEdit.js +++ b/www/manager6/qemu/HDEdit.js @@ -18,6 +18,8 @@ Ext.define('PVE.qemu.HDInputPanel', { isSCSI: false, isVirtIO: false, isSCSISingle: false, + isQcow2: false, + qcow2CacheSizeBasedOnDisk: false, }, }, @@ -54,6 +56,22 @@ Ext.define('PVE.qemu.HDInputPanel', { vm.set('isSCSISingle', value === 'virtio-scsi-single'); }, }, + 'field[name=diskformat]': { + change: function (f, value) { + this.getViewModel().set('isQcow2', value === 'qcow2'); + }, + afterrender: function (f) { + this.getViewModel().set('isQcow2', f.getValue() === 'qcow2'); + }, + }, + 'field[name=qcow2CacheSizeBasedOnDisk]': { + change: function (f, value) { + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', value); + }, + afterrender: function (f) { + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', f.getValue()); + }, + }, }, init: function (view) { @@ -88,6 +106,20 @@ Ext.define('PVE.qemu.HDInputPanel', { me.drive.format = values.diskformat; } + if (me.drive?.format === 'qcow2' || me.drive?.file?.endsWith('.qcow2')) { + if (values.qcow2CacheSize) { + me.drive['qcow2-cache-size'] = values.qcow2CacheSize; + } else { + delete me.drive['qcow2-cache-size']; + } + } + + PVE.Utils.propertyStringSet( + me.drive, + values.qcow2CacheSizeBasedOnDisk, + 'qcow2-cache-size-based-on-disk', + ); + PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); @@ -156,6 +188,8 @@ Ext.define('PVE.qemu.HDInputPanel', { values.iothread = PVE.Parser.parseBoolean(drive.iothread); values.readOnly = PVE.Parser.parseBoolean(drive.ro); values.aio = drive.aio || '__default__'; + values.qcow2CacheSizeBasedOnDisk = drive['qcow2-cache-size-based-on-disk']; + values.qcow2CacheSize = drive['qcow2-cache-size']; values.mbps_rd = drive.mbps_rd; values.mbps_wr = drive.mbps_wr; @@ -167,6 +201,12 @@ Ext.define('PVE.qemu.HDInputPanel', { values.iops_wr_max = drive.iops_wr_max; me.setValues(values); + + me.getViewModel().set( + 'isQcow2', + values.diskformat === 'qcow2' || values.hdimage?.endsWith('.qcow2'), + ); + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', values.qcow2CacheSizeBasedOnDisk); }, setNodename: function (nodename) { @@ -355,6 +395,33 @@ Ext.define('PVE.qemu.HDInputPanel', { disabled: '{!isVirtIO && !isSCSI}', }, }, + { + xtype: 'numberfield', + name: 'qcow2CacheSize', + minValue: 1, + fieldLabel: gettext('qcow2 cache') + ' (MiB)', + emptyText: gettext('Default cache size'), + bind: { + disabled: '{!isQcow2 || qcow2CacheSizeBasedOnDisk}', + }, + listeners: { + disable: function (field) { + console.log(field); + field.setValue(null); + }, + }, + }, + { + xtype: 'proxmoxcheckbox', + name: 'qcow2CacheSizeBasedOnDisk', + reference: 'qcow2CacheSizeBasedOnDisk', + defaultValue: 0, + fieldLabel: gettext('qcow2 cache based on disk'), + clearOnDisable: true, + bind: { + disabled: '{!isQcow2}', + }, + }, ); advancedColumn2.push( -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config 2026-06-05 15:35 ` [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config Erik Fastermann @ 2026-07-16 8:25 ` Dominik Csapak 0 siblings, 0 replies; 7+ messages in thread From: Dominik Csapak @ 2026-07-16 8:25 UTC (permalink / raw) To: Erik Fastermann, pve-devel a few comments inline On 6/5/26 5:34 PM, Erik Fastermann wrote: > Add the option to control the size of the qcow2 cache in the vm disk > create and edit dialogs. This can provide significant performance gains > in some vm configurations. > > For a detailed explanation see the QEMU docs [0]. Currently only the > cache-size is configurable in the frontend, either directly or based on > the size of the disk. > > [0] https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt > > Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> > --- > www/manager6/Parser.js | 2 +- > www/manager6/qemu/HDEdit.js | 67 +++++++++++++++++++++++++++++++++++++ > 2 files changed, 68 insertions(+), 1 deletion(-) > > diff --git a/www/manager6/Parser.js b/www/manager6/Parser.js > index 36df8e9d..f8d2bdad 100644 > --- a/www/manager6/Parser.js > +++ b/www/manager6/Parser.js > @@ -207,7 +207,7 @@ Ext.define('PVE.Parser', { > if (!p || p.match(/^\s*$/)) { > return undefined; // continue > } > - let match = p.match(/^([a-z_]+)=(\S+)$/); > + let match = p.match(/^([0-9a-z-]+)=(\S+)$/); > if (!match) { > if (!p.match(/[=]/)) { > res.file = p; > diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js > index 1bb2bfda..3716a0b0 100644 > --- a/www/manager6/qemu/HDEdit.js > +++ b/www/manager6/qemu/HDEdit.js > @@ -18,6 +18,8 @@ Ext.define('PVE.qemu.HDInputPanel', { > isSCSI: false, > isVirtIO: false, > isSCSISingle: false, > + isQcow2: false, > + qcow2CacheSizeBasedOnDisk: false, > }, > }, > > @@ -54,6 +56,22 @@ Ext.define('PVE.qemu.HDInputPanel', { > vm.set('isSCSISingle', value === 'virtio-scsi-single'); > }, > }, > + 'field[name=diskformat]': { > + change: function (f, value) { > + this.getViewModel().set('isQcow2', value === 'qcow2'); > + }, > + afterrender: function (f) { > + this.getViewModel().set('isQcow2', f.getValue() === 'qcow2'); > + }, why is this necessary? the change handler should take care of that on the first change? > + }, > + 'field[name=qcow2CacheSizeBasedOnDisk]': { > + change: function (f, value) { > + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', value); > + }, IIRC this should not be necessary if you give the field a reference? > + afterrender: function (f) { > + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', f.getValue()); > + }, same question here as for the isqcow2 > + }, > }, > > init: function (view) { > @@ -88,6 +106,20 @@ Ext.define('PVE.qemu.HDInputPanel', { > me.drive.format = values.diskformat; > } > > + if (me.drive?.format === 'qcow2' || me.drive?.file?.endsWith('.qcow2')) { > + if (values.qcow2CacheSize) { > + me.drive['qcow2-cache-size'] = values.qcow2CacheSize; > + } else { > + delete me.drive['qcow2-cache-size']; > + } > + } > + > + PVE.Utils.propertyStringSet( > + me.drive, > + values.qcow2CacheSizeBasedOnDisk, > + 'qcow2-cache-size-based-on-disk', > + ); > + > PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); > PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); > PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); > @@ -156,6 +188,8 @@ Ext.define('PVE.qemu.HDInputPanel', { > values.iothread = PVE.Parser.parseBoolean(drive.iothread); > values.readOnly = PVE.Parser.parseBoolean(drive.ro); > values.aio = drive.aio || '__default__'; > + values.qcow2CacheSizeBasedOnDisk = drive['qcow2-cache-size-based-on-disk']; > + values.qcow2CacheSize = drive['qcow2-cache-size']; > > values.mbps_rd = drive.mbps_rd; > values.mbps_wr = drive.mbps_wr; > @@ -167,6 +201,12 @@ Ext.define('PVE.qemu.HDInputPanel', { > values.iops_wr_max = drive.iops_wr_max; > > me.setValues(values); > + > + me.getViewModel().set( > + 'isQcow2', > + values.diskformat === 'qcow2' || values.hdimage?.endsWith('.qcow2'), > + ); > + this.getViewModel().set('qcow2CacheSizeBasedOnDisk', values.qcow2CacheSizeBasedOnDisk); > }, > > setNodename: function (nodename) { > @@ -355,6 +395,33 @@ Ext.define('PVE.qemu.HDInputPanel', { > disabled: '{!isVirtIO && !isSCSI}', > }, > }, > + { > + xtype: 'numberfield', > + name: 'qcow2CacheSize', > + minValue: 1, > + fieldLabel: gettext('qcow2 cache') + ' (MiB)', > + emptyText: gettext('Default cache size'), as mentioned in the other patch, documenting the qemu default here would be nice (if it does not change too often) > + bind: { > + disabled: '{!isQcow2 || qcow2CacheSizeBasedOnDisk}', > + }, > + listeners: { > + disable: function (field) { > + console.log(field); > + field.setValue(null); > + }, > + }, > + }, > + { > + xtype: 'proxmoxcheckbox', > + name: 'qcow2CacheSizeBasedOnDisk', > + reference: 'qcow2CacheSizeBasedOnDisk', > + defaultValue: 0, > + fieldLabel: gettext('qcow2 cache based on disk'), > + clearOnDisable: true, > + bind: { > + disabled: '{!isQcow2}', > + }, > + }, > ); > > advancedColumn2.push( ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH pve-docs 3/3] partially fix #1989: qm: document qcow2 cache size config 2026-06-05 15:35 [PATCH docs/manager/qemu-server 0/3] partially fix #1989: add QEMU qcow2 cache options Erik Fastermann 2026-06-05 15:35 ` [PATCH qemu-server 1/3] partially fix #1989: disk: add " Erik Fastermann 2026-06-05 15:35 ` [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config Erik Fastermann @ 2026-06-05 15:35 ` Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak 2 siblings, 1 reply; 7+ messages in thread From: Erik Fastermann @ 2026-06-05 15:35 UTC (permalink / raw) To: pve-devel; +Cc: Erik Fastermann Add a new subsection to document the new qcow2 cache config options. Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> --- qm.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qm.adoc b/qm.adoc index e6b7918..2f1e7d4 100644 --- a/qm.adoc +++ b/qm.adoc @@ -341,6 +341,16 @@ underlying storage. Another benefit is reduced latency (hangs) in the guest for very I/O-intensive host workloads, since neither the main thread nor a vCPU thread can be blocked by disk I/O. +[[qm_hard_disk_qcow2_cache]] +Cache Size (qcow2) +^^^^^^^^^^^^^^^^^^ +Fine-tuning the qcow2 L2/refcount cache behavior can provide significant +performance gains. See the +https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt[QEMU documentation] +for a detailed explanation of these options. You can either configure the cache +size directly or let Proxmox VE automatically choose a cache size based on the +disk image size. All other options can be managed with the API. + [[qm_cpu]] CPU ~~~ -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH pve-docs 3/3] partially fix #1989: qm: document qcow2 cache size config 2026-06-05 15:35 ` [PATCH pve-docs 3/3] partially fix #1989: qm: document " Erik Fastermann @ 2026-07-16 8:25 ` Dominik Csapak 0 siblings, 0 replies; 7+ messages in thread From: Dominik Csapak @ 2026-07-16 8:25 UTC (permalink / raw) To: Erik Fastermann, pve-devel On 6/5/26 5:35 PM, Erik Fastermann wrote: > Add a new subsection to document the new qcow2 cache config options. > > Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com> > --- > qm.adoc | 10 ++++++++++ > 1 file changed, 10 insertions(+) > > diff --git a/qm.adoc b/qm.adoc > index e6b7918..2f1e7d4 100644 > --- a/qm.adoc > +++ b/qm.adoc > @@ -341,6 +341,16 @@ underlying storage. Another benefit is reduced latency (hangs) in the guest for > very I/O-intensive host workloads, since neither the main thread nor a vCPU > thread can be blocked by disk I/O. > > +[[qm_hard_disk_qcow2_cache]] > +Cache Size (qcow2) > +^^^^^^^^^^^^^^^^^^ > +Fine-tuning the qcow2 L2/refcount cache behavior can provide significant > +performance gains. See the > +https://gitlab.com/qemu-project/qemu/-/blob/master/docs/qcow2-cache.txt[QEMU documentation] > +for a detailed explanation of these options. You can either configure the cache > +size directly or let Proxmox VE automatically choose a cache size based on the > +disk image size. All other options can be managed with the API. > + i would have liked a short explanation how the auto-sizing algorithm works. I guess as an admin this would be the first thing i'd ask if i see an 'auto' option (e.g. to gauge the memory requirements for my vms). > [[qm_cpu]] > CPU > ~~~ ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-16 8:25 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-05 15:35 [PATCH docs/manager/qemu-server 0/3] partially fix #1989: add QEMU qcow2 cache options Erik Fastermann 2026-06-05 15:35 ` [PATCH qemu-server 1/3] partially fix #1989: disk: add " Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak 2026-06-05 15:35 ` [PATCH pve-manager 2/3] partially fix #1989: ui: qemu: disk: add qcow2 cache size config Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak 2026-06-05 15:35 ` [PATCH pve-docs 3/3] partially fix #1989: qm: document " Erik Fastermann 2026-07-16 8:25 ` Dominik Csapak
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox