cache read performance improvements

This commit is contained in:
ston1th 2022-05-19 01:29:23 +02:00
commit f69d3149f3
14 changed files with 140 additions and 71 deletions

View file

@ -8,9 +8,5 @@
* implement read ahead buffer? * implement read ahead buffer?
* add breadcrumb * add breadcrumb
* add copyright footer
* add stopped status?
* replace stop with preload link
* should redirect back to preloads page
* config file? * config file?
* whitelist allowed src paths * whitelist allowed src paths

View file

@ -6,6 +6,7 @@ import (
"context" "context"
"flag" "flag"
"net/http" "net/http"
_ "net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -51,6 +52,9 @@ func main() {
flag.Int64Var(&quota, "quota", 1, "max disk usage quota for the dst cache in GiB") flag.Int64Var(&quota, "quota", 1, "max disk usage quota for the dst cache in GiB")
flag.BoolVar(&block, "block", true, "block until sftp is connected") flag.BoolVar(&block, "block", true, "block until sftp is connected")
flag.Parse() flag.Parse()
go func() {
http.ListenAndServe("127.0.0.1:7777", nil)
}()
log = klogr.New().WithName("main") log = klogr.New().WithName("main")
log.Info("starting cachefs", "version", version) log.Info("starting cachefs", "version", version)

View file

@ -15,6 +15,7 @@ import (
type File struct { type File struct {
log logr.Logger log logr.Logger
f provider.File f provider.File
cache provider.File
md *Metadata md *Metadata
offset int64 offset int64
offline bool offline bool
@ -83,7 +84,7 @@ func (f *File) Preload(ctx context.Context, unlock func()) {
func (f *File) Read(p []byte) (n int, err error) { func (f *File) Read(p []byte) (n int, err error) {
log := f.log log := f.log
if f.hasChunk(len(p)) { if f.hasChunk(len(p)) {
n, err = f.md.ReadAt(p, f.offset) n, err = f.cache.ReadAt(p, f.offset)
if err == io.EOF { if err == io.EOF {
return return
} }
@ -172,8 +173,8 @@ func (f *File) Stat() (os.FileInfo, error) {
} }
func (f *File) Close() error { func (f *File) Close() error {
if f.offline { if !f.offline && f.cache != nil {
return nil f.cache.Close()
} }
return f.f.Close() return f.f.Close()
} }

View file

@ -135,6 +135,7 @@ func (fs *FS) OpenDst(name string) (f http.File, err error) {
f = &File{ f = &File{
log: log.WithName("file"), log: log.WithName("file"),
f: file, f: file,
cache: file,
md: md, md: md,
offline: true, offline: true,
} }
@ -198,13 +199,25 @@ func (fs *FS) Open(name string) (f http.File, err error) {
f = &Dir{log: log.WithName("dir"), f: file, dc: fs.dc} f = &Dir{log: log.WithName("dir"), f: file, dc: fs.dc}
return return
} }
md := fs.mh.Metadata(name, fi.Size())
var cache provider.File
if offline { if offline {
log.V(2).Info("file offline mode") log.V(2).Info("file offline mode")
cache = file
} else {
cache, err = fs.dst.Open(name)
if err != nil {
if !skipLog(name) {
log.Error(err, "error opening cache file")
}
return
}
} }
md := fs.mh.Metadata(name, fi.Size())
f = &File{ f = &File{
log: log.WithName("file"), log: log.WithName("file"),
f: file, f: file,
cache: cache,
md: md, md: md,
offline: offline, offline: offline,
} }
@ -231,7 +244,7 @@ func (fs *FS) openCacheFile(name string, size int64) (df provider.File, err erro
log.Error(err, "error opening cache file") log.Error(err, "error opening cache file")
return return
} }
if truncate { if truncate && size > 0 {
err = df.Truncate(size) err = df.Truncate(size)
if err != nil { if err != nil {
log.Error(err, "error truncating cache file") log.Error(err, "error truncating cache file")

View file

@ -389,16 +389,16 @@ func (md *Metadata) AddChunk(off int64, n int) {
md.Chunks.Add(off, n) md.Chunks.Add(off, n)
} }
func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) { //func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) {
if md.f == nil { // if md.f == nil {
err := md.openCacheFile() // err := md.openCacheFile()
if err != nil { // if err != nil {
return 0, err // return 0, err
} // }
} // }
atomic.StoreInt64(&md.Atime, now()) // atomic.StoreInt64(&md.Atime, now())
return md.f.ReadAt(p, pos) // return md.f.ReadAt(p, pos)
} //}
func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) { func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) {
if md.f == nil { if md.f == nil {

View file

@ -22,6 +22,9 @@ func (q *queue) Add(ph *PreloadHandler, name string, prio int) {
for _, p := range *q { for _, p := range *q {
if p.Name == name { if p.Name == name {
p.Prio += prio p.Prio += prio
if p.Prio < 0 {
p.Prio = 0
}
q.Sort() q.Sort()
return return
} }

View file

@ -139,7 +139,8 @@ func (f *file) Seek(offset int64, whence int) (n int64, err error) {
return return
} }
f.nonce = [NonceSize]byte{} f.nonce = [NonceSize]byte{}
inc(&f.nonce, cn) //inc(&f.nonce, cn)
put(&f.nonce, cn)
f.r.nonce = f.nonce f.r.nonce = f.nonce
f.w.nonce = f.nonce f.w.nonce = f.nonce
f.r.off = roff f.r.off = roff
@ -187,7 +188,8 @@ func (f *file) WriteAt(data []byte, pos int64) (n int, err error) {
c, ok := f.w.cm[cn] c, ok := f.w.cm[cn]
if !ok { if !ok {
c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn} c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn}
inc(&c.nonce, cn) //inc(&c.nonce, cn)
put(&c.nonce, cn)
f.w.cm[cn] = c f.w.cm[cn] = c
if f.w.last == nil { if f.w.last == nil {
f.w.last = c f.w.last = c
@ -199,7 +201,6 @@ func (f *file) WriteAt(data []byte, pos int64) (n int, err error) {
if c.full() { if c.full() {
// async flush // async flush
go func() { go func() {
//_, err = c.flush(notLastChunk)
c.flush(notLastChunk) c.flush(notLastChunk)
}() }()
delete(f.w.cm, cn) delete(f.w.cm, cn)

View file

@ -3,6 +3,7 @@ package crypto
import ( import (
"cachefs/pkg/provider" "cachefs/pkg/provider"
"crypto/rand" "crypto/rand"
"encoding/binary"
"io" "io"
"math" "math"
) )
@ -29,7 +30,6 @@ func initNonce(f provider.File) (nonce []byte, err error) {
return return
} }
_, err = f.Write(nonce) _, err = f.Write(nonce)
return
} }
return return
} }
@ -76,3 +76,7 @@ func inc(nonce *[NonceSize]byte, n int64) {
incNonce(nonce) incNonce(nonce)
} }
} }
func put(nonce *[NonceSize]byte, n int64) {
binary.BigEndian.PutUint64((*nonce)[3:11], uint64(n))
}

View file

@ -0,0 +1,15 @@
package crypto
import (
"testing"
)
func BenchmarkInc(b *testing.B) {
var n [NonceSize]byte
inc(&n, int64(b.N))
}
func BenchmarkPut(b *testing.B) {
var n [NonceSize]byte
put(&n, int64(b.N))
}

View file

@ -10,7 +10,11 @@ import (
type cryptoReader interface { type cryptoReader interface {
io.Reader io.Reader
io.ReaderAt }
type nonce struct {
N [NonceSize]byte
C int
} }
type reader struct { type reader struct {
@ -21,9 +25,10 @@ type reader struct {
buf [FullSize]byte buf [FullSize]byte
off int64 off int64
err error err error
nonce [NonceSize]byte nonce [NonceSize]byte
cn int64 cn int64
outBuf []byte
} }
func newReader(key []byte, src cryptoReader) (*reader, error) { func newReader(key []byte, src cryptoReader) (*reader, error) {
@ -32,9 +37,10 @@ func newReader(key []byte, src cryptoReader) (*reader, error) {
return nil, err return nil, err
} }
return &reader{ return &reader{
a: aead, a: aead,
src: src, src: src,
cn: -1, cn: -1,
outBuf: make([]byte, 0, FullSize),
}, nil }, nil
} }
@ -91,19 +97,24 @@ func (r *reader) readChunk() (last bool, err error) {
return false, err return false, err
} }
outBuf := make([]byte, 0, ChunkSize) //outBuf := make([]byte, 0, ChunkSize)
out, err := r.a.Open(outBuf, r.nonce[:], in, nil) out, err := r.a.Open(r.outBuf, r.nonce[:], in, nil)
if err != nil && !last { if err != nil && !last {
// Check if this was a full-length final chunk. // Check if this was a full-length final chunk.
last = true last = true
setLastChunkFlag(&r.nonce) setLastChunkFlag(&r.nonce)
out, err = r.a.Open(outBuf, r.nonce[:], in, nil) out, err = r.a.Open(r.outBuf, r.nonce[:], in, nil)
} }
if err != nil { if err != nil {
return false, errors.New("failed to decrypt and authenticate payload chunk") return false, errors.New("failed to decrypt and authenticate payload chunk")
} }
incNonce(&r.nonce) incNonce(&r.nonce)
r.unread = r.buf[:copy(r.buf[:], out)] //r.unread = r.buf[:copy(r.buf[:], out)]
size := FullSize
if len(out) < FullSize {
size = len(out)
}
r.unread = out[:size]
return last, nil return last, nil
} }

34
pkg/provider/sftp/file.go Normal file
View file

@ -0,0 +1,34 @@
// Copyright (C) 2022 Marius Schellenberger
package sftp
import (
"io/fs"
"github.com/pkg/sftp"
)
type file struct {
*sftp.File
fs *FS
}
func (f *file) ReadDir(_ int) (d []fs.DirEntry, err error) {
list, err := f.Readdir(0)
if err != nil {
return
}
d = make([]fs.DirEntry, len(list))
for i, v := range list {
d[i] = fs.FileInfoToDirEntry(v)
}
return
}
func (f *file) Readdir(_ int) ([]fs.FileInfo, error) {
return f.fs.client.ReadDir(f.Name())
}
func (file) Fd() uintptr {
return 0
}

View file

@ -101,19 +101,27 @@ func (fs *FS) connect(ctx context.Context) {
go fs.keepAlive(ctx) go fs.keepAlive(ctx)
fs.keepalive = true fs.keepalive = true
} }
fs.client, err = sftp.NewClient(fs.c, client, err := sftp.NewClient(fs.c,
sftp.UseFstat(true), sftp.UseFstat(true),
sftp.UseConcurrentReads(true), sftp.UseConcurrentReads(true),
sftp.UseConcurrentWrites(true), sftp.UseConcurrentWrites(true),
//sftp.MaxPacketChecked(32768*2),
//sftp.MaxPacketUnchecked(32768*2),
//sftp.MaxConcurrentRequestsPerFile(128),
) )
select { if err == nil {
case <-ctx.Done(): fs.client = client
return select {
case e := <-fs.wait(ctx): case <-ctx.Done():
log.V(2).Info("connection lost", "err", e) return
fs.conn.Close() case e := <-fs.wait(ctx):
fs.client = connError{} log.V(2).Info("connection lost", "err", e)
fs.c = nil fs.conn.Close()
fs.client = connError{}
fs.c = nil
}
} else {
log.Error(err, "error creating client")
} }
} else { } else {
log.Error(err, "error connecting") log.Error(err, "error connecting")
@ -197,31 +205,6 @@ func (fs *FS) path(p string) string {
return filepath.Join(fs.root, filepath.FromSlash(path.Clean("/"+p))) return filepath.Join(fs.root, filepath.FromSlash(path.Clean("/"+p)))
} }
type file struct {
*sftp.File
fs *FS
}
func (f *file) ReadDir(_ int) (d []fs.DirEntry, err error) {
list, err := f.Readdir(0)
if err != nil {
return
}
d = make([]fs.DirEntry, len(list))
for i, v := range list {
d[i] = fs.FileInfoToDirEntry(v)
}
return
}
func (f *file) Readdir(_ int) ([]fs.FileInfo, error) {
return f.fs.client.ReadDir(f.Name())
}
func (file) Fd() uintptr {
return 0
}
func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) { func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) {
if u.User == nil { if u.User == nil {
return nil, errors.New("missing username") return nil, errors.New("missing username")
@ -233,8 +216,9 @@ func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) {
c = &ssh.ClientConfig{ c = &ssh.ClientConfig{
Config: ssh.Config{ Config: ssh.Config{
//KeyExchanges: []string{"curve25519-sha256"}, //KeyExchanges: []string{"curve25519-sha256"},
Ciphers: []string{"aes128-ctr"}, //Ciphers: []string{"aes128-ctr"},
MACs: []string{"hmac-sha2-256"}, Ciphers: []string{"chacha20-poly1305@openssh.com"},
//MACs: []string{"hmac-sha2-256"},
}, },
User: u.User.Username(), User: u.User.Username(),
Timeout: time.Second * 30, Timeout: time.Second * 30,

View file

@ -111,5 +111,8 @@ span {
<article> <article>
{{template "body" .}} {{template "body" .}}
</article> </article>
<footer>
<pre>© CacheFS v1.0 <a href="https://git.giftfish.de/ston1th/cachefs" rel="nofollow noreferrer noopener" target="_blank">[Source]</a></pre>
</footer>
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
{{define "body" -}} {{define "body" -}}
<pre>[v]: show video <pre>[v]: show video
[n]: skip file caching [n]: stream file without caching
[p]: preload file [p]: preload file
<a href="/?o=preloads">[Preloads]</a></pre> <a href="/?o=preloads">[Preloads]</a></pre>
<table> <table>