Files
kopia/cli/config.go
Julio López d886957c7d feat(snapshots): reduce kernel page-cache pressure during snapshots (#5437)
Add fadvise hints to backup read path to reduce OS cache pollution

Issue FADV_SEQUENTIAL at open and FADV_DONTNEED at close for every file read during backup. This tells the kernel to read-ahead aggressively and then reclaim pages once the file is fully consumed, reducing cache pressure from large backups without adding measurable overhead.

Uses SyscallConn().Control() instead of os.File.Fd() to avoid switching file descriptors to blocking mode, which would remove them from Go's epoll poller and stall the upload pipeline.

Incremental FADV_DONTNEED during reads was tested and removed — it adds ~15% overhead on large-file workloads by fighting the kernel's own LRU. Restore write-path cache eviction was also tested and not included.

Authored-by: Rajat Gupta rajat.gupta@veeam.com
2026-06-26 19:29:13 -07:00

135 lines
3.1 KiB
Go

package cli
import (
"context"
"os"
"os/signal"
"path/filepath"
"runtime"
"syscall"
"github.com/pkg/errors"
"github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/localfs"
"github.com/kopia/kopia/internal/ospath"
"github.com/kopia/kopia/repo"
)
func (c *App) onRepositoryFatalError(f func(err error)) {
c.onFatalErrorCallbacks = append(c.onFatalErrorCallbacks, f)
}
func (c *App) onTerminate(f func()) {
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, syscall.SIGTERM)
go func() {
// invoke the function when either real or simulated Ctrl-C signal is delivered
select {
case v := <-c.simulatedCtrlC:
if !v {
return
}
case <-s:
}
f()
}()
}
func (c *App) openRepository(ctx context.Context, required bool) (repo.Repository, error) {
if _, err := os.Stat(c.repositoryConfigFileName()); os.IsNotExist(err) {
if !required {
return nil, nil
}
return nil, errors.New("repository is not connected. See https://kopia.io/docs/repositories/")
}
c.maybePrintUpdateNotification(ctx)
pass, err := c.getPasswordFromFlags(ctx, false, true)
if err != nil {
return nil, errors.Wrap(err, "get password")
}
r, err := repo.Open(ctx, c.repositoryConfigFileName(), pass, c.optionsFromFlags(ctx))
if os.IsNotExist(err) {
return nil, errors.New("not connected to a repository, use 'kopia connect'")
}
return r, errors.Wrap(err, "unable to open repository")
}
func (c *App) optionsFromFlags(ctx context.Context) *repo.Options {
return &repo.Options{
TraceStorage: c.traceStorage,
DisableRepositoryLog: c.disableRepositoryLog,
UpgradeOwnerID: c.upgradeOwnerID,
DoNotWaitForUpgrade: c.doNotWaitForUpgrade,
ContentLogWriter: c.contentLogWriter,
// when a fatal error is encountered in the repository, run all registered callbacks
// and exit the program.
OnFatalError: func(err error) {
log(ctx).Debugf("onFatalError: %v", err)
for _, cb := range c.onFatalErrorCallbacks {
cb(err)
}
c.exitWithError(err)
},
TestOnlyIgnoreMissingRequiredFeatures: c.testonlyIgnoreMissingRequiredFeatures,
}
}
func (c *App) repositoryConfigFileName() string {
if filepath.Base(c.configPath) != c.configPath {
return c.configPath
}
// bare filename specified without any directory (absolute or relative)
// resolve against OS-specific directory.
return filepath.Join(ospath.ConfigDir(), c.configPath)
}
func resolveSymlink(path string) (string, error) {
st, err := os.Lstat(path)
if err != nil {
return "", errors.Wrap(err, "stat")
}
if (st.Mode() & os.ModeSymlink) == 0 {
return path, nil
}
//nolint:wrapcheck
return filepath.EvalSymlinks(path)
}
func getLocalFSEntry(ctx context.Context, path0 string, opts localfs.Options) (fs.Entry, error) {
path, err := resolveSymlink(path0)
if err != nil {
return nil, errors.Wrap(err, "resolveSymlink")
}
if path != path0 {
log(ctx).Infof("%v resolved to %v", path0, path)
}
e, err := localfs.NewEntryWithOptions(path, opts)
if err != nil {
return nil, errors.Wrap(err, "can't get local fs entry")
}
return e, nil
}
func isWindows() bool {
return runtime.GOOS == "windows"
}