Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
725 lines
26 KiB
Go
725 lines
26 KiB
Go
package filer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/stats"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
|
)
|
|
|
|
type MetaAggregator struct {
|
|
filer *Filer
|
|
self pb.ServerAddress
|
|
isLeader bool
|
|
grpcDialOption grpc.DialOption
|
|
MetaLogBuffer *log_buffer.LogBuffer
|
|
peerChans map[pb.ServerAddress]chan struct{}
|
|
peerChansLock sync.Mutex
|
|
// peerWatermarks tracks, per subscribed peer (self included), the newest
|
|
// timestamp received on that peer's stream (event or idle heartbeat).
|
|
// The minimum is a delivery low-watermark: MetaLogBuffer is complete up
|
|
// to it, so a subscriber held at or below it cannot miss a late-merged
|
|
// peer event. An unsignalled peer pins it at zero.
|
|
peerWatermarks map[pb.ServerAddress]int64
|
|
// peerFlushWatermarks tracks each peer's reported flush watermark:
|
|
// everything at or below it is on that peer's disk. The minimum bounds
|
|
// persisted-log reads — beyond it a peer may still land a log file (or
|
|
// chunk) whose events a passed cursor would skip.
|
|
peerFlushWatermarks map[pb.ServerAddress]int64
|
|
// peerRemovedAtNs marks peers the master removed, with the removal time.
|
|
// A removed peer keeps participating in the low-watermarks for a grace
|
|
// period: removal usually means a flap (frozen or partitioned filer)
|
|
// whose unflushed events still exist, and de-accounting it at once would
|
|
// let subscribers advance past them. A re-add clears the mark; a peer
|
|
// gone past the grace is dropped so it cannot pin the low-watermarks.
|
|
peerRemovedAtNs map[pb.ServerAddress]int64
|
|
// lowWatermarkTsNs and lowFlushWatermarkTsNs are the last minima signalled
|
|
// on deliveryAdvanced and flushAdvanced, each closed and replaced when its
|
|
// own minimum rises. Held readers park on the one that bounds them:
|
|
// arriving data cannot release a watermark hold, only a peer reporting
|
|
// the progress that hold waits on. The two are kept apart because a
|
|
// delivery advance - one per event a peer streams - cannot release a read
|
|
// held at the flush watermark, and waking it costs a whole pass.
|
|
lowWatermarkTsNs int64
|
|
lowFlushWatermarkTsNs int64
|
|
deliveryAdvanced chan struct{}
|
|
flushAdvanced chan struct{}
|
|
remotePeerArrived chan struct{}
|
|
peerWatermarksLock sync.Mutex
|
|
}
|
|
|
|
// MetaAggregator only aggregates data "on the fly". The logs are not re-persisted to disk.
|
|
// The old data comes from what each LocalMetadata persisted on disk.
|
|
func NewMetaAggregator(filer *Filer, self pb.ServerAddress, grpcDialOption grpc.DialOption) *MetaAggregator {
|
|
t := &MetaAggregator{
|
|
filer: filer,
|
|
self: self,
|
|
grpcDialOption: grpcDialOption,
|
|
peerChans: make(map[pb.ServerAddress]chan struct{}),
|
|
peerWatermarks: make(map[pb.ServerAddress]int64),
|
|
peerFlushWatermarks: make(map[pb.ServerAddress]int64),
|
|
peerRemovedAtNs: make(map[pb.ServerAddress]int64),
|
|
}
|
|
// nil notifyFn: aggregated subscribers wake through the buffer's
|
|
// subscriber channels, not a cond.
|
|
t.MetaLogBuffer = log_buffer.NewLogBuffer("aggr", LogFlushInterval, nil, nil, nil)
|
|
return t
|
|
}
|
|
|
|
func (ma *MetaAggregator) OnPeerUpdate(update *master_pb.ClusterNodeUpdate, startFrom time.Time) {
|
|
ma.peerChansLock.Lock()
|
|
defer ma.peerChansLock.Unlock()
|
|
|
|
address := pb.ServerAddress(update.Address)
|
|
if update.IsAdd {
|
|
// the peer is already followed, restarting would only lose the events
|
|
// in between
|
|
if _, found := ma.peerChans[address]; found {
|
|
return
|
|
}
|
|
stopChan := make(chan struct{})
|
|
ma.peerChans[address] = stopChan
|
|
if address != ma.self {
|
|
ma.noteRemotePeerArrivalLocked()
|
|
}
|
|
// Account for the peer before its stream signals; keep prior values
|
|
// on reconnect.
|
|
ma.initPeerWatermark(address)
|
|
go ma.loopSubscribeToOneFiler(ma.filer, ma.self, address, startFrom, stopChan)
|
|
} else {
|
|
if prevChan, found := ma.peerChans[address]; found {
|
|
close(prevChan)
|
|
delete(ma.peerChans, address)
|
|
}
|
|
// Only mark: dropping the watermarks at once would let subscribers
|
|
// advance past a flapping peer's unflushed events (see peerRemovedAtNs).
|
|
ma.markPeerWatermarkRemoved(address)
|
|
}
|
|
}
|
|
|
|
// peerWatermarkRemovalGrace is how long a removed peer keeps participating
|
|
// in the low-watermarks. Matches the subscribe loops' settled horizon, which
|
|
// already bounds a stale watermark's influence within it.
|
|
const peerWatermarkRemovalGrace = 2 * LogFlushInterval
|
|
|
|
// initPeerWatermark adds the peer with a zero (unknown) watermark; a re-added
|
|
// peer keeps its prior values and stops being considered removed.
|
|
func (ma *MetaAggregator) initPeerWatermark(peer pb.ServerAddress) {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if _, found := ma.peerWatermarks[peer]; !found {
|
|
ma.peerWatermarks[peer] = 0
|
|
}
|
|
if _, found := ma.peerFlushWatermarks[peer]; !found {
|
|
ma.peerFlushWatermarks[peer] = 0
|
|
}
|
|
delete(ma.peerRemovedAtNs, peer)
|
|
ma.noteLowWatermarksLocked()
|
|
}
|
|
|
|
func (ma *MetaAggregator) markPeerWatermarkRemoved(peer pb.ServerAddress) {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if _, found := ma.peerWatermarks[peer]; !found {
|
|
return
|
|
}
|
|
// First removal time wins: duplicate removals must not refresh the grace.
|
|
if _, marked := ma.peerRemovedAtNs[peer]; !marked {
|
|
ma.peerRemovedAtNs[peer] = time.Now().UnixNano()
|
|
}
|
|
}
|
|
|
|
// dropExpiredRemovedPeersLocked drops peers whose removal outlived the grace.
|
|
// Caller must hold peerWatermarksLock.
|
|
func (ma *MetaAggregator) dropExpiredRemovedPeersLocked() {
|
|
if len(ma.peerRemovedAtNs) == 0 {
|
|
return
|
|
}
|
|
cutoff := time.Now().UnixNano() - int64(peerWatermarkRemovalGrace)
|
|
for peer, removedAt := range ma.peerRemovedAtNs {
|
|
if removedAt < cutoff {
|
|
delete(ma.peerRemovedAtNs, peer)
|
|
delete(ma.peerWatermarks, peer)
|
|
delete(ma.peerFlushWatermarks, peer)
|
|
}
|
|
}
|
|
// Dropping the peer that pinned a minimum raises it, same as it reporting.
|
|
ma.noteLowWatermarksLocked()
|
|
}
|
|
|
|
// advancePeerWatermark records the peer as received-through tsNs. Monotonic,
|
|
// and only for tracked peers: a dropped peer's straggler must not recreate
|
|
// its entry and pin the low-watermark.
|
|
func (ma *MetaAggregator) advancePeerWatermark(peer pb.ServerAddress, tsNs int64) {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if cur, found := ma.peerWatermarks[peer]; found && tsNs > cur {
|
|
ma.peerWatermarks[peer] = tsNs
|
|
ma.noteLowWatermarksLocked()
|
|
}
|
|
}
|
|
|
|
// advancePeerFlushWatermark records the peer's reported flush watermark.
|
|
// Monotonic, and only for tracked peers (see advancePeerWatermark).
|
|
func (ma *MetaAggregator) advancePeerFlushWatermark(peer pb.ServerAddress, tsNs int64) {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if cur, found := ma.peerFlushWatermarks[peer]; found && tsNs > cur {
|
|
ma.peerFlushWatermarks[peer] = tsNs
|
|
ma.noteLowWatermarksLocked()
|
|
}
|
|
}
|
|
|
|
// PeerLowFlushWatermarkTsNs returns the minimum reported flush watermark
|
|
// across all current peers: every peer's events at or below this time are on
|
|
// disk. Returns 0 when any peer has not reported yet (completeness unknown).
|
|
func (ma *MetaAggregator) PeerLowFlushWatermarkTsNs() int64 {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
ma.dropExpiredRemovedPeersLocked()
|
|
return lowWatermarkOf(ma.peerFlushWatermarks)
|
|
}
|
|
|
|
// PeerLowWatermarkTsNs returns the minimum received-through timestamp across
|
|
// all current peers (self included): MetaLogBuffer is complete up to this
|
|
// time. Returns 0 when any peer has not signalled yet (or none are tracked),
|
|
// i.e. completeness is unknown.
|
|
func (ma *MetaAggregator) PeerLowWatermarkTsNs() int64 {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
ma.dropExpiredRemovedPeersLocked()
|
|
return lowWatermarkOf(ma.peerWatermarks)
|
|
}
|
|
|
|
// DeliveryWatermarkAdvancedChan returns a channel closed the next time the
|
|
// delivery low-watermark rises, which is what releases an in-memory read held
|
|
// at it. PeerLowFlushWatermarkTsNs has FlushWatermarkAdvancedChan. Callers
|
|
// must take the channel before reading the watermark they hold at, so a rise
|
|
// in between wakes them instead of being missed.
|
|
func (ma *MetaAggregator) DeliveryWatermarkAdvancedChan() <-chan struct{} {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if ma.deliveryAdvanced == nil {
|
|
ma.deliveryAdvanced = make(chan struct{})
|
|
}
|
|
return ma.deliveryAdvanced
|
|
}
|
|
|
|
// FlushWatermarkAdvancedChan returns a channel closed the next time the flush
|
|
// low-watermark rises, which is what releases a persisted-log read held at it.
|
|
func (ma *MetaAggregator) FlushWatermarkAdvancedChan() <-chan struct{} {
|
|
ma.peerWatermarksLock.Lock()
|
|
defer ma.peerWatermarksLock.Unlock()
|
|
if ma.flushAdvanced == nil {
|
|
ma.flushAdvanced = make(chan struct{})
|
|
}
|
|
return ma.flushAdvanced
|
|
}
|
|
|
|
// noteLowWatermarksLocked recomputes both minima and wakes the readers parked
|
|
// on each one that rose. Caller must hold peerWatermarksLock.
|
|
func (ma *MetaAggregator) noteLowWatermarksLocked() {
|
|
low, flushLow := lowWatermarkOf(ma.peerWatermarks), lowWatermarkOf(ma.peerFlushWatermarks)
|
|
// Falls are recorded too (a joining peer sits at 0 until it signals), so
|
|
// the climb back out of one is seen as a rise.
|
|
if low > ma.lowWatermarkTsNs {
|
|
ma.deliveryAdvanced = closeWatermarkChan(ma.deliveryAdvanced)
|
|
}
|
|
if flushLow > ma.lowFlushWatermarkTsNs {
|
|
ma.flushAdvanced = closeWatermarkChan(ma.flushAdvanced)
|
|
}
|
|
ma.lowWatermarkTsNs, ma.lowFlushWatermarkTsNs = low, flushLow
|
|
}
|
|
|
|
// closeWatermarkChan wakes everyone parked on ch and clears it, so the next
|
|
// caller parks on a fresh one.
|
|
func closeWatermarkChan(ch chan struct{}) chan struct{} {
|
|
if ch != nil {
|
|
close(ch)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// lowWatermarkOf returns the minimum across the tracked peers, or 0 when none
|
|
// are tracked: a peer that has not signalled sits at 0 and pins the minimum
|
|
// there, which is what makes completeness unknown.
|
|
func lowWatermarkOf(watermarks map[pb.ServerAddress]int64) int64 {
|
|
if len(watermarks) == 0 {
|
|
return 0
|
|
}
|
|
var low int64 = math.MaxInt64
|
|
for _, tsNs := range watermarks {
|
|
if tsNs < low {
|
|
low = tsNs
|
|
}
|
|
}
|
|
return low
|
|
}
|
|
|
|
func (ma *MetaAggregator) HasRemotePeers() bool {
|
|
ma.peerChansLock.Lock()
|
|
defer ma.peerChansLock.Unlock()
|
|
return ma.hasRemotePeersLocked()
|
|
}
|
|
|
|
func (ma *MetaAggregator) hasRemotePeersLocked() bool {
|
|
for address := range ma.peerChans {
|
|
if address != ma.self {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemotePeerArrivedChan returns a channel closed when the aggregator learns
|
|
// its first remote peer, or nil if one is already known.
|
|
func (ma *MetaAggregator) RemotePeerArrivedChan() <-chan struct{} {
|
|
ma.peerChansLock.Lock()
|
|
defer ma.peerChansLock.Unlock()
|
|
if ma.hasRemotePeersLocked() {
|
|
return nil
|
|
}
|
|
return ma.remotePeerArrivedChanLocked()
|
|
}
|
|
|
|
// remotePeerArrivedChanLocked arms and returns the arrival channel. Caller must hold peerChansLock.
|
|
func (ma *MetaAggregator) remotePeerArrivedChanLocked() <-chan struct{} {
|
|
if ma.remotePeerArrived == nil {
|
|
ma.remotePeerArrived = make(chan struct{})
|
|
}
|
|
return ma.remotePeerArrived
|
|
}
|
|
|
|
// noteRemotePeerArrivalLocked closes the arrival channel when a remote peer is added. Caller must hold peerChansLock.
|
|
func (ma *MetaAggregator) noteRemotePeerArrivalLocked() {
|
|
ma.remotePeerArrived = closeWatermarkChan(ma.remotePeerArrived)
|
|
}
|
|
|
|
// HasPeer reports whether address is currently a tracked filer peer (or this
|
|
// filer's own address). Callers use this to gate operations on known cluster
|
|
// members.
|
|
func (ma *MetaAggregator) HasPeer(address pb.ServerAddress) bool {
|
|
if address == ma.self || address.Equals(ma.self) {
|
|
return true
|
|
}
|
|
ma.peerChansLock.Lock()
|
|
defer ma.peerChansLock.Unlock()
|
|
for peer := range ma.peerChans {
|
|
if peer == address || peer.Equals(address) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ma *MetaAggregator) loopSubscribeToOneFiler(f *Filer, self pb.ServerAddress, peer pb.ServerAddress, startFrom time.Time, stopChan chan struct{}) {
|
|
lastTsNs := startFrom.UnixNano()
|
|
for {
|
|
glog.V(0).Infof("loopSubscribeToOneFiler read %s start from %v %d", peer, time.Unix(0, lastTsNs), lastTsNs)
|
|
nextLastTsNs, err := ma.doSubscribeToOneFiler(f, self, peer, lastTsNs, stopChan)
|
|
|
|
// check stopChan to see if we should stop
|
|
select {
|
|
case <-stopChan:
|
|
glog.V(0).Infof("stop subscribing peer %s meta change", peer)
|
|
return
|
|
default:
|
|
}
|
|
|
|
if err != nil {
|
|
errLvl := glog.Level(0)
|
|
if strings.Contains(err.Error(), "duplicated local subscription detected") {
|
|
errLvl = glog.Level(4)
|
|
}
|
|
glog.V(errLvl).Infof("subscribing remote %s meta change: %v", peer, err)
|
|
}
|
|
if lastTsNs < nextLastTsNs {
|
|
lastTsNs = nextLastTsNs
|
|
}
|
|
time.Sleep(1733 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func (ma *MetaAggregator) doSubscribeToOneFiler(f *Filer, self pb.ServerAddress, peer pb.ServerAddress, startFrom int64, stopChan <-chan struct{}) (int64, error) {
|
|
|
|
/*
|
|
Each filer reads the "filer.store.id", which is the store's signature when filer starts.
|
|
|
|
When reading from other filers' local meta changes:
|
|
* if the received change does not contain signature from self, apply the change to current filer store.
|
|
|
|
Upon connecting to other filers, need to remember their signature and their offsets.
|
|
|
|
*/
|
|
|
|
var maybeReplicateMetadataChange func(*filer_pb.SubscribeMetadataResponse)
|
|
lastPersistTime := time.Now()
|
|
lastTsNs := startFrom
|
|
|
|
peerSignature, err := ma.readFilerStoreSignature(peer)
|
|
if err != nil {
|
|
return lastTsNs, fmt.Errorf("connecting to peer filer %s: %v", peer, err)
|
|
}
|
|
|
|
// when filer store is not shared by multiple filers
|
|
if peerSignature != f.Signature {
|
|
if prevTsNs, err := ma.readOffset(f, peer, peerSignature); err == nil {
|
|
lastTsNs = prevTsNs
|
|
} else if errors.Is(err, ErrKvNotFound) {
|
|
// No stored offset — this is the first time connecting to this peer.
|
|
// Traverse the peer's full metadata tree so we get pre-existing data.
|
|
// Record time before traversal and subtract a safety margin to
|
|
// account for clock skew between this filer and the peer. Any
|
|
// duplicate events replayed during the overlap are harmless since
|
|
// Replay does upserts. We use wall-clock time (same domain as the
|
|
// metadata stream TsNs) rather than entry Mtime which is a
|
|
// different concept and can be set to arbitrary values.
|
|
preTraverseTime := time.Now()
|
|
glog.V(0).Infof("no previous offset for peer %s, starting full metadata sync", peer)
|
|
if traverseErr := ma.traversePeerMetadata(f, peer); traverseErr != nil {
|
|
return lastTsNs, fmt.Errorf("initial metadata sync from %s: %v", peer, traverseErr)
|
|
}
|
|
lastTsNs = preTraverseTime.Add(-time.Minute).UnixNano()
|
|
if err := ma.updateOffset(f, peer, peerSignature, lastTsNs); err != nil {
|
|
return lastTsNs, fmt.Errorf("save bootstrap offset for peer %s: %w", peer, err)
|
|
}
|
|
glog.V(0).Infof("completed full metadata sync from peer %s, will stream changes from %v", peer, time.Unix(0, lastTsNs))
|
|
} else {
|
|
return lastTsNs, fmt.Errorf("read offset for peer %s: %w", peer, err)
|
|
}
|
|
defer func(prevTsNs int64) {
|
|
if lastTsNs != prevTsNs && lastTsNs != lastPersistTime.UnixNano() {
|
|
if err := ma.updateOffset(f, peer, peerSignature, lastTsNs); err == nil {
|
|
glog.V(0).Infof("last sync time with %s at %v (%d)", peer, time.Unix(0, lastTsNs), lastTsNs)
|
|
} else {
|
|
glog.Errorf("failed to save last sync time with %s at %v (%d)", peer, time.Unix(0, lastTsNs), lastTsNs)
|
|
}
|
|
}
|
|
}(lastTsNs)
|
|
|
|
glog.V(0).Infof("follow peer: %v, last %v (%d)", peer, time.Unix(0, lastTsNs), lastTsNs)
|
|
var counter int64
|
|
var synced bool
|
|
maybeReplicateMetadataChange = func(event *filer_pb.SubscribeMetadataResponse) {
|
|
replicateMetadataChange(f.Store, peer, event)
|
|
counter++
|
|
if lastPersistTime.Add(time.Minute).Before(time.Now()) {
|
|
if err := ma.updateOffset(f, peer, peerSignature, event.TsNs); err == nil {
|
|
if event.TsNs < time.Now().Add(-2*time.Minute).UnixNano() {
|
|
glog.V(0).Infof("sync with %s progressed to: %v %0.2f/sec", peer, time.Unix(0, event.TsNs), float64(counter)/60.0)
|
|
} else if !synced {
|
|
synced = true
|
|
glog.V(0).Infof("synced with %s", peer)
|
|
}
|
|
lastPersistTime = time.Now()
|
|
counter = 0
|
|
} else {
|
|
glog.V(0).Infof("failed to update offset for %v: %v", peer, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
processEventFn := func(event *filer_pb.SubscribeMetadataResponse) error {
|
|
data, err := proto.Marshal(event)
|
|
if err != nil {
|
|
glog.Errorf("failed to marshal subscribed filer_pb.SubscribeMetadataResponse %+v: %v", event, err)
|
|
return err
|
|
}
|
|
dir := event.Directory
|
|
// println("received meta change", dir, "size", len(data))
|
|
if err := ma.MetaLogBuffer.AddDataToBuffer([]byte(dir), data, event.TsNs); err != nil {
|
|
glog.Errorf("failed to add data to log buffer for %s: %v", dir, err)
|
|
return err
|
|
}
|
|
if maybeReplicateMetadataChange != nil {
|
|
maybeReplicateMetadataChange(event)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// The stream will deliver everything after lastTsNs, so the aggregated
|
|
// buffer is (still) complete for this peer up to that point.
|
|
ma.advancePeerWatermark(peer, lastTsNs)
|
|
|
|
glog.V(0).Infof("subscribing remote %s meta change: %v, clientId:%d", peer, time.Unix(0, lastTsNs), ma.filer.UniqueFilerId)
|
|
err = pb.WithFilerClient(true, 0, peer, ma.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
// Stop the stream promptly on removal; the blocking Recv below would
|
|
// otherwise only notice when the stream errors on its own.
|
|
go func() {
|
|
select {
|
|
case <-stopChan:
|
|
cancel()
|
|
case <-ctx.Done():
|
|
}
|
|
}()
|
|
atomic.AddInt32(&ma.filer.UniqueFilerEpoch, 1)
|
|
// Construct a log file reader that reads chunks via the peer filer's LookupVolume.
|
|
lookupFn := LookupFn(filerClient{client})
|
|
logFileReaderFn := func(chunks []*filer_pb.FileChunk) (io.ReadCloser, error) {
|
|
return NewChunkStreamReaderFromLookup(ctx, lookupFn, chunks), nil
|
|
}
|
|
|
|
stream, err := client.SubscribeLocalMetadata(ctx, &filer_pb.SubscribeMetadataRequest{
|
|
ClientName: "filer:" + string(self),
|
|
PathPrefix: "/",
|
|
SinceNs: lastTsNs,
|
|
ClientId: ma.filer.UniqueFilerId,
|
|
ClientEpoch: atomic.LoadInt32(&ma.filer.UniqueFilerEpoch),
|
|
ClientSupportsBatching: true,
|
|
ClientSupportsMetadataChunks: true,
|
|
// Idle heartbeats keep a quiet peer from freezing the low-watermark.
|
|
ClientSupportsIdleHeartbeat: true,
|
|
})
|
|
if err != nil {
|
|
glog.V(0).Infof("SubscribeLocalMetadata %v: %v", peer, err)
|
|
return fmt.Errorf("subscribe: %w", err)
|
|
}
|
|
|
|
processOne := func(event *filer_pb.SubscribeMetadataResponse) error {
|
|
if err := processEventFn(event); err != nil {
|
|
glog.V(0).Infof("SubscribeLocalMetadata process %v: %v", event, err)
|
|
return fmt.Errorf("process %v: %w", event, err)
|
|
}
|
|
f.onMetadataChangeEvent(event)
|
|
lastTsNs = event.TsNs
|
|
ma.advancePeerWatermark(peer, event.TsNs)
|
|
return nil
|
|
}
|
|
|
|
var pendingRefs []*filer_pb.LogFileChunkRef
|
|
|
|
for {
|
|
resp, listenErr := stream.Recv()
|
|
if listenErr == io.EOF {
|
|
return nil
|
|
}
|
|
if listenErr != nil {
|
|
glog.V(0).Infof("SubscribeLocalMetadata stream %v: %v", peer, listenErr)
|
|
return listenErr
|
|
}
|
|
|
|
// Accumulate log file chunk references
|
|
if len(resp.LogFileRefs) > 0 {
|
|
pendingRefs = append(pendingRefs, resp.LogFileRefs...)
|
|
continue
|
|
}
|
|
|
|
// Process accumulated refs (transition from disk to in-memory)
|
|
if len(pendingRefs) > 0 {
|
|
lastTs, readErr := pb.ReadLogFileRefs(pendingRefs, logFileReaderFn,
|
|
lastTsNs, 0, pb.PathFilter{PathPrefix: "/"},
|
|
func(event *filer_pb.SubscribeMetadataResponse) error {
|
|
return processOne(event)
|
|
})
|
|
if readErr != nil {
|
|
return fmt.Errorf("%w from %s: %w", pb.ErrLogFileRead, peer, readErr)
|
|
}
|
|
if lastTs > 0 {
|
|
lastTsNs = lastTs
|
|
}
|
|
pendingRefs = nil
|
|
}
|
|
|
|
if resp.EventNotification != nil {
|
|
if err := processOne(resp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Batched events, with the envelope's nil guard mirrored. A nested
|
|
// control message still carries watermark state - dropping it
|
|
// would make healthy flush progress look stalled.
|
|
for _, batchedEvent := range resp.Events {
|
|
if batchedEvent.FlushedTsNs > 0 {
|
|
ma.advancePeerFlushWatermark(peer, batchedEvent.FlushedTsNs)
|
|
}
|
|
if batchedEvent.EventNotification == nil {
|
|
if batchedEvent.TsNs > 0 {
|
|
ma.advancePeerWatermark(peer, batchedEvent.TsNs)
|
|
}
|
|
continue
|
|
}
|
|
if err := processOne(batchedEvent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Idle heartbeat: advance only the watermark, not lastTsNs, so a
|
|
// reconnect still resumes from the last real event.
|
|
if resp.EventNotification == nil && len(resp.Events) == 0 && resp.TsNs > 0 {
|
|
ma.advancePeerWatermark(peer, resp.TsNs)
|
|
}
|
|
if resp.FlushedTsNs > 0 {
|
|
ma.advancePeerFlushWatermark(peer, resp.FlushedTsNs)
|
|
}
|
|
}
|
|
})
|
|
return lastTsNs, err
|
|
}
|
|
|
|
// replicateMetadataChange retries transient Replay failures with bounded
|
|
// backoff. A failure that outlives the retry budget is counted and logged,
|
|
// then skipped: blocking on an event that can never replay would stall every
|
|
// later event from this peer, which is worse than one entry staying stale.
|
|
func replicateMetadataChange(store FilerStore, peer pb.ServerAddress, event *filer_pb.SubscribeMetadataResponse) {
|
|
err := util.Retry("replicate metadata change from "+string(peer), func() error {
|
|
return Replay(store, event)
|
|
})
|
|
if err == nil {
|
|
return
|
|
}
|
|
stats.FilerMetaAggregatorReplayFailures.WithLabelValues(string(peer)).Inc()
|
|
name := event.GetEventNotification().GetNewEntry().GetName()
|
|
if name == "" {
|
|
name = event.GetEventNotification().GetOldEntry().GetName()
|
|
}
|
|
glog.Errorf("giving up replicating metadata change from %s for %s/%s (ts=%d): %v", peer, event.Directory, name, event.TsNs, err)
|
|
}
|
|
|
|
// traversePeerMetadata does a full BFS traversal of a peer filer's metadata
|
|
// and inserts all entries into the local store. This is used when a filer
|
|
// connects to a peer for the first time and needs to bootstrap pre-existing data.
|
|
func (ma *MetaAggregator) traversePeerMetadata(f *Filer, peer pb.ServerAddress) error {
|
|
return pb.WithFilerClient(true, 0, peer, ma.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
stream, err := client.TraverseBfsMetadata(ctx, &filer_pb.TraverseBfsMetadataRequest{
|
|
Directory: "/",
|
|
ExcludedPrefixes: []string{SystemLogDir},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("traverse bfs metadata: %w", err)
|
|
}
|
|
var count int64
|
|
for {
|
|
resp, recvErr := stream.Recv()
|
|
if recvErr == io.EOF {
|
|
break
|
|
}
|
|
if recvErr != nil {
|
|
return fmt.Errorf("traverse bfs metadata recv: %w", recvErr)
|
|
}
|
|
if resp.Entry == nil {
|
|
continue
|
|
}
|
|
fullpath := util.Join(resp.Directory, resp.Entry.Name)
|
|
entry := FromPbEntry(resp.Directory, resp.Entry)
|
|
if insertErr := f.Store.InsertEntry(context.Background(), entry); insertErr != nil {
|
|
// Entry may already exist (root dir, or partial previous bootstrap).
|
|
existing, findErr := f.Store.FindEntry(context.Background(), entry.FullPath)
|
|
if findErr != nil {
|
|
return fmt.Errorf("insert entry %s: %w", fullpath, insertErr)
|
|
}
|
|
// Only overwrite if the peer's entry is newer.
|
|
if entry.Attr.Mtime.After(existing.Attr.Mtime) {
|
|
if updateErr := f.Store.UpdateEntry(context.Background(), entry); updateErr != nil {
|
|
return fmt.Errorf("update entry %s: %w", fullpath, updateErr)
|
|
}
|
|
} else {
|
|
glog.V(1).Infof("skip older peer entry %s (peer mtime %v <= local mtime %v)", fullpath, entry.Attr.Mtime, existing.Attr.Mtime)
|
|
}
|
|
}
|
|
count++
|
|
if count%10000 == 0 {
|
|
glog.V(0).Infof("synced %d entries from peer %s", count, peer)
|
|
}
|
|
}
|
|
glog.V(0).Infof("synced %d entries total from peer %s", count, peer)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (ma *MetaAggregator) readFilerStoreSignature(peer pb.ServerAddress) (sig int32, err error) {
|
|
err = pb.WithFilerClient(false, 0, peer, ma.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sig = resp.Signature
|
|
return nil
|
|
})
|
|
return
|
|
}
|
|
|
|
const (
|
|
MetaOffsetPrefix = "Meta"
|
|
)
|
|
|
|
func GetPeerMetaOffsetKey(peerSignature int32) []byte {
|
|
key := []byte(MetaOffsetPrefix + "xxxx")
|
|
util.Uint32toBytes(key[len(MetaOffsetPrefix):], uint32(peerSignature))
|
|
return key
|
|
}
|
|
|
|
func (ma *MetaAggregator) readOffset(f *Filer, peer pb.ServerAddress, peerSignature int32) (lastTsNs int64, err error) {
|
|
|
|
key := GetPeerMetaOffsetKey(peerSignature)
|
|
|
|
value, err := f.Store.KvGet(context.Background(), key)
|
|
|
|
if err != nil {
|
|
return 0, fmt.Errorf("readOffset %s : %w", peer, err)
|
|
}
|
|
|
|
lastTsNs = int64(util.BytesToUint64(value))
|
|
|
|
glog.V(0).Infof("readOffset %s : %d", peer, lastTsNs)
|
|
|
|
return
|
|
}
|
|
|
|
func (ma *MetaAggregator) updateOffset(f *Filer, peer pb.ServerAddress, peerSignature int32, lastTsNs int64) (err error) {
|
|
|
|
key := GetPeerMetaOffsetKey(peerSignature)
|
|
|
|
value := make([]byte, 8)
|
|
util.Uint64toBytes(value, uint64(lastTsNs))
|
|
|
|
err = f.Store.KvPut(context.Background(), key, value)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("updateOffset %s : %v", peer, err)
|
|
}
|
|
|
|
glog.V(4).Infof("updateOffset %s : %d", peer, lastTsNs)
|
|
|
|
return
|
|
}
|
|
|
|
// filerClient adapts a SeaweedFilerClient to the FilerClient interface
|
|
// for use with LookupFn. Used by MetaAggregator to resolve volume IDs
|
|
// on peer filers.
|
|
type filerClient struct {
|
|
client filer_pb.SeaweedFilerClient
|
|
}
|
|
|
|
func (fc filerClient) WithFilerClient(streamingMode bool, fn func(filer_pb.SeaweedFilerClient) error) error {
|
|
return fn(fc.client)
|
|
}
|
|
|
|
func (fc filerClient) AdjustedUrl(location *filer_pb.Location) string {
|
|
return location.Url
|
|
}
|
|
|
|
func (fc filerClient) GetDataCenter() string {
|
|
return ""
|
|
}
|