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 0 : func (r *LogRecycler) Stats() (count int, size uint64) {
98 0 : r.mu.Lock()
99 0 : defer r.mu.Unlock()
100 0 : count = len(r.mu.logs)
101 0 : for i := 0; i < count; i++ {
102 0 : size += r.mu.logs[i].FileSize
103 0 : }
104 0 : 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 : }
|