From 20f4fd9985f1cafd69c0ff0f3ebb7327fd58385d Mon Sep 17 00:00:00 2001 From: FQHSLycopene <110231817+FQHSLycopene@users.noreply.github.com> Date: Fri, 24 Apr 2026 04:52:58 +0800 Subject: [PATCH] fix(storage): use ceil division for EC shard slots in maxVolumeCount (#9196) * fix(storage): use ceil division for EC shard slots in maxVolumeCount * fix(topology): use ceil division for EC shard slots consistently Applies the same ceiling-division formula used in store.go to the four remaining master-side sites that computed volume-slots consumed by EC shards with off-by-one approximations: - disk.go ToDiskInfo / Disk.ToDiskInfo used (n+1)/d, which under-counts slots for non-multiples of DataShardsCount, over-reporting FreeVolumeCount. - DiskUsageCounts.FreeSpace and NodeImpl.AvailableSpaceFor subtracted n/d + 1, which over-counts slots at multiples of DataShardsCount, under-reporting free space (and suppressing volume growth on nodes that still had room). All four now use (n + DataShardsCount - 1) / DataShardsCount, matching store.go:393, store.go:810, and command_ec_decode.go:422. * refactor(topology): extract ecShardSlots helper Deduplicates the (n + DataShardsCount - 1) / DataShardsCount ceiling expression now used by ToDiskInfo, DiskUsageCounts.FreeSpace, Disk.ToDiskInfo, and AvailableSpaceFor. Addresses PR review feedback. --------- Co-authored-by: Chris Lu --- weed/storage/store.go | 2 +- weed/topology/disk.go | 16 +++++++++------- weed/topology/node.go | 6 +----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/weed/storage/store.go b/weed/storage/store.go index 09bc6dd65..205aad7b8 100644 --- a/weed/storage/store.go +++ b/weed/storage/store.go @@ -807,7 +807,7 @@ func (s *Store) MaybeAdjustVolumeMax() (hasChanges bool) { } volCount := diskLocation.VolumesLen() ecShardCount := diskLocation.EcShardCount() - maxVolumeCount := int32(volCount) + int32((ecShardCount+erasure_coding.DataShardsCount)/erasure_coding.DataShardsCount) + maxVolumeCount := int32(volCount) + int32((ecShardCount+erasure_coding.DataShardsCount-1)/erasure_coding.DataShardsCount) if unclaimedSpaces > int64(volumeSizeLimit) { maxVolumeCount += int32(uint64(unclaimedSpaces)/volumeSizeLimit) - 1 } diff --git a/weed/topology/disk.go b/weed/topology/disk.go index 3616ff928..b5ce0b60c 100644 --- a/weed/topology/disk.go +++ b/weed/topology/disk.go @@ -23,6 +23,12 @@ type Disk struct { ecShardsLock sync.RWMutex } +// ecShardSlots returns the number of volume slots consumed by the given +// number of EC shards, rounded up to whole-volume equivalents. +func ecShardSlots(ecShardCount int64) int64 { + return (ecShardCount + erasure_coding.DataShardsCount - 1) / erasure_coding.DataShardsCount +} + func NewDisk(diskType string) *Disk { s := &Disk{} s.id = NodeId(diskType) @@ -67,7 +73,7 @@ func (d *DiskUsages) ToDiskInfo() map[string]*master_pb.DiskInfo { m := &master_pb.DiskInfo{ VolumeCount: diskUsageCounts.volumeCount, MaxVolumeCount: diskUsageCounts.maxVolumeCount, - FreeVolumeCount: diskUsageCounts.maxVolumeCount - (diskUsageCounts.volumeCount - diskUsageCounts.remoteVolumeCount) - (diskUsageCounts.ecShardCount+1)/erasure_coding.DataShardsCount, + FreeVolumeCount: diskUsageCounts.maxVolumeCount - (diskUsageCounts.volumeCount - diskUsageCounts.remoteVolumeCount) - ecShardSlots(diskUsageCounts.ecShardCount), ActiveVolumeCount: diskUsageCounts.activeVolumeCount, RemoteVolumeCount: diskUsageCounts.remoteVolumeCount, } @@ -111,11 +117,7 @@ func (a *DiskUsageCounts) addDiskUsageCounts(b *DiskUsageCounts) { } func (a *DiskUsageCounts) FreeSpace() int64 { - freeVolumeSlotCount := a.maxVolumeCount + a.remoteVolumeCount - a.volumeCount - if a.ecShardCount > 0 { - freeVolumeSlotCount = freeVolumeSlotCount - a.ecShardCount/erasure_coding.DataShardsCount - 1 - } - return freeVolumeSlotCount + return a.maxVolumeCount + a.remoteVolumeCount - a.volumeCount - ecShardSlots(a.ecShardCount) } func (du *DiskUsages) getOrCreateDisk(diskType types.DiskType) *DiskUsageCounts { @@ -265,7 +267,7 @@ func (d *Disk) ToDiskInfo() *master_pb.DiskInfo { Type: string(d.Id()), VolumeCount: diskUsage.volumeCount, MaxVolumeCount: diskUsage.maxVolumeCount, - FreeVolumeCount: diskUsage.maxVolumeCount - (diskUsage.volumeCount - diskUsage.remoteVolumeCount) - (diskUsage.ecShardCount+1)/erasure_coding.DataShardsCount, + FreeVolumeCount: diskUsage.maxVolumeCount - (diskUsage.volumeCount - diskUsage.remoteVolumeCount) - ecShardSlots(diskUsage.ecShardCount), ActiveVolumeCount: diskUsage.activeVolumeCount, RemoteVolumeCount: diskUsage.remoteVolumeCount, DiskId: diskId, diff --git a/weed/topology/node.go b/weed/topology/node.go index 6b82dd223..3a11d0433 100644 --- a/weed/topology/node.go +++ b/weed/topology/node.go @@ -11,7 +11,6 @@ import ( "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/stats" - "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" "github.com/seaweedfs/seaweedfs/weed/storage/needle" "github.com/seaweedfs/seaweedfs/weed/storage/types" ) @@ -272,10 +271,7 @@ func (n *NodeImpl) getOrCreateDisk(diskType types.DiskType) *DiskUsageCounts { func (n *NodeImpl) AvailableSpaceFor(option *VolumeGrowOption) int64 { t := n.getOrCreateDisk(option.DiskType) freeVolumeSlotCount := atomic.LoadInt64(&t.maxVolumeCount) + atomic.LoadInt64(&t.remoteVolumeCount) - atomic.LoadInt64(&t.volumeCount) - ecShardCount := atomic.LoadInt64(&t.ecShardCount) - if ecShardCount > 0 { - freeVolumeSlotCount = freeVolumeSlotCount - ecShardCount/erasure_coding.DataShardsCount - 1 - } + freeVolumeSlotCount -= ecShardSlots(atomic.LoadInt64(&t.ecShardCount)) return freeVolumeSlotCount }