[lxc-devel] [lxd/master] Detect and shrink large boltdb files
freeekanayaka on Github
lxc-bot at linuxcontainers.org
Fri Nov 2 14:52:20 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 361 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181102/41fa4a9f/attachment.bin>
-------------- next part --------------
From afc3095159b000d03862c7e064df7cf844f327fb Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at 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.ekanayaka at 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 {
More information about the lxc-devel
mailing list