The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5237
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
From afc3095159b000d03862c7e064df7cf844f327fb Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <free.ekanay...@canonical.com> Date: Fri, 2 Nov 2018 15:11:49 +0100 Subject: [PATCH] Detect and shrink large boltdb files Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com> --- lxd/daemon.go | 33 ++++++++++++--- lxd/patches.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/lxd/daemon.go b/lxd/daemon.go index 750e0585fd..0974ed420b 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -477,6 +477,34 @@ func (d *Daemon) init() error { return err } + clustered, err := cluster.Enabled(d.db) + if err != nil { + return err + } + + // If not already applied, run the daemon patch that shrinks the boltdb + // file. We can't run this daemon patch later on along with the other + // ones because it needs to run before we open the cluster database. + appliedPatches, err := d.db.Patches() + if err != nil { + return errors.Wrap(err, "Fetch applied daemon patches") + } + if !shared.StringInSlice("shrink_logs_db_file", appliedPatches) { + if !clustered { + // We actually run the patch only if this lxd daemon is + // not clustered. + err := patchShrinkLogsDBFile("", d) + if err != nil { + return errors.Wrap(err, "Shrink logs.db file") + } + } + + err = d.db.PatchesMarkApplied("shrink_logs_db_file") + if err != nil { + return err + } + } + /* Setup dqlite */ clusterLogLevel := "ERROR" if shared.StringInSlice("dqlite", trace) { @@ -529,11 +557,6 @@ func (d *Daemon) init() error { return err } - clustered, err := cluster.Enabled(d.db) - if err != nil { - return err - } - /* Open the cluster database */ for { logger.Info("Initializing global database") diff --git a/lxd/patches.go b/lxd/patches.go index 3fa3252844..9c6b4bc72c 100644 --- a/lxd/patches.go +++ b/lxd/patches.go @@ -3,11 +3,16 @@ package main import ( "fmt" "io/ioutil" + stdlog "log" "os" "path/filepath" "strings" "syscall" + "time" + "github.com/boltdb/bolt" + "github.com/hashicorp/raft" + "github.com/hashicorp/raft-boltdb" "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" "github.com/lxc/lxd/lxd/db/query" @@ -36,6 +41,7 @@ import ( */ var patches = []patch{ + {name: "shrink_logs_db_file", run: patchShrinkLogsDBFile}, {name: "invalid_profile_names", run: patchInvalidProfileNames}, {name: "leftover_profile_config", run: patchLeftoverProfileConfig}, {name: "network_permissions", run: patchNetworkPermissions}, @@ -173,6 +179,110 @@ func patchNetworkPermissions(name string, d *Daemon) error { return nil } +// Shrink a database/global/logs.db that grew unwildly due to a bug in the 3.6 +// release. +func patchShrinkLogsDBFile(name string, d *Daemon) error { + dir := filepath.Join(d.os.VarDir, "database", "global") + info, err := os.Stat(filepath.Join(dir, "logs.db")) + if err != nil && !os.IsNotExist(err) { + return errors.Wrap(err, "Get the size of the boltdb database") + } + + if info.Size() < 1024*1024*100 { + // Only try to shrink databases bigger than 100 Megabytes. + return nil + } + + snaps, err := raft.NewFileSnapshotStoreWithLogger( + dir, 2, stdlog.New(ioutil.Discard, "", 0)) + if err != nil { + return errors.Wrap(err, "Open snapshots") + } + + metas, err := snaps.List() + if err != nil { + return errors.Wrap(err, "Fetch snapshots") + } + + if len(metas) == 0 { + // No snapshot is available, we can't shrink. This should never + // happen, in practice. + logger.Warnf("Can't shrink boltdb store, no raft snapshot is available") + return nil + } + + meta := metas[0] // The most recent snapshot. + + // Copy all log entries from the current boltdb file into a new one, + // which will be smaller since it excludes all truncated entries that + pathCur := filepath.Join(dir, "logs.db") + // got allocated before the latest snapshot. + logsCur, err := raftboltdb.New(raftboltdb.Options{ + Path: pathCur, + BoltOptions: &bolt.Options{ + Timeout: 10 * time.Second, + ReadOnly: true, + }, + }) + if err != nil { + return errors.Wrap(err, "Open current boltdb store") + } + defer logsCur.Close() + + pathNew := filepath.Join(dir, "logs.db.new") + logsNew, err := raftboltdb.New(raftboltdb.Options{ + Path: pathNew, + BoltOptions: &bolt.Options{Timeout: 10 * time.Second}, + }) + if err != nil { + return errors.Wrap(err, "Open new boltdb store") + } + defer logsNew.Close() + + lastIndex, err := logsCur.LastIndex() + if err != nil { + return errors.Wrap(err, "Get most recent raft index") + } + + for index := meta.Index; index <= lastIndex; index++ { + log := &raft.Log{} + + err := logsCur.GetLog(index, log) + if err != nil { + return errors.Wrapf(err, "Get raft entry at index %d", index) + } + + err = logsNew.StoreLog(log) + if err != nil { + return errors.Wrapf(err, "Store raft entry at index %d", index) + } + } + + term, err := logsCur.GetUint64([]byte("CurrentTerm")) + if err != nil { + return errors.Wrap(err, "Get current term") + } + err = logsNew.SetUint64([]byte("CurrentTerm"), term) + if err != nil { + return errors.Wrap(err, "Store current term") + } + + logsCur.Close() + logsNew.Close() + + err = os.Remove(pathCur) + if err != nil { + return errors.Wrap(err, "Remove current boltdb store") + } + + err = os.Rename(pathNew, pathCur) + if err != nil { + return errors.Wrap(err, "Rename new boltdb store") + } + + return nil +} + func patchStorageApi(name string, d *Daemon) error { var daemonConfig map[string]string err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel