From bf4c8439e8262fb9793cff87963ea293c024f9ca Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 26 Jan 2020 15:13:28 +0100 Subject: [PATCH] lib/db: Configurable block GC time (#6295) Also retain the interval over restarts by storing last GC time in the database. This to make sure that GC eventually happens even if the interval is configured to a long time (say, a month). --- cmd/syncthing/main.go | 4 ++++ lib/db/lowlevel.go | 36 ++++++++++++++++++++++++++++++++++-- lib/db/schemaupdater.go | 2 ++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 4fd37807f..3c8319f22 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -119,6 +119,10 @@ are mostly useful for developers. Use with care. "h", "m" and "s" abbreviations for hours minutes and seconds. Valid values are like "720h", "30s", etc. + STGCBLOCKSEVERY Set to a time interval to override the default database + block GC interval of 13 hours. Same format as the + STRECHECKDBEVERY variable. + GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all available CPU cores. diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go index ef3290bfa..7ea6435f3 100644 --- a/lib/db/lowlevel.go +++ b/lib/db/lowlevel.go @@ -9,6 +9,7 @@ package db import ( "bytes" "encoding/binary" + "os" "time" "github.com/syncthing/syncthing/lib/db/backend" @@ -25,9 +26,18 @@ const ( // false positive rate instead. blockGCBloomCapacity = 100000 blockGCBloomFalsePositiveRate = 0.01 // 1% - blockGCInterval = 13 * time.Hour + blockGCDefaultInterval = 13 * time.Hour + blockGCTimeKey = "lastGCTime" ) +var blockGCInterval = blockGCDefaultInterval + +func init() { + if dur, err := time.ParseDuration(os.Getenv("STGCBLOCKSEVERY")); err == nil { + blockGCInterval = dur + } +} + // Lowlevel is the lowest level database interface. It has a very simple // purpose: hold the actual backend database, and the in-memory state // that belong to that database. In the same way that a single on disk @@ -463,7 +473,7 @@ func (db *Lowlevel) dropPrefix(prefix []byte) error { } func (db *Lowlevel) gcRunner() { - t := time.NewTicker(blockGCInterval) + t := time.NewTimer(db.timeUntil(blockGCTimeKey, blockGCInterval)) defer t.Stop() for { select { @@ -473,10 +483,32 @@ func (db *Lowlevel) gcRunner() { if err := db.gcBlocks(); err != nil { l.Warnln("Database block GC failed:", err) } + db.recordTime(blockGCTimeKey) + t.Reset(db.timeUntil(blockGCTimeKey, blockGCInterval)) } } } +// recordTime records the current time under the given key, affecting the +// next call to timeUntil with the same key. +func (db *Lowlevel) recordTime(key string) { + miscDB := NewMiscDataNamespace(db) + _ = miscDB.PutInt64(key, time.Now().Unix()) // error wilfully ignored +} + +// timeUntil returns how long we should wait until the next interval, or +// zero if it should happen directly. +func (db *Lowlevel) timeUntil(key string, every time.Duration) time.Duration { + miscDB := NewMiscDataNamespace(db) + lastTime, _, _ := miscDB.Int64(key) // error wilfully ignored + nextTime := time.Unix(lastTime, 0).Add(every) + sleepTime := time.Until(nextTime) + if sleepTime < 0 { + sleepTime = 0 + } + return sleepTime +} + func (db *Lowlevel) gcBlocks() error { // The block GC uses a bloom filter to track used block lists. This means // iterating over all items, adding their block lists to the filter, then diff --git a/lib/db/schemaupdater.go b/lib/db/schemaupdater.go index 803394f31..d06590430 100644 --- a/lib/db/schemaupdater.go +++ b/lib/db/schemaupdater.go @@ -451,5 +451,7 @@ func (db *schemaUpdater) updateSchema7to8(_ int) error { return err } + db.recordTime(blockGCTimeKey) + return t.commit() }