github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/log_recycler_test.go (about)

     1  // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package pebble
     6  
     7  import (
     8  	"testing"
     9  
    10  	"github.com/cockroachdb/pebble/internal/base"
    11  	"github.com/cockroachdb/pebble/vfs"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func (r *logRecycler) logNums() []FileNum {
    16  	r.mu.Lock()
    17  	defer r.mu.Unlock()
    18  	return fileInfoNums(r.mu.logs)
    19  }
    20  
    21  func (r *logRecycler) maxLogNum() FileNum {
    22  	r.mu.Lock()
    23  	defer r.mu.Unlock()
    24  	return r.mu.maxLogNum
    25  }
    26  
    27  func TestLogRecycler(t *testing.T) {
    28  	r := logRecycler{limit: 3, minRecycleLogNum: 4}
    29  
    30  	// Logs below the min-recycle number are not recycled.
    31  	require.False(t, r.add(fileInfo{base.FileNum(1).DiskFileNum(), 0}))
    32  	require.False(t, r.add(fileInfo{base.FileNum(2).DiskFileNum(), 0}))
    33  	require.False(t, r.add(fileInfo{base.FileNum(3).DiskFileNum(), 0}))
    34  
    35  	// Logs are recycled up to the limit.
    36  	require.True(t, r.add(fileInfo{base.FileNum(4).DiskFileNum(), 0}))
    37  	require.EqualValues(t, []FileNum{4}, r.logNums())
    38  	require.EqualValues(t, 4, r.maxLogNum())
    39  	fi, ok := r.peek()
    40  	require.True(t, ok)
    41  	require.EqualValues(t, uint64(4), uint64(fi.fileNum.FileNum()))
    42  	require.True(t, r.add(fileInfo{base.FileNum(5).DiskFileNum(), 0}))
    43  	require.EqualValues(t, []FileNum{4, 5}, r.logNums())
    44  	require.EqualValues(t, 5, r.maxLogNum())
    45  	require.True(t, r.add(fileInfo{base.FileNum(6).DiskFileNum(), 0}))
    46  	require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums())
    47  	require.EqualValues(t, 6, r.maxLogNum())
    48  
    49  	// Trying to add a file past the limit fails.
    50  	require.False(t, r.add(fileInfo{base.FileNum(7).DiskFileNum(), 0}))
    51  	require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums())
    52  	require.EqualValues(t, 7, r.maxLogNum())
    53  
    54  	// Trying to add a previously recycled file returns success, but the internal
    55  	// state is unchanged.
    56  	require.True(t, r.add(fileInfo{base.FileNum(4).DiskFileNum(), 0}))
    57  	require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums())
    58  	require.EqualValues(t, 7, r.maxLogNum())
    59  
    60  	// An error is returned if we try to pop an element other than the first.
    61  	require.Regexp(t, `invalid 000005 vs \[000004 000005 000006\]`, r.pop(5))
    62  
    63  	require.NoError(t, r.pop(4))
    64  	require.EqualValues(t, []FileNum{5, 6}, r.logNums())
    65  
    66  	// Log number 7 was already considered, so it won't be recycled.
    67  	require.True(t, r.add(fileInfo{base.FileNum(7).DiskFileNum(), 0}))
    68  	require.EqualValues(t, []FileNum{5, 6}, r.logNums())
    69  
    70  	require.True(t, r.add(fileInfo{base.FileNum(8).DiskFileNum(), 0}))
    71  	require.EqualValues(t, []FileNum{5, 6, 8}, r.logNums())
    72  	require.EqualValues(t, 8, r.maxLogNum())
    73  
    74  	require.NoError(t, r.pop(5))
    75  	require.EqualValues(t, []FileNum{6, 8}, r.logNums())
    76  	require.NoError(t, r.pop(6))
    77  	require.EqualValues(t, []FileNum{8}, r.logNums())
    78  	require.NoError(t, r.pop(8))
    79  	require.EqualValues(t, []FileNum(nil), r.logNums())
    80  
    81  	require.Regexp(t, `empty`, r.pop(9))
    82  }
    83  
    84  func TestRecycleLogs(t *testing.T) {
    85  	mem := vfs.NewMem()
    86  	d, err := Open("", &Options{
    87  		FS: mem,
    88  	})
    89  	require.NoError(t, err)
    90  
    91  	logNum := func() FileNum {
    92  		d.mu.Lock()
    93  		defer d.mu.Unlock()
    94  		return d.mu.log.queue[len(d.mu.log.queue)-1].fileNum.FileNum()
    95  	}
    96  	logCount := func() int {
    97  		d.mu.Lock()
    98  		defer d.mu.Unlock()
    99  		return len(d.mu.log.queue)
   100  	}
   101  
   102  	// Flush the memtable a few times, forcing rotation of the WAL. We should see
   103  	// the recycled logs change as expected.
   104  	require.EqualValues(t, []FileNum(nil), d.logRecycler.logNums())
   105  	curLog := logNum()
   106  
   107  	require.NoError(t, d.Flush())
   108  
   109  	require.EqualValues(t, []FileNum{curLog}, d.logRecycler.logNums())
   110  	curLog = logNum()
   111  
   112  	require.NoError(t, d.Flush())
   113  
   114  	require.EqualValues(t, []FileNum{curLog}, d.logRecycler.logNums())
   115  
   116  	require.NoError(t, d.Close())
   117  
   118  	d, err = Open("", &Options{
   119  		FS: mem,
   120  	})
   121  	require.NoError(t, err)
   122  	metrics := d.Metrics()
   123  	if n := logCount(); n != int(metrics.WAL.Files) {
   124  		t.Fatalf("expected %d WAL files, but found %d", n, metrics.WAL.Files)
   125  	}
   126  	if n, sz := d.logRecycler.stats(); n != int(metrics.WAL.ObsoleteFiles) {
   127  		t.Fatalf("expected %d obsolete WAL files, but found %d", n, metrics.WAL.ObsoleteFiles)
   128  	} else if sz != metrics.WAL.ObsoletePhysicalSize {
   129  		t.Fatalf("expected %d obsolete physical WAL size, but found %d", sz, metrics.WAL.ObsoletePhysicalSize)
   130  	}
   131  	if recycled := d.logRecycler.logNums(); len(recycled) != 0 {
   132  		t.Fatalf("expected no recycled WAL files after recovery, but found %d", recycled)
   133  	}
   134  	require.NoError(t, d.Close())
   135  }