浏览代码

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).
Jakob Borg 5 年之前
父节点
当前提交
bf4c8439e8
共有 3 个文件被更改,包括 40 次插入2 次删除
  1. 4 0
      cmd/syncthing/main.go
  2. 34 2
      lib/db/lowlevel.go
  3. 2 0
      lib/db/schemaupdater.go

+ 4 - 0
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.
 

+ 34 - 2
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

+ 2 - 0
lib/db/schemaupdater.go

@@ -451,5 +451,7 @@ func (db *schemaUpdater) updateSchema7to8(_ int) error {
 		return err
 	}
 
+	db.recordTime(blockGCTimeKey)
+
 	return t.commit()
 }