feat: expose thin-pool chunk_size, metadata size, and zeroing as StorageClass params - #57
abonillabeeche wants to merge 2 commits into
Conversation
…ageClass params
The driver previously ran `lvcreate --thinpool` with only `-l90%FREE`, letting
LVM auto-select all pool creation parameters. LVM's `generic` chunk_size policy
scales chunk_size with pool_size to keep metadata bounded, so multi-TB pools
land on 8-16 MiB chunks by design. On random 4K workloads this produces up to
a 4096:1 allocation write-amp - severe enough that a field customer measured
326 random 4K IOPS on hardware capable of 10x that.
Expose three new StorageClass parameters honored at first-time pool creation:
chunkSize - lvcreate --chunksize (e.g. 512K, 128K, 1M)
poolMetadataSize - lvcreate --poolmetadatasize (e.g. 16G)
zeroBlocks - lvcreate --zero y|n ("true"/"false")
The chart's shipped `harvester-lvm-thin` StorageClass now sets sane defaults:
chunkSize: 512K (LVM upstream `performance` policy seed; matches
kernel dm-thin admin guide's general-purpose
recommendation; conservative vs Nutanix/VMware
1 MiB; under Red Hat Gluster's 2 MiB "reduce COW
problems" cap; supports pools up to 128 TB)
poolMetadataSize: 16G (covers 128 TB pools at 512K chunks; formula:
metadata_bytes = 64 * (pool_size / chunk_size))
zeroBlocks: false (adds --zero n; single-tenant Harvester nodes
don't need per-chunk zero-on-allocate)
Also render the previously-dead `storageClasses:` values block via a new
`templates/storageclass.yaml`. Ships two SCs by default:
- harvester-lvm-thin (dm-thin with the params above)
- harvester-lvm-striped (fat-provisioned; thin params N/A)
Backwards compatible: empty parameter strings preserve current LVM auto-select
behavior. Existing pools are unaffected (chunk_size cannot be changed after
pool creation - this only helps future pool creations).
Test coverage: pkg/lvm/lvm_test.go covers the arg-builder for all three
parameters, including empty (auto-select), full (chart defaults), and edge
cases (case-insensitive zeroBlocks, unknown values).
References:
- Linux dm-thin admin guide (chunk_size guidance): "512 KiB general;
64 KiB snapshot-heavy". https://docs.kernel.org/admin-guide/device-mapper/thin-provisioning.html
- LVM `performance` policy default (512K seed):
https://github.com/lvmteam/lvm2/blob/master/lib/config/defaults.h
- Red Hat Gluster admin guide (2 MiB cap "to reduce COW problems")
- Docker devicemapper (RHEL era) direct-lvm default: 512K
- Field evidence: customer thin-pool auto-selected 16 MiB chunks on a
multi-TB pool, DI_RANDOM = 326 IOPS.
Signed-off-by: Alejandro Bonilla <abonilla@suse.com>
Set the shipped default dm-thin chunkSize to 1M so it aligns with the full-stripe width of common hardware-RAID layouts (e.g. 4 data disks x 256K strip = 1M stripe). A sub-stripe chunk (e.g. 512K on a 1M stripe) forces partial-stripe read-modify-write and, on parity RAID, widens the write-hole window. Update chart values, example StorageClass, comments, and the chart-defaults unit test. Signed-off-by: Alejandro Bonilla <abonilla@suse.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@WebberHuang1118 is this PR covered in your #64 ? Let me know if I need to rebase. |
|
Thanks for checking. After further review, I don’t think the approach in this PR is suitable. The Therefore, #64 will not include thin-pool control. We’ll address this separately as a future enhancement in harvester/harvester#11335. No need to rebase for now. Thanks! |
|
This pull request is now in conflict. Could you fix it @abonillabeeche? 🙏 |
|
Re the conflict ping: this PR is parked behind harvester/harvester#11335, per @WebberHuang1118's review above — thin-pool geometry belongs to the pool, not to a StorageClass, and it is immutable after creation, so a per-SC parameter is the wrong surface. Rather than let the reasoning get lost, I have carried the field data over to that enhancement (harvester/harvester#11335 (comment)): the 4096:1 allocation write-amp from auto-selected 8–16 MiB chunks on multi-TB pools, the customer's 326 random-4K IOPS, the case for a 1 MiB default, and the two API implications (spec only applies at first pool creation; observed chunk size needs to be reported in status, with a drift condition for pools that cannot be converted). Not rebasing, per your "no need to rebase for now" above — this comment is just to preserve the data before the PR is closed out as superseded by #11335. harvester/docs#1083 documents the tuning for users in the meantime. |
Summary
The driver previously ran
lvcreate --thinpoolwith only-l90%FREE, letting LVM auto-select all pool creation parameters. LVM'sgenericchunk_size policy scales chunk_size with pool size to keep metadata bounded — so multi-TB pools land on 8-16 MiB chunks by design. On random 4K workloads this produces up to a 4096:1 allocation write-amp — severe enough that a field customer measured 326 random 4K IOPS on hardware capable of at least 10x that.This PR exposes three new StorageClass parameters honored at first-time pool creation and ships sensible defaults through the chart.
Changes
Driver (
pkg/lvm,cmd/provisioner):CreateLVSsignature extended withchunkSize, poolMetadataSize, zeroBlocks string.buildThinPoolCreateArgsassembles thelvcreateline and omits any flag whose value is the empty string (preserves backwards compat).controllerserver.goextracts the three new parameters fromStorageClass.parameters.cmd/provisioner/createlv.gogrows three matching CLI flags.cmd/provisioner/clonelv.gopasses empty strings (thin pool always exists at clone time, so pool-creation flags are moot).Chart (
deploy/charts):templates/storageclass.yamlrenders thestorageClasses:values block (previously declared but never rendered).harvester-lvm-thin—type: dm-thin, withchunkSize: 1M,poolMetadataSize: 16G,zeroBlocks: falseharvester-lvm-striped—type: striped, no thin-pool paramslinear/striped/mirrorkeys retained (disabled by default) for backwards compat.Example (
examples/storageclass-dm-thin.yaml):Tests (
pkg/lvm/lvm_test.go, new):buildThinPoolCreateArgscovering empty (auto-select), full (chart defaults), case-insensitive zeroBlocks, and edge cases. 7 subtests, all pass.Why 1M default
Harvester runs predominantly on hardware-RAID-backed volume groups, where the dominant factor is stripe alignment, not the general-purpose seed. A chunk equal to the RAID full-stripe width — (number of data disks) × (strip size) — makes every first-touch allocation a whole-stripe write. A sub-stripe chunk (for example
512Kon a 1 MiB stripe) forces partial-stripe read-modify-write and, on parity RAID (5/6), widens the write-hole window, so an unclean shutdown without a protected controller cache (BBU/FBWC) can leave a stripe with inconsistent parity.1Maligns with the most common power-of-two layouts (e.g. 4 data disks × 256 KiB strip = 1 MiB stripe), matches the fixed 1 MiB block used by mainstream hypervisor stacks, and stays under the 2 MiB CoW cap:performancepolicyOperators whose RAID full stripe differs (e.g. a 3-data-disk RAID5 = 768 KiB, or a single-disk / non-RAID VG where alignment does not apply) should override
chunkSizeto match; snapshot-heavy tenants can drop to128K.Why 16G metadata default
Metadata bytes per chunk ≈ 64. So
metadata_size ≈ 64 × (pool_size / chunk_size). At 1M chunks:A larger chunk needs less metadata to address the same capacity, so moving from 512K to 1M doubles the pool size that 16G covers — the entire practical Harvester deployment range with headroom.
Why
zeroBlocks: falsedefaultAdds
--zero nat pool creation, disabling per-chunk zero-write on allocation. On single-tenant Harvester nodes this halves the write-amp on first-touch to unallocated regions. Multi-tenant clusters that care about inter-tenant data-leak isolation can opt in withzeroBlocks: true.Backwards compatibility
chunk_sizecannot be changed after pool creation.Test plan
go test ./pkg/lvm/... -run TestBuildThinPoolCreateArgs -v)go build ./...cleango vet ./pkg/lvm/... ./cmd/provisioner/...cleanhelm lint deploy/chartscleanhelm template deploy/chartsrenders both SCs correctly with parameterstests/bats/) — maintainers to verify on Linux CI (requires a real LVM device, cannot be run from macOS dev env)dmsetup table <vg>-thinpool-tpoolshowsskip_block_zeroingand 1M chunk (2048sectors)Field evidence
Customer running everything from our runbook ended up with a 16 MiB chunk_size thin pool (LVM auto-select). Diskspd on comparable KubeVirt+VMDP+LVM hardware in our lab achieves 15,000+ QD1 4K IOPS with an explicit chunk size and
zero=n; the customer's auto-selected pool measured 326 IOPS on the same workload class. The fix is setting an explicit, stripe-aligned chunk size at pool creation.Related: harvester/harvester#11128 (LVM CSI cross-driver panic — separate bug, but same field investigation surfaced both).
🤖 Generated with Claude Code