mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-12 23:09:33 +01:00
* replication: let the local sink check a delete against the live source LocalSink removed the destination path for any delete it was handed. A delete that reaches the sink after the source recreated the path (a peer filer's event delivered late, or a historical event replayed) removed the newer file, and no later event brings it back. DeleteEntryVersion receives the deleted entry. Before removing an existing destination, the local sink looks up the source path and keeps the destination when the source holds an entry of the same kind committed after the deleted version (commit_ts_ns, or mtime for unstamped entries). A live entry that is the deleted version itself or older means the source store lags the event, so the delete proceeds. A failed lookup returns the error so the event is retried instead of deleting a file that may be live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer.sync, filer.replicate: hand the deleted version to the sink Pure deletes, renames without a native move and moves out of the watched directory now go through sink.DeleteEntryVersion with the old entry, so a sink that checks the live source (the local sink) can keep a path the source recreated after the deleted version. Other sinks fall back to DeleteEntry unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * replication: order the local sink check by the delete's mutation time A source filer can hold a version written concurrently with the delete: newer than the deleted entry but older than the delete itself. The source's own replay applies the delete over it, so keeping the backup copy because it outranks the deleted entry left a file the source no longer has. The sink now gets the delete's mutation time (MutationTsNs, else TsNs) and keeps the destination only when the live entry was committed after it, the order Filer.Replay keeps. That also covers a stamped replacement of an unstamped entry whose mtime did not move. filer.replicate events carry no time, so they keep the deleted entry's stamp as the bound, and mtime only when nothing is stamped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * replication: count an unstamped live entry from its mtime A source filer that predates commit stamps stores entries with no CommitTsNs, so comparing the live stamp with the delete's time never kept a replacement there. An unstamped live entry now counts from its mtime against the delete's time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * replication: keep a stamped live entry when the deleted one is unstamped and the delete has no time filer.replicate gets no delete time from the notification queue. When the deleted entry has no commit stamp either, a stamped live entry was written after it, so its mtime no longer decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
4.2 KiB
Go
88 lines
4.2 KiB
Go
package sink
|
|
|
|
import (
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/replication/source"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
type ReplicationSink interface {
|
|
GetName() string
|
|
Initialize(configuration util.Configuration, prefix string) error
|
|
DeleteEntry(key string, isDirectory, deleteIncludeChunks bool, signatures []int32) error
|
|
CreateEntry(key string, entry *filer_pb.Entry, signatures []int32) error
|
|
UpdateEntry(key string, oldEntry *filer_pb.Entry, newParentPath string, newEntry *filer_pb.Entry, deleteIncludeChunks bool, signatures []int32) (foundExistingEntry bool, err error)
|
|
GetSinkToDirectory() string
|
|
// GetDestinationIdentity distinguishes this sink's write destination from
|
|
// any other destination the same sink type could write to: endpoint or
|
|
// account, bucket or container, and directory. filer.backup keys its resume
|
|
// checkpoint on it, so two configurations writing to different places must
|
|
// not share a value.
|
|
GetDestinationIdentity() string
|
|
SetSourceFiler(s *source.FilerSource)
|
|
IsIncremental() bool
|
|
}
|
|
|
|
// EntryMover is an optional capability for sinks that can relocate an entry
|
|
// natively, in one atomic step, instead of create-then-delete. Drivers prefer
|
|
// it for a rename so a failed copy can never leave the source deleted with no
|
|
// committed destination, a directory move never deletes descendants before they
|
|
// are recreated, and the entry's chunks are neither re-copied nor leaked.
|
|
type EntryMover interface {
|
|
MoveEntry(oldKey, newKey string, oldEntry, newEntry *filer_pb.Entry, signatures []int32) error
|
|
}
|
|
|
|
// MetadataOnlyDeleter is an optional capability for sinks whose own delete
|
|
// events reach remote write-back daemons: the destination filer re-emits a
|
|
// replicated delete, so a source delete that kept the remote object must stay
|
|
// metadata-only there too.
|
|
type MetadataOnlyDeleter interface {
|
|
DeleteEntryKeepingRemoteObject(key string, isDirectory, deleteIncludeChunks bool, signatures []int32) error
|
|
}
|
|
|
|
// LatestVersionWriter is an optional capability for sinks whose CreateEntry
|
|
// guards a replayed write against the live source at the key's own path. A
|
|
// caller that already resolved the current source state — a flattened latest
|
|
// version lives under a different source path than the key it lands on — must
|
|
// not be skipped as stale by that check.
|
|
type LatestVersionWriter interface {
|
|
CreateEntryLatest(key string, entry *filer_pb.Entry, signatures []int32) error
|
|
}
|
|
|
|
func CreateEntryLatest(s ReplicationSink, key string, entry *filer_pb.Entry, signatures []int32) error {
|
|
if w, ok := s.(LatestVersionWriter); ok {
|
|
return w.CreateEntryLatest(key, entry, signatures)
|
|
}
|
|
return s.CreateEntry(key, entry, signatures)
|
|
}
|
|
|
|
func DeleteEntry(s ReplicationSink, key string, isDirectory, deleteIncludeChunks, keepRemoteObject bool, signatures []int32) error {
|
|
if deleter, ok := s.(MetadataOnlyDeleter); ok && keepRemoteObject {
|
|
return deleter.DeleteEntryKeepingRemoteObject(key, isDirectory, deleteIncludeChunks, signatures)
|
|
}
|
|
return s.DeleteEntry(key, isDirectory, deleteIncludeChunks, signatures)
|
|
}
|
|
|
|
// VersionedDeleter is an optional capability for sinks that check a delete
|
|
// against the live source before removing the destination: a delete that
|
|
// reaches the sink after the source recreated the path must not remove the
|
|
// newer destination, since no later event brings it back. tsNs is the
|
|
// delete's mutation time, or 0 when the event does not carry one.
|
|
type VersionedDeleter interface {
|
|
DeleteEntryVersion(key string, oldEntry *filer_pb.Entry, tsNs int64, deleteIncludeChunks bool, signatures []int32) error
|
|
}
|
|
|
|
func DeleteEntryVersion(s ReplicationSink, key string, oldEntry *filer_pb.Entry, tsNs int64, deleteIncludeChunks, keepRemoteObject bool, signatures []int32) error {
|
|
if _, ok := s.(MetadataOnlyDeleter); ok && keepRemoteObject {
|
|
return DeleteEntry(s, key, oldEntry.IsDirectory, deleteIncludeChunks, keepRemoteObject, signatures)
|
|
}
|
|
if deleter, ok := s.(VersionedDeleter); ok {
|
|
return deleter.DeleteEntryVersion(key, oldEntry, tsNs, deleteIncludeChunks, signatures)
|
|
}
|
|
return DeleteEntry(s, key, oldEntry.IsDirectory, deleteIncludeChunks, keepRemoteObject, signatures)
|
|
}
|
|
|
|
var (
|
|
Sinks []ReplicationSink
|
|
)
|