mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +00:00
lib/backup: fixes path generation for windows (#4133)
replaces custom fsync function with standard Fsync methods for files. fixes pattern matching for parts and properly generate backup path for local fs. https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70
This commit is contained in:
parent
5d0299ac19
commit
7bfa1d7d9e
8 changed files with 27 additions and 62 deletions
|
@ -155,7 +155,7 @@ See the docs at https://docs.victoriametrics.com/vmbackup.html .
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSrcFS() (*fslocal.FS, error) {
|
func newSrcFS() (*fslocal.FS, error) {
|
||||||
snapshotPath := *storageDataPath + "/snapshots/" + *snapshotName
|
snapshotPath := filepath.Join(*storageDataPath, "snapshots", *snapshotName)
|
||||||
|
|
||||||
// Verify the snapshot exists.
|
// Verify the snapshot exists.
|
||||||
f, err := os.Open(snapshotPath)
|
f, err := os.Open(snapshotPath)
|
||||||
|
|
|
@ -43,6 +43,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
||||||
|
|
||||||
* BUGFIX: reduce the probability of sudden increase in the number of small parts on systems with small number of CPU cores.
|
* BUGFIX: reduce the probability of sudden increase in the number of small parts on systems with small number of CPU cores.
|
||||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): fix performance issue when migrating data from VictoriaMetrics according to [these docs](https://docs.victoriametrics.com/vmctl.html#migrating-data-from-victoriametrics). Add the ability to speed up the data migration via `--vm-native-disable-retries` command-line flag. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4092).
|
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): fix performance issue when migrating data from VictoriaMetrics according to [these docs](https://docs.victoriametrics.com/vmctl.html#migrating-data-from-victoriametrics). Add the ability to speed up the data migration via `--vm-native-disable-retries` command-line flag. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4092).
|
||||||
|
* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup.html): fixes compatibility with Windows OS. For details see [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70)
|
||||||
* BUGFIX: do not panic at windows during snapshot deletion. Note, at windows process restart required for complete snapshot delete. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70#issuecomment-1491529183) for details.
|
* BUGFIX: do not panic at windows during snapshot deletion. Note, at windows process restart required for complete snapshot delete. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70#issuecomment-1491529183) for details.
|
||||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): fix a panic when the duration in the query contains uppercase `M` suffix. Such a suffix isn't allowed to use in durations, since it clashes with `a million` suffix, e.g. it isn't clear whether `rate(metric[5M])` means rate over 5 minutes, 5 months or 5 million seconds. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3589) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4120) issues.
|
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): fix a panic when the duration in the query contains uppercase `M` suffix. Such a suffix isn't allowed to use in durations, since it clashes with `a million` suffix, e.g. it isn't clear whether `rate(metric[5M])` means rate over 5 minutes, 5 months or 5 million seconds. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3589) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4120) issues.
|
||||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): retry failed read request on the closed connection one more time. This improves rules execution reliability when connection between vmalert and datasource closes unexpectedly.
|
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): retry failed read request on the closed connection one more time. This improves rules execution reliability when connection between vmalert and datasource closes unexpectedly.
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -83,12 +83,7 @@ func (b *Backup) Run() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func storeMetadata(src *fslocal.FS, dst common.RemoteFS) error {
|
func storeMetadata(src *fslocal.FS, dst common.RemoteFS) error {
|
||||||
last := strings.LastIndex(src.Dir, "/")
|
snapshotName := filepath.Base(src.Dir)
|
||||||
if last < 0 {
|
|
||||||
return fmt.Errorf("cannot decode snapshot location %q", src.Dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotName := src.Dir[last+1:]
|
|
||||||
snapshotTime, err := snapshot.Time(snapshotName)
|
snapshotTime, err := snapshot.Time(snapshotName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot decode snapshot name %q: %w", snapshotName, err)
|
return fmt.Errorf("cannot decode snapshot name %q: %w", snapshotName, err)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package actions
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -190,7 +191,7 @@ func NewRemoteFS(path string) (common.RemoteFS, error) {
|
||||||
dir := path[n+len("://"):]
|
dir := path[n+len("://"):]
|
||||||
switch scheme {
|
switch scheme {
|
||||||
case "fs":
|
case "fs":
|
||||||
if !strings.HasPrefix(dir, "/") {
|
if !filepath.IsAbs(dir) {
|
||||||
return nil, fmt.Errorf("dir must be absolute; got %q", dir)
|
return nil, fmt.Errorf("dir must be absolute; got %q", dir)
|
||||||
}
|
}
|
||||||
fs := &fsremote.FS{
|
fs := &fsremote.FS{
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (p *Part) RemotePath(prefix string) string {
|
||||||
return fmt.Sprintf("%s/%s/%016X_%016X_%016X", prefix, p.Path, p.FileSize, p.Offset, p.Size)
|
return fmt.Sprintf("%s/%s/%016X_%016X_%016X", prefix, p.Path, p.FileSize, p.Offset, p.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
var partNameRegexp = regexp.MustCompile(`^(.+)/([0-9A-F]{16})_([0-9A-F]{16})_([0-9A-F]{16})$`)
|
var partNameRegexp = regexp.MustCompile(`^(.+)[/\\]([0-9A-F]{16})_([0-9A-F]{16})_([0-9A-F]{16})$`)
|
||||||
|
|
||||||
// ParseFromRemotePath parses p from remotePath.
|
// ParseFromRemotePath parses p from remotePath.
|
||||||
//
|
//
|
||||||
|
|
|
@ -10,36 +10,6 @@ import (
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FsyncFile fsyncs path contents and the parent directory contents.
|
|
||||||
func FsyncFile(path string) error {
|
|
||||||
if err := fsync(path); err != nil {
|
|
||||||
_ = os.RemoveAll(path)
|
|
||||||
return fmt.Errorf("cannot fsync file %q: %w", path, err)
|
|
||||||
}
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if err := fsync(dir); err != nil {
|
|
||||||
return fmt.Errorf("cannot fsync dir %q: %w", dir, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FsyncDir fsyncs dir contents.
|
|
||||||
func FsyncDir(dir string) error {
|
|
||||||
return fsync(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsync(path string) error {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := f.Sync(); err != nil {
|
|
||||||
_ = f.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendFiles appends all the files from dir to dst.
|
// AppendFiles appends all the files from dir to dst.
|
||||||
//
|
//
|
||||||
// All the appended files will have dir prefix.
|
// All the appended files will have dir prefix.
|
||||||
|
@ -77,7 +47,7 @@ func appendFilesInternal(dst []string, d *os.File) ([]string, error) {
|
||||||
// Do not take into account special files.
|
// Do not take into account special files.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := dir + "/" + name
|
path := filepath.Join(dir, name)
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
// Process directory
|
// Process directory
|
||||||
dst, err = AppendFiles(dst, path)
|
dst, err = AppendFiles(dst, path)
|
||||||
|
@ -112,13 +82,13 @@ func appendFilesInternal(dst []string, d *os.File) ([]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot list files at %q from symlink %q: %w", pathReal, path, err)
|
return nil, fmt.Errorf("cannot list files at %q from symlink %q: %w", pathReal, path, err)
|
||||||
}
|
}
|
||||||
pathReal += "/"
|
pathReal += string(filepath.Separator)
|
||||||
for i := len(dst); i < len(dstNew); i++ {
|
for i := len(dst); i < len(dstNew); i++ {
|
||||||
x := dstNew[i]
|
x := dstNew[i]
|
||||||
if !strings.HasPrefix(x, pathReal) {
|
if !strings.HasPrefix(x, pathReal) {
|
||||||
return nil, fmt.Errorf("unexpected prefix for path %q; want %q", x, pathReal)
|
return nil, fmt.Errorf("unexpected prefix for path %q; want %q", x, pathReal)
|
||||||
}
|
}
|
||||||
dstNew[i] = path + "/" + x[len(pathReal):]
|
dstNew[i] = filepath.Join(path, x[len(pathReal):])
|
||||||
}
|
}
|
||||||
dst = dstNew
|
dst = dstNew
|
||||||
continue
|
continue
|
||||||
|
@ -182,7 +152,7 @@ func removeEmptyDirsInternal(d *os.File) (bool, error) {
|
||||||
if name == "." || name == ".." {
|
if name == "." || name == ".." {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := dir + "/" + name
|
path := filepath.Join(dir, name)
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
// Process directory
|
// Process directory
|
||||||
ok, err := removeEmptyDirs(path)
|
ok, err := removeEmptyDirs(path)
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (fs *FS) ListParts() ([]common.Part, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts []common.Part
|
var parts []common.Part
|
||||||
dir += "/"
|
dir += string(filepath.Separator)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if !strings.HasPrefix(file, dir) {
|
if !strings.HasPrefix(file, dir) {
|
||||||
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
||||||
|
@ -187,11 +187,7 @@ func (fs *FS) mkdirAll(filePath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) path(p common.Part) string {
|
func (fs *FS) path(p common.Part) string {
|
||||||
dir := fs.Dir
|
return filepath.Join(fs.Dir, p.Path)
|
||||||
for strings.HasSuffix(dir, "/") {
|
|
||||||
dir = dir[:len(dir)-1]
|
|
||||||
}
|
|
||||||
return fs.Dir + "/" + p.Path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type limitedReadCloser struct {
|
type limitedReadCloser struct {
|
||||||
|
@ -235,9 +231,11 @@ func (wc *writeCloser) Write(p []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *writeCloser) Close() error {
|
func (wc *writeCloser) Close() error {
|
||||||
|
wc.w.MustFlush(true)
|
||||||
wc.w.MustClose()
|
wc.w.MustClose()
|
||||||
if wc.n != 0 {
|
if wc.n != 0 {
|
||||||
return fmt.Errorf("missing data writes for %d bytes", wc.n)
|
return fmt.Errorf("missing data writes for %d bytes", wc.n)
|
||||||
}
|
}
|
||||||
return fscommon.FsyncFile(wc.path)
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
||||||
|
libfs "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ func (fs *FS) ListParts() ([]common.Part, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts []common.Part
|
var parts []common.Part
|
||||||
dir += "/"
|
dir += string(filepath.Separator)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if !strings.HasPrefix(file, dir) {
|
if !strings.HasPrefix(file, dir) {
|
||||||
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
||||||
|
@ -101,7 +102,8 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||||
}
|
}
|
||||||
// Attempt to create hardlink from srcPath to dstPath.
|
// Attempt to create hardlink from srcPath to dstPath.
|
||||||
if err := os.Link(srcPath, dstPath); err == nil {
|
if err := os.Link(srcPath, dstPath); err == nil {
|
||||||
return fscommon.FsyncFile(dstPath)
|
libfs.MustSyncPath(dstPath)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot create hardlink. Just copy file contents
|
// Cannot create hardlink. Just copy file contents
|
||||||
|
@ -115,6 +117,9 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||||
return fmt.Errorf("cannot create destination file: %w", err)
|
return fmt.Errorf("cannot create destination file: %w", err)
|
||||||
}
|
}
|
||||||
n, err := io.Copy(dstFile, srcFile)
|
n, err := io.Copy(dstFile, srcFile)
|
||||||
|
if err := dstFile.Sync(); err != nil {
|
||||||
|
return fmt.Errorf("cannot fsync dstFile: %q: %w", dstFile.Name(), err)
|
||||||
|
}
|
||||||
if err1 := dstFile.Close(); err1 != nil {
|
if err1 := dstFile.Close(); err1 != nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
|
@ -129,10 +134,6 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||||
_ = os.RemoveAll(dstPath)
|
_ = os.RemoveAll(dstPath)
|
||||||
return fmt.Errorf("unexpected number of bytes copied from %q to %q; got %d bytes; want %d bytes", srcPath, dstPath, n, p.Size)
|
return fmt.Errorf("unexpected number of bytes copied from %q to %q; got %d bytes; want %d bytes", srcPath, dstPath, n, p.Size)
|
||||||
}
|
}
|
||||||
if err := fscommon.FsyncFile(dstPath); err != nil {
|
|
||||||
_ = os.RemoveAll(dstPath)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +168,9 @@ func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
|
||||||
return fmt.Errorf("cannot create file %q: %w", path, err)
|
return fmt.Errorf("cannot create file %q: %w", path, err)
|
||||||
}
|
}
|
||||||
n, err := io.Copy(w, r)
|
n, err := io.Copy(w, r)
|
||||||
|
if err := w.Sync(); err != nil {
|
||||||
|
return fmt.Errorf("cannot fsync file: %q: %w", w.Name(), err)
|
||||||
|
}
|
||||||
if err1 := w.Close(); err1 != nil && err == nil {
|
if err1 := w.Close(); err1 != nil && err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
|
@ -178,10 +182,6 @@ func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
|
||||||
_ = os.RemoveAll(path)
|
_ = os.RemoveAll(path)
|
||||||
return fmt.Errorf("wrong data size uploaded to %q; got %d bytes; want %d bytes", path, n, p.Size)
|
return fmt.Errorf("wrong data size uploaded to %q; got %d bytes; want %d bytes", path, n, p.Size)
|
||||||
}
|
}
|
||||||
if err := fscommon.FsyncFile(path); err != nil {
|
|
||||||
_ = os.RemoveAll(path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ func (fs *FS) mkdirAll(filePath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) path(p common.Part) string {
|
func (fs *FS) path(p common.Part) string {
|
||||||
return p.RemotePath(fs.Dir)
|
return filepath.Join(fs.Dir, p.Path, fmt.Sprintf("%016X_%016X_%016X", p.FileSize, p.Offset, p.Size))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFile deletes filePath at fs.
|
// DeleteFile deletes filePath at fs.
|
||||||
|
|
Loading…
Reference in a new issue