From cce3bab0e266446daa954562252f4779db891b5e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 7 Aug 2026 01:26:54 -0700 Subject: [PATCH] perf(weed/topology): gather a node's volumes into one slice (#10617) * perf(weed/topology): preallocate the node's volume concatenation A node's volumes are gathered per disk and concatenated into a slice grown from nil, so a server with several disks reallocates and copies its way up. The writable-volume refresh loop does this for every node every few seconds. BenchmarkDataNodeGetVolumes/8Disks 322551844 B/op -> 121602326 B/op * perf(weed/topology): fill one slice across a node's disks Each disk built its own right-sized copy of its volumes, and the node then copied all of them again into the combined slice. Appending into the caller's slice makes it one allocation whatever the disk count, which halves even the single-disk case. BenchmarkDataNodeGetVolumes 1Disks 121602326 B/op 2 allocs/op -> 60801314 B/op 1 allocs/op 8Disks 121602326 B/op 9 allocs/op -> 60801024 B/op 1 allocs/op --- weed/topology/data_node.go | 10 +++++++--- weed/topology/disk.go | 15 ++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/weed/topology/data_node.go b/weed/topology/data_node.go index b3f2edda9..8c013f001 100644 --- a/weed/topology/data_node.go +++ b/weed/topology/data_node.go @@ -200,11 +200,15 @@ func (dn *DataNode) AdjustDiskUsageBytes(diskTotalBytes, diskFreeBytes map[strin func (dn *DataNode) GetVolumes() (ret []storage.VolumeInfo) { dn.RLock() + defer dn.RUnlock() + total := 0 for _, c := range dn.children { - disk := c.(*Disk) - ret = append(ret, disk.GetVolumes()...) + total += c.(*Disk).VolumeCount() + } + ret = make([]storage.VolumeInfo, 0, total) + for _, c := range dn.children { + ret = c.(*Disk).AppendVolumes(ret) } - dn.RUnlock() return ret } diff --git a/weed/topology/disk.go b/weed/topology/disk.go index cec5ffe28..3d42fa7e5 100644 --- a/weed/topology/disk.go +++ b/weed/topology/disk.go @@ -202,14 +202,19 @@ func (d *Disk) doAddOrUpdateVolume(v storage.VolumeInfo) (isNew, isChanged bool) return } -func (d *Disk) GetVolumes() (ret []storage.VolumeInfo) { +func (d *Disk) GetVolumes() []storage.VolumeInfo { + return d.AppendVolumes(make([]storage.VolumeInfo, 0, d.VolumeCount())) +} + +// AppendVolumes appends the disk's volumes to dst, so a caller gathering +// several disks fills one slice instead of concatenating a copy per disk. +func (d *Disk) AppendVolumes(dst []storage.VolumeInfo) []storage.VolumeInfo { d.RLock() - ret = make([]storage.VolumeInfo, 0, len(d.volumes)) + defer d.RUnlock() for _, v := range d.volumes { - ret = append(ret, v) + dst = append(dst, v) } - d.RUnlock() - return ret + return dst } func (d *Disk) VolumeCount() int {