mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-09 14:50:56 +00:00
This introduces a better set of defaults for large databases. I've experimentally determined that it results in much better throughput in a couple of scenarios with large databases, but I can't give any guarantees the values are always optimal. They're probably no worse than the defaults though.
This commit is contained in:
parent
e910acdc17
commit
90b70c7a16
@ -491,7 +491,7 @@ func checkUpgrade() upgrade.Release {
|
|||||||
|
|
||||||
func performUpgrade(release upgrade.Release) {
|
func performUpgrade(release upgrade.Release) {
|
||||||
// Use leveldb database locks to protect against concurrent upgrades
|
// Use leveldb database locks to protect against concurrent upgrades
|
||||||
_, err := syncthing.OpenGoleveldb(locations.Get(locations.Database))
|
_, err := syncthing.OpenGoleveldb(locations.Get(locations.Database), config.TuningAuto)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = upgrade.To(release)
|
err = upgrade.To(release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -583,7 +583,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbFile := locations.Get(locations.Database)
|
dbFile := locations.Get(locations.Database)
|
||||||
ldb, err := syncthing.OpenGoleveldb(dbFile)
|
ldb, err := syncthing.OpenGoleveldb(dbFile, cfg.Options().DatabaseTuning)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warnln("Error opening database:", err)
|
l.Warnln("Error opening database:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -57,6 +57,7 @@ type OptionsConfiguration struct {
|
|||||||
StunKeepaliveStartS int `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
|
StunKeepaliveStartS int `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
|
||||||
StunKeepaliveMinS int `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"` // 0 for off
|
StunKeepaliveMinS int `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"` // 0 for off
|
||||||
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
|
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
|
||||||
|
DatabaseTuning Tuning `xml:"databaseTuning" json:"databaseTuning" restart:"true"`
|
||||||
|
|
||||||
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
|
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
|
||||||
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
|
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
|
||||||
|
47
lib/config/tuning.go
Normal file
47
lib/config/tuning.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (C) 2019 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
type Tuning int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// N.b. these constants must match those in lib/db.Tuning!
|
||||||
|
TuningAuto Tuning = iota // default is auto
|
||||||
|
TuningSmall
|
||||||
|
TuningLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Tuning) String() string {
|
||||||
|
switch t {
|
||||||
|
case TuningAuto:
|
||||||
|
return "auto"
|
||||||
|
case TuningSmall:
|
||||||
|
return "small"
|
||||||
|
case TuningLarge:
|
||||||
|
return "large"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tuning) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(t.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tuning) UnmarshalText(bs []byte) error {
|
||||||
|
switch string(bs) {
|
||||||
|
case "auto":
|
||||||
|
*t = TuningAuto
|
||||||
|
case "small":
|
||||||
|
*t = TuningSmall
|
||||||
|
case "large":
|
||||||
|
*t = TuningLarge
|
||||||
|
default:
|
||||||
|
*t = TuningAuto
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
26
lib/config/tuning_test.go
Normal file
26
lib/config/tuning_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (C) 2019 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
|
"github.com/syncthing/syncthing/lib/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTuningMatches(t *testing.T) {
|
||||||
|
if int(config.TuningAuto) != int(db.TuningAuto) {
|
||||||
|
t.Error("mismatch for TuningAuto")
|
||||||
|
}
|
||||||
|
if int(config.TuningSmall) != int(db.TuningSmall) {
|
||||||
|
t.Error("mismatch for TuningSmall")
|
||||||
|
}
|
||||||
|
if int(config.TuningLarge) != int(db.TuningLarge) {
|
||||||
|
t.Error("mismatch for TuningLarge")
|
||||||
|
}
|
||||||
|
}
|
@ -23,11 +23,26 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
dbMaxOpenFiles = 100
|
dbMaxOpenFiles = 100
|
||||||
dbWriteBuffer = 16 << 20
|
dbFlushBatch = 4 << MiB
|
||||||
|
|
||||||
|
// A large database is > 200 MiB. It's a mostly arbitrary value, but
|
||||||
|
// it's also the case that each file is 2 MiB by default and when we
|
||||||
|
// have dbMaxOpenFiles of them we will need to start thrashing fd:s.
|
||||||
|
// Switching to large database settings causes larger files to be used
|
||||||
|
// when compacting, reducing the number.
|
||||||
|
dbLargeThreshold = dbMaxOpenFiles * (2 << MiB)
|
||||||
|
|
||||||
|
KiB = 10
|
||||||
|
MiB = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Tuning int
|
||||||
dbFlushBatch = debugEnvValue("WriteBuffer", dbWriteBuffer) / 4 // Some leeway for any leveldb in-memory optimizations
|
|
||||||
|
const (
|
||||||
|
// N.b. these constants must match those in lib/config.Tuning!
|
||||||
|
TuningAuto Tuning = iota
|
||||||
|
TuningSmall
|
||||||
|
TuningLarge
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lowlevel is the lowest level database interface. It has a very simple
|
// Lowlevel is the lowest level database interface. It has a very simple
|
||||||
@ -49,18 +64,58 @@ type Lowlevel struct {
|
|||||||
// Open attempts to open the database at the given location, and runs
|
// Open attempts to open the database at the given location, and runs
|
||||||
// recovery on it if opening fails. Worst case, if recovery is not possible,
|
// recovery on it if opening fails. Worst case, if recovery is not possible,
|
||||||
// the database is erased and created from scratch.
|
// the database is erased and created from scratch.
|
||||||
func Open(location string) (*Lowlevel, error) {
|
func Open(location string, tuning Tuning) (*Lowlevel, error) {
|
||||||
|
opts := optsFor(location, tuning)
|
||||||
|
return open(location, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optsFor returns the database options to use when opening a database with
|
||||||
|
// the given location and tuning. Settings can be overridden by debug
|
||||||
|
// environment variables.
|
||||||
|
func optsFor(location string, tuning Tuning) *opt.Options {
|
||||||
|
large := false
|
||||||
|
switch tuning {
|
||||||
|
case TuningLarge:
|
||||||
|
large = true
|
||||||
|
case TuningAuto:
|
||||||
|
large = dbIsLarge(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Set defaults used for small databases.
|
||||||
|
defaultBlockCacheCapacity = 0 // 0 means let leveldb use default
|
||||||
|
defaultBlockSize = 0
|
||||||
|
defaultCompactionTableSize = 0
|
||||||
|
defaultCompactionTableSizeMultiplier = 0
|
||||||
|
defaultWriteBuffer = 16 << MiB // increased from leveldb default of 4 MiB
|
||||||
|
defaultCompactionL0Trigger = opt.DefaultCompactionL0Trigger // explicit because we use it as base for other stuff
|
||||||
|
)
|
||||||
|
|
||||||
|
if large {
|
||||||
|
// Change the parameters for better throughput at the price of some
|
||||||
|
// RAM and larger files. This results in larger batches of writes
|
||||||
|
// and compaction at a lower frequency.
|
||||||
|
l.Infoln("Using large-database tuning")
|
||||||
|
|
||||||
|
defaultBlockCacheCapacity = 64 << MiB
|
||||||
|
defaultBlockSize = 64 << KiB
|
||||||
|
defaultCompactionTableSize = 16 << MiB
|
||||||
|
defaultCompactionTableSizeMultiplier = 20 // 2.0 after division by ten
|
||||||
|
defaultWriteBuffer = 64 << MiB
|
||||||
|
defaultCompactionL0Trigger = 8 // number of l0 files
|
||||||
|
}
|
||||||
|
|
||||||
opts := &opt.Options{
|
opts := &opt.Options{
|
||||||
BlockCacheCapacity: debugEnvValue("BlockCacheCapacity", 0),
|
BlockCacheCapacity: debugEnvValue("BlockCacheCapacity", defaultBlockCacheCapacity),
|
||||||
BlockCacheEvictRemoved: debugEnvValue("BlockCacheEvictRemoved", 0) != 0,
|
BlockCacheEvictRemoved: debugEnvValue("BlockCacheEvictRemoved", 0) != 0,
|
||||||
BlockRestartInterval: debugEnvValue("BlockRestartInterval", 0),
|
BlockRestartInterval: debugEnvValue("BlockRestartInterval", 0),
|
||||||
BlockSize: debugEnvValue("BlockSize", 0),
|
BlockSize: debugEnvValue("BlockSize", defaultBlockSize),
|
||||||
CompactionExpandLimitFactor: debugEnvValue("CompactionExpandLimitFactor", 0),
|
CompactionExpandLimitFactor: debugEnvValue("CompactionExpandLimitFactor", 0),
|
||||||
CompactionGPOverlapsFactor: debugEnvValue("CompactionGPOverlapsFactor", 0),
|
CompactionGPOverlapsFactor: debugEnvValue("CompactionGPOverlapsFactor", 0),
|
||||||
CompactionL0Trigger: debugEnvValue("CompactionL0Trigger", 0),
|
CompactionL0Trigger: debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger),
|
||||||
CompactionSourceLimitFactor: debugEnvValue("CompactionSourceLimitFactor", 0),
|
CompactionSourceLimitFactor: debugEnvValue("CompactionSourceLimitFactor", 0),
|
||||||
CompactionTableSize: debugEnvValue("CompactionTableSize", 0),
|
CompactionTableSize: debugEnvValue("CompactionTableSize", defaultCompactionTableSize),
|
||||||
CompactionTableSizeMultiplier: float64(debugEnvValue("CompactionTableSizeMultiplier", 0)) / 10.0,
|
CompactionTableSizeMultiplier: float64(debugEnvValue("CompactionTableSizeMultiplier", defaultCompactionTableSizeMultiplier)) / 10.0,
|
||||||
CompactionTotalSize: debugEnvValue("CompactionTotalSize", 0),
|
CompactionTotalSize: debugEnvValue("CompactionTotalSize", 0),
|
||||||
CompactionTotalSizeMultiplier: float64(debugEnvValue("CompactionTotalSizeMultiplier", 0)) / 10.0,
|
CompactionTotalSizeMultiplier: float64(debugEnvValue("CompactionTotalSizeMultiplier", 0)) / 10.0,
|
||||||
DisableBufferPool: debugEnvValue("DisableBufferPool", 0) != 0,
|
DisableBufferPool: debugEnvValue("DisableBufferPool", 0) != 0,
|
||||||
@ -70,15 +125,16 @@ func Open(location string) (*Lowlevel, error) {
|
|||||||
NoSync: debugEnvValue("NoSync", 0) != 0,
|
NoSync: debugEnvValue("NoSync", 0) != 0,
|
||||||
NoWriteMerge: debugEnvValue("NoWriteMerge", 0) != 0,
|
NoWriteMerge: debugEnvValue("NoWriteMerge", 0) != 0,
|
||||||
OpenFilesCacheCapacity: debugEnvValue("OpenFilesCacheCapacity", dbMaxOpenFiles),
|
OpenFilesCacheCapacity: debugEnvValue("OpenFilesCacheCapacity", dbMaxOpenFiles),
|
||||||
WriteBuffer: debugEnvValue("WriteBuffer", dbWriteBuffer),
|
WriteBuffer: debugEnvValue("WriteBuffer", defaultWriteBuffer),
|
||||||
// The write slowdown and pause can be overridden, but even if they
|
// The write slowdown and pause can be overridden, but even if they
|
||||||
// are not and the compaction trigger is overridden we need to
|
// are not and the compaction trigger is overridden we need to
|
||||||
// adjust so that we don't pause writes for L0 compaction before we
|
// adjust so that we don't pause writes for L0 compaction before we
|
||||||
// even *start* L0 compaction...
|
// even *start* L0 compaction...
|
||||||
WriteL0SlowdownTrigger: debugEnvValue("WriteL0SlowdownTrigger", 2*debugEnvValue("CompactionL0Trigger", opt.DefaultCompactionL0Trigger)),
|
WriteL0SlowdownTrigger: debugEnvValue("WriteL0SlowdownTrigger", 2*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
|
||||||
WriteL0PauseTrigger: debugEnvValue("WriteL0SlowdownTrigger", 3*debugEnvValue("CompactionL0Trigger", opt.DefaultCompactionL0Trigger)),
|
WriteL0PauseTrigger: debugEnvValue("WriteL0SlowdownTrigger", 3*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
|
||||||
}
|
}
|
||||||
return open(location, opts)
|
|
||||||
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenRO attempts to open the database at the given location, read only.
|
// OpenRO attempts to open the database at the given location, read only.
|
||||||
@ -114,6 +170,7 @@ func open(location string, opts *opt.Options) (*Lowlevel, error) {
|
|||||||
l.Warnln("Compacting database:", err)
|
l.Warnln("Compacting database:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewLowlevel(db, location), nil
|
return NewLowlevel(db, location), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +264,31 @@ func (db *Lowlevel) Close() {
|
|||||||
db.DB.Close()
|
db.DB.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dbIsLarge returns whether the estimated size of the database at location
|
||||||
|
// is large enough to warrant optimization for large databases.
|
||||||
|
func dbIsLarge(location string) bool {
|
||||||
|
dir, err := os.Open(location)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fis, err := dir.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var size int64
|
||||||
|
for _, fi := range fis {
|
||||||
|
if fi.Name() == "LOG" {
|
||||||
|
// don't count the size
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size += fi.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return size > dbLargeThreshold
|
||||||
|
}
|
||||||
|
|
||||||
// NewLowlevel wraps the given *leveldb.DB into a *lowlevel
|
// NewLowlevel wraps the given *leveldb.DB into a *lowlevel
|
||||||
func NewLowlevel(db *leveldb.DB, location string) *Lowlevel {
|
func NewLowlevel(db *leveldb.DB, location string) *Lowlevel {
|
||||||
return &Lowlevel{
|
return &Lowlevel{
|
||||||
|
@ -729,7 +729,7 @@ func BenchmarkUpdateOneFile(b *testing.B) {
|
|||||||
protocol.FileInfo{Name: "zajksdhaskjdh/askjdhaskjdashkajshd/kasjdhaskjdhaskdjhaskdjash/dkjashdaksjdhaskdjahskdjh", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)},
|
protocol.FileInfo{Name: "zajksdhaskjdh/askjdhaskjdashkajshd/kasjdhaskjdhaskdjhaskdjash/dkjashdaksjdhaskdjahskdjh", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)},
|
||||||
}
|
}
|
||||||
|
|
||||||
ldb, err := db.Open("testdata/benchmarkupdate.db")
|
ldb, err := db.Open("testdata/benchmarkupdate.db", db.TuningAuto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,6 @@ func copyFile(src, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenGoleveldb(path string) (*db.Lowlevel, error) {
|
func OpenGoleveldb(path string, tuning config.Tuning) (*db.Lowlevel, error) {
|
||||||
return db.Open(path)
|
return db.Open(path, db.Tuning(tuning))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user