Free a cached blob once no file still to restore needs it (closes #324)
check / check (push) Canceled after 0s

A restore of selected paths, or one where files failed under
--skip-errors, kept the blobs those files share in the temporary cache
until it ended: the sweeper freed a blob only once every file in the
snapshot database that uses it had been restored, and files outside the
selection or that failed never are.

The sweeper now asks the restore plan, through pendingFileUsesBlob,
whether any pending file still uses a cached blob. The plan holds only
this run's files and drops each one once it is restored or has failed.

The new test backs up 16 KiB files so it stays quick; the locality
backup helper takes the file size for that.

Model: opus-5-5
This commit was merged in pull request #372.
This commit is contained in:
2026-10-14 01:47:48 +02:00
parent dd3667c8e9
commit 66ea7f5f45
8 changed files with 222 additions and 123 deletions
+15 -11
View File
@@ -63,6 +63,10 @@ type localityCopy struct {
data []byte
}
// localitySourceBytes is the size of each source file of the locality
// fixture.
const localitySourceBytes = 1024 * 1024
// buildLocalityFixture writes the adversarial source layout described
// in TestRestoreLocalityAndReadAt: 15 sources of 1 MiB each (which the
// backup packs into 3 blobs of 5 chunks) plus 9 interleaved-name copies
@@ -73,7 +77,6 @@ func buildLocalityFixture(
t.Helper()
const (
srcBytes = 1024 * 1024
srcCount = 15
blobsCount = 3
perBlob = srcCount / blobsCount
@@ -83,7 +86,7 @@ func buildLocalityFixture(
for i := range srcCount {
s := &localitySource{
path: fmt.Sprintf("src-%03d.bin", i+1),
data: randomBytes(t, srcBytes),
data: randomBytes(t, localitySourceBytes),
}
sources[i] = s
require.NoError(t, afero.WriteFile(
@@ -115,19 +118,20 @@ func buildLocalityFixture(
// setupLocalityBackup runs the backup half of the locality test: a
// snapshot of dataDir into a file storer with sizes tuned so 15 one-chunk
// files pack into 3 blobs. The index database is closed before returning
// so the restore half runs from remote bytes only.
// files of srcBytes pack into 3 blobs. The index database is closed
// before returning so the restore half runs from remote bytes only.
func setupLocalityBackup(
ctx context.Context, t *testing.T, fs afero.Fs,
dataDir, storeDir, dbPath string,
dataDir, storeDir, dbPath string, srcBytes int64,
) (*config.Config, *storage.FileStorer, string) {
t.Helper()
// chunkSize avg = 4 MiB makes minSize = 1 MiB, so a 1 MiB file
// becomes one chunk. maxBlobSize = 5 MiB packs exactly 5 chunks
// per blob, yielding 3 blobs from 15 source files.
chunkSize := int64(4 * 1024 * 1024)
maxBlobSize := int64(5 * 1024 * 1024)
// An average chunk size of four times srcBytes makes the minimum
// chunk srcBytes, so each source file becomes one chunk. A blob limit
// of five times srcBytes packs exactly 5 chunks per blob, yielding 3
// blobs from 15 source files.
chunkSize := 4 * srcBytes
maxBlobSize := 5 * srcBytes
storer, err := storage.NewFileStorer(storeDir)
require.NoError(t, err)
@@ -208,7 +212,7 @@ func TestRestoreLocalityAndReadAt(t *testing.T) {
ctx := context.Background()
cfg, storer, snapshotID := setupLocalityBackup(
ctx, t, fs, dataDir, storeDir, dbPath)
ctx, t, fs, dataDir, storeDir, dbPath, localitySourceBytes)
// Wrap the storer so we can count downloads per blob key.
counter := newCountingStorer(storer)