chore(sqlite): use normalised tables for file names and versions (#10383)
This changes the files table to use normalisation for the names and
versions. The idea is that these are often common between all remote
devices, and repeating an integer is more efficient than repeating a
long string. A new benchmark bears this out; for a database with 100k
files shared between 31 devices, with some worst case assumption on
version vector size, the database is reduced in size by 50% and the test
finishes quicker:
Current:
db_bench_test.go:322: Total size: 6263.70 MiB
--- PASS: TestBenchmarkSizeManyFilesRemotes (1084.89s)
New:
db_bench_test.go:326: Total size: 3049.95 MiB
--- PASS: TestBenchmarkSizeManyFilesRemotes (776.97s)
The other benchmarks end up about the same within the margin of
variability, with one possible exception being that RemoteNeed seems to
be a little slower on average:
old files/s new files/s
Update/n=RemoteNeed/size=1000-8 5.051k 4.654k
Update/n=RemoteNeed/size=2000-8 5.201k 4.384k
Update/n=RemoteNeed/size=4000-8 4.943k 4.242k
Update/n=RemoteNeed/size=8000-8 5.099k 3.527k
Update/n=RemoteNeed/size=16000-8 3.686k 3.847k
Update/n=RemoteNeed/size=30000-8 4.456k 3.482k
I'm not sure why, possibly that query can be optimised anyhow.
Signed-off-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
@@ -8,11 +8,13 @@ package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/timeutil"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
)
|
||||
@@ -223,7 +225,7 @@ func BenchmarkUpdate(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestBenchmarkDropAllRemote(t *testing.T) {
|
||||
if testing.Short() {
|
||||
if testing.Short() || os.Getenv("LONG_TEST") == "" {
|
||||
t.Skip("slow test")
|
||||
}
|
||||
|
||||
@@ -266,3 +268,61 @@ func TestBenchmarkDropAllRemote(t *testing.T) {
|
||||
d := time.Since(t0)
|
||||
t.Log("drop all took", d)
|
||||
}
|
||||
|
||||
func TestBenchmarkSizeManyFilesRemotes(t *testing.T) {
|
||||
// Reports the database size for a setup with many files and many remote
|
||||
// devices each announcing every files, with fairly long file names and
|
||||
// "worst case" version vectors.
|
||||
|
||||
if testing.Short() || os.Getenv("LONG_TEST") == "" {
|
||||
t.Skip("slow test")
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// This is equivalent to about 800 GiB in 100k files (i.e., 8 MiB per
|
||||
// file), shared between 31 devices where each have touched every file.
|
||||
const numFiles = 1e5
|
||||
const numRemotes = 30
|
||||
const numBlocks = 64
|
||||
const filenameLen = 64
|
||||
|
||||
fs := make([]protocol.FileInfo, 1000)
|
||||
n := 0
|
||||
seq := 0
|
||||
for n < numFiles {
|
||||
for i := range fs {
|
||||
seq++
|
||||
fs[i] = genFile(rand.String(filenameLen), numBlocks, seq)
|
||||
for r := range numRemotes {
|
||||
fs[i].Version = fs[i].Version.Update(42 + protocol.ShortID(r))
|
||||
}
|
||||
}
|
||||
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for r := range numRemotes {
|
||||
if err := db.Update(folderID, protocol.DeviceID{byte(42 + r)}, fs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
n += len(fs)
|
||||
t.Log(n, (numRemotes+1)*n)
|
||||
}
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
size := osutil.DirSize(dir)
|
||||
t.Logf("Total size: %.02f MiB", float64(size)/1024/1024)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user