Skip to content

Commit 0e5d38a

Browse files
committed
storage: fix PebbleFileRegistry bug that drops entry on rollover
The writeToRegistryFile method first writes the new batch, containing file mappings, to the registry file, and then if the registry file is too big, creates a new registry file. The new registry file is populated with the contents of the map, which doesn't yet contain the edits in the batch, resulting in a loss of these edits when the file registry is reopened. This PR changes the logic to first rollover if the registry file is too big, and then writes the batch to the new file. This bug has existed since the record writer based registry was implemented cockroachdb@239377a. When it leads to a loss of a file mapping in the registry, it will be noticed by Pebble as a corruption (so not a silent failure) since the file corresponding to the mapping will be assumed to be unencrypted, but can't be successfully read as an unencrypted file. Since we have not seen this occur in production settings, we suspect that an observable mapping loss is rare because compactions typically rewrite the files in those lost mappings before the file registry is reopened. Epic: none Fixes: cockroachdb#106617 Release note: None
1 parent bfa276d commit 0e5d38a

File tree

2 files changed

+93
-14
lines changed

2 files changed

+93
-14
lines changed

pkg/storage/pebble_file_registry.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,23 @@ func (r *PebbleFileRegistry) applyBatch(batch *enginepb.RegistryUpdateBatch) {
398398
}
399399

400400
func (r *PebbleFileRegistry) writeToRegistryFile(batch *enginepb.RegistryUpdateBatch) error {
401-
// Create a new file registry file if one doesn't exist yet.
402-
if r.mu.registryWriter == nil {
401+
// Create a new file registry file if one doesn't exist yet, or we need to
402+
// rollover.
403+
nilWriter := r.mu.registryWriter == nil
404+
if nilWriter || r.mu.registryWriter.Size() > maxRegistrySize {
405+
// If !nilWriter, exceeded the size threshold: create a new file registry
406+
// file to hopefully shrink the size of the file.
407+
//
408+
// TODO(storage-team): fix this rollover logic to not rollover with each
409+
// change if the total number of entries is large enough to exceed
410+
// maxRegistrySize. Do something similar to what we do with the Pebble
411+
// MANIFEST rollover.
403412
if err := r.createNewRegistryFile(); err != nil {
404-
return errors.Wrap(err, "creating new registry file")
413+
if nilWriter {
414+
return errors.Wrap(err, "creating new registry file")
415+
} else {
416+
return errors.Wrap(err, "rotating registry file")
417+
}
405418
}
406419
}
407420
w, err := r.mu.registryWriter.Next()
@@ -418,17 +431,7 @@ func (r *PebbleFileRegistry) writeToRegistryFile(batch *enginepb.RegistryUpdateB
418431
if err := r.mu.registryWriter.Flush(); err != nil {
419432
return err
420433
}
421-
if err := r.mu.registryFile.Sync(); err != nil {
422-
return err
423-
}
424-
// Create a new file registry file to hopefully shrink the size of the file
425-
// if we have exceeded the max registry size.
426-
if r.mu.registryWriter.Size() > maxRegistrySize {
427-
if err := r.createNewRegistryFile(); err != nil {
428-
return errors.Wrap(err, "rotating registry file")
429-
}
430-
}
431-
return nil
434+
return r.mu.registryFile.Sync()
432435
}
433436

434437
func makeRegistryFilename(iter uint64) string {

pkg/storage/pebble_file_registry_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"bytes"
1515
"fmt"
1616
"io"
17+
"math/rand"
1718
"os"
1819
"runtime/debug"
1920
"sort"
@@ -500,3 +501,78 @@ func (f loggingFile) Sync() error {
500501
fmt.Fprintf(f.w, "sync(%q)\n", f.name)
501502
return f.File.Sync()
502503
}
504+
505+
func TestFileRegistryRollover(t *testing.T) {
506+
defer leaktest.AfterTest(t)()
507+
defer log.Scope(t).Close(t)
508+
509+
const dir = "/mydb"
510+
mem := vfs.NewMem()
511+
require.NoError(t, mem.MkdirAll(dir, 0755))
512+
registry := &PebbleFileRegistry{FS: mem, DBDir: dir}
513+
require.NoError(t, registry.Load())
514+
515+
// All the registry files created so far. Some may have been subsequently
516+
// deleted.
517+
var registryFiles []string
518+
accumRegistryFiles := func() {
519+
registry.mu.Lock()
520+
defer registry.mu.Unlock()
521+
n := len(registryFiles)
522+
if registry.mu.registryFilename != "" &&
523+
(n == 0 || registry.mu.registryFilename != registryFiles[n-1]) {
524+
registryFiles = append(registryFiles, registry.mu.registryFilename)
525+
n++
526+
}
527+
}
528+
numAddedEntries := 0
529+
// Large settings slice, so that the test rolls over registry files quickly.
530+
encryptionSettings := make([]byte, 1<<20)
531+
rand.Read(encryptionSettings)
532+
// Check that the entries we have added are in the file registry and there
533+
// isn't an additional entry.
534+
checkAddedEntries := func() {
535+
for i := 0; i < numAddedEntries; i++ {
536+
filename := fmt.Sprintf("%04d", i)
537+
entry := registry.GetFileEntry(filename)
538+
require.NotNil(t, entry)
539+
require.Equal(t, filename, string(entry.EncryptionSettings[len(entry.EncryptionSettings)-4:]))
540+
}
541+
// Does not have an additional entry.
542+
filename := fmt.Sprintf("%04d", numAddedEntries)
543+
entry := registry.GetFileEntry(filename)
544+
require.Nil(t, entry)
545+
}
546+
addEntry := func() {
547+
filename := fmt.Sprintf("%04d", numAddedEntries)
548+
// Create a file for this added entry so that it doesn't get cleaned up
549+
// when we reopen the file registry.
550+
f, err := mem.Create(mem.PathJoin(dir, filename))
551+
require.NoError(t, err)
552+
require.NoError(t, f.Sync())
553+
require.NoError(t, f.Close())
554+
fileEntry := &enginepb.FileEntry{EnvType: enginepb.EnvType_Data}
555+
fileEntry.EncryptionSettings = append(encryptionSettings, []byte(filename)...)
556+
require.NoError(t, registry.SetFileEntry(filename, fileEntry))
557+
numAddedEntries++
558+
}
559+
for {
560+
created := len(registryFiles)
561+
accumRegistryFiles()
562+
if created != len(registryFiles) {
563+
// Rolled over.
564+
checkAddedEntries()
565+
}
566+
// Rollover a few times.
567+
if len(registryFiles) == 4 {
568+
break
569+
}
570+
addEntry()
571+
}
572+
require.NoError(t, registry.Close())
573+
registry = &PebbleFileRegistry{FS: mem, DBDir: dir}
574+
require.NoError(t, registry.Load())
575+
// Check added entries again.
576+
checkAddedEntries()
577+
require.NoError(t, registry.Close())
578+
}

0 commit comments

Comments
 (0)