mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-29 00:10:42 +01:00
fix(admin): list all masters and dedupe EC file counts in dashboard (#9093)
* fix(admin): list all masters and dedupe EC file counts in dashboard Dashboard -> Master Nodes only ever showed the currently connected master because getMasterNodesStatus hard-coded a single entry. Replace it with a RaftListClusterServers call that returns every master in the raft group and tags the real leader, falling back to the current master only if the raft call fails. Buckets -> Object Store Buckets could render 0 objects for a bucket backed by an EC volume. Every shard holder reports the same whole-volume file_count (read from the replicated .ecx), so the first-seen value wins; if that first node had not yet finished loading .ecx it reported 0 and pinned the aggregate at 0. Take the max across reporting nodes instead. The dashboard header total_files also dropped after volumes were converted to erasure coding because getTopologyViaGRPC never folded EC file_count into topology.TotalFiles. Aggregate it with the same max/sum dedupe. * fix(admin): address PR review comments - bound RaftListClusterServers with a 3s timeout so the dashboard endpoint cannot hang on a stalled master - pre-validate raft addresses with net.SplitHostPort before calling pb.GrpcAddressToServerAddress, which otherwise glog.Fatalf's on a malformed entry and would crash the admin process - when raft is unreachable, mark the fallback master as not-leader rather than claiming leadership the code cannot verify - warn when summed EC delete_count exceeds file_count while folding into topology.TotalFiles, matching collectCollectionStats * fix(admin): distinguish empty raft response from RPC failure When RaftListClusterServers returns successfully with no servers, raft is not initialized (standalone/non-raft cluster), so the single fallback master is the leader. Only treat the fallback as a non-leader when the RPC actually failed. * fix(admin): remove misleading Objects column from S3 buckets page The bucket "Objects" column displayed needle counts from volume collection stats, not actual S3 object counts. This is confusing because a single S3 object can span multiple needles (multipart uploads, versions) and the count is inaccurate for EC volumes. Remove the ObjectCount field from S3Bucket, the Objects table column, the sort-by-objects handler, the detail-view row, and both CSV export references. * fix(admin): correct cell indexes in fallback bucket CSV export After the Objects column was removed, the fallback CSV exporter in admin.js still used stale cell indexes: cells[1] mapped to Owner (not Created), cells[2] to Created (not Size), cells[3] to Logical Size (not Quota). Align all indexes with the current table column order and include Owner, Logical Size, and Physical Size.
This commit is contained in:
1 parent
dfecd664f9
commit
46b801aedb
40 files changed
+1100
-1025
No files matched your search
@@ -543,13 +543,6 @@ func (s *AdminServer) sortBuckets(buckets []S3Bucket, sortBy, sortOrder string)
|
||||
}
|
||||
return a.CreatedAt.Before(b.CreatedAt)
|
||||
}
|
||||
case "objects":
|
||||
if a.ObjectCount != b.ObjectCount {
|
||||
if desc {
|
||||
return a.ObjectCount > b.ObjectCount
|
||||
}
|
||||
return a.ObjectCount < b.ObjectCount
|
||||
}
|
||||
case "logical_size":
|
||||
if a.LogicalSize != b.LogicalSize {
|
||||
if desc {
|
||||
@@ -646,14 +639,11 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) {
|
||||
// Determine collection name for this bucket
|
||||
collectionName := getCollectionName(filerConfig.FilerGroup, bucketName)
|
||||
|
||||
// Get size and object count from collection data
|
||||
var physicalSize int64
|
||||
var logicalSize int64
|
||||
var objectCount int64
|
||||
if collectionData, exists := collectionMap[collectionName]; exists {
|
||||
physicalSize = collectionData.PhysicalSize
|
||||
logicalSize = collectionData.LogicalSize
|
||||
objectCount = collectionData.FileCount
|
||||
}
|
||||
|
||||
// Get quota information from entry
|
||||
@@ -695,7 +685,6 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) {
|
||||
CreatedAt: createdAt,
|
||||
LogicalSize: logicalSize,
|
||||
PhysicalSize: physicalSize,
|
||||
ObjectCount: objectCount,
|
||||
LastModified: lastModified,
|
||||
Quota: quota,
|
||||
QuotaEnabled: quotaEnabled,
|
||||
@@ -750,7 +739,6 @@ func (s *AdminServer) GetBucketDetails(bucketName string) (*BucketDetails, error
|
||||
} else if data, ok := stats[collectionName]; ok {
|
||||
details.Bucket.LogicalSize = data.LogicalSize
|
||||
details.Bucket.PhysicalSize = data.PhysicalSize
|
||||
details.Bucket.ObjectCount = data.FileCount
|
||||
}
|
||||
|
||||
err = s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
@@ -1672,11 +1660,13 @@ type collectionStats struct {
|
||||
FileCount int64
|
||||
}
|
||||
|
||||
// ecVolumeCounts is used to correctly combine EC volume counts reported by
|
||||
// multiple nodes. Every node holding any shard of an EC volume reports the
|
||||
// same file_count (total entries in the replicated .ecx), so we dedupe it
|
||||
// per volume id. In contrast, a needle delete is applied on exactly one
|
||||
// shard holder, so each node reports its own local tombstone count and the
|
||||
// ecVolumeCounts combines EC volume counts reported by multiple nodes.
|
||||
// Every node holding any shard of an EC volume reports the same file_count
|
||||
// (total entries in the replicated .ecx), so we dedupe it per volume id by
|
||||
// taking the max — a node that has not yet finished loading .ecx would
|
||||
// otherwise pin the aggregate at 0 and zero out the bucket object count.
|
||||
// In contrast, a needle delete is recorded locally on the shard holder
|
||||
// that served it, so each node reports its own tombstone count and the
|
||||
// true delete total is the sum across nodes.
|
||||
type ecVolumeCounts struct {
|
||||
collection string
|
||||
@@ -1723,11 +1713,19 @@ func collectCollectionStats(topologyInfo *master_pb.TopologyInfo) map[string]col
|
||||
data.LogicalSize += int64(shards.MinusParityShards().TotalSize())
|
||||
collectionMap[collection] = data
|
||||
|
||||
// fileCount is volume-wide (same .ecx on every shard
|
||||
// holder) so take the max to dedupe — a node that has
|
||||
// not yet finished loading .ecx reports 0 and must not
|
||||
// pin the aggregate. deleteCount is node-local and is
|
||||
// summed across shard holders.
|
||||
agg, ok := ecVolumeAgg[ecShardInfo.Id]
|
||||
if !ok {
|
||||
agg = &ecVolumeCounts{collection: collection, fileCount: ecShardInfo.FileCount}
|
||||
agg = &ecVolumeCounts{collection: collection}
|
||||
ecVolumeAgg[ecShardInfo.Id] = agg
|
||||
}
|
||||
if ecShardInfo.FileCount > agg.fileCount {
|
||||
agg.fileCount = ecShardInfo.FileCount
|
||||
}
|
||||
agg.deleteCount += ecShardInfo.DeleteCount
|
||||
}
|
||||
}
|
||||
@@ -1736,16 +1734,13 @@ func collectCollectionStats(topologyInfo *master_pb.TopologyInfo) map[string]col
|
||||
}
|
||||
|
||||
// Fold EC per-volume counts into the collection totals. fileCount is
|
||||
// already deduped (set once per volume), deleteCount is the sum of
|
||||
// local tombstones across every node holding shards of the volume.
|
||||
// deduped via max across every node reporting shards for the volume;
|
||||
// deleteCount is summed across the same nodes.
|
||||
for vid, agg := range ecVolumeAgg {
|
||||
data := collectionMap[agg.collection]
|
||||
if agg.fileCount >= agg.deleteCount {
|
||||
data.FileCount += int64(agg.fileCount - agg.deleteCount)
|
||||
} else {
|
||||
// Should not happen in steady state — indicates a node reporting
|
||||
// a stale fileCount, a skewed heartbeat, or a delete-counter bug.
|
||||
// Defend the UI by skipping the add and surface the anomaly.
|
||||
glog.Warningf("ec volume %d in collection %q: summed delete_count=%d exceeds file_count=%d; skipping object count",
|
||||
vid, agg.collection, agg.deleteCount, agg.fileCount)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user