github.com/matrixorigin/matrixone@v1.2.0/pkg/logservice/snapshot.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package logservice 16 17 import ( 18 "fmt" 19 "path/filepath" 20 "sort" 21 "sync" 22 23 "github.com/lni/vfs" 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 ) 26 27 const ( 28 defaultExportedDirMode = 0755 29 snapshotPathPattern = "snapshot-%016X" 30 ) 31 32 // ISnapshotManager is an interface that managers snapshots. 33 type ISnapshotManager interface { 34 // Init initialize snapshots by loading exported snapshots. 35 Init(shardID uint64, replicaID uint64) error 36 // Count returns the number of snapshots in the manager. 37 Count(shardID uint64, replicaID uint64) int 38 // Add adds a new snapshot for specified shard. 39 Add(shardID uint64, replicaID uint64, index uint64) error 40 // Remove removes snapshots whose index is LE than index. 41 Remove(shardID uint64, replicaID uint64, index uint64) error 42 // EvalImportSnapshot returns the source directory and index of 43 // the biggest snapshot of the shard. 44 EvalImportSnapshot(shardID uint64, replicaID uint64, index uint64) (string, uint64) 45 } 46 47 // ISnapshotItem is an interface that represents a snapshot item. 48 type ISnapshotItem interface { 49 // Exists returns if the snapshot item exists. 50 Exists() bool 51 // Remove removes the snapshot item. 52 Remove() error 53 // Valid check the legality of the snapshot item. Only the names of 54 // the files in it are checked. 55 Valid() (bool, error) 56 } 57 58 // snapshotIndex is type indicates the index of snapshot. 59 type snapshotIndex uint64 60 61 // nodeID contains shardID and replicaID. 62 type nodeID struct { 63 shardID uint64 64 replicaID uint64 65 } 66 67 // snapshotItem represents a snapshot item, with index and directory in it. 68 type snapshotItem struct { 69 fs vfs.FS 70 index snapshotIndex 71 dir string 72 } 73 74 var snapshotItemPool = sync.Pool{ 75 New: func() interface{} { 76 return new(snapshotItem) 77 }, 78 } 79 80 func getSnapshotItem(si snapshotItem) *snapshotItem { 81 item := snapshotItemPool.Get().(*snapshotItem) 82 *item = si 83 return item 84 } 85 86 func putSnapshotItem(item *snapshotItem) { 87 *item = snapshotItem{} 88 snapshotItemPool.Put(item) 89 } 90 91 // Exists implements the ISnapshotItem interface. 92 func (si *snapshotItem) Exists() bool { 93 _, err := si.fs.Stat(si.dir) 94 return err == nil 95 } 96 97 // Remove implements the ISnapshotItem interface. 98 func (si *snapshotItem) Remove() error { 99 return si.fs.RemoveAll(si.dir) 100 } 101 102 // Valid implements the ISnapshotItem interface. 103 func (si *snapshotItem) Valid() (bool, error) { 104 names, err := si.fs.List(si.dir) 105 if err != nil { 106 return false, err 107 } 108 if len(names) != 2 { 109 return false, moerr.NewInternalErrorNoCtx("file number is not correct: %d", len(names)) 110 } 111 sort.Strings(names) 112 var index uint64 113 _, err = fmt.Sscanf(names[0], snapshotPathPattern+".gbsnap", &index) 114 if err != nil { 115 return false, err 116 } 117 if snapshotIndex(index) != si.index { 118 return false, moerr.NewInternalErrorNoCtx("index of dir %d and file %d are different", 119 si.index, index) 120 } 121 if names[1] != "snapshot.metadata" { 122 return false, moerr.NewInternalErrorNoCtx("no snapshot.metadata file") 123 } 124 return true, nil 125 } 126 127 // snapshotRecord contains snapshot items for the specified shard. 128 type snapshotRecord struct { 129 fs vfs.FS 130 nodeID nodeID 131 // items are sorted by .index 132 items []*snapshotItem 133 } 134 135 func newNodeSnapshot(fs vfs.FS, nodeID nodeID) *snapshotRecord { 136 return &snapshotRecord{ 137 fs: fs, 138 nodeID: nodeID, 139 items: make([]*snapshotItem, 0), 140 } 141 } 142 143 func (ss *snapshotRecord) first() *snapshotItem { 144 if len(ss.items) == 0 { 145 return nil 146 } 147 return ss.items[0] 148 } 149 150 func (ss *snapshotRecord) last() *snapshotItem { 151 l := len(ss.items) 152 if l == 0 { 153 return nil 154 } 155 return ss.items[l-1] 156 } 157 158 func (ss *snapshotRecord) add(index snapshotIndex, dir string) error { 159 last := ss.last() 160 if last != nil && index < last.index { 161 return moerr.NewInternalErrorNoCtx("snapshot with smaller index %d than current biggest one %d", 162 index, last.index) 163 } 164 si := getSnapshotItem(snapshotItem{fs: ss.fs, index: index, dir: dir}) 165 if !si.Exists() { 166 return moerr.NewInternalErrorNoCtx("snapshot file does not exist for shard-replica %d-%d, index %d, dir %s", 167 ss.nodeID.shardID, ss.nodeID.replicaID, index, dir) 168 } 169 v, err := si.Valid() 170 if err != nil { 171 return err 172 } 173 if v { 174 ss.items = append(ss.items, si) 175 } 176 return nil 177 } 178 179 // removeFirst removes the first snapshot from snapshot item list. 180 func (ss *snapshotRecord) removeFirst() error { 181 if first := ss.first(); first != nil { 182 if err := first.Remove(); err != nil { 183 return err 184 } 185 } 186 putSnapshotItem(ss.items[0]) 187 ss.items = ss.items[1:] 188 return nil 189 } 190 191 // remove the snapshots whose index is LE than the index. 192 func (ss *snapshotRecord) remove(index snapshotIndex) error { 193 items := make([]*snapshotItem, len(ss.items)) 194 copy(items, ss.items) 195 for _, si := range items { 196 if si.index <= index { 197 if err := ss.removeFirst(); err != nil { 198 return err 199 } 200 } else { 201 break 202 } 203 } 204 return nil 205 } 206 207 // snapshotManager manages the exported snapshots created by dragonboat. 208 // In dragonboat, snapshot are taken with method SyncRequestSnapshot, 209 // which accepts a snapshot option. By default, a new snapshot will be 210 // taken at the applied index and log entries are removed at the LSN 211 // parameter which is in snapshot option. LSN must be less than the applied 212 // index. This works in normal state machine. 213 // Unfortunately, there are two separate state machines in MO. One is 214 // in logservice and the other is in DN. The snapshot cannot be taken 215 // like that in this case, because when a replica starts, it applies the 216 // latest snapshot, whose index is greater than truncate LSN. As a result, 217 // the TN cannot read the log entries between truncate LSN and snapshot 218 // index to replay. 219 // The solution is, set Exported to true in snapshot option. This prevents 220 // taking an active snapshot, and just export the snapshot files to an external 221 // directory. When truncate LSN is greater than any snapshot index in the 222 // external directory, the snapshot would be imported to system. And then, 223 // the log entries can be removed safely. 224 type snapshotManager struct { 225 cfg *Config 226 snapshots map[nodeID]*snapshotRecord // shardID => *snapshotRecord 227 } 228 229 // newSnapshotManager makes a new snapshot. 230 func newSnapshotManager(cfg *Config) *snapshotManager { 231 return &snapshotManager{ 232 cfg: cfg, 233 snapshots: make(map[nodeID]*snapshotRecord), 234 } 235 } 236 237 func (sm *snapshotManager) exportPath(shardID uint64, replicaID uint64) string { 238 parts := make([]string, 3) 239 dir := sm.cfg.SnapshotExportDir 240 shardPart := fmt.Sprintf("shard-%d", shardID) 241 replicaPart := fmt.Sprintf("replica-%d", replicaID) 242 parts = append(parts, dir, shardPart, replicaPart) 243 return filepath.Join(parts...) 244 } 245 246 func (sm *snapshotManager) snapshotPath(nodeID nodeID, index snapshotIndex) string { 247 parts := make([]string, 2) 248 snapshotPart := fmt.Sprintf(snapshotPathPattern, index) 249 parts = append(parts, sm.exportPath(nodeID.shardID, nodeID.replicaID), snapshotPart) 250 return filepath.Join(parts...) 251 } 252 253 func (sm *snapshotManager) prepareDir(path string) error { 254 s, err := sm.cfg.FS.Stat(path) 255 if err != nil { 256 if e := sm.cfg.FS.MkdirAll(path, defaultExportedDirMode); e != nil { 257 return e 258 } 259 return nil 260 } 261 if !s.IsDir() { 262 return moerr.NewInternalErrorNoCtx("%s is not a dir", path) 263 } 264 return nil 265 } 266 267 func (sm *snapshotManager) parse(dir string) (int, error) { 268 var index int 269 _, err := fmt.Sscanf(dir, snapshotPathPattern, &index) 270 if err != nil { 271 return 0, err 272 } 273 return index, nil 274 } 275 276 // Init implements the ISnapshotManager interface. 277 func (sm *snapshotManager) Init(shardID uint64, replicaID uint64) error { 278 path := sm.exportPath(shardID, replicaID) 279 if err := sm.prepareDir(path); err != nil { 280 return err 281 } 282 names, err := sm.cfg.FS.List(path) 283 if err != nil { 284 return err 285 } 286 indexes := make([]int, len(names)) 287 for _, name := range names { 288 index, err := sm.parse(name) 289 if err != nil { 290 continue 291 } 292 indexes = append(indexes, index) 293 } 294 // The snapshots in the manager must be sorted. 295 sort.Ints(indexes) 296 for _, idx := range indexes { 297 if idx > 0 { 298 _ = sm.Add(shardID, replicaID, uint64(idx)) 299 } 300 } 301 return nil 302 } 303 304 // Count implements the ISnapshotManager interface. 305 func (sm *snapshotManager) Count(shardID uint64, replicaID uint64) int { 306 nid := nodeID{shardID: shardID, replicaID: replicaID} 307 if s, ok := sm.snapshots[nid]; ok { 308 return len(s.items) 309 } 310 return 0 311 } 312 313 // Add implements the ISnapshotManager interface. 314 func (sm *snapshotManager) Add(shardID uint64, replicaID uint64, index uint64) error { 315 si := snapshotIndex(index) 316 nid := nodeID{shardID: shardID, replicaID: replicaID} 317 dir := sm.snapshotPath(nid, si) 318 _, ok := sm.snapshots[nid] 319 if !ok { 320 sm.snapshots[nid] = newNodeSnapshot(sm.cfg.FS, nid) 321 } 322 return sm.snapshots[nid].add(si, dir) 323 } 324 325 // Remove implements the ISnapshotManager interface. 326 func (sm *snapshotManager) Remove(shardID uint64, replicaID uint64, index uint64) error { 327 si := snapshotIndex(index) 328 nid := nodeID{shardID: shardID, replicaID: replicaID} 329 if ss, ok := sm.snapshots[nid]; ok { 330 return ss.remove(si) 331 } 332 return nil 333 } 334 335 // EvalImportSnapshot implements the ISnapshotManager interface. 336 func (sm *snapshotManager) EvalImportSnapshot(shardID uint64, replicaID uint64, index uint64) (string, uint64) { 337 nid := nodeID{shardID: shardID, replicaID: replicaID} 338 ss, ok := sm.snapshots[nid] 339 if !ok { 340 return "", 0 341 } 342 343 var dir string 344 var si snapshotIndex 345 for _, item := range ss.items { 346 if item == nil { 347 return "", 0 348 } 349 // Find the bigger one, break and return the smaller one. 350 if uint64(item.index) > index { 351 break 352 } 353 dir = item.dir 354 si = item.index 355 } 356 return dir, uint64(si) 357 }