mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-20 21:14:25 +02:00
* pb: heartbeat fields for the federated volume-id bound (#11763) Heartbeat carries max_volume_id_seen and last_seen_topology_id up, and HeartbeatResponse echoes max_volume_id and topology_id down, so the allocation bound is replicated to every volume server. * master: echo the volume-id bound and fold reported bounds in (#11763) Every heartbeat response carries the leader current max volume id and topology id; every heartbeat reported max_volume_id_seen is folded into the topology bound via UpAdjustMaxVolumeId. * volume: persist a per-dir vid stamp of the highest id ever seen (#11763) The store keeps max_volume_id_seen + last_seen_topology_id in a .vid_stamp file per volume dir, fed by heartbeat echoes, AllocateVolume, and local volume/EC creation, seeded at boot from stamps and local holdings, and reported in every heartbeat. A rebuilt master recovers the bound from the first surviving veteran report. * volume-rust: mirror the vid stamp and heartbeat echo handling (#11763) Same mechanism as the Go volume server: .vid_stamp per dir, bound folded in from add_location scans and allocate/mount calls, echoed fields applied on heartbeat responses, reported on every heartbeat. * volume: serialize vid stamp writes and fsync the directory (#11763) * volume-rust: serialize vid stamp writes and fsync the directory (#11763) * master: echo the allocation bound to connected servers on change The bound was only sent at connect time or on digest mismatch, so a healthy stream never learned later increases. If the high-id server then went offline and the master lost state, survivors restored a stale bound and the id could be reissued. Resend the heartbeat response whenever the bound or topology id moved. * volume: retry failed vid stamp saves on later updates A transient write failure left the in-memory bound raised but the stamp stale, and repeat updates at the same bound never retried. Track a dirty flag so any later update re-attempts the save. * volume: cover stamp bootstrap for pre-stamp dirs in test * volume-rust: retry failed vid stamp saves, mirroring Go * volume-rust: stamp every dir with the combined bound on add_location A dir whose own stamp sat below the combined bound kept its stale value after fold; losing the higher-stamped disk then restarted the server below ids it had already learned. Rewrite all stamps after each location folds in. * volume: stamp mounted volume and ec shard ids (#11763) * volume-rust: stamp mounted volume ids, mirroring Go (#11763) * volume: retry dirty vid stamp save on unchanged topology id (#11763) * volume-rust: retry dirty vid stamp save on unchanged topology id (#11763) * master: record echoed bound from the sent heartbeat response (#11763)
149 lines
3.6 KiB
Go
149 lines
3.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
const vidStampFileName = ".vid_stamp"
|
|
|
|
type vidStamp struct {
|
|
MaxVolumeId uint32 `json:"maxVolumeId"`
|
|
TopologyId string `json:"topologyId,omitempty"`
|
|
}
|
|
|
|
func (s *Store) MaxVolumeIdSeen() needle.VolumeId {
|
|
return needle.VolumeId(s.maxVolumeIdSeen.Load())
|
|
}
|
|
|
|
func (s *Store) LastSeenTopologyId() string {
|
|
if v := s.lastSeenTopologyId.Load(); v != nil {
|
|
return v.(string)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// UpdateMaxVolumeIdSeen raises the server's persisted bound and stamps every
|
|
// volume dir when it grows. Feeds: heartbeat echoes, AllocateVolume, local
|
|
// volume and EC shard creation.
|
|
func (s *Store) UpdateMaxVolumeIdSeen(vid needle.VolumeId) {
|
|
grew := false
|
|
for {
|
|
current := s.maxVolumeIdSeen.Load()
|
|
if current >= uint32(vid) {
|
|
break
|
|
}
|
|
if s.maxVolumeIdSeen.CompareAndSwap(current, uint32(vid)) {
|
|
grew = true
|
|
break
|
|
}
|
|
}
|
|
if s.vidStampReady.Load() && (grew || s.vidStampDirty.Load()) {
|
|
s.saveVidStamps()
|
|
}
|
|
}
|
|
|
|
func (s *Store) UpdateLastSeenTopologyId(id string) {
|
|
if id == "" || (s.LastSeenTopologyId() == id && !s.vidStampDirty.Load()) {
|
|
return
|
|
}
|
|
s.lastSeenTopologyId.Store(id)
|
|
if s.vidStampReady.Load() {
|
|
s.saveVidStamps()
|
|
}
|
|
}
|
|
|
|
// bootstrapMaxVolumeIdSeen seeds the bound from persisted stamps plus the max
|
|
// of every volume and EC shard id on disk, so a server upgraded mid-incident
|
|
// still reports a bound covering its own holdings.
|
|
func (s *Store) bootstrapMaxVolumeIdSeen() {
|
|
var maxVid needle.VolumeId
|
|
for _, location := range s.Locations {
|
|
if stamp, err := readVidStamp(location.Directory); err == nil {
|
|
if needle.VolumeId(stamp.MaxVolumeId) > maxVid {
|
|
maxVid = needle.VolumeId(stamp.MaxVolumeId)
|
|
}
|
|
if stamp.TopologyId != "" && s.LastSeenTopologyId() == "" {
|
|
s.lastSeenTopologyId.Store(stamp.TopologyId)
|
|
}
|
|
}
|
|
location.volumesLock.RLock()
|
|
for vid := range location.volumes {
|
|
if vid > maxVid {
|
|
maxVid = vid
|
|
}
|
|
}
|
|
location.volumesLock.RUnlock()
|
|
location.ecVolumesLock.RLock()
|
|
for vid := range location.ecVolumes {
|
|
if vid > maxVid {
|
|
maxVid = vid
|
|
}
|
|
}
|
|
location.ecVolumesLock.RUnlock()
|
|
}
|
|
s.maxVolumeIdSeen.Store(uint32(maxVid))
|
|
s.vidStampReady.Store(true)
|
|
s.saveVidStamps()
|
|
}
|
|
|
|
func (s *Store) saveVidStamps() {
|
|
s.vidStampLock.Lock()
|
|
defer s.vidStampLock.Unlock()
|
|
stamp, err := json.Marshal(vidStamp{
|
|
MaxVolumeId: s.maxVolumeIdSeen.Load(),
|
|
TopologyId: s.LastSeenTopologyId(),
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
failed := false
|
|
for _, location := range s.Locations {
|
|
if err := writeVidStamp(location.Directory, stamp); err != nil {
|
|
glog.Warningf("write vid stamp in %s: %v", location.Directory, err)
|
|
failed = true
|
|
}
|
|
}
|
|
s.vidStampDirty.Store(failed)
|
|
}
|
|
|
|
func readVidStamp(dir string) (*vidStamp, error) {
|
|
data, err := os.ReadFile(filepath.Join(dir, vidStampFileName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stamp := &vidStamp{}
|
|
if err := json.Unmarshal(data, stamp); err != nil {
|
|
return nil, err
|
|
}
|
|
return stamp, nil
|
|
}
|
|
|
|
func writeVidStamp(dir string, stamp []byte) error {
|
|
tmp := filepath.Join(dir, vidStampFileName+".tmp")
|
|
f, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := f.Write(stamp); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
if err := f.Sync(); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Rename(tmp, filepath.Join(dir, vidStampFileName)); err != nil {
|
|
return err
|
|
}
|
|
return util.FsyncDir(dir)
|
|
}
|