mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
FileCount and DeleteCount were int, so each cost a word on every replica the master holds. A volume caps at 30GB on a 4-byte-offset build and 8TB on a 5-byte one, and neither holds 4.29 billion needles. That takes VolumeInfo from 120 bytes to 112, which is its own size class rather than rounding up into the 128 one, so a replica costs 135.7 bytes in the map instead of 151.7 -- about 25MB across the 1.6M replicas in a cluster the size of the one this came from. Counts are narrowed where they are read rather than assigned across, so a report claiming more than a volume can hold pins at the ceiling instead of wrapping to a small number.
195 lines
5.6 KiB
Go
195 lines
5.6 KiB
Go
package topology
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
)
|
|
|
|
// maxTrackedLocations is how many replicas of one volume can carry per-node
|
|
// state. Beyond that the flags are simply not recorded, which loses a read-only
|
|
// or oversized marking rather than misreporting one.
|
|
const maxTrackedLocations = 32
|
|
|
|
type VolumeLocationList struct {
|
|
list []*DataNode
|
|
// readOnly and oversized mirror list by index, so the per-node state that
|
|
// used to need an index of its own rides along with the location it
|
|
// describes. Both are only ever asked whether any node reports them.
|
|
readOnly uint32
|
|
oversized uint32
|
|
}
|
|
|
|
func NewVolumeLocationList() *VolumeLocationList {
|
|
return &VolumeLocationList{}
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) String() string {
|
|
return fmt.Sprintf("%v", dnll.list)
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) Copy() *VolumeLocationList {
|
|
list := make([]*DataNode, len(dnll.list))
|
|
copy(list, dnll.list)
|
|
return &VolumeLocationList{
|
|
list: list,
|
|
readOnly: dnll.readOnly,
|
|
oversized: dnll.oversized,
|
|
}
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) indexOf(loc *DataNode) int {
|
|
for i, dnl := range dnll.list {
|
|
if loc.Ip == dnl.Ip && loc.Port == dnl.Port {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func setFlag(flags *uint32, index int, on bool) {
|
|
if index < 0 || index >= maxTrackedLocations {
|
|
return
|
|
}
|
|
if on {
|
|
*flags |= 1 << uint(index)
|
|
} else {
|
|
*flags &^= 1 << uint(index)
|
|
}
|
|
}
|
|
|
|
func hasFlag(flags uint32, index int) bool {
|
|
return index >= 0 && index < maxTrackedLocations && flags&(1<<uint(index)) != 0
|
|
}
|
|
|
|
// removeFlag drops the bit at index and closes the gap, keeping the flags
|
|
// aligned with the locations after one is removed.
|
|
func removeFlag(flags *uint32, index int) {
|
|
if index < 0 || index >= maxTrackedLocations {
|
|
return
|
|
}
|
|
low := *flags & (1<<uint(index) - 1)
|
|
high := *flags >> uint(index+1) << uint(index)
|
|
*flags = low | high
|
|
}
|
|
|
|
// SetReadOnly records whether loc reports the volume read-only.
|
|
func (dnll *VolumeLocationList) SetReadOnly(loc *DataNode, readOnly bool) {
|
|
setFlag(&dnll.readOnly, dnll.indexOf(loc), readOnly)
|
|
}
|
|
|
|
// AnyReadOnly reports whether any location has the volume read-only.
|
|
func (dnll *VolumeLocationList) AnyReadOnly() bool {
|
|
return dnll != nil && dnll.readOnly != 0
|
|
}
|
|
|
|
// SetOversized records whether loc reports the volume past the size limit.
|
|
func (dnll *VolumeLocationList) SetOversized(loc *DataNode, oversized bool) {
|
|
setFlag(&dnll.oversized, dnll.indexOf(loc), oversized)
|
|
}
|
|
|
|
// AnyOversized reports whether any location has the volume past the size limit.
|
|
func (dnll *VolumeLocationList) AnyOversized() bool {
|
|
return dnll != nil && dnll.oversized != 0
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) Head() *DataNode {
|
|
//mark first node as master volume
|
|
if dnll.Length() == 0 {
|
|
return nil
|
|
}
|
|
return dnll.list[0]
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) Rest() []*DataNode {
|
|
//mark first node as master volume
|
|
return dnll.list[1:]
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) Length() int {
|
|
if dnll == nil {
|
|
return 0
|
|
}
|
|
return len(dnll.list)
|
|
}
|
|
|
|
// Set adds loc, or replaces the entry at the same address, returning the node it
|
|
// displaced. Two volume servers can share an address -- GetOrCreateDataNode keys
|
|
// on the reported id and refuses to merge a new id onto an address an older node
|
|
// still claims -- so the displaced node is not necessarily loc, and callers
|
|
// tracking which node a volume is reachable through must move their bookkeeping
|
|
// off it rather than assume loc already owned the entry.
|
|
func (dnll *VolumeLocationList) Set(loc *DataNode) (displaced *DataNode) {
|
|
for i := 0; i < len(dnll.list); i++ {
|
|
if loc.Ip == dnll.list[i].Ip && loc.Port == dnll.list[i].Port {
|
|
displaced = dnll.list[i]
|
|
dnll.list[i] = loc
|
|
return displaced
|
|
}
|
|
}
|
|
dnll.list = append(dnll.list, loc)
|
|
return nil
|
|
}
|
|
|
|
// Remove drops the entry at loc's address and returns the node removed, or nil
|
|
// if the volume was not reachable there. As with Set, the removed node is
|
|
// matched by address and need not be loc.
|
|
func (dnll *VolumeLocationList) Remove(loc *DataNode) (removed *DataNode) {
|
|
for i, dnl := range dnll.list {
|
|
if loc.Ip == dnl.Ip && loc.Port == dnl.Port {
|
|
dnll.list = append(dnll.list[:i], dnll.list[i+1:]...)
|
|
removeFlag(&dnll.readOnly, i)
|
|
removeFlag(&dnll.oversized, i)
|
|
return dnl
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (dnll *VolumeLocationList) Refresh(freshThreshHold int64) {
|
|
var changed bool
|
|
for _, dnl := range dnll.list {
|
|
if dnl.LastSeen < freshThreshHold {
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
if !changed {
|
|
return
|
|
}
|
|
// The flags index the locations, so dropping some means rebuilding both
|
|
// rather than leaving bits describing whoever moved into their place.
|
|
var l []*DataNode
|
|
var readOnly, oversized uint32
|
|
for i, dnl := range dnll.list {
|
|
if dnl.LastSeen < freshThreshHold {
|
|
continue
|
|
}
|
|
setFlag(&readOnly, len(l), hasFlag(dnll.readOnly, i))
|
|
setFlag(&oversized, len(l), hasFlag(dnll.oversized, i))
|
|
l = append(l, dnl)
|
|
}
|
|
dnll.list, dnll.readOnly, dnll.oversized = l, readOnly, oversized
|
|
}
|
|
|
|
// Stats returns logic size and count. Both subtractions are clamped: the
|
|
// deleted counters are maintained apart from the totals they come off and can
|
|
// transiently exceed them.
|
|
func (dnll *VolumeLocationList) Stats(vid needle.VolumeId, freshThreshHold int64) (size uint64, fileCount int) {
|
|
for _, dnl := range dnll.list {
|
|
if dnl.LastSeen < freshThreshHold {
|
|
vinfo, err := dnl.GetVolumesById(vid)
|
|
if err == nil {
|
|
if vinfo.Size > vinfo.DeletedByteCount {
|
|
size = vinfo.Size - vinfo.DeletedByteCount
|
|
}
|
|
if vinfo.FileCount > vinfo.DeleteCount {
|
|
fileCount = int(vinfo.FileCount - vinfo.DeleteCount)
|
|
}
|
|
return size, fileCount
|
|
}
|
|
}
|
|
}
|
|
return 0, 0
|
|
}
|