// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } if err := os.MkdirAll(dirpath, privateDirMode); err != nil { return nil, err } p := path.Join(dirpath, walName(0, 0)) f, err := os.OpenFile(p, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) if err != nil { return nil, err } l, err := fileutil.NewLock(f.Name()) if err != nil { return nil, err } err = l.Lock() if err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, seq: 0, f: f, encoder: newEncoder(f, 0), } w.locks = append(w.locks, l) if err := w.saveCrc(0); err != nil { return nil, err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } if err = w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } return w, nil }
// Cut closes current file written and creates a new one ready to append. func (w *WAL) Cut() error { // create a new wal file with name sequence + 1 fpath := path.Join(w.dir, walName(w.seq+1, w.enti+1)) f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) if err != nil { return err } l, err := fileutil.NewLock(f.Name()) if err != nil { return err } err = l.Lock() if err != nil { return err } w.locks = append(w.locks, l) if err = w.sync(); err != nil { return err } w.f.Close() // update writer and save the previous crc w.f = f w.seq++ prevCrc := w.encoder.crc.Sum32() w.encoder = newEncoder(w.f, prevCrc) if err := w.saveCrc(prevCrc); err != nil { return err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: w.metadata}); err != nil { return err } if err := w.SaveState(&w.state); err != nil { return err } return w.sync() }
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 fmt.Errorf("malformed snapshot filename %s: %v", oldestSnapshot, err) } // 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 fmt.Errorf("could not parse WAL name %s: %v", walName, err) } 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.NewLock(walPath) if err != nil { continue } err = l.TryLock() if err != nil { return fmt.Errorf("could not lock old WAL file %s for removal: %v", wals[i], err) } err = os.Remove(walPath) l.Unlock() l.Destroy() if err != nil { return fmt.Errorf("error removing old WAL file %s: %v", wals[i], err) } } return nil }
// cut closes current file written and creates a new one ready to append. // cut first creates a temp wal file and writes necessary headers into it. // Then cut atomtically rename temp wal file to a wal file. func (w *WAL) cut() error { // close old wal file if err := w.sync(); err != nil { return err } if err := w.f.Close(); err != nil { return err } fpath := path.Join(w.dir, walName(w.seq+1, w.enti+1)) ftpath := fpath + ".tmp" // create a temp wal file with name sequence + 1, or tuncate the existing one ft, err := os.OpenFile(ftpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } // update writer and save the previous crc w.f = ft prevCrc := w.encoder.crc.Sum32() w.encoder = newEncoder(w.f, prevCrc) if err := w.saveCrc(prevCrc); err != nil { return err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: w.metadata}); err != nil { return err } if err := w.saveState(&w.state); err != nil { return err } // close temp wal file if err := w.sync(); err != nil { return err } if err := w.f.Close(); err != nil { return err } // atomically move temp wal file to wal file if err := os.Rename(ftpath, fpath); err != nil { return err } // open the wal file and update writer again f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND, 0600) if err != nil { return err } err = fileutil.Preallocate(f, segmentSizeBytes) if err != nil { plog.Errorf("failed to allocate space when creating new wal file (%v)", err) return err } w.f = f prevCrc = w.encoder.crc.Sum32() w.encoder = newEncoder(w.f, prevCrc) // lock the new wal file l, err := fileutil.NewLock(f.Name()) if err != nil { return err } err = l.Lock() if err != nil { return err } w.locks = append(w.locks, l) // increase the wal seq w.seq++ plog.Infof("segmented wal file %v is created", fpath) return nil }
func openAtIndex(dirpath string, snap walpb.Snapshot, write bool) (*WAL, error) { names, err := fileutil.ReadDir(dirpath) if err != nil { return nil, err } names = checkWalNames(names) if len(names) == 0 { return nil, ErrFileNotFound } nameIndex, ok := searchIndex(names, snap.Index) if !ok || !isValidSeq(names[nameIndex:]) { return nil, ErrFileNotFound } // open the wal files for reading rcs := make([]io.ReadCloser, 0) ls := make([]fileutil.Lock, 0) for _, name := range names[nameIndex:] { f, err := os.Open(path.Join(dirpath, name)) if err != nil { return nil, err } l, err := fileutil.NewLock(f.Name()) if err != nil { return nil, err } err = l.TryLock() if err != nil { if write { return nil, err } } rcs = append(rcs, f) ls = append(ls, l) } rc := MultiReadCloser(rcs...) // create a WAL ready for reading w := &WAL{ dir: dirpath, start: snap, decoder: newDecoder(rc), locks: ls, } if write { // open the lastest wal file for appending seq, _, err := parseWalName(names[len(names)-1]) if err != nil { rc.Close() return nil, err } last := path.Join(dirpath, names[len(names)-1]) f, err := os.OpenFile(last, os.O_WRONLY|os.O_APPEND, 0) if err != nil { rc.Close() return nil, err } err = fileutil.Preallocate(f, segmentSizeBytes) if err != nil { rc.Close() plog.Errorf("failed to allocate space when creating new wal file (%v)", err) return nil, err } w.f = f w.seq = seq } return w, nil }
func openAtIndex(dirpath string, index uint64, all bool) (*WAL, error) { names, err := fileutil.ReadDir(dirpath) if err != nil { return nil, err } names = checkWalNames(names) if len(names) == 0 { return nil, ErrFileNotFound } sort.Sort(sort.StringSlice(names)) nameIndex, ok := searchIndex(names, index) if !ok || !isValidSeq(names[nameIndex:]) { return nil, ErrFileNotFound } // open the wal files for reading rcs := make([]io.ReadCloser, 0) ls := make([]fileutil.Lock, 0) for _, name := range names[nameIndex:] { f, err := os.Open(path.Join(dirpath, name)) if err != nil { return nil, err } l, err := fileutil.NewLock(f.Name()) if err != nil { return nil, err } err = l.TryLock() if err != nil { if all { return nil, err } else { log.Printf("wal: opened all the files until %s, since it is still in use by an etcd server", name) break } } rcs = append(rcs, f) ls = append(ls, l) } rc := MultiReadCloser(rcs...) // open the lastest wal file for appending seq, _, err := parseWalName(names[len(names)-1]) if err != nil { rc.Close() return nil, err } last := path.Join(dirpath, names[len(names)-1]) f, err := os.OpenFile(last, os.O_WRONLY|os.O_APPEND, 0) if err != nil { rc.Close() return nil, err } // create a WAL ready for reading w := &WAL{ dir: dirpath, ri: index, decoder: newDecoder(rc), f: f, seq: seq, locks: ls, } return w, nil }