mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-16 21:10:47 +08:00
Added multi-database support to snapshot module
This commit is contained in:
@@ -250,34 +250,36 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
ApplyDeleteKey: echovault.raftApplyDeleteKey,
|
ApplyDeleteKey: echovault.raftApplyDeleteKey,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// TODO: Update snapshot engine to support multiple databases.
|
|
||||||
// Set up standalone snapshot engine
|
// Set up standalone snapshot engine
|
||||||
// echovault.snapshotEngine = snapshot.NewSnapshotEngine(
|
echovault.snapshotEngine = snapshot.NewSnapshotEngine(
|
||||||
// snapshot.WithClock(echovault.clock),
|
snapshot.WithClock(echovault.clock),
|
||||||
// snapshot.WithDirectory(echovault.config.DataDir),
|
snapshot.WithDirectory(echovault.config.DataDir),
|
||||||
// snapshot.WithThreshold(echovault.config.SnapShotThreshold),
|
snapshot.WithThreshold(echovault.config.SnapShotThreshold),
|
||||||
// snapshot.WithInterval(echovault.config.SnapshotInterval),
|
snapshot.WithInterval(echovault.config.SnapshotInterval),
|
||||||
// snapshot.WithStartSnapshotFunc(echovault.startSnapshot),
|
snapshot.WithStartSnapshotFunc(echovault.startSnapshot),
|
||||||
// snapshot.WithFinishSnapshotFunc(echovault.finishSnapshot),
|
snapshot.WithFinishSnapshotFunc(echovault.finishSnapshot),
|
||||||
// snapshot.WithSetLatestSnapshotTimeFunc(echovault.setLatestSnapshot),
|
snapshot.WithSetLatestSnapshotTimeFunc(echovault.setLatestSnapshot),
|
||||||
// snapshot.WithGetLatestSnapshotTimeFunc(echovault.getLatestSnapshotTime),
|
snapshot.WithGetLatestSnapshotTimeFunc(echovault.getLatestSnapshotTime),
|
||||||
// snapshot.WithGetStateFunc(func() map[string]internal.KeyData {
|
snapshot.WithGetStateFunc(func() map[int]map[string]internal.KeyData {
|
||||||
// state := make(map[string]internal.KeyData)
|
state := make(map[int]map[string]internal.KeyData)
|
||||||
// for k, v := range echovault.getState() {
|
for database, data := range echovault.getState() {
|
||||||
// if data, ok := v.(internal.KeyData); ok {
|
state[database] = make(map[string]internal.KeyData)
|
||||||
// state[k] = data
|
for key, value := range data {
|
||||||
// }
|
if keyData, ok := value.(internal.KeyData); ok {
|
||||||
// }
|
state[database][key] = keyData
|
||||||
// return state
|
}
|
||||||
// }),
|
}
|
||||||
// snapshot.WithSetKeyDataFunc(func(key string, data internal.KeyData) {
|
}
|
||||||
// ctx := context.Background()
|
return state
|
||||||
// if err := echovault.setValues(ctx, map[string]interface{}{key: data.Value}); err != nil {
|
}),
|
||||||
// log.Println(err)
|
snapshot.WithSetKeyDataFunc(func(database int, key string, data internal.KeyData) {
|
||||||
// }
|
ctx := context.WithValue(context.Background(), "Database", database)
|
||||||
// echovault.setExpiry(ctx, key, data.ExpireAt, false)
|
if err := echovault.setValues(ctx, map[string]interface{}{key: data.Value}); err != nil {
|
||||||
// }),
|
log.Println(err)
|
||||||
// )
|
}
|
||||||
|
echovault.setExpiry(ctx, key, data.ExpireAt, false)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: Update AOF engine to support multiple databases.
|
// TODO: Update AOF engine to support multiple databases.
|
||||||
// Set up standalone AOF engine
|
// Set up standalone AOF engine
|
||||||
|
@@ -883,7 +883,7 @@ func Test_Standalone(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
dataDir string
|
dataDir string
|
||||||
values map[string]string
|
values map[int]map[string]string
|
||||||
snapshotFunc func(mockServer *EchoVault) error
|
snapshotFunc func(mockServer *EchoVault) error
|
||||||
lastSaveFunc func(mockServer *EchoVault) (int, error)
|
lastSaveFunc func(mockServer *EchoVault) (int, error)
|
||||||
wantLastSave int
|
wantLastSave int
|
||||||
@@ -891,11 +891,9 @@ func Test_Standalone(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "1. Snapshot in embedded instance",
|
name: "1. Snapshot in embedded instance",
|
||||||
dataDir: path.Join(dataDir, "embedded_instance"),
|
dataDir: path.Join(dataDir, "embedded_instance"),
|
||||||
values: map[string]string{
|
values: map[int]map[string]string{
|
||||||
"key5": "value5",
|
0: {"key5": "value-05", "key6": "value-06", "key7": "value-07", "key8": "value-08"},
|
||||||
"key6": "value6",
|
1: {"key5": "value-15", "key6": "value-16", "key7": "value-17", "key8": "value-18"},
|
||||||
"key7": "value7",
|
|
||||||
"key8": "value8",
|
|
||||||
},
|
},
|
||||||
snapshotFunc: func(mockServer *EchoVault) error {
|
snapshotFunc: func(mockServer *EchoVault) error {
|
||||||
if _, err := mockServer.Save(); err != nil {
|
if _, err := mockServer.Save(); err != nil {
|
||||||
@@ -937,10 +935,13 @@ func Test_Standalone(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Trigger some write commands
|
// Trigger some write commands
|
||||||
for key, value := range test.values {
|
for database, data := range test.values {
|
||||||
if _, _, err = mockServer.Set(key, value, SetOptions{}); err != nil {
|
_ = mockServer.SelectDB(database)
|
||||||
t.Error(err)
|
for key, value := range data {
|
||||||
return
|
if _, _, err = mockServer.Set(key, value, SetOptions{}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,15 +963,18 @@ func Test_Standalone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check that all the key/value pairs have been restored into the store.
|
// Check that all the key/value pairs have been restored into the store.
|
||||||
for key, value := range test.values {
|
for database, data := range test.values {
|
||||||
res, err := mockServer.Get(key)
|
_ = mockServer.SelectDB(database)
|
||||||
if err != nil {
|
for key, value := range data {
|
||||||
t.Error(err)
|
res, err := mockServer.Get(key)
|
||||||
return
|
if err != nil {
|
||||||
}
|
t.Error(err)
|
||||||
if res != value {
|
return
|
||||||
t.Errorf("expected value at key \"%s\" to be \"%s\", got \"%s\"", key, value, res)
|
}
|
||||||
return
|
if res != value {
|
||||||
|
t.Errorf("expected value at key \"%s\" to be \"%s\", got \"%s\"", key, value, res)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -215,8 +215,7 @@ func (server *EchoVault) setValues(ctx context.Context, entries map[string]inter
|
|||||||
ExpireAt: expireAt,
|
ExpireAt: expireAt,
|
||||||
}
|
}
|
||||||
if !server.isInCluster() {
|
if !server.isInCluster() {
|
||||||
// TODO: Enable this when snapshot engine has support for multiple databases.
|
server.snapshotEngine.IncrementChangeCount()
|
||||||
// server.snapshotEngine.IncrementChangeCount()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +317,7 @@ func (server *EchoVault) getState() map[int]map[string]interface{} {
|
|||||||
}
|
}
|
||||||
data := make(map[int]map[string]interface{})
|
data := make(map[int]map[string]interface{})
|
||||||
for db, store := range server.store {
|
for db, store := range server.store {
|
||||||
|
data[db] = make(map[string]interface{})
|
||||||
for k, v := range store {
|
for k, v := range store {
|
||||||
data[db][k] = v
|
data[db][k] = v
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ func createEchoVaultWithConfig(conf config.Config) *EchoVault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func presetValue(server *EchoVault, ctx context.Context, key string, value interface{}) error {
|
func presetValue(server *EchoVault, ctx context.Context, key string, value interface{}) error {
|
||||||
ctx = context.WithValue(ctx, "Database", "0")
|
ctx = context.WithValue(ctx, "Database", 0)
|
||||||
if err := server.setValues(ctx, map[string]interface{}{key: value}); err != nil {
|
if err := server.setValues(ctx, map[string]interface{}{key: value}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ func presetValue(server *EchoVault, ctx context.Context, key string, value inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
func presetKeyData(server *EchoVault, ctx context.Context, key string, data internal.KeyData) {
|
func presetKeyData(server *EchoVault, ctx context.Context, key string, data internal.KeyData) {
|
||||||
ctx = context.WithValue(ctx, "Database", "0")
|
ctx = context.WithValue(ctx, "Database", 0)
|
||||||
_ = server.setValues(ctx, map[string]interface{}{key: data.Value})
|
_ = server.setValues(ctx, map[string]interface{}{key: data.Value})
|
||||||
server.setExpiry(ctx, key, data.ExpireAt, false)
|
server.setExpiry(ctx, key, data.ExpireAt, false)
|
||||||
}
|
}
|
||||||
|
@@ -15,9 +15,17 @@
|
|||||||
package snapshot
|
package snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
"github.com/echovault/echovault/internal/clock"
|
"github.com/echovault/echovault/internal/clock"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -38,10 +46,10 @@ type Engine struct {
|
|||||||
snapshotThreshold uint64
|
snapshotThreshold uint64
|
||||||
startSnapshotFunc func()
|
startSnapshotFunc func()
|
||||||
finishSnapshotFunc func()
|
finishSnapshotFunc func()
|
||||||
getStateFunc func() map[string]internal.KeyData
|
getStateFunc func() map[int]map[string]internal.KeyData
|
||||||
setLatestSnapshotTimeFunc func(msec int64)
|
setLatestSnapshotTimeFunc func(msec int64)
|
||||||
getLatestSnapshotTimeFunc func() int64
|
getLatestSnapshotTimeFunc func() int64
|
||||||
setKeyDataFunc func(key string, data internal.KeyData)
|
setKeyDataFunc func(database int, key string, data internal.KeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithClock(clock clock.Clock) func(engine *Engine) {
|
func WithClock(clock clock.Clock) func(engine *Engine) {
|
||||||
@@ -80,7 +88,7 @@ func WithFinishSnapshotFunc(f func()) func(engine *Engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithGetStateFunc(f func() map[string]internal.KeyData) func(engine *Engine) {
|
func WithGetStateFunc(f func() map[int]map[string]internal.KeyData) func(engine *Engine) {
|
||||||
return func(engine *Engine) {
|
return func(engine *Engine) {
|
||||||
engine.getStateFunc = f
|
engine.getStateFunc = f
|
||||||
}
|
}
|
||||||
@@ -98,7 +106,7 @@ func WithGetLatestSnapshotTimeFunc(f func() int64) func(engine *Engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithSetKeyDataFunc(f func(key string, data internal.KeyData)) func(engine *Engine) {
|
func WithSetKeyDataFunc(f func(database int, key string, data internal.KeyData)) func(engine *Engine) {
|
||||||
return func(engine *Engine) {
|
return func(engine *Engine) {
|
||||||
engine.setKeyDataFunc = f
|
engine.setKeyDataFunc = f
|
||||||
}
|
}
|
||||||
@@ -113,10 +121,10 @@ func NewSnapshotEngine(options ...func(engine *Engine)) *Engine {
|
|||||||
snapshotThreshold: 1000,
|
snapshotThreshold: 1000,
|
||||||
startSnapshotFunc: func() {},
|
startSnapshotFunc: func() {},
|
||||||
finishSnapshotFunc: func() {},
|
finishSnapshotFunc: func() {},
|
||||||
getStateFunc: func() map[string]internal.KeyData {
|
getStateFunc: func() map[int]map[string]internal.KeyData {
|
||||||
return map[string]internal.KeyData{}
|
return make(map[int]map[string]internal.KeyData)
|
||||||
},
|
},
|
||||||
setKeyDataFunc: func(key string, data internal.KeyData) {},
|
setKeyDataFunc: func(database int, key string, data internal.KeyData) {},
|
||||||
setLatestSnapshotTimeFunc: func(msec int64) {},
|
setLatestSnapshotTimeFunc: func(msec int64) {},
|
||||||
getLatestSnapshotTimeFunc: func() int64 {
|
getLatestSnapshotTimeFunc: func() int64 {
|
||||||
return 0
|
return 0
|
||||||
@@ -148,213 +156,211 @@ func NewSnapshotEngine(options ...func(engine *Engine)) *Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) TakeSnapshot() error {
|
func (engine *Engine) TakeSnapshot() error {
|
||||||
// TODO: Update to support multiple databases.
|
engine.startSnapshotFunc()
|
||||||
|
defer engine.finishSnapshotFunc()
|
||||||
|
|
||||||
//engine.startSnapshotFunc()
|
// Extract current time
|
||||||
//defer engine.finishSnapshotFunc()
|
msec := engine.clock.Now().UnixMilli()
|
||||||
//
|
|
||||||
//// Extract current time
|
// Update manifest file to indicate the latest snapshot.
|
||||||
//msec := engine.clock.Now().UnixMilli()
|
// If manifest file does not exist, create it.
|
||||||
//
|
// Manifest object will contain the following information:
|
||||||
//// Update manifest file to indicate the latest snapshot.
|
// 1. Hash of the snapshot contents.
|
||||||
//// If manifest file does not exist, create it.
|
// 2. Unix time of the latest snapshot taken.
|
||||||
//// Manifest object will contain the following information:
|
// The information above will be used to determine whether a snapshot should be taken.
|
||||||
//// 1. Hash of the snapshot contents.
|
// If the hash of the current state equals the hash in the manifest file, skip the snapshot.
|
||||||
//// 2. Unix time of the latest snapshot taken.
|
// Otherwise, take the snapshot and update the latest snapshot timestamp and hash in the manifest file.
|
||||||
//// The information above will be used to determine whether a snapshot should be taken.
|
|
||||||
//// If the hash of the current state equals the hash in the manifest file, skip the snapshot.
|
var firstSnapshot bool // Tracks whether the snapshot being attempted is the first one
|
||||||
//// Otherwise, take the snapshot and update the latest snapshot timestamp and hash in the manifest file.
|
|
||||||
//
|
dirname := path.Join(engine.directory, "snapshots")
|
||||||
//var firstSnapshot bool // Tracks whether the snapshot being attempted is the first one
|
if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
|
||||||
//
|
log.Println(err)
|
||||||
//dirname := path.Join(engine.directory, "snapshots")
|
return err
|
||||||
//if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
// Open manifest file
|
||||||
//}
|
var mf *os.File
|
||||||
//
|
mf, err := os.Open(path.Join(dirname, "manifest.bin"))
|
||||||
//// Open manifest file
|
if err != nil {
|
||||||
//var mf *os.File
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
//mf, err := os.Open(path.Join(dirname, "manifest.bin"))
|
// Create file if it does not exist
|
||||||
//if err != nil {
|
mf, err = os.Create(path.Join(dirname, "manifest.bin"))
|
||||||
// if errors.Is(err, fs.ErrNotExist) {
|
if err != nil {
|
||||||
// // Create file if it does not exist
|
log.Println(err)
|
||||||
// mf, err = os.Create(path.Join(dirname, "manifest.bin"))
|
return err
|
||||||
// if err != nil {
|
}
|
||||||
// log.Println(err)
|
firstSnapshot = true
|
||||||
// return err
|
} else {
|
||||||
// }
|
log.Println(err)
|
||||||
// firstSnapshot = true
|
return err
|
||||||
// } else {
|
}
|
||||||
// log.Println(err)
|
}
|
||||||
// return err
|
|
||||||
// }
|
md, err := io.ReadAll(mf)
|
||||||
//}
|
if err != nil {
|
||||||
//
|
log.Println(err)
|
||||||
//md, err := io.ReadAll(mf)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
if err := mf.Close(); err != nil {
|
||||||
// return err
|
log.Println(err)
|
||||||
//}
|
return err
|
||||||
//if err := mf.Close(); err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
manifest := new(Manifest)
|
||||||
//}
|
|
||||||
//
|
if !firstSnapshot {
|
||||||
//manifest := new(Manifest)
|
if err = json.Unmarshal(md, manifest); err != nil {
|
||||||
//
|
log.Println(err)
|
||||||
//if !firstSnapshot {
|
return err
|
||||||
// if err = json.Unmarshal(md, manifest); err != nil {
|
}
|
||||||
// log.Println(err)
|
}
|
||||||
// return err
|
|
||||||
// }
|
// Get current state
|
||||||
//}
|
snapshotObject := internal.SnapshotObject{
|
||||||
//
|
State: internal.FilterExpiredKeys(engine.clock.Now(), engine.getStateFunc()),
|
||||||
//// Get current state
|
LatestSnapshotMilliseconds: engine.getLatestSnapshotTimeFunc(),
|
||||||
//snapshotObject := internal.SnapshotObject{
|
}
|
||||||
// State: internal.FilterExpiredKeys(engine.clock.Now(), engine.getStateFunc()),
|
out, err := json.Marshal(snapshotObject)
|
||||||
// LatestSnapshotMilliseconds: engine.getLatestSnapshotTimeFunc(),
|
if err != nil {
|
||||||
//}
|
log.Println(err)
|
||||||
//out, err := json.Marshal(snapshotObject)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
snapshotHash := md5.Sum(out)
|
||||||
//}
|
if snapshotHash == manifest.LatestSnapshotHash {
|
||||||
//
|
return errors.New("nothing new to snapshot")
|
||||||
//snapshotHash := md5.Sum(out)
|
}
|
||||||
//if snapshotHash == manifest.LatestSnapshotHash {
|
|
||||||
// return errors.New("nothing new to snapshot")
|
// Update the snapshotObject
|
||||||
//}
|
snapshotObject.LatestSnapshotMilliseconds = msec
|
||||||
//
|
// Marshal the updated snapshotObject
|
||||||
//// Update the snapshotObject
|
out, err = json.Marshal(snapshotObject)
|
||||||
//snapshotObject.LatestSnapshotMilliseconds = msec
|
if err != nil {
|
||||||
//// Marshal the updated snapshotObject
|
log.Println(err)
|
||||||
//out, err = json.Marshal(snapshotObject)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
// os.Create will replace the old manifest file
|
||||||
//}
|
mf, err = os.Create(path.Join(dirname, "manifest.bin"))
|
||||||
//
|
if err != nil {
|
||||||
//// os.Create will replace the old manifest file
|
log.Println(err)
|
||||||
//mf, err = os.Create(path.Join(dirname, "manifest.bin"))
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
// Write the latest manifest data
|
||||||
//}
|
manifest = &Manifest{
|
||||||
//
|
LatestSnapshotHash: md5.Sum(out),
|
||||||
//// Write the latest manifest data
|
LatestSnapshotMilliseconds: msec,
|
||||||
//manifest = &Manifest{
|
}
|
||||||
// LatestSnapshotHash: md5.Sum(out),
|
mo, err := json.Marshal(manifest)
|
||||||
// LatestSnapshotMilliseconds: msec,
|
if err != nil {
|
||||||
//}
|
log.Println(err)
|
||||||
//mo, err := json.Marshal(manifest)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
if _, err = mf.Write(mo); err != nil {
|
||||||
// return err
|
log.Println(err)
|
||||||
//}
|
return err
|
||||||
//if _, err = mf.Write(mo); err != nil {
|
}
|
||||||
// log.Println(err)
|
if err = mf.Sync(); err != nil {
|
||||||
// return err
|
log.Println(err)
|
||||||
//}
|
}
|
||||||
//if err = mf.Sync(); err != nil {
|
if err = mf.Close(); err != nil {
|
||||||
// log.Println(err)
|
log.Println(err)
|
||||||
//}
|
return err
|
||||||
//if err = mf.Close(); err != nil {
|
}
|
||||||
// log.Println(err)
|
|
||||||
// return err
|
// Create snapshot directory
|
||||||
//}
|
dirname = path.Join(engine.directory, "snapshots", fmt.Sprintf("%d", msec))
|
||||||
//
|
if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
|
||||||
//// Create snapshot directory
|
return err
|
||||||
//dirname = path.Join(engine.directory, "snapshots", fmt.Sprintf("%d", msec))
|
}
|
||||||
//if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
|
|
||||||
// return err
|
// Create snapshot file
|
||||||
//}
|
f, err := os.OpenFile(path.Join(dirname, "state.bin"), os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||||
//
|
if err != nil {
|
||||||
//// Create snapshot file
|
log.Println(err)
|
||||||
//f, err := os.OpenFile(path.Join(dirname, "state.bin"), os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// log.Println(err)
|
defer func() {
|
||||||
// return err
|
if err := f.Close(); err != nil {
|
||||||
//}
|
log.Println(err)
|
||||||
//defer func() {
|
}
|
||||||
// if err := f.Close(); err != nil {
|
}()
|
||||||
// log.Println(err)
|
|
||||||
// }
|
// Write state to file
|
||||||
//}()
|
if _, err = f.Write(out); err != nil {
|
||||||
//
|
return err
|
||||||
//// Write state to file
|
}
|
||||||
//if _, err = f.Write(out); err != nil {
|
if err = f.Sync(); err != nil {
|
||||||
// return err
|
log.Println(err)
|
||||||
//}
|
}
|
||||||
//if err = f.Sync(); err != nil {
|
|
||||||
// log.Println(err)
|
// Set the latest snapshot in unix milliseconds
|
||||||
//}
|
engine.setLatestSnapshotTimeFunc(msec)
|
||||||
//
|
|
||||||
//// Set the latest snapshot in unix milliseconds
|
// Reset the change count
|
||||||
//engine.setLatestSnapshotTimeFunc(msec)
|
engine.resetChangeCount()
|
||||||
//
|
|
||||||
//// Reset the change count
|
|
||||||
//engine.resetChangeCount()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Restore() error {
|
func (engine *Engine) Restore() error {
|
||||||
// TODO: Update to support multiple databases.
|
mf, err := os.Open(path.Join(engine.directory, "snapshots", "manifest.bin"))
|
||||||
|
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return errors.New("no snapshot manifest, skipping snapshot restore")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
//mf, err := os.Open(path.Join(engine.directory, "snapshots", "manifest.bin"))
|
manifest := new(Manifest)
|
||||||
//if err != nil && errors.Is(err, fs.ErrNotExist) {
|
|
||||||
// return errors.New("no snapshot manifest, skipping snapshot restore")
|
md, err := io.ReadAll(mf)
|
||||||
//}
|
if err != nil {
|
||||||
//if err != nil {
|
return err
|
||||||
// return err
|
}
|
||||||
//}
|
|
||||||
//
|
if err = json.Unmarshal(md, manifest); err != nil {
|
||||||
//manifest := new(Manifest)
|
return err
|
||||||
//
|
}
|
||||||
//md, err := io.ReadAll(mf)
|
|
||||||
//if err != nil {
|
if manifest.LatestSnapshotMilliseconds == 0 {
|
||||||
// return err
|
return errors.New("no snapshot to restore")
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//if err = json.Unmarshal(md, manifest); err != nil {
|
sf, err := os.Open(path.Join(
|
||||||
// return err
|
engine.directory,
|
||||||
//}
|
"snapshots",
|
||||||
//
|
fmt.Sprintf("%d", manifest.LatestSnapshotMilliseconds),
|
||||||
//if manifest.LatestSnapshotMilliseconds == 0 {
|
"state.bin"))
|
||||||
// return errors.New("no snapshot to restore")
|
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||||
//}
|
return fmt.Errorf("snapshot file %d/state.bin not found, skipping snapshot", manifest.LatestSnapshotMilliseconds)
|
||||||
//
|
}
|
||||||
//sf, err := os.Open(path.Join(
|
if err != nil {
|
||||||
// engine.directory,
|
return err
|
||||||
// "snapshots",
|
}
|
||||||
// fmt.Sprintf("%d", manifest.LatestSnapshotMilliseconds),
|
|
||||||
// "state.bin"))
|
sd, err := io.ReadAll(sf)
|
||||||
//if err != nil && errors.Is(err, fs.ErrNotExist) {
|
if err != nil {
|
||||||
// return fmt.Errorf("snapshot file %d/state.bin not found, skipping snapshot", manifest.LatestSnapshotMilliseconds)
|
return nil
|
||||||
//}
|
}
|
||||||
//if err != nil {
|
|
||||||
// return err
|
snapshotObject := new(internal.SnapshotObject)
|
||||||
//}
|
|
||||||
//
|
if err = json.Unmarshal(sd, snapshotObject); err != nil {
|
||||||
//sd, err := io.ReadAll(sf)
|
return err
|
||||||
//if err != nil {
|
}
|
||||||
// return nil
|
|
||||||
//}
|
engine.setLatestSnapshotTimeFunc(snapshotObject.LatestSnapshotMilliseconds)
|
||||||
//
|
|
||||||
//snapshotObject := new(internal.SnapshotObject)
|
for database, data := range internal.FilterExpiredKeys(engine.clock.Now(), snapshotObject.State) {
|
||||||
//
|
for key, value := range data {
|
||||||
//if err = json.Unmarshal(sd, snapshotObject); err != nil {
|
engine.setKeyDataFunc(database, key, value)
|
||||||
// return err
|
}
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//engine.setLatestSnapshotTimeFunc(snapshotObject.LatestSnapshotMilliseconds)
|
log.Println("successfully restored latest snapshot")
|
||||||
//
|
|
||||||
//for key, data := range internal.FilterExpiredKeys(engine.clock.Now(), snapshotObject.State) {
|
|
||||||
// engine.setKeyDataFunc(key, data)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//log.Println("successfully restored latest snapshot")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -44,20 +44,33 @@ func Test_SnapshotEngine(t *testing.T) {
|
|||||||
snapshotInProgress.Store(false)
|
snapshotInProgress.Store(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := map[string]internal.KeyData{
|
state := map[int]map[string]internal.KeyData{
|
||||||
"key1": {Value: "value1", ExpireAt: clock.NewClock().Now().Add(13 * time.Second)},
|
0: {
|
||||||
"key2": {Value: "value2", ExpireAt: clock.NewClock().Now().Add(43 * time.Minute)},
|
"key1": {Value: "value-01", ExpireAt: clock.NewClock().Now().Add(13 * time.Second)},
|
||||||
"key3": {Value: "value3", ExpireAt: clock.NewClock().Now().Add(112 * time.Millisecond)},
|
"key2": {Value: "value-02", ExpireAt: clock.NewClock().Now().Add(43 * time.Minute)},
|
||||||
"key4": {Value: "value4", ExpireAt: clock.NewClock().Now().Add(23 * time.Second)},
|
"key3": {Value: "value-03", ExpireAt: clock.NewClock().Now().Add(112 * time.Millisecond)},
|
||||||
"key5": {Value: "value5", ExpireAt: clock.NewClock().Now().Add(121 * time.Millisecond)},
|
"key4": {Value: "value-04", ExpireAt: clock.NewClock().Now().Add(23 * time.Second)},
|
||||||
|
"key5": {Value: "value-45", ExpireAt: clock.NewClock().Now().Add(121 * time.Millisecond)},
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
"key1": {Value: "value1", ExpireAt: clock.NewClock().Now().Add(13 * time.Second)},
|
||||||
|
"key2": {Value: "value2", ExpireAt: clock.NewClock().Now().Add(43 * time.Minute)},
|
||||||
|
"key3": {Value: "value3", ExpireAt: clock.NewClock().Now().Add(112 * time.Millisecond)},
|
||||||
|
"key4": {Value: "value4", ExpireAt: clock.NewClock().Now().Add(23 * time.Second)},
|
||||||
|
"key5": {Value: "value5", ExpireAt: clock.NewClock().Now().Add(121 * time.Millisecond)},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
getStateFunc := func() map[string]internal.KeyData {
|
|
||||||
|
getStateFunc := func() map[int]map[string]internal.KeyData {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
restoredState := map[string]internal.KeyData{}
|
restoredState := make(map[int]map[string]internal.KeyData)
|
||||||
setKeyDataFunc := func(key string, data internal.KeyData) {
|
setKeyDataFunc := func(database int, key string, data internal.KeyData) {
|
||||||
restoredState[key] = data
|
if restoredState[database] == nil {
|
||||||
|
restoredState[database] = make(map[string]internal.KeyData)
|
||||||
|
}
|
||||||
|
restoredState[database][key] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestSnapshotTime int64
|
var latestSnapshotTime int64
|
||||||
@@ -85,11 +98,13 @@ func Test_SnapshotEngine(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add more records to the state
|
// Add more records to each database in the state
|
||||||
for i := 0; i < 5; i++ {
|
for database, _ := range state {
|
||||||
state[fmt.Sprintf("key%d", i)] = internal.KeyData{
|
for i := 0; i < 5; i++ {
|
||||||
Value: fmt.Sprintf("value%d", i),
|
state[database][fmt.Sprintf("key%d", i)] = internal.KeyData{
|
||||||
ExpireAt: clock.NewClock().Now().Add(time.Duration(i) * time.Second),
|
Value: fmt.Sprintf("value%d", i),
|
||||||
|
ExpireAt: clock.NewClock().Now().Add(time.Duration(i) * time.Second),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,12 +121,14 @@ func Test_SnapshotEngine(t *testing.T) {
|
|||||||
t.Errorf("expected restored state to be length %d, got %d", len(state), len(restoredState))
|
t.Errorf("expected restored state to be length %d, got %d", len(state), len(restoredState))
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, data := range restoredState {
|
for database, data := range restoredState {
|
||||||
if state[key].Value != data.Value {
|
for key, keyData := range data {
|
||||||
t.Errorf("expected value %v for key %s, got %v", state[key].Value, key, data.Value)
|
if state[database][key].Value != keyData.Value {
|
||||||
}
|
t.Errorf("expected value %v for key %s, got %v", state[database][key].Value, key, keyData.Value)
|
||||||
if !state[key].ExpireAt.Equal(data.ExpireAt) {
|
}
|
||||||
t.Errorf("expected expiry time %v for key %s, got %v", state[key].ExpireAt, key, data.ExpireAt)
|
if !state[database][key].ExpireAt.Equal(keyData.ExpireAt) {
|
||||||
|
t.Errorf("expected expiry time %v for key %s, got %v", state[database][key].ExpireAt, key, keyData.ExpireAt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user