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 <chris.lu@gmail.com>
This commit is contained in:
FQHSLycopene
2026-04-23 13:52:58 -07:00
committed by GitHub
co-authored by Chris Lu
parent 0fcd5173be
commit 20f4fd9985
3 changed files with 11 additions and 13 deletions
+1 -1
View File
@@ -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
}
+9 -7
View File
@@ -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,
+1 -5
View File
@@ -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
}