mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 06:20:48 +02:00
* volume: never compact a volume tiered to remote storage A tiered volume's data backend is the remote object, but compaction reads the local .dat path. When tiering removed the local .dat, every vacuum of a tiered volume with garbage fails with "no such file or directory", and the master's sweep and the worker's vacuum task keep selecting it. When the local .dat was kept, the compaction succeeds against the stale copy and the commit reloads the volume to read the remote object (old layout) through the compacted index, so live needles stop reading. Mirror the Rust volume server (#11545): - CompactByIndex and CompactByVolumeData refuse a volume with a remote file before creating .cpd/.cpx ("volume N is tiered to remote storage, cannot compact"). - CommitCompact refuses under the data-file locks when the volume became tiered during the copy, and removes .cpd/.cpx. - Store.CheckCompactVolume reports no garbage for a tiered volume, so the master's vacuum and volume.vacuum do not select it. - The worker's vacuum detection skips a volume with a remote copy, as balance and EC detection already do. Tests (each fails on master): TestCompactRefusesTieredVolume, TestCommitCompactRefusesVolumeTieredDuringCopy (on master the commit succeeds and a surviving needle no longer reads), TestPickPrimaryReplicaSkipsTieredVolumes. * volume: skip tiered replicas in vacuum selection, keep their real ratio Reporting a zero garbage ratio for a tiered volume hid it from volume.tier.compact, whose preflight reads that ratio while the volume is still remote, and did not exclude it from a vacuum with a zero threshold. Report the real ratio again, as the Rust volume server does, and have the master's vacuum check skip a replica whose volume info names remote storage, whatever the threshold or request. When CommitCompact refuses a volume tiered during the copy, also close the temporary needle map and remove .cpldb: nothing else would, since no later vacuum selects the volume. The tiered-compaction test no longer depends on random needle sizes reaching a 0.3 garbage ratio. * volume tier: do not attach a copy of a .dat a compaction replaced The upload copies the .dat, then records the remote file in the .vif and switches the backend. A compaction commit in between swaps in a compacted .dat and index; the upload then served the old-layout remote object through the new index, so live needles stopped reading. CommitCompact's tiered check cannot see this: the volume is not tiered until the switch. AttachRemoteDatFile records the remote file and switches the backend under the data-file locks, and refuses when the backend is no longer the file that was copied. The upload then removes the unused remote copy. * volume tier: keep a committed .vif entry when only the dir fsync fails AttachRemoteDatFile rolled back the in-memory remote entry on any SaveVolumeInfo error. A NotCrashDurableError comes after the rename, so the .vif on disk already names the copy; with the entry gone, the upload handler deleted that object, and a reload would serve from a missing remote file. Keep the entry and switch to the copy as the .vif says, and still return the error so the caller keeps the local .dat. * vacuum worker: filter tiered replicas instead of skipping the volume A partially tiered volume is the leftover of an interrupted volume.tier.upload; skipping the whole volume left garbage on its local replicas unreclaimed. Per-replica filtering matches the master's batchVacuumVolumeCheck and is safe: a tiered replica refuses both compaction and commit. --------- Co-authored-by: Javier de la Torre <jatorre@lakehousebox.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
71 lines
2.7 KiB
Go
71 lines
2.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"errors"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/backend"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/volume_info"
|
|
)
|
|
|
|
// When the .vif rename commits but the directory fsync fails, the saved .vif
|
|
// names the remote copy. AttachRemoteDatFile must keep that entry (the upload
|
|
// handler deletes a copy the volume info does not name) and serve from the
|
|
// copy the .vif points at, while still reporting the error.
|
|
func TestAttachRemoteDatFileKeepsACommittedVifEntry(t *testing.T) {
|
|
b := newLocalDirBackend(t)
|
|
registerTestBackend(t, b)
|
|
|
|
dir := t.TempDir()
|
|
v, err := NewVolume(dir, dir, "", 86, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
|
|
require.NoError(t, err)
|
|
defer v.Close()
|
|
for i := 1; i <= 5; i++ {
|
|
_, _, _, err := v.writeNeedle2(newRandomNeedle(uint64(i)), true, false, false)
|
|
require.NoError(t, err)
|
|
}
|
|
diskFile, ok := v.DataBackend.(*backend.DiskFile)
|
|
require.True(t, ok)
|
|
key, size, err := b.CopyFile(diskFile.File, nil, 0)
|
|
require.NoError(t, err)
|
|
|
|
saved := saveVolumeInfoFile
|
|
saveVolumeInfoFile = func(fileName string, info *volume_server_pb.VolumeInfo) error {
|
|
if err := saved(fileName, info); err != nil {
|
|
return err
|
|
}
|
|
return &volume_info.NotCrashDurableError{FileName: fileName, Err: syscall.EIO}
|
|
}
|
|
defer func() { saveVolumeInfoFile = saved }()
|
|
|
|
bType, bId := backend.BackendNameToTypeId(testBackendName)
|
|
err = v.AttachRemoteDatFile(diskFile, &volume_server_pb.RemoteFile{
|
|
BackendType: bType, BackendId: bId, Key: key, FileSize: uint64(size),
|
|
ModifiedTime: uint64(time.Now().Unix()), Extension: ".dat",
|
|
})
|
|
var ndErr *volume_info.NotCrashDurableError
|
|
require.True(t, errors.As(err, &ndErr), "the fsync failure is still reported: %v", err)
|
|
|
|
onDisk, _, _, err := volume_info.MaybeLoadVolumeInfo(v.FileName(".vif"))
|
|
require.NoError(t, err)
|
|
require.Len(t, onDisk.GetFiles(), 1)
|
|
require.Equal(t, key, onDisk.GetFiles()[0].Key)
|
|
require.Len(t, v.GetVolumeInfo().GetFiles(), 1, "the in-memory entry was rolled back while the .vif names the copy")
|
|
require.Equal(t, key, v.GetVolumeInfo().GetFiles()[0].Key)
|
|
require.True(t, v.HasRemoteFile())
|
|
_, isDisk := v.DataBackend.(*backend.DiskFile)
|
|
require.False(t, isDisk, "the live backend must be the copy the .vif names")
|
|
|
|
n := &needle.Needle{Id: types.Uint64ToNeedleId(3)}
|
|
_, err = v.readNeedle(n, &ReadOption{}, nil)
|
|
require.NoError(t, err)
|
|
}
|