func (n *Node) saveSnapshot(snapshot raftpb.Snapshot, keepOldSnapshots uint64) error { err := n.wal.SaveSnapshot(walpb.Snapshot{ Index: snapshot.Metadata.Index, Term: snapshot.Metadata.Term, }) if err != nil { return err } err = n.snapshotter.SaveSnap(snapshot) if err != nil { return err } err = n.wal.ReleaseLockTo(snapshot.Metadata.Index) if err != nil { return err } // Delete any older snapshots curSnapshot := fmt.Sprintf("%016x-%016x%s", snapshot.Metadata.Term, snapshot.Metadata.Index, ".snap") dirents, err := ioutil.ReadDir(n.snapDir()) if err != nil { return err } var snapshots []string for _, dirent := range dirents { if strings.HasSuffix(dirent.Name(), ".snap") { snapshots = append(snapshots, dirent.Name()) } } // Sort snapshot filenames in reverse lexical order sort.Sort(sort.Reverse(sort.StringSlice(snapshots))) // Ignore any snapshots that are older than the current snapshot. // Delete the others. Rather than doing lexical comparisons, we look // at what exists before/after the current snapshot in the slice. // This means that if the current snapshot doesn't appear in the // directory for some strange reason, we won't delete anything, which // is the safe behavior. curSnapshotIdx := -1 var ( removeErr error oldestSnapshot string ) for i, snapFile := range snapshots { if curSnapshotIdx >= 0 && i > curSnapshotIdx { if uint64(i-curSnapshotIdx) > keepOldSnapshots { err := os.Remove(filepath.Join(n.snapDir(), snapFile)) if err != nil && removeErr == nil { removeErr = err } continue } } else if snapFile == curSnapshot { curSnapshotIdx = i } oldestSnapshot = snapFile } if removeErr != nil { return removeErr } // Remove any WAL files that only contain data from before the oldest // remaining snapshot. if oldestSnapshot == "" { return nil } // Parse index out of oldest snapshot's filename var snapTerm, snapIndex uint64 _, err = fmt.Sscanf(oldestSnapshot, "%016x-%016x.snap", &snapTerm, &snapIndex) if err != nil { return errors.Wrapf(err, "malformed snapshot filename %s", oldestSnapshot) } // List the WALs dirents, err = ioutil.ReadDir(n.walDir()) if err != nil { return err } var wals []string for _, dirent := range dirents { if strings.HasSuffix(dirent.Name(), ".wal") { wals = append(wals, dirent.Name()) } } // Sort WAL filenames in lexical order sort.Sort(sort.StringSlice(wals)) found := false deleteUntil := -1 for i, walName := range wals { var walSeq, walIndex uint64 _, err = fmt.Sscanf(walName, "%016x-%016x.wal", &walSeq, &walIndex) if err != nil { return errors.Wrapf(err, "could not parse WAL name %s", walName) } if walIndex >= snapIndex { deleteUntil = i - 1 found = true break } } // If all WAL files started with indices below the oldest snapshot's // index, we can delete all but the newest WAL file. if !found && len(wals) != 0 { deleteUntil = len(wals) - 1 } for i := 0; i < deleteUntil; i++ { walPath := filepath.Join(n.walDir(), wals[i]) l, err := fileutil.TryLockFile(walPath, os.O_WRONLY, fileutil.PrivateFileMode) if err != nil { return errors.Wrapf(err, "could not lock old WAL file %s for removal", wals[i]) } err = os.Remove(walPath) l.Close() if err != nil { return errors.Wrapf(err, "error removing old WAL file %s", wals[i]) } } return nil }
func openAtIndex(dirpath string, snap walpb.Snapshot, write bool) (*WAL, error) { names, err := readWalNames(dirpath) if err != nil { return nil, err } nameIndex, ok := searchIndex(names, snap.Index) if !ok || !isValidSeq(names[nameIndex:]) { return nil, ErrFileNotFound } // open the wal files rcs := make([]io.ReadCloser, 0) rs := make([]io.Reader, 0) ls := make([]*fileutil.LockedFile, 0) for _, name := range names[nameIndex:] { p := path.Join(dirpath, name) if write { l, err := fileutil.TryLockFile(p, os.O_RDWR, fileutil.PrivateFileMode) if err != nil { closeAll(rcs...) return nil, err } ls = append(ls, l) rcs = append(rcs, l) } else { rf, err := os.OpenFile(p, os.O_RDONLY, fileutil.PrivateFileMode) if err != nil { closeAll(rcs...) return nil, err } ls = append(ls, nil) rcs = append(rcs, rf) } rs = append(rs, rcs[len(rcs)-1]) } closer := func() error { return closeAll(rcs...) } // create a WAL ready for reading w := &WAL{ dir: dirpath, start: snap, decoder: newDecoder(rs...), readClose: closer, locks: ls, } if write { // write reuses the file descriptors from read; don't close so // WAL can append without dropping the file lock w.readClose = nil if _, _, err := parseWalName(path.Base(w.tail().Name())); err != nil { closer() return nil, err } w.fp = newFilePipeline(w.dir, SegmentSizeBytes) } return w, nil }
// GC garbage collects snapshots and wals older than the provided index and term func (e *EncryptedRaftLogger) GC(index uint64, term uint64, keepOldSnapshots uint64) error { // Delete any older snapshots curSnapshot := fmt.Sprintf("%016x-%016x%s", term, index, ".snap") snapshots, err := ListSnapshots(e.snapDir()) if err != nil { return err } // Ignore any snapshots that are older than the current snapshot. // Delete the others. Rather than doing lexical comparisons, we look // at what exists before/after the current snapshot in the slice. // This means that if the current snapshot doesn't appear in the // directory for some strange reason, we won't delete anything, which // is the safe behavior. curSnapshotIdx := -1 var ( removeErr error oldestSnapshot string ) for i, snapFile := range snapshots { if curSnapshotIdx >= 0 && i > curSnapshotIdx { if uint64(i-curSnapshotIdx) > keepOldSnapshots { err := os.Remove(filepath.Join(e.snapDir(), snapFile)) if err != nil && removeErr == nil { removeErr = err } continue } } else if snapFile == curSnapshot { curSnapshotIdx = i } oldestSnapshot = snapFile } if removeErr != nil { return removeErr } // Remove any WAL files that only contain data from before the oldest // remaining snapshot. if oldestSnapshot == "" { return nil } // Parse index out of oldest snapshot's filename var snapTerm, snapIndex uint64 _, err = fmt.Sscanf(oldestSnapshot, "%016x-%016x.snap", &snapTerm, &snapIndex) if err != nil { return errors.Wrapf(err, "malformed snapshot filename %s", oldestSnapshot) } wals, err := ListWALs(e.walDir()) if err != nil { return err } found := false deleteUntil := -1 for i, walName := range wals { var walSeq, walIndex uint64 _, err = fmt.Sscanf(walName, "%016x-%016x.wal", &walSeq, &walIndex) if err != nil { return errors.Wrapf(err, "could not parse WAL name %s", walName) } if walIndex >= snapIndex { deleteUntil = i - 1 found = true break } } // If all WAL files started with indices below the oldest snapshot's // index, we can delete all but the newest WAL file. if !found && len(wals) != 0 { deleteUntil = len(wals) - 1 } for i := 0; i < deleteUntil; i++ { walPath := filepath.Join(e.walDir(), wals[i]) l, err := fileutil.TryLockFile(walPath, os.O_WRONLY, fileutil.PrivateFileMode) if err != nil { return errors.Wrapf(err, "could not lock old WAL file %s for removal", wals[i]) } err = os.Remove(walPath) l.Close() if err != nil { return errors.Wrapf(err, "error removing old WAL file %s", wals[i]) } } return nil }