From 2ec899bdee9fd456bdb4729c93c09577ed8d07c8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 7 Aug 2026 00:28:32 -0700 Subject: [PATCH] perf(weed/topology): diff a heartbeat without copying the volume map (#10608) * perf(weed/topology): keep only volume ids in the heartbeat membership set The map is used solely to test whether a known volume is still present, but it copied the whole 152-byte VolumeInfo for every volume in the heartbeat. Presize it too, since the count is known. BenchmarkSyncDataNodeRegistration/100000Volumes 199670102 B/op -> 180157310 B/op * perf(weed/topology): diff a heartbeat without copying the volume map To find volumes the data node no longer reports, UpdateVolumes copied every VolumeInfo on the node into a fresh slice, then deleted the missing ones one at a time. At 100k volumes that is a 15MB copy per heartbeat to usually find nothing. Scan the disk maps in place instead and return only what was removed. BenchmarkSyncDataNodeRegistration/100000Volumes 180157310 B/op -> 87806436 B/op --- weed/topology/data_node.go | 16 ++++++---------- weed/topology/disk.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/weed/topology/data_node.go b/weed/topology/data_node.go index d20b6194f..a1ad201a9 100644 --- a/weed/topology/data_node.go +++ b/weed/topology/data_node.go @@ -78,22 +78,18 @@ func (dn *DataNode) doAddOrUpdateVolume(v storage.VolumeInfo) (isNew, isChanged // used in master to notify master clients of these changes. func (dn *DataNode) UpdateVolumes(actualVolumes []storage.VolumeInfo) (newVolumes, deletedVolumes, changedVolumes []storage.VolumeInfo) { - actualVolumeMap := make(map[needle.VolumeId]storage.VolumeInfo) + actualVolumeIds := make(map[needle.VolumeId]struct{}, len(actualVolumes)) for _, v := range actualVolumes { - actualVolumeMap[v.Id] = v + actualVolumeIds[v.Id] = struct{}{} } dn.Lock() defer dn.Unlock() - existingVolumes := dn.getVolumes() - - for _, v := range existingVolumes { - vid := v.Id - if _, ok := actualVolumeMap[vid]; !ok { - glog.V(0).Infoln("Deleting volume id:", vid) - disk := dn.getOrCreateDisk(v.DiskType) - disk.DeleteVolumeById(vid) + for _, c := range dn.children { + disk := c.(*Disk) + for _, v := range disk.RemoveVolumesNotIn(actualVolumeIds) { + glog.V(0).Infoln("Deleting volume id:", v.Id) deletedVolumes = append(deletedVolumes, v) deltaDiskUsage := &DiskUsageCounts{} diff --git a/weed/topology/disk.go b/weed/topology/disk.go index e77715e88..a7f819eb9 100644 --- a/weed/topology/disk.go +++ b/weed/topology/disk.go @@ -211,6 +211,21 @@ func (d *Disk) GetVolumes() (ret []storage.VolumeInfo) { return ret } +// RemoveVolumesNotIn drops the volumes whose ids are absent from keep and +// returns them, so a heartbeat can be diffed without first copying the whole +// volume map out. +func (d *Disk) RemoveVolumesNotIn(keep map[needle.VolumeId]struct{}) (removed []storage.VolumeInfo) { + d.Lock() + defer d.Unlock() + for vid, v := range d.volumes { + if _, ok := keep[vid]; !ok { + removed = append(removed, v) + delete(d.volumes, vid) + } + } + return removed +} + func (d *Disk) GetVolumesById(id needle.VolumeId) (storage.VolumeInfo, error) { d.RLock() defer d.RUnlock()