Browse Source

Faster synchronization with asynchronous deletion of old checkpoints

feature/fastsync-async-delete-old-checkpoints
Oleksandr Brezhniev 3 years ago
parent
commit
5aa2b0e977
4 changed files with 123 additions and 6 deletions
  1. +18
    -6
      db/kvdb/kvdb.go
  2. +56
    -0
      db/kvdb/kvdb_test.go
  3. +6
    -0
      db/statedb/statedb.go
  4. +43
    -0
      db/statedb/statedb_test.go

+ 18
- 6
db/kvdb/kvdb.go

@ -49,6 +49,8 @@ type KVDB struct {
CurrentIdx common.Idx CurrentIdx common.Idx
CurrentBatch common.BatchNum CurrentBatch common.BatchNum
m sync.Mutex m sync.Mutex
mutexDelOld sync.Mutex
wg sync.WaitGroup
last *Last last *Last
} }
@ -444,10 +446,15 @@ func (k *KVDB) MakeCheckpoint() error {
return tracerr.Wrap(err) return tracerr.Wrap(err)
} }
} }
// delete old checkpoints
if err := k.deleteOldCheckpoints(); err != nil {
return tracerr.Wrap(err)
}
k.wg.Add(1)
go func() {
delErr := k.DeleteOldCheckpoints()
if delErr != nil {
log.Errorw("delete old checkpoints failed", "err", delErr)
}
k.wg.Done()
}()
return nil return nil
} }
@ -509,9 +516,12 @@ func (k *KVDB) ListCheckpoints() ([]int, error) {
return checkpoints, nil return checkpoints, nil
} }
// deleteOldCheckpoints deletes old checkpoints when there are more than
// DeleteOldCheckpoints deletes old checkpoints when there are more than
// `s.keep` checkpoints // `s.keep` checkpoints
func (k *KVDB) deleteOldCheckpoints() error {
func (k *KVDB) DeleteOldCheckpoints() error {
k.mutexDelOld.Lock()
defer k.mutexDelOld.Unlock()
list, err := k.ListCheckpoints() list, err := k.ListCheckpoints()
if err != nil { if err != nil {
return tracerr.Wrap(err) return tracerr.Wrap(err)
@ -584,4 +594,6 @@ func (k *KVDB) Close() {
if k.last != nil { if k.last != nil {
k.last.close() k.last.close()
} }
// wait for deletion of old checkpoints
k.wg.Wait()
} }

+ 56
- 0
db/kvdb/kvdb_test.go

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"sync"
"testing" "testing"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
@ -190,12 +191,67 @@ func TestDeleteOldCheckpoints(t *testing.T) {
for i := 0; i < numCheckpoints; i++ { for i := 0; i < numCheckpoints; i++ {
err = db.MakeCheckpoint() err = db.MakeCheckpoint()
require.NoError(t, err) require.NoError(t, err)
err = db.DeleteOldCheckpoints()
require.NoError(t, err)
checkpoints, err := db.ListCheckpoints() checkpoints, err := db.ListCheckpoints()
require.NoError(t, err) require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep) assert.LessOrEqual(t, len(checkpoints), keep)
} }
} }
func TestConcurrentDeleteOldCheckpoints(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.NoError(t, err)
defer require.NoError(t, os.RemoveAll(dir))
keep := 16
db, err := NewKVDB(Config{Path: dir, Keep: keep})
require.NoError(t, err)
numCheckpoints := 32
var wg sync.WaitGroup
wg.Add(numCheckpoints)
// do checkpoints and check that we never have more than `keep`
// checkpoints.
// 1 async DeleteOldCheckpoint after 1 MakeCheckpoint
for i := 0; i < numCheckpoints; i++ {
err = db.MakeCheckpoint()
require.NoError(t, err)
go func() {
err = db.DeleteOldCheckpoints()
require.NoError(t, err)
wg.Done()
}()
}
wg.Wait()
checkpoints, err := db.ListCheckpoints()
require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep)
wg.Add(numCheckpoints)
// do checkpoints and check that we never have more than `keep`
// checkpoints
// 32 concurrent DeleteOldCheckpoint after 32 MakeCheckpoint
for i := 0; i < numCheckpoints; i++ {
err = db.MakeCheckpoint()
require.NoError(t, err)
}
for i := 0; i < numCheckpoints; i++ {
go func() {
err = db.DeleteOldCheckpoints()
require.NoError(t, err)
wg.Done()
}()
}
wg.Wait()
checkpoints, err = db.ListCheckpoints()
require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep)
}
func TestGetCurrentIdx(t *testing.T) { func TestGetCurrentIdx(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb") dir, err := ioutil.TempDir("", "tmpdb")
require.NoError(t, err) require.NoError(t, err)

+ 6
- 0
db/statedb/statedb.go

@ -227,6 +227,12 @@ func (s *StateDB) MakeCheckpoint() error {
return s.db.MakeCheckpoint() return s.db.MakeCheckpoint()
} }
// DeleteOldCheckpoints deletes old checkpoints when there are more than
// `cfg.keep` checkpoints
func (s *StateDB) DeleteOldCheckpoints() error {
return s.db.DeleteOldCheckpoints()
}
// CurrentBatch returns the current in-memory CurrentBatch of the StateDB.db // CurrentBatch returns the current in-memory CurrentBatch of the StateDB.db
func (s *StateDB) CurrentBatch() common.BatchNum { func (s *StateDB) CurrentBatch() common.BatchNum {
return s.db.CurrentBatch return s.db.CurrentBatch

+ 43
- 0
db/statedb/statedb_test.go

@ -7,6 +7,7 @@ import (
"math/big" "math/big"
"os" "os"
"strings" "strings"
"sync"
"testing" "testing"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
@ -588,6 +589,48 @@ func TestDeleteOldCheckpoints(t *testing.T) {
for i := 0; i < numCheckpoints; i++ { for i := 0; i < numCheckpoints; i++ {
err = sdb.MakeCheckpoint() err = sdb.MakeCheckpoint()
require.NoError(t, err) require.NoError(t, err)
err := sdb.DeleteOldCheckpoints()
require.NoError(t, err)
checkpoints, err := sdb.db.ListCheckpoints()
require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep)
}
}
// TestConcurrentDeleteOldCheckpoints performs almost the same test than
// kvdb/kvdb_test.go TestConcurrentDeleteOldCheckpoints, but over the StateDB
func TestConcurrentDeleteOldCheckpoints(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.NoError(t, err)
defer require.NoError(t, os.RemoveAll(dir))
keep := 16
sdb, err := NewStateDB(Config{Path: dir, Keep: keep, Type: TypeSynchronizer, NLevels: 32})
require.NoError(t, err)
numCheckpoints := 32
// do checkpoints and check that we never have more than `keep`
// checkpoints
for i := 0; i < numCheckpoints; i++ {
err = sdb.MakeCheckpoint()
require.NoError(t, err)
wg := sync.WaitGroup{}
n := 10
wg.Add(n)
for j := 0; j < n; j++ {
go func() {
err := sdb.DeleteOldCheckpoints()
require.NoError(t, err)
checkpoints, err := sdb.db.ListCheckpoints()
require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep)
wg.Done()
}()
_, err := sdb.db.ListCheckpoints()
// only checking here for absence of errors, not the count of checkpoints
require.NoError(t, err)
}
wg.Wait()
checkpoints, err := sdb.db.ListCheckpoints() checkpoints, err := sdb.db.ListCheckpoints()
require.NoError(t, err) require.NoError(t, err)
assert.LessOrEqual(t, len(checkpoints), keep) assert.LessOrEqual(t, len(checkpoints), keep)

Loading…
Cancel
Save