mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
df on a mount shows the space the cluster gives up to the data: every replica of a regular volume, every shard of an ec one. That is the honest answer for capacity planning, but it is not the question a user asks when they want to know how much of their data is stored. Add -df.logical. The master reports the logical sizes alongside the raw ones: one replica per regular volume, the data shards of each ec volume counted once. Free space is divided by the copies the requested replication makes, so used plus available stays the amount of data the mount can still write, and it comes off the cluster-wide usage rather than one collection's, since capacity is cluster-wide too. Statistics through a filer resolves an unset replication to the filer's default rather than the master's, matching where the writes it is sizing for actually land. The flag governs the quota check too, so a mount has one notion of how much it is using. A filer that predates the new fields sends zeros, and the mount keeps reporting the raw sizes.
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
)
|
|
|
|
const (
|
|
// Default quota check interval
|
|
defaultQuotaCheckInterval = 61 * time.Second
|
|
// Faster check interval when approaching quota (within 10%)
|
|
fastQuotaCheckInterval = 5 * time.Second
|
|
// Threshold for switching to fast check (90% of quota)
|
|
quotaWarningThreshold = 0.9
|
|
)
|
|
|
|
// uncommittedBytes tracks bytes written locally but not yet reflected in filer statistics.
|
|
// This is used for real-time quota enforcement between periodic checks.
|
|
var uncommittedBytes int64
|
|
|
|
// AddUncommittedBytes adds bytes to the uncommitted write counter.
|
|
// Called when data is written to the mount.
|
|
func (wfs *WFS) AddUncommittedBytes(bytes int64) {
|
|
if wfs.option.Quota > 0 {
|
|
atomic.AddInt64(&uncommittedBytes, bytes)
|
|
}
|
|
}
|
|
|
|
// SubtractUncommittedBytes subtracts bytes from the uncommitted counter.
|
|
// Called when data is flushed to filer or on quota refresh.
|
|
func (wfs *WFS) SubtractUncommittedBytes(bytes int64) {
|
|
if wfs.option.Quota > 0 {
|
|
current := atomic.AddInt64(&uncommittedBytes, -bytes)
|
|
// Don't let it go negative
|
|
if current < 0 {
|
|
atomic.StoreInt64(&uncommittedBytes, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResetUncommittedBytes resets the counter after a quota check syncs with filer.
|
|
func (wfs *WFS) ResetUncommittedBytes() {
|
|
atomic.StoreInt64(&uncommittedBytes, 0)
|
|
}
|
|
|
|
// GetUncommittedBytes returns the current uncommitted byte count.
|
|
func (wfs *WFS) GetUncommittedBytes() int64 {
|
|
return atomic.LoadInt64(&uncommittedBytes)
|
|
}
|
|
|
|
// IsOverQuotaWithUncommitted checks if quota is exceeded including uncommitted writes.
|
|
// This provides real-time quota enforcement between periodic checks.
|
|
func (wfs *WFS) IsOverQuotaWithUncommitted() bool {
|
|
if wfs.option.Quota <= 0 {
|
|
return false
|
|
}
|
|
if wfs.IsOverQuota {
|
|
return true
|
|
}
|
|
// Check if uncommitted writes would exceed quota
|
|
uncommitted := atomic.LoadInt64(&uncommittedBytes)
|
|
_, usedSize := wfs.diskSizes()
|
|
return (int64(usedSize) + uncommitted) > wfs.option.Quota
|
|
}
|
|
|
|
func (wfs *WFS) loopCheckQuota() {
|
|
|
|
// Skip quota checking if no quota is set
|
|
if wfs.option.Quota <= 0 {
|
|
return
|
|
}
|
|
|
|
// Check quota immediately on mount, don't wait for first interval
|
|
wfs.checkQuotaOnce()
|
|
|
|
for {
|
|
// Adaptive interval: check more frequently when approaching quota
|
|
interval := wfs.getQuotaCheckInterval()
|
|
time.Sleep(interval)
|
|
|
|
if wfs.option.Quota <= 0 {
|
|
continue
|
|
}
|
|
|
|
wfs.checkQuotaOnce()
|
|
}
|
|
}
|
|
|
|
// getQuotaCheckInterval returns the check interval based on current usage.
|
|
// Returns a shorter interval when approaching quota limit.
|
|
func (wfs *WFS) getQuotaCheckInterval() time.Duration {
|
|
if wfs.option.Quota <= 0 {
|
|
return defaultQuotaCheckInterval
|
|
}
|
|
|
|
_, usedSize := wfs.diskSizes()
|
|
uncommitted := atomic.LoadInt64(&uncommittedBytes)
|
|
totalUsed := int64(usedSize) + uncommitted
|
|
|
|
// If we're at 90% or more of quota, check more frequently
|
|
if float64(totalUsed) >= float64(wfs.option.Quota)*quotaWarningThreshold {
|
|
return fastQuotaCheckInterval
|
|
}
|
|
return defaultQuotaCheckInterval
|
|
}
|
|
|
|
func (wfs *WFS) checkQuotaOnce() {
|
|
err := wfs.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
|
|
request := &filer_pb.StatisticsRequest{
|
|
Collection: wfs.option.Collection,
|
|
Replication: wfs.option.Replication,
|
|
Ttl: fmt.Sprintf("%ds", wfs.option.TtlSec),
|
|
DiskType: string(wfs.option.DiskType),
|
|
}
|
|
|
|
resp, err := client.Statistics(context.Background(), request)
|
|
if err != nil {
|
|
glog.V(0).Infof("reading quota usage %v: %v", request, err)
|
|
return err
|
|
}
|
|
glog.V(4).Infof("read quota usage: %+v", resp)
|
|
|
|
// Update the stats cache with latest filer data
|
|
wfs.stats.UsedSize = resp.UsedSize
|
|
wfs.stats.TotalSize = resp.TotalSize
|
|
wfs.stats.LogicalUsedSize = resp.LogicalUsedSize
|
|
wfs.stats.LogicalTotalSize = resp.LogicalTotalSize
|
|
|
|
// Reset uncommitted counter since we now have fresh data from filer
|
|
wfs.ResetUncommittedBytes()
|
|
|
|
_, usedSize := wfs.diskSizes()
|
|
isOverQuota := int64(usedSize) > wfs.option.Quota
|
|
if isOverQuota && !wfs.IsOverQuota {
|
|
glog.Warningf("Quota Exceeded! quota:%d used:%d", wfs.option.Quota, usedSize)
|
|
} else if !isOverQuota && wfs.IsOverQuota {
|
|
glog.Warningf("Within quota limit! quota:%d used:%d", wfs.option.Quota, usedSize)
|
|
}
|
|
wfs.IsOverQuota = isOverQuota
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
glog.Warningf("read quota usage: %v", err)
|
|
}
|
|
}
|