mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-13 07:19:18 +01:00
s3: route versioned PutObject finalize off the DLM (#9631)
s3: route versioned PutObject finalize off the distributed lock A versioned write's finalize (flip the .versions pointer to the newest version, demote the prior latest) now runs as a single RECOMPUTE_LATEST ObjectTransaction on the object's owner filer, under its per-path lock, instead of the unserialized updateLatestVersionInDirectory. The version file is written first; the owner re-derives the pointer by scanning the directory. RECOMPUTE_LATEST gains size_to_key / mtime_to_key to cache the chosen version's size and mtime on the pointer, and demote_key / demote_value to stamp the displaced prior latest (NoncurrentSinceNs for lifecycle) when the pointer moves. Falls back to updateLatestVersionInDirectory when no owner is known yet.
This commit is contained in:
1 parent
32aa70ab59
commit
db954b5503
8 files changed
+246
-18
No files matched your search
@@ -8,6 +8,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/cluster"
|
||||
@@ -411,6 +412,12 @@ func (fs *FilerServer) applyRecomputeLatest(ctx context.Context, m *filer_pb.Obj
|
||||
pointer.Extended = make(map[string][]byte)
|
||||
}
|
||||
|
||||
// Remember the prior chosen child so it can be demoted once the pointer moves.
|
||||
var priorName string
|
||||
if rc.NameToKey != "" {
|
||||
priorName = string(pointer.Extended[rc.NameToKey])
|
||||
}
|
||||
|
||||
// The store streams entries ascending by name. For the lowest-name pick we
|
||||
// only need the first entry, so cap the listing at one; for the highest-name
|
||||
// pick we must scan all and keep the last (the store has no reverse order).
|
||||
@@ -427,12 +434,15 @@ func (fs *FilerServer) applyRecomputeLatest(ctx context.Context, m *filer_pb.Obj
|
||||
return listErr
|
||||
}
|
||||
|
||||
cleared := []string{rc.NameToKey, rc.SizeToKey, rc.MtimeToKey}
|
||||
if chosen == nil {
|
||||
for pointerKey := range rc.CopyExtended {
|
||||
delete(pointer.Extended, pointerKey)
|
||||
}
|
||||
if rc.NameToKey != "" {
|
||||
delete(pointer.Extended, rc.NameToKey)
|
||||
for _, k := range cleared {
|
||||
if k != "" {
|
||||
delete(pointer.Extended, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for pointerKey, sourceKey := range rc.CopyExtended {
|
||||
@@ -445,9 +455,39 @@ func (fs *FilerServer) applyRecomputeLatest(ctx context.Context, m *filer_pb.Obj
|
||||
if rc.NameToKey != "" {
|
||||
pointer.Extended[rc.NameToKey] = []byte(chosen.Name())
|
||||
}
|
||||
if rc.SizeToKey != "" {
|
||||
pointer.Extended[rc.SizeToKey] = []byte(strconv.FormatUint(chosen.FileSize, 10))
|
||||
}
|
||||
if rc.MtimeToKey != "" {
|
||||
pointer.Extended[rc.MtimeToKey] = []byte(strconv.FormatInt(chosen.Mtime.Unix(), 10))
|
||||
}
|
||||
}
|
||||
|
||||
return fs.filer.UpdateEntry(ctx, pointer, pointer)
|
||||
if err := fs.filer.UpdateEntry(ctx, pointer, pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stamp the displaced prior child (e.g. NoncurrentSinceNs for lifecycle).
|
||||
newName := ""
|
||||
if chosen != nil {
|
||||
newName = chosen.Name()
|
||||
}
|
||||
if rc.DemoteKey != "" && priorName != "" && priorName != newName {
|
||||
priorEntry, perr := fs.filer.FindEntry(ctx, util.NewFullPath(rc.ScanDir, priorName))
|
||||
if perr == filer_pb.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
if priorEntry.Extended == nil {
|
||||
priorEntry.Extended = make(map[string][]byte)
|
||||
}
|
||||
priorEntry.Extended[rc.DemoteKey] = rc.DemoteValue
|
||||
return fs.filer.UpdateEntry(ctx, priorEntry, priorEntry)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FilerServer) UpdateEntry(ctx context.Context, req *filer_pb.UpdateEntryRequest) (*filer_pb.UpdateEntryResponse, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package weed_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -431,3 +432,61 @@ func TestObjectTransactionIdempotentNoops(t *testing.T) {
|
||||
t.Fatalf("no-op mutations should not error: %q", resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// RECOMPUTE_LATEST copies the chosen child's size/mtime to the pointer and
|
||||
// stamps the demote key on the prior latest when the pointer moves.
|
||||
func TestObjectTransactionRecomputeDemoteAndAttrs(t *testing.T) {
|
||||
t0 := time.Unix(1700000000, 0)
|
||||
t1 := time.Unix(1700000100, 0)
|
||||
mk := func(inode uint64, mt time.Time, size uint64, id string) *filer.Entry {
|
||||
return &filer.Entry{
|
||||
Attr: filer.Attr{Inode: inode, Mtime: mt, Crtime: mt, Mode: 0644, FileSize: size},
|
||||
Extended: map[string][]byte{"vid": []byte(id)},
|
||||
}
|
||||
}
|
||||
fs, store := newTxnTestServer(map[string]*filer.Entry{
|
||||
"/buckets/b/obj/.versions": {
|
||||
Attr: filer.Attr{Inode: 2, Mtime: t0, Crtime: t0, Mode: 0755 | (1 << 31)},
|
||||
Extended: map[string][]byte{"latestName": []byte("v1.ver"), "latestVid": []byte("v1")},
|
||||
},
|
||||
"/buckets/b/obj/.versions/v1.ver": mk(10, t0, 100, "v1"),
|
||||
"/buckets/b/obj/.versions/v2.ver": mk(11, t1, 250, "v2"),
|
||||
})
|
||||
|
||||
resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
|
||||
LockKey: "/buckets/b/obj",
|
||||
Mutations: []*filer_pb.ObjectMutation{{
|
||||
Type: filer_pb.ObjectMutation_RECOMPUTE_LATEST, Directory: "/buckets/b/obj", Name: ".versions",
|
||||
Recompute: &filer_pb.Recompute{
|
||||
ScanDir: "/buckets/b/obj/.versions",
|
||||
Descending: true,
|
||||
CopyExtended: map[string]string{"latestVid": "vid"},
|
||||
NameToKey: "latestName",
|
||||
SizeToKey: "latestSize",
|
||||
MtimeToKey: "latestMtime",
|
||||
DemoteKey: "noncurrentSince",
|
||||
DemoteValue: []byte("999"),
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil || resp.Error != "" {
|
||||
t.Fatalf("txn failed: err=%v resp=%q", err, resp.Error)
|
||||
}
|
||||
|
||||
ptr := store.entries["/buckets/b/obj/.versions"].Extended
|
||||
if string(ptr["latestName"]) != "v2.ver" || string(ptr["latestVid"]) != "v2" {
|
||||
t.Fatalf("pointer not moved to v2: name=%s vid=%s", ptr["latestName"], ptr["latestVid"])
|
||||
}
|
||||
if string(ptr["latestSize"]) != "250" {
|
||||
t.Errorf("latestSize = %s, want 250", ptr["latestSize"])
|
||||
}
|
||||
if want := strconv.FormatInt(t1.Unix(), 10); string(ptr["latestMtime"]) != want {
|
||||
t.Errorf("latestMtime = %s, want %s", ptr["latestMtime"], want)
|
||||
}
|
||||
if got := store.entries["/buckets/b/obj/.versions/v1.ver"].Extended["noncurrentSince"]; string(got) != "999" {
|
||||
t.Errorf("prior latest v1.ver noncurrentSince = %q, want 999", got)
|
||||
}
|
||||
if _, ok := store.entries["/buckets/b/obj/.versions/v2.ver"].Extended["noncurrentSince"]; ok {
|
||||
t.Errorf("new latest v2.ver should not be demoted")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user