Skip to content

Commit

Permalink
conprof: fix genjidb(badger) gc doesn't work problem(#115)(#119) (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
crazycs520 authored Apr 28, 2022
1 parent 13a598d commit 1ab7159
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 48 deletions.
77 changes: 77 additions & 0 deletions component/conprof/store/store_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package store

import (
"context"
"io/ioutil"
"math/rand"
"os"
"runtime"
"testing"
"time"

"github.com/dgraph-io/badger/v3"
"github.com/genjidb/genji"
"github.com/genjidb/genji/engine/badgerengine"
"github.com/pingcap/ng-monitoring/component/conprof/meta"
"github.com/pingcap/ng-monitoring/config"
"github.com/pingcap/ng-monitoring/utils/testutil"
Expand Down Expand Up @@ -143,6 +148,78 @@ func testProfileStorage(t *testing.T, tmpDir string, baseTs int64, cleanCache bo
require.NoError(t, err)
}

func TestGenjiDBGCBug(t *testing.T) {
// related issue: https://github.com/genjidb/genji/issues/454
tmpDir, err := ioutil.TempDir(os.TempDir(), "ngm-test-genjidb-gc.*")
require.NoError(t, err)
defer func() {
err := os.RemoveAll(tmpDir)
require.NoError(t, err)
}()
opts := badger.DefaultOptions(tmpDir).
WithMemTableSize(1024 * 1024).
WithNumLevelZeroTables(1).
WithNumLevelZeroTablesStall(2).WithValueThreshold(128 * 1024)

engine, err := badgerengine.NewEngine(opts)
require.NoError(t, err)
db, err := genji.New(context.Background(), engine)
require.NoError(t, err)
defer db.Close()
badgerDB := engine.DB

sql := "CREATE TABLE IF NOT EXISTS t (ts INTEGER PRIMARY KEY, data BLOB)"
err = db.Exec(sql)
require.NoError(t, err)
// triage the bug in https://github.com/genjidb/genji/issues/454.
err = db.Exec(sql)
require.NoError(t, err)

ts := time.Now().Unix()
for i := 0; i < 1000; i++ {
data := mockProfile()
sql = "INSERT INTO t (ts, data) VALUES (?, ?)"
err = db.Exec(sql, int(ts)+i, data)
require.NoError(t, err)
}

sql = "DELETE FROM t WHERE ts <= ?"
err = db.Exec(sql, int(ts)+1000)
require.NoError(t, err)

// Insert 1000 kv to make sure the deleted kv will been compacted.
for i := 1000; i < 2000; i++ {
data := mockProfile()
sql = "INSERT INTO t (ts, data) VALUES (?, ?)"
err = db.Exec(sql, int(ts)+i, data)
require.NoError(t, err)
}

err = badgerDB.Flatten(runtime.NumCPU())
require.NoError(t, err)

keyCount := 0
err = badgerDB.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.AllVersions = true
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
valueSize := it.Item().ValueSize()
if valueSize <= 1024 {
continue
}
keyCount++
}
return nil
})
// Without the pr-fix, the keyCount will be 2000, since those deleted keys don't been gc.
require.Equal(t, keyCount, 1000)

err = db.Close()
require.NoError(t, err)
}

func mockProfile() []byte {
size := 50*1024 + rand.Intn(50*1024)
data := make([]byte, size)
Expand Down
46 changes: 0 additions & 46 deletions database/document/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package document
import (
"context"
"path"
"time"

"github.com/dgraph-io/badger/v3"
"github.com/genjidb/genji"
Expand Down Expand Up @@ -43,51 +42,6 @@ func Init(cfg *config.Config) {
documentDB = db
}

func doGCLoop(db *badger.DB, closed chan struct{}) {
log.Info("badger start to run value log gc loop")
ticker := time.NewTicker(10 * time.Minute)
defer func() {
ticker.Stop()
log.Info("badger stop running value log gc loop")
}()

// run gc when started.
runValueLogGC(db)
for {
select {
case <-ticker.C:
runValueLogGC(db)
case <-closed:
return
}
}
}

func runValueLogGC(db *badger.DB) {
defer func() {
r := recover()
if r != nil {
log.Error("panic when run badger value log",
zap.Reflect("r", r),
zap.Stack("stack trace"))
}
}()

// at most do 10 value log gc each time.
for i := 0; i < 10; i++ {
err := db.RunValueLogGC(0.1)
if err != nil {
if err == badger.ErrNoRewrite {
log.Info("badger has no value log need gc now")
} else {
log.Error("badger run value log gc failed", zap.Error(err))
}
return
}
log.Info("badger run value log gc success")
}
}

func Get() *genji.DB {
return documentDB
}
Expand Down
47 changes: 47 additions & 0 deletions database/document/document_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package document

import (
"io/ioutil"
"os"
"testing"
"time"

"github.com/pingcap/ng-monitoring/utils/testutil"
"github.com/stretchr/testify/require"
)

func TestGC(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "ngm-test-.*")
require.NoError(t, err)
defer func() {
err := os.RemoveAll(tmpDir)
require.NoError(t, err)
}()

db := testutil.NewBadgerDB(t, tmpDir)
ts, err := getLastFlattenTs(db)
require.NoError(t, err)
require.Equal(t, int64(0), ts)

ts = time.Now().Unix()
err = storeLastFlattenTs(db, ts)
require.NoError(t, err)

lastTs, err := getLastFlattenTs(db)
require.NoError(t, err)
require.Equal(t, ts, lastTs)

require.False(t, needFlatten(db))
runGC(db)

lastTs = ts - int64(flattenInterval/time.Second)
err = storeLastFlattenTs(db, lastTs)
require.NoError(t, err)
require.True(t, needFlatten(db))

runGC(db)
lastFlattenTs, err := getLastFlattenTs(db)
require.NoError(t, err)
require.NotEqual(t, lastTs, lastFlattenTs)
require.Less(t, time.Now().Unix()-lastFlattenTs, int64(10))
}
122 changes: 122 additions & 0 deletions database/document/gc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package document

import (
"runtime"
"strconv"
"time"

"github.com/dgraph-io/badger/v3"
"github.com/pingcap/log"
"go.uber.org/zap"
)

var (
lastFlattenTsKey = []byte("last_flatten_ts")
flattenInterval = time.Hour * 24
)

func doGCLoop(db *badger.DB, closed chan struct{}) {
log.Info("badger start to run value log gc loop")
ticker := time.NewTicker(10 * time.Minute)
defer func() {
ticker.Stop()
log.Info("badger stop running value log gc loop")
}()

// run gc when started.
runGC(db)
for {
select {
case <-ticker.C:
runGC(db)
case <-closed:
return
}
}
}

func runGC(db *badger.DB) {
defer func() {
r := recover()
if r != nil {
log.Error("panic when run badger gc",
zap.Reflect("r", r),
zap.Stack("stack trace"))
}
}()

tryFlattenIfNeeded(db)
runValueLogGC(db)
}

func runValueLogGC(db *badger.DB) {
// at most do 10 value log gc each time.
for i := 0; i < 10; i++ {
err := db.RunValueLogGC(0.1)
if err != nil {
if err == badger.ErrNoRewrite {
log.Info("badger has no value log need gc now")
} else {
log.Error("badger run value log gc failed", zap.Error(err))
}
return
}
log.Info("badger run value log gc success")
}
}

// tryFlattenIfNeeded try to do flatten if needed.
// Flatten uses to remove the old version keys, otherwise, the value log gc won't release the disk space which occupied
// by the old version keys.
func tryFlattenIfNeeded(db *badger.DB) {
if !needFlatten(db) {
return
}
err := db.Flatten(runtime.NumCPU()/2 + 1)
if err != nil {
log.Error("badger flatten failed", zap.Error(err))
return
}
ts := time.Now().Unix()
err = storeLastFlattenTs(db, ts)
if err != nil {
log.Error("badger store last flatten ts failed", zap.Error(err))
return
}
log.Info("badger flatten success", zap.Int64("ts", ts))
}

func needFlatten(db *badger.DB) bool {
ts, err := getLastFlattenTs(db)
if err != nil {
log.Error("badger get last flatten ts failed", zap.Error(err))
}
interval := time.Now().Unix() - ts
return time.Duration(interval)*time.Second >= flattenInterval
}

func getLastFlattenTs(db *badger.DB) (int64, error) {
ts := int64(0)
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get(lastFlattenTsKey)
if err != nil {
if err == badger.ErrKeyNotFound {
return nil
}
return err
}
err = item.Value(func(val []byte) error {
ts, err = strconv.ParseInt(string(val), 10, 64)
return err
})
return err
})
return ts, err
}

func storeLastFlattenTs(db *badger.DB, ts int64) error {
return db.Update(func(txn *badger.Txn) error {
v := strconv.FormatInt(ts, 10)
return txn.Set(lastFlattenTsKey, []byte(v))
})
}
1 change: 1 addition & 0 deletions database/timeseries/syscall_not_linux_unix.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !linux
// +build !linux

package timeseries
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ require (
)

replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

replace github.com/genjidb/genji/engine/badgerengine => github.com/crazycs520/genji/engine/badgerengine v0.12.1-0.20220328082424-727a2d089bde
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crazycs520/genji/engine/badgerengine v0.12.1-0.20220328082424-727a2d089bde h1:URhoDN38NcI0UfTnCQZQ6h1254HPAlf/rDdwCDz03dI=
github.com/crazycs520/genji/engine/badgerengine v0.12.1-0.20220328082424-727a2d089bde/go.mod h1:VQwifaTDGx8niLOyHeVdZNJ4cCXsxV3Ub4zgJc+Bfck=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
Expand Down Expand Up @@ -278,8 +280,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/genjidb/genji v0.13.0 h1:WKjuoy4TRemCxVgcZ1/+POGKhjkDj3ZSTB/zlHmS7l0=
github.com/genjidb/genji v0.13.0/go.mod h1:2QAT8ZovhZiEDQCaeOMI4HW5UiLm6PmXUxlUiKEuSjU=
github.com/genjidb/genji/engine/badgerengine v0.13.0 h1:eLJKW7mWV2aIDTI8F+vck2Xetuq76TtayLz5Ed7NRko=
github.com/genjidb/genji/engine/badgerengine v0.13.0/go.mod h1:9a5HvewPm08l82xzujrbSF2XQijTG8N2zVLp9tWbfe4=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
Expand Down
11 changes: 11 additions & 0 deletions utils/testutil/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ func NewGenjiDB(t *testing.T, storagePath string) *genji.DB {
return db
}

func NewBadgerDB(t *testing.T, storagePath string) *badger.DB {
opts := badger.DefaultOptions(storagePath).
WithZSTDCompressionLevel(3).
WithBlockSize(8 * 1024).
WithValueThreshold(128 * 1024)

engine, err := badgerengine.NewEngine(opts)
require.NoError(t, err)
return engine.DB
}

type MockProfileServer struct {
Addr string
Port uint
Expand Down

0 comments on commit 1ab7159

Please sign in to comment.