mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-05 22:12:04 +02:00
filer: skip UpdateEntry when inline content is unchanged (#11580)
* filer: skip UpdateEntry when inline content is unchanged SaveInsideFiler rewrites config files (IAM identities, filer.conf, remote mappings, policies) unconditionally. Each no-op UpdateEntry is a metadata event the local meta log persists to /topics/.system/log, which appends a chunk to a volume. A client that rewrites identical config on a timer, e.g. the seaweedfs-operator 5-minute resync calling UpdateUser with unchanged actions, keeps .dat/.idx files growing on an otherwise idle cluster and prevents HDD spindown (seaweedfs/seaweedfs#11571). Skip the UpdateEntry when the stored inline content is byte-identical, so unchanged writes produce no metadata event and no volume writes. * filer: test that identical SaveInsideFiler writes skip UpdateEntry * filer: require stamped Md5 before skipping identical writes An entry holding identical content but no Md5 (written before hashing, or by a tool that cleared it) would never get the stamp that IF_ETAG_MATCH conditional writes key off. Skip only when both the stored content and its Md5 match, so one write still lands to repair the stamp.
This commit is contained in:
1 parent
562afa8ec9
commit
eafe79ebff
3 files changed
+58
-2
No files matched your search
@@ -262,8 +262,9 @@ func TestClearReadOnly(t *testing.T) {
|
||||
type fakeFilerConfClient struct {
|
||||
filer_pb.SeaweedFilerClient
|
||||
|
||||
mu sync.Mutex
|
||||
entries map[string]*filer_pb.Entry // key: dir+"/"+name
|
||||
mu sync.Mutex
|
||||
entries map[string]*filer_pb.Entry // key: dir+"/"+name
|
||||
updateCalls int
|
||||
}
|
||||
|
||||
func newFakeFilerConfClient() *fakeFilerConfClient {
|
||||
@@ -310,6 +311,7 @@ func (c *fakeFilerConfClient) CreateEntry(_ context.Context, in *filer_pb.Create
|
||||
func (c *fakeFilerConfClient) UpdateEntry(_ context.Context, in *filer_pb.UpdateEntryRequest, _ ...grpc.CallOption) (*filer_pb.UpdateEntryResponse, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.updateCalls++
|
||||
key := c.key(in.Directory, in.Entry.Name)
|
||||
if !conditionHoldsLocked(in.Condition, c.entries[key]) {
|
||||
return nil, errors.New("precondition failed")
|
||||
|
||||
@@ -75,6 +75,9 @@ func SaveInsideFiler(ctx context.Context, client filer_pb.SeaweedFilerClient, di
|
||||
})
|
||||
} else if err == nil {
|
||||
entry := resp.Entry
|
||||
if bytes.Equal(entry.Content, content) && bytes.Equal(entry.GetAttributes().GetMd5(), contentMd5[:]) {
|
||||
return nil
|
||||
}
|
||||
entry.Content = content
|
||||
entry.Attributes.Mtime = time.Now().Unix()
|
||||
entry.Attributes.FileSize = uint64(len(content))
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Rewriting identical inline content must not issue UpdateEntry: each one is a
|
||||
// metadata event the local meta log persists to /topics/.system/log, and a
|
||||
// client that rewrites the same config on a timer (e.g. the operator's 5-minute
|
||||
// resync) keeps the volumes growing on an otherwise idle cluster.
|
||||
func TestSaveInsideFilerSkipsIdenticalContent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := newFakeFilerConfClient()
|
||||
|
||||
require.NoError(t, SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "a.json", []byte("v1")))
|
||||
require.Equal(t, 0, client.updateCalls)
|
||||
|
||||
require.NoError(t, SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "a.json", []byte("v1")))
|
||||
assert.Equal(t, 0, client.updateCalls)
|
||||
|
||||
require.NoError(t, SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "a.json", []byte("v2")))
|
||||
assert.Equal(t, 1, client.updateCalls)
|
||||
|
||||
content, err := ReadInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "a.json")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("v2"), content)
|
||||
}
|
||||
|
||||
// A legacy entry holding identical bytes but no Md5 still gets one write to
|
||||
// stamp the hash that IF_ETAG_MATCH conditional writes key off; later
|
||||
// identical writes skip.
|
||||
func TestSaveInsideFilerStampsMd5OnLegacyEntry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := newFakeFilerConfClient()
|
||||
client.entries[client.key(DirectoryEtcSeaweedFS, "b.json")] = &filer_pb.Entry{
|
||||
Name: "b.json",
|
||||
Content: []byte("v1"),
|
||||
Attributes: &filer_pb.FuseAttributes{},
|
||||
}
|
||||
|
||||
require.NoError(t, SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "b.json", []byte("v1")))
|
||||
assert.Equal(t, 1, client.updateCalls)
|
||||
|
||||
require.NoError(t, SaveInsideFiler(ctx, client, DirectoryEtcSeaweedFS, "b.json", []byte("v1")))
|
||||
assert.Equal(t, 1, client.updateCalls)
|
||||
}
|
||||
Reference in new issue
Block a user