topology: clamp the deleted-vs-total subtractions in volume stats (#10633)

VolumeLocationList.Stats subtracts the deleted figures from the totals to
report live size and needle count. Both deleted figures are maintained as
counters independent of the totals they come off, so either can transiently
exceed its total, and neither subtraction was clamped.

Unclamped, the size wraps to ~16 EB. The count is signed so it merely goes
negative, but VolumeLayout.Stats converts it with uint64(fileCount), which
turns it into ~1.8e19 just the same. Either one swamps the cluster totals
behind /dir/status, /vol/status and Topology.CollectionVolumeStats.

commandFsMergeVolumes.getVolumeSize had the same unclamped subtraction, where
a wrapped size reads as a volume far too large to join any merge plan.

Clamped to zero, matching the guards already in CollectionInfo.LogicalSize
and the admin server's logical-size accumulator.
This commit is contained in:
Chris Lu
2026-08-07 19:49:44 -07:00
committed by GitHub
parent 4527947afc
commit 08f0ba5564
4 changed files with 52 additions and 2 deletions
+5
View File
@@ -465,7 +465,12 @@ func (c *commandFsMergeVolumes) getVolumeSizeBasedOnPlan(plan map[needle.VolumeI
return size
}
// getVolumeSize is the volume's live data size, clamped since
// DeletedByteCount can transiently exceed Size.
func (c *commandFsMergeVolumes) getVolumeSize(volume *master_pb.VolumeInformationMessage) uint64 {
if volume.Size < volume.DeletedByteCount {
return 0
}
return volume.Size - volume.DeletedByteCount
}
@@ -123,3 +123,18 @@ func TestCreateMergePlan_DirectedRejectsIneligible(t *testing.T) {
})
}
}
// A volume reporting more deleted bytes than it holds must read as empty.
func TestGetVolumeSize_ClampsDeletedOverSize(t *testing.T) {
c := newMergeCmd(250000)
overDeleted := &master_pb.VolumeInformationMessage{Id: 1, Size: 1000, DeletedByteCount: 4000}
if got := c.getVolumeSize(overDeleted); got != 0 {
t.Errorf("expected 0 for a volume with more deleted than stored, got %d", got)
}
healthy := &master_pb.VolumeInformationMessage{Id: 2, Size: 3000, DeletedByteCount: 1000}
if got := c.getVolumeSize(healthy); got != 2000 {
t.Errorf("expected 2000, got %d", got)
}
}