github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/storage/storage_mem.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package storage 12 13 import ( 14 "bytes" 15 "fmt" 16 "sort" 17 "strconv" 18 "time" 19 20 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 23 "github.com/olekukonko/tablewriter" 24 ) 25 26 type memStorage struct { 27 mu struct { 28 syncutil.RWMutex 29 buckets []ctpb.Entry 30 scale time.Duration 31 } 32 } 33 34 var _ SingleStorage = (*memStorage)(nil) 35 36 // NewMemStorage initializes a SingleStorage backed by an in-memory slice that 37 // represents the given number of buckets, where the i-th bucket holds a closed 38 // timestamp approximately 2^i*scale in the past. 39 func NewMemStorage(scale time.Duration, buckets int) SingleStorage { 40 m := &memStorage{} 41 m.mu.buckets = make([]ctpb.Entry, buckets) 42 m.mu.scale = scale 43 return m 44 } 45 46 func (m *memStorage) String() string { 47 m.mu.RLock() 48 defer m.mu.RUnlock() 49 50 var buf bytes.Buffer 51 tw := tablewriter.NewWriter(&buf) 52 53 header := make([]string, 1+len(m.mu.buckets)) 54 header[0] = "" 55 align := make([]int, 1+len(m.mu.buckets)) 56 align[0] = tablewriter.ALIGN_LEFT 57 58 for i := range m.mu.buckets { 59 header[1+i] = m.mu.buckets[i].ClosedTimestamp.String() + "\nage=" + time.Duration( 60 m.mu.buckets[0].ClosedTimestamp.WallTime-m.mu.buckets[i].ClosedTimestamp.WallTime, 61 ).String() + " (target ≤" + m.bucketMaxAge(i).String() + ")\nepoch=" + fmt.Sprintf("%d", m.mu.buckets[i].Epoch) 62 align[1+i] = tablewriter.ALIGN_RIGHT 63 } 64 tw.SetAutoFormatHeaders(false) 65 tw.SetColumnAlignment(align) 66 tw.SetHeader(header) 67 tw.SetHeaderLine(true) 68 tw.SetRowLine(false) 69 tw.SetColumnSeparator(" ") 70 tw.SetBorder(true) 71 tw.SetTrimWhiteSpaceAtEOL(true) 72 rangeIDs := make([]roachpb.RangeID, 0, len(m.mu.buckets[0].MLAI)) 73 for rangeID := range m.mu.buckets[0].MLAI { 74 rangeIDs = append(rangeIDs, rangeID) 75 } 76 sort.Slice(rangeIDs, func(i, j int) bool { 77 return rangeIDs[i] < rangeIDs[j] 78 }) 79 80 row := make([]string, 1+len(m.mu.buckets)) 81 for _, rangeID := range rangeIDs { 82 row[0] = "r" + strconv.FormatInt(int64(rangeID), 10) 83 for i, entry := range m.mu.buckets { 84 lai, ok := entry.MLAI[rangeID] 85 if ok { 86 row[1+i] = strconv.FormatInt(int64(lai), 10) 87 } else { 88 row[1+i] = "" 89 } 90 } 91 tw.Append(row) 92 } 93 94 tw.Render() 95 return buf.String() 96 } 97 98 func (m *memStorage) bucketMaxAge(index int) time.Duration { 99 if index == 0 { 100 return 0 101 } 102 return (1 << uint(index-1)) * m.mu.scale 103 } 104 105 func (m *memStorage) Add(e ctpb.Entry) { 106 m.mu.Lock() 107 defer m.mu.Unlock() 108 109 now := e.ClosedTimestamp.WallTime 110 111 for i := 0; i < len(m.mu.buckets); i++ { 112 if time.Duration(now-m.mu.buckets[i].ClosedTimestamp.WallTime) <= m.bucketMaxAge(i) { 113 break 114 } 115 mergedEntry := merge(m.mu.buckets[i], e) 116 e = m.mu.buckets[i] 117 m.mu.buckets[i] = mergedEntry 118 } 119 } 120 121 func (m *memStorage) VisitAscending(f func(ctpb.Entry) (done bool)) { 122 m.mu.RLock() 123 defer m.mu.RUnlock() 124 125 for i := len(m.mu.buckets) - 1; i >= 0; i-- { 126 entry := m.mu.buckets[i] 127 if entry.Epoch == 0 { 128 // Skip empty buckets. 129 continue 130 } 131 if f(entry) { 132 return 133 } 134 } 135 } 136 137 func (m *memStorage) VisitDescending(f func(ctpb.Entry) (done bool)) { 138 m.mu.RLock() 139 defer m.mu.RUnlock() 140 141 for l, i := len(m.mu.buckets), 0; i < l; i++ { 142 entry := m.mu.buckets[i] 143 // Stop once we hit an empty bucket (which implies that all further buckets 144 // are also empty), or once the visitor is satisfied. 145 if entry.Epoch == 0 || f(entry) { 146 return 147 } 148 } 149 } 150 151 func (m *memStorage) Clear() { 152 m.mu.Lock() 153 defer m.mu.Unlock() 154 for i := 0; i < len(m.mu.buckets); i++ { 155 m.mu.buckets[i] = ctpb.Entry{} 156 } 157 } 158 159 func merge(e, ee ctpb.Entry) ctpb.Entry { 160 // TODO(tschottdorf): if either of these hit, check that what we're 161 // returning has Full set. If we make it past, check that either of 162 // them has it set. The first Entry the Storage sees for an epoch must have it 163 // set, so the assertions should never fire. 164 if e.Epoch < ee.Epoch { 165 return ee 166 } else if e.Epoch > ee.Epoch { 167 return e 168 } 169 170 // Epochs match, so we can actually update. 171 172 // Initialize re as a deep copy of e. 173 re := e 174 re.MLAI = map[roachpb.RangeID]ctpb.LAI{} 175 for rangeID, mlai := range e.MLAI { 176 re.MLAI[rangeID] = mlai 177 } 178 // The result is full if either operand is. 179 re.Full = e.Full || ee.Full 180 // Use the larger of both timestamps with the union of the MLAIs, preferring larger 181 // ones on conflict. 182 re.ClosedTimestamp.Forward(ee.ClosedTimestamp) 183 for rangeID, mlai := range ee.MLAI { 184 if cur, found := re.MLAI[rangeID]; !found || cur < mlai { 185 re.MLAI[rangeID] = mlai 186 } 187 } 188 return re 189 }