Line data Source code
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 wal 6 : 7 : import ( 8 : "sync" 9 : 10 : "github.com/cockroachdb/errors" 11 : "github.com/cockroachdb/pebble/internal/base" 12 : ) 13 : 14 : // TODO(sumeer): hide LogRecycler once rest of Pebble is using wal.Manager. 15 : 16 : // LogRecycler recycles WAL log files. It holds a set of log file numbers that 17 : // are available for reuse. Writing to a recycled log file is faster than to a 18 : // new log file on some common filesystems (xfs, and ext3/4) due to avoiding 19 : // metadata updates. 20 : type LogRecycler struct { 21 : // The maximum number of log files to maintain for recycling. 22 : limit int 23 : 24 : // The minimum log number that is allowed to be recycled. Log numbers smaller 25 : // than this will be subject to immediate deletion. This is used to prevent 26 : // recycling a log written by a previous instance of the DB which may not 27 : // have had log recycling enabled. If that previous instance of the DB was 28 : // RocksDB, the old non-recyclable log record headers will be present. 29 : minRecycleLogNum base.DiskFileNum 30 : 31 : mu struct { 32 : sync.Mutex 33 : logs []base.FileInfo 34 : maxLogNum base.DiskFileNum 35 : } 36 : } 37 : 38 : // Init initialized the LogRecycler. 39 1 : func (r *LogRecycler) Init(maxNumLogFiles int) { 40 1 : r.limit = maxNumLogFiles 41 1 : } 42 : 43 : // MinRecycleLogNum returns the current minimum log number that is allowed to 44 : // be recycled. 45 1 : func (r *LogRecycler) MinRecycleLogNum() NumWAL { 46 1 : return NumWAL(r.minRecycleLogNum) 47 1 : } 48 : 49 : // SetMinRecycleLogNum sets the minimum log number that is allowed to be 50 : // recycled. 51 1 : func (r *LogRecycler) SetMinRecycleLogNum(n NumWAL) { 52 1 : r.minRecycleLogNum = base.DiskFileNum(n) 53 1 : } 54 : 55 : // Add attempts to recycle the log file specified by logInfo. Returns true if 56 : // the log file should not be deleted (i.e. the log is being recycled), and 57 : // false otherwise. 58 0 : func (r *LogRecycler) Add(logInfo base.FileInfo) bool { 59 0 : if logInfo.FileNum < r.minRecycleLogNum { 60 0 : return false 61 0 : } 62 : 63 0 : r.mu.Lock() 64 0 : defer r.mu.Unlock() 65 0 : 66 0 : if logInfo.FileNum <= r.mu.maxLogNum { 67 0 : // The log file number was already considered for recycling. Don't consider 68 0 : // it again. This avoids a race between adding the same log file for 69 0 : // recycling multiple times, and removing the log file for actual 70 0 : // reuse. Note that we return true because the log was already considered 71 0 : // for recycling and either it was deleted on the previous attempt (which 72 0 : // means we shouldn't get here) or it was recycled and thus the file 73 0 : // shouldn't be deleted. 74 0 : return true 75 0 : } 76 0 : r.mu.maxLogNum = logInfo.FileNum 77 0 : if len(r.mu.logs) >= r.limit { 78 0 : return false 79 0 : } 80 0 : r.mu.logs = append(r.mu.logs, logInfo) 81 0 : return true 82 : } 83 : 84 : // Peek returns the log at the head of the recycling queue, or the zero value 85 : // fileInfo and false if the queue is empty. 86 1 : func (r *LogRecycler) Peek() (base.FileInfo, bool) { 87 1 : r.mu.Lock() 88 1 : defer r.mu.Unlock() 89 1 : 90 1 : if len(r.mu.logs) == 0 { 91 1 : return base.FileInfo{}, false 92 1 : } 93 0 : return r.mu.logs[0], true 94 : } 95 : 96 : // Stats return current stats. 97 1 : func (r *LogRecycler) Stats() (count int, size uint64) { 98 1 : r.mu.Lock() 99 1 : defer r.mu.Unlock() 100 1 : count = len(r.mu.logs) 101 1 : for i := 0; i < count; i++ { 102 0 : size += r.mu.logs[i].FileSize 103 0 : } 104 1 : return count, size 105 : } 106 : 107 : // Pop removes the log number at the head of the recycling queue, enforcing 108 : // that it matches the specified logNum. An error is returned of the recycling 109 : // queue is empty or the head log number does not match the specified one. 110 0 : func (r *LogRecycler) Pop(logNum base.DiskFileNum) error { 111 0 : r.mu.Lock() 112 0 : defer r.mu.Unlock() 113 0 : 114 0 : if len(r.mu.logs) == 0 { 115 0 : return errors.New("pebble: log recycler empty") 116 0 : } 117 0 : if r.mu.logs[0].FileNum != logNum { 118 0 : return errors.Errorf("pebble: log recycler invalid %d vs %v", logNum, errors.Safe(fileInfoNums(r.mu.logs))) 119 0 : } 120 0 : r.mu.logs = r.mu.logs[1:] 121 0 : return nil 122 : } 123 : 124 : // LogNumsForTesting returns the current set of recyclable logs. 125 0 : func (r *LogRecycler) LogNumsForTesting() []base.DiskFileNum { 126 0 : r.mu.Lock() 127 0 : defer r.mu.Unlock() 128 0 : return fileInfoNums(r.mu.logs) 129 0 : } 130 : 131 0 : func (r *LogRecycler) maxLogNumForTesting() base.DiskFileNum { 132 0 : r.mu.Lock() 133 0 : defer r.mu.Unlock() 134 0 : return r.mu.maxLogNum 135 0 : } 136 : 137 0 : func fileInfoNums(finfos []base.FileInfo) []base.DiskFileNum { 138 0 : if len(finfos) == 0 { 139 0 : return nil 140 0 : } 141 0 : nums := make([]base.DiskFileNum, len(finfos)) 142 0 : for i := range finfos { 143 0 : nums[i] = finfos[i].FileNum 144 0 : } 145 0 : return nums 146 : }