mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
* pb: ask the master what each collection holds Callers tracking usage were sent every volume in the cluster to add up themselves, which is the master's largest single allocation. * topology: summarise what each collection holds One pass over the topology, allocating per collection rather than per volume. Regular volumes count once each for logical totals and once per replica for physical, taken from the lookup index, which is already keyed by volume and so needs no set of seen ids. Ec shards are node-local so their sizes sum, while the file and delete counts describe the volume and resolve once every holder has been seen. Replicas of one volume disagree while a write is landing or a heartbeat is late. Walking a full listing took whichever replica the map iteration reached first, so the answer moved between runs; this takes the largest, which is stable and never reports usage below what some replica already holds. * s3: take bucket sizes from the master's summary The bucket size metrics pulled the whole volume list once a minute and added it up, which cost the master 184.6MB of allocation and 17.8MB on the wire for six numbers per collection. VolumeList over 550k volumes 184.6 MB allocated, 17.8 MB on the wire CollectionStatistics 176 bytes allocated, 47 bytes on the wire The aggregation moves to the master with it, so the cases the removed tests covered are now asserted against it directly. * topology: count the replica holding the most live data Quotas are enforced on size less deletions, and the replica with the biggest raw size can be the one that has deleted the most. Counting it reported a bucket smaller than it is and would leave one writable over its quota, which is the opposite of what picking the largest was meant to guarantee. * topology: cap a volume's deletions at what it holds Live usage is read as a collection's size less its deletions, so a volume reporting more deleted bytes than it has cancels live bytes belonging to other volumes in the same bucket and reports it smaller than it is. Replica selection already floored that volume's own live size at zero; the totals have to agree with it.
149 lines
4.9 KiB
Go
149 lines
4.9 KiB
Go
package topology
|
|
|
|
import (
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
)
|
|
|
|
// CollectionStatistics is what a collection holds, summarised so that callers
|
|
// tracking usage do not have to be sent every volume in the cluster to add it
|
|
// up themselves.
|
|
type CollectionStatistics struct {
|
|
Collection string
|
|
FileCount uint64
|
|
DeleteCount uint64
|
|
DeletedByteCount uint64
|
|
// Size counts one copy of the data: a single replica of a regular volume,
|
|
// the data shards of an ec volume.
|
|
Size uint64
|
|
// PhysicalSize counts what is on disk: every replica, and parity shards.
|
|
PhysicalSize uint64
|
|
VolumeCount uint64
|
|
}
|
|
|
|
type ecStatsKey struct {
|
|
collection string
|
|
volumeId needle.VolumeId
|
|
}
|
|
|
|
// ecFileCounts holds the per-volume counts that can only be resolved once every
|
|
// shard holder has been seen.
|
|
type ecFileCounts struct {
|
|
collection string
|
|
fileCount uint64
|
|
deleteCount uint64
|
|
}
|
|
|
|
// CollectionStatistics summarises every collection in one pass over the
|
|
// topology, allocating per collection rather than per volume.
|
|
func (t *Topology) CollectionStatistics() []*CollectionStatistics {
|
|
byCollection := make(map[string]*CollectionStatistics)
|
|
statsFor := func(collection string) *CollectionStatistics {
|
|
stats, found := byCollection[collection]
|
|
if !found {
|
|
stats = &CollectionStatistics{Collection: collection}
|
|
byCollection[collection] = stats
|
|
}
|
|
return stats
|
|
}
|
|
|
|
// Regular volumes are counted once each for logical totals and once per
|
|
// replica for physical, which the lookup index gives without a set of seen
|
|
// ids: it is already keyed by volume.
|
|
for _, c := range t.collectionMap.Items() {
|
|
collection := c.(*Collection)
|
|
for _, vl := range collection.GetAllVolumeLayouts() {
|
|
vl.accessLock.RLock()
|
|
for vid, locations := range vl.vid2location {
|
|
stats := statsFor(collection.Name)
|
|
// Replicas of one volume can disagree while a write is landing
|
|
// or a heartbeat is late. Count the one holding the most live
|
|
// data, so the answer does not depend on which replica is
|
|
// looked at first and usage is never reported lower than some
|
|
// replica already holds. Quotas are enforced on size less
|
|
// deletions, so that is what has to be the largest -- a replica
|
|
// with the biggest raw size can be the one that has deleted the
|
|
// most, and picking it would leave an over-quota bucket
|
|
// writable.
|
|
var largest storage.VolumeInfo
|
|
var largestLive uint64
|
|
found := false
|
|
for _, dn := range locations.list {
|
|
v, err := dn.GetVolumesById(vid)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
stats.PhysicalSize += v.Size
|
|
live := v.Size
|
|
if v.DeletedByteCount < live {
|
|
live -= v.DeletedByteCount
|
|
} else {
|
|
live = 0
|
|
}
|
|
if !found || live > largestLive {
|
|
largest, largestLive, found = v, live, true
|
|
}
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
stats.Size += largest.Size
|
|
stats.FileCount += uint64(largest.FileCount)
|
|
stats.DeleteCount += uint64(largest.DeleteCount)
|
|
// Never more deletions than the volume holds. Live usage is
|
|
// read as the collection's size less its deletions, so a volume
|
|
// reporting more deleted bytes than it has would cancel live
|
|
// bytes belonging to other volumes and report the bucket
|
|
// smaller than it is.
|
|
stats.DeletedByteCount += largest.Size - largestLive
|
|
stats.VolumeCount++
|
|
}
|
|
vl.accessLock.RUnlock()
|
|
}
|
|
}
|
|
|
|
// Ec shards are node-local rather than replicated, so their sizes sum
|
|
// across holders. The file and delete counts describe the volume rather
|
|
// than the shard, so they resolve once every holder has been seen.
|
|
perEcVolume := make(map[ecStatsKey]*ecFileCounts)
|
|
for _, dcNode := range t.Children() {
|
|
for _, rackNode := range dcNode.(*DataCenter).Children() {
|
|
for _, dnNode := range rackNode.(*Rack).Children() {
|
|
for _, ecInfo := range dnNode.(*DataNode).GetEcShards() {
|
|
message := ecInfo.ToVolumeEcShardInformationMessage()
|
|
stats := statsFor(ecInfo.Collection)
|
|
stats.PhysicalSize += uint64(erasure_coding.EcShardsTotalSize(message))
|
|
stats.Size += uint64(erasure_coding.EcShardsDataSize(message, 0))
|
|
|
|
key := ecStatsKey{collection: ecInfo.Collection, volumeId: ecInfo.VolumeId}
|
|
counts, found := perEcVolume[key]
|
|
if !found {
|
|
counts = &ecFileCounts{collection: ecInfo.Collection}
|
|
perEcVolume[key] = counts
|
|
stats.VolumeCount++
|
|
}
|
|
if message.FileCount > counts.fileCount {
|
|
counts.fileCount = message.FileCount
|
|
}
|
|
counts.deleteCount += message.DeleteCount
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, counts := range perEcVolume {
|
|
stats := byCollection[counts.collection]
|
|
if stats == nil {
|
|
continue
|
|
}
|
|
stats.FileCount += counts.fileCount
|
|
stats.DeleteCount += counts.deleteCount
|
|
}
|
|
|
|
ret := make([]*CollectionStatistics, 0, len(byCollection))
|
|
for _, stats := range byCollection {
|
|
ret = append(ret, stats)
|
|
}
|
|
return ret
|
|
}
|