Skip to content

Commit 619eb1c

Browse files
fix: restore in-memory Manifest on write error (#23552) (#23578)
Do not update the `FileSet` or `activeLogFile` field in the in-memory Partition structure if the Manifest file is not correctly saved to the disk. closes #23553 (cherry picked from commit a8732dc) closes #23554
1 parent 37562c7 commit 619eb1c

File tree

3 files changed

+165
-40
lines changed

3 files changed

+165
-40
lines changed

tsdb/index/tsi1/index.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,7 @@ func (i *Index) Open() (rErr error) {
309309

310310
func (i *Index) cleanUpFail(err *error) {
311311
if nil != *err {
312-
for _, p := range i.partitions {
313-
if (p != nil) && p.IsOpen() {
314-
if e := p.Close(); e != nil {
315-
i.logger.Warn("Failed to clean up partition")
316-
}
317-
}
318-
}
312+
i.close()
319313
}
320314
}
321315

@@ -352,16 +346,24 @@ func (i *Index) Close() error {
352346
// Lock index and close partitions.
353347
i.mu.Lock()
354348
defer i.mu.Unlock()
349+
return i.close()
350+
}
355351

352+
// close closes the index without locking
353+
func (i *Index) close() (rErr error) {
356354
for _, p := range i.partitions {
357-
if err := p.Close(); err != nil {
358-
return err
355+
if (p != nil) && p.IsOpen() {
356+
if pErr := p.Close(); pErr != nil {
357+
i.logger.Warn("Failed to clean up partition", zap.String("path", p.Path()))
358+
if rErr == nil {
359+
rErr = pErr
360+
}
361+
}
359362
}
360363
}
361-
362364
// Mark index as closed.
363365
i.opened = false
364-
return nil
366+
return rErr
365367
}
366368

367369
// Path returns the path the index was opened with.

tsdb/index/tsi1/partition.go

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,13 @@ type Partition struct {
8686

8787
// Index's version.
8888
version int
89+
90+
manifestPathFn func() string
8991
}
9092

9193
// NewPartition returns a new instance of Partition.
9294
func NewPartition(sfile *tsdb.SeriesFile, path string) *Partition {
93-
return &Partition{
95+
p := &Partition{
9496
closing: make(chan struct{}),
9597
path: path,
9698
sfile: sfile,
@@ -104,6 +106,8 @@ func NewPartition(sfile *tsdb.SeriesFile, path string) *Partition {
104106
logger: zap.NewNop(),
105107
version: Version,
106108
}
109+
p.manifestPathFn = p.manifestPath
110+
return p
107111
}
108112

109113
// bytes estimates the memory footprint of this Partition, in bytes.
@@ -408,27 +412,44 @@ func (p *Partition) nextSequence() int {
408412
return p.seq
409413
}
410414

411-
// ManifestPath returns the path to the index's manifest file.
412415
func (p *Partition) ManifestPath() string {
416+
return p.manifestPathFn()
417+
}
418+
419+
// ManifestPath returns the path to the index's manifest file.
420+
func (p *Partition) manifestPath() string {
413421
return filepath.Join(p.path, ManifestFileName)
414422
}
415423

416424
// Manifest returns a manifest for the index.
417425
func (p *Partition) Manifest() *Manifest {
426+
return p.manifest(p.fileSet)
427+
}
428+
429+
// manifest returns a manifest for the index, possibly using a
430+
// new FileSet to account for compaction or log prepending
431+
func (p *Partition) manifest(newFileSet *FileSet) *Manifest {
418432
m := &Manifest{
419433
Levels: p.levels,
420-
Files: make([]string, len(p.fileSet.files)),
434+
Files: make([]string, len(newFileSet.files)),
421435
Version: p.version,
422436
path: p.ManifestPath(),
423437
}
424438

425-
for j, f := range p.fileSet.files {
439+
for j, f := range newFileSet.files {
426440
m.Files[j] = filepath.Base(f.Path())
427441
}
428442

429443
return m
430444
}
431445

446+
// SetManifestPathForTest is only to force a bad path in testing
447+
func (p *Partition) SetManifestPathForTest(path string) {
448+
p.mu.Lock()
449+
defer p.mu.Unlock()
450+
p.manifestPathFn = func() string { return path }
451+
}
452+
432453
// WithLogger sets the logger for the index.
433454
func (p *Partition) WithLogger(logger *zap.Logger) {
434455
p.logger = logger.With(zap.String("index", "tsi"))
@@ -468,28 +489,42 @@ func (p *Partition) retainFileSet() *FileSet {
468489
}
469490

470491
// FileN returns the active files in the file set.
471-
func (p *Partition) FileN() int { return len(p.fileSet.files) }
492+
func (p *Partition) FileN() int {
493+
p.mu.RLock()
494+
defer p.mu.RUnlock()
495+
return len(p.fileSet.files)
496+
}
472497

473498
// prependActiveLogFile adds a new log file so that the current log file can be compacted.
474-
func (p *Partition) prependActiveLogFile() error {
499+
func (p *Partition) prependActiveLogFile() (rErr error) {
475500
// Open file and insert it into the first position.
476501
f, err := p.openLogFile(filepath.Join(p.path, FormatLogFileName(p.nextSequence())))
477502
if err != nil {
478503
return err
479504
}
480-
p.activeLogFile = f
505+
var oldActiveFile *LogFile
506+
p.activeLogFile, oldActiveFile = f, p.activeLogFile
481507

482-
// Prepend and generate new fileset.
483-
p.fileSet = p.fileSet.PrependLogFile(f)
508+
// Prepend and generate new fileset but do not yet update the partition
509+
newFileSet := p.fileSet.PrependLogFile(f)
510+
511+
errors2.Capture(&rErr, func() error {
512+
if rErr != nil {
513+
// close the new file.
514+
f.Close()
515+
p.activeLogFile = oldActiveFile
516+
}
517+
return rErr
518+
})()
484519

485520
// Write new manifest.
486-
manifestSize, err := p.Manifest().Write()
521+
manifestSize, err := p.manifest(newFileSet).Write()
487522
if err != nil {
488-
// TODO: Close index if write fails.
489-
p.logger.Error("manifest write failed, index is potentially damaged", zap.Error(err))
490-
return err
523+
return fmt.Errorf("manifest write failed for %q: %w", p.ManifestPath(), err)
491524
}
492525
p.manifestSize = manifestSize
526+
// Store the new FileSet in the partition now that the manifest has been written
527+
p.fileSet = newFileSet
493528
return nil
494529
}
495530

@@ -1113,20 +1148,28 @@ func (p *Partition) compactToLevel(files []*IndexFile, level int, interrupt <-ch
11131148
}
11141149

11151150
// Obtain lock to swap in index file and write manifest.
1116-
if err := func() error {
1151+
if err := func() (rErr error) {
11171152
p.mu.Lock()
11181153
defer p.mu.Unlock()
11191154

11201155
// Replace previous files with new index file.
1121-
p.fileSet = p.fileSet.MustReplace(IndexFiles(files).Files(), file)
1156+
newFileSet := p.fileSet.MustReplace(IndexFiles(files).Files(), file)
11221157

11231158
// Write new manifest.
1124-
manifestSize, err := p.Manifest().Write()
1159+
manifestSize, err := p.manifest(newFileSet).Write()
1160+
defer errors2.Capture(&rErr, func() error {
1161+
if rErr != nil {
1162+
// Close the new file to avoid leaks.
1163+
file.Close()
1164+
}
1165+
return rErr
1166+
})()
11251167
if err != nil {
1126-
// TODO: Close index if write fails.
1127-
return err
1168+
return fmt.Errorf("manifest file write failed compacting index %q: %w", p.ManifestPath(), err)
11281169
}
11291170
p.manifestSize = manifestSize
1171+
// Store the new FileSet in the partition now that the manifest has been written
1172+
p.fileSet = newFileSet
11301173
return nil
11311174
}(); err != nil {
11321175
log.Error("Cannot write manifest", zap.Error(err))
@@ -1262,20 +1305,29 @@ func (p *Partition) compactLogFile(logFile *LogFile) {
12621305
}
12631306

12641307
// Obtain lock to swap in index file and write manifest.
1265-
if err := func() error {
1308+
if err := func() (rErr error) {
12661309
p.mu.Lock()
12671310
defer p.mu.Unlock()
12681311

12691312
// Replace previous log file with index file.
1270-
p.fileSet = p.fileSet.MustReplace([]File{logFile}, file)
1313+
newFileSet := p.fileSet.MustReplace([]File{logFile}, file)
1314+
1315+
defer errors2.Capture(&rErr, func() error {
1316+
if rErr != nil {
1317+
// close new file
1318+
file.Close()
1319+
}
1320+
return rErr
1321+
})()
12711322

12721323
// Write new manifest.
1273-
manifestSize, err := p.Manifest().Write()
1324+
manifestSize, err := p.manifest(newFileSet).Write()
1325+
12741326
if err != nil {
1275-
// TODO: Close index if write fails.
1276-
return err
1327+
return fmt.Errorf("manifest file write failed compacting log file %q: %w", p.ManifestPath(), err)
12771328
}
1278-
1329+
// Store the new FileSet in the partition now that the manifest has been written
1330+
p.fileSet = newFileSet
12791331
p.manifestSize = manifestSize
12801332
return nil
12811333
}(); err != nil {
@@ -1389,6 +1441,7 @@ func (m *Manifest) Validate() error {
13891441
// Write writes the manifest file to the provided path, returning the number of
13901442
// bytes written and an error, if any.
13911443
func (m *Manifest) Write() (int64, error) {
1444+
var tmp string
13921445
buf, err := json.MarshalIndent(m, "", " ")
13931446
if err != nil {
13941447
return 0, fmt.Errorf("failed marshaling %q: %w", m.path, err)
@@ -1401,25 +1454,30 @@ func (m *Manifest) Write() (int64, error) {
14011454
return 0, err
14021455
}
14031456

1404-
tmp := f.Name()
14051457
// In correct operation, Remove() should fail because the file was renamed
14061458
defer os.Remove(tmp)
1459+
14071460
err = func() (rErr error) {
14081461
// Close() before rename for Windows
14091462
defer errors2.Capture(&rErr, f.Close)()
1463+
1464+
tmp = f.Name()
1465+
1466+
if err = f.Chmod(0666); err != nil {
1467+
return fmt.Errorf("failed setting permissions on manifest file %q: %w", tmp, err)
1468+
}
14101469
if _, err = f.Write(buf); err != nil {
14111470
return fmt.Errorf("failed writing temporary manifest file %q: %w", tmp, err)
14121471
}
1472+
if err = f.Sync(); err != nil {
1473+
return fmt.Errorf("failed syncing temporary manifest file to disk %q: %w", tmp, err)
1474+
}
14131475
return nil
14141476
}()
14151477
if err != nil {
14161478
return 0, err
14171479
}
14181480

1419-
if err = os.Chmod(tmp, 0666); err != nil {
1420-
return 0, err
1421-
}
1422-
14231481
if err = os.Rename(tmp, m.path); err != nil {
14241482
return 0, err
14251483
}

tsdb/index/tsi1/partition_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"syscall"
89
"testing"
910

1011
"github.com/influxdata/influxdb/v2/tsdb"
@@ -82,6 +83,70 @@ func TestPartition_Manifest(t *testing.T) {
8283
})
8384
}
8485

86+
var badManifestPath string = filepath.Join(os.DevNull, tsi1.ManifestFileName)
87+
88+
func TestPartition_Manifest_Write_Fail(t *testing.T) {
89+
t.Run("write MANIFEST", func(t *testing.T) {
90+
m := tsi1.NewManifest(badManifestPath)
91+
_, err := m.Write()
92+
if !errors.Is(err, syscall.ENOTDIR) {
93+
t.Fatalf("expected: syscall.ENOTDIR, got %T: %v", err, err)
94+
}
95+
})
96+
}
97+
98+
func TestPartition_PrependLogFile_Write_Fail(t *testing.T) {
99+
t.Run("write MANIFEST", func(t *testing.T) {
100+
sfile := MustOpenSeriesFile()
101+
defer sfile.Close()
102+
103+
p := MustOpenPartition(sfile.SeriesFile)
104+
defer func() {
105+
if err := p.Close(); err != nil {
106+
t.Fatalf("error closing partition: %v", err)
107+
}
108+
}()
109+
p.Partition.MaxLogFileSize = -1
110+
fileN := p.FileN()
111+
p.CheckLogFile()
112+
if fileN >= p.FileN() {
113+
t.Fatalf("manifest write prepending log file should have succeeded but number of files did not change correctly: expected more than %d files, got %d files", fileN, p.FileN())
114+
}
115+
p.SetManifestPathForTest(badManifestPath)
116+
fileN = p.FileN()
117+
p.CheckLogFile()
118+
if fileN != p.FileN() {
119+
t.Fatalf("manifest write prepending log file should have failed, but number of files changed: expected %d files, got %d files", fileN, p.FileN())
120+
}
121+
})
122+
}
123+
124+
func TestPartition_Compact_Write_Fail(t *testing.T) {
125+
t.Run("write MANIFEST", func(t *testing.T) {
126+
sfile := MustOpenSeriesFile()
127+
defer sfile.Close()
128+
129+
p := MustOpenPartition(sfile.SeriesFile)
130+
defer func() {
131+
if err := p.Close(); err != nil {
132+
t.Fatalf("error closing partition: %v", err)
133+
}
134+
}()
135+
p.Partition.MaxLogFileSize = -1
136+
fileN := p.FileN()
137+
p.Compact()
138+
if (1 + fileN) != p.FileN() {
139+
t.Fatalf("manifest write in compaction should have succeeded, but number of files did not change correctly: expected %d files, got %d files", fileN+1, p.FileN())
140+
}
141+
p.SetManifestPathForTest(badManifestPath)
142+
fileN = p.FileN()
143+
p.Compact()
144+
if fileN != p.FileN() {
145+
t.Fatalf("manifest write should have failed the compaction, but number of files changed: expected %d files, got %d files", fileN, p.FileN())
146+
}
147+
})
148+
}
149+
85150
// Partition is a test wrapper for tsi1.Partition.
86151
type Partition struct {
87152
*tsi1.Partition

0 commit comments

Comments
 (0)