Lazy loading (#3958)
* types packages is imported more than onece * lazy-loading * fix bugs * fix bugs * fix unit tests * fix test error * rename function * unload ldb after initial startup * Don't load ldb when starting volume server if ldbtimeout is set. * remove uncessary unloadldb * Update weed/command/server.go Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> * Update weed/command/volume.go Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> Co-authored-by: guol-fnst <goul-fnst@fujitsu.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
@@ -26,12 +28,18 @@ var watermarkKey = []byte("idx_entry_watermark")
|
||||
|
||||
type LevelDbNeedleMap struct {
|
||||
baseNeedleMapper
|
||||
dbFileName string
|
||||
db *leveldb.DB
|
||||
dbFileName string
|
||||
db *leveldb.DB
|
||||
ldbOpts *opt.Options
|
||||
ldbAccessLock sync.RWMutex
|
||||
exitChan chan bool
|
||||
// no need to use atomic
|
||||
accessFlag int64
|
||||
ldbTimeout int64
|
||||
recordCount uint64
|
||||
}
|
||||
|
||||
func NewLevelDbNeedleMap(dbFileName string, indexFile *os.File, opts *opt.Options) (m *LevelDbNeedleMap, err error) {
|
||||
func NewLevelDbNeedleMap(dbFileName string, indexFile *os.File, opts *opt.Options, ldbTimeout int64) (m *LevelDbNeedleMap, err error) {
|
||||
m = &LevelDbNeedleMap{dbFileName: dbFileName}
|
||||
m.indexFile = indexFile
|
||||
if !isLevelDbFresh(dbFileName, indexFile) {
|
||||
@@ -46,27 +54,36 @@ func NewLevelDbNeedleMap(dbFileName string, indexFile *os.File, opts *opt.Option
|
||||
}
|
||||
glog.V(1).Infof("Opening %s...", dbFileName)
|
||||
|
||||
if m.db, err = leveldb.OpenFile(dbFileName, opts); err != nil {
|
||||
if errors.IsCorrupted(err) {
|
||||
m.db, err = leveldb.RecoverFile(dbFileName, opts)
|
||||
if m.ldbTimeout == 0 {
|
||||
if m.db, err = leveldb.OpenFile(dbFileName, opts); err != nil {
|
||||
if errors.IsCorrupted(err) {
|
||||
m.db, err = leveldb.RecoverFile(dbFileName, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(0).Infof("Loading %s... , watermark: %d", dbFileName, getWatermark(m.db))
|
||||
m.recordCount = uint64(m.indexFileOffset / NeedleMapEntrySize)
|
||||
watermark := (m.recordCount / watermarkBatchSize) * watermarkBatchSize
|
||||
err = setWatermark(m.db, watermark)
|
||||
if err != nil {
|
||||
glog.Fatalf("set watermark for %s error: %s\n", dbFileName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(0).Infof("Loading %s... , watermark: %d", dbFileName, getWatermark(m.db))
|
||||
m.recordCount = uint64(m.indexFileOffset / NeedleMapEntrySize)
|
||||
watermark := (m.recordCount / watermarkBatchSize) * watermarkBatchSize
|
||||
err = setWatermark(m.db, watermark)
|
||||
if err != nil {
|
||||
glog.Fatalf("set watermark for %s error: %s\n", dbFileName, err)
|
||||
return
|
||||
}
|
||||
mm, indexLoadError := newNeedleMapMetricFromIndexFile(indexFile)
|
||||
if indexLoadError != nil {
|
||||
return nil, indexLoadError
|
||||
}
|
||||
m.mapMetric = *mm
|
||||
m.ldbTimeout = ldbTimeout
|
||||
if m.ldbTimeout > 0 {
|
||||
m.ldbOpts = opts
|
||||
m.exitChan = make(chan bool, 1)
|
||||
m.accessFlag = 0
|
||||
go lazyLoadingRoutine(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +133,14 @@ func generateLevelDbFile(dbFileName string, indexFile *os.File) error {
|
||||
|
||||
func (m *LevelDbNeedleMap) Get(key NeedleId) (element *needle_map.NeedleValue, ok bool) {
|
||||
bytes := make([]byte, NeedleIdSize)
|
||||
if m.ldbTimeout > 0 {
|
||||
m.ldbAccessLock.RLock()
|
||||
defer m.ldbAccessLock.RUnlock()
|
||||
loadErr := reloadLdb(m)
|
||||
if loadErr != nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
NeedleIdToBytes(bytes[0:NeedleIdSize], key)
|
||||
data, err := m.db.Get(bytes, nil)
|
||||
if err != nil || len(data) != OffsetSize+SizeSize {
|
||||
@@ -129,6 +154,14 @@ func (m *LevelDbNeedleMap) Get(key NeedleId) (element *needle_map.NeedleValue, o
|
||||
func (m *LevelDbNeedleMap) Put(key NeedleId, offset Offset, size Size) error {
|
||||
var oldSize Size
|
||||
var watermark uint64
|
||||
if m.ldbTimeout > 0 {
|
||||
m.ldbAccessLock.RLock()
|
||||
defer m.ldbAccessLock.RUnlock()
|
||||
loadErr := reloadLdb(m)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
}
|
||||
}
|
||||
if oldNeedle, ok := m.Get(key); ok {
|
||||
oldSize = oldNeedle.Size
|
||||
}
|
||||
@@ -188,6 +221,14 @@ func levelDbDelete(db *leveldb.DB, key NeedleId) error {
|
||||
|
||||
func (m *LevelDbNeedleMap) Delete(key NeedleId, offset Offset) error {
|
||||
var watermark uint64
|
||||
if m.ldbTimeout > 0 {
|
||||
m.ldbAccessLock.RLock()
|
||||
defer m.ldbAccessLock.RUnlock()
|
||||
loadErr := reloadLdb(m)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
}
|
||||
}
|
||||
oldNeedle, found := m.Get(key)
|
||||
if !found || oldNeedle.Size.IsDeleted() {
|
||||
return nil
|
||||
@@ -223,6 +264,9 @@ func (m *LevelDbNeedleMap) Close() {
|
||||
glog.Warningf("close levelDB failed: %v", err)
|
||||
}
|
||||
}
|
||||
if m.ldbTimeout > 0 {
|
||||
m.exitChan <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LevelDbNeedleMap) Destroy() error {
|
||||
@@ -231,7 +275,7 @@ func (m *LevelDbNeedleMap) Destroy() error {
|
||||
return os.RemoveAll(m.dbFileName)
|
||||
}
|
||||
|
||||
func (m *LevelDbNeedleMap) UpdateNeedleMap(v *Volume, indexFile *os.File, opts *opt.Options) error {
|
||||
func (m *LevelDbNeedleMap) UpdateNeedleMap(v *Volume, indexFile *os.File, opts *opt.Options, ldbTimeout int64) error {
|
||||
if v.nm != nil {
|
||||
v.nm.Close()
|
||||
v.nm = nil
|
||||
@@ -280,6 +324,13 @@ func (m *LevelDbNeedleMap) UpdateNeedleMap(v *Volume, indexFile *os.File, opts *
|
||||
}
|
||||
v.nm = m
|
||||
v.tmpNm = nil
|
||||
m.ldbTimeout = ldbTimeout
|
||||
if m.ldbTimeout > 0 {
|
||||
m.ldbOpts = opts
|
||||
m.exitChan = make(chan bool, 1)
|
||||
m.accessFlag = 0
|
||||
go lazyLoadingRoutine(m)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -348,3 +399,61 @@ func (m *LevelDbNeedleMap) DoOffsetLoading(v *Volume, indexFile *os.File, startF
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func reloadLdb(m *LevelDbNeedleMap) (err error) {
|
||||
if m.db != nil {
|
||||
return nil
|
||||
}
|
||||
glog.V(1).Infof("reloading leveldb %s", m.dbFileName)
|
||||
m.accessFlag = 1
|
||||
if m.db, err = leveldb.OpenFile(m.dbFileName, m.ldbOpts); err != nil {
|
||||
if errors.IsCorrupted(err) {
|
||||
m.db, err = leveldb.RecoverFile(m.dbFileName, m.ldbOpts)
|
||||
}
|
||||
if err != nil {
|
||||
glog.Fatalf("RecoverFile %s failed:%v", m.dbFileName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unloadLdb(m *LevelDbNeedleMap) (err error) {
|
||||
m.ldbAccessLock.Lock()
|
||||
defer m.ldbAccessLock.Unlock()
|
||||
if m.db != nil {
|
||||
glog.V(1).Infof("reached max idle count, unload leveldb, %s", m.dbFileName)
|
||||
m.db.Close()
|
||||
m.db = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lazyLoadingRoutine(m *LevelDbNeedleMap) (err error) {
|
||||
glog.V(1).Infof("lazyLoadingRoutine %s", m.dbFileName)
|
||||
var accessRecord int64
|
||||
accessRecord = 1
|
||||
for {
|
||||
select {
|
||||
case exit := <-m.exitChan:
|
||||
if exit {
|
||||
glog.V(1).Infof("exit from lazyLoadingRoutine")
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Hour * 1):
|
||||
glog.V(1).Infof("timeout %s", m.dbFileName)
|
||||
if m.accessFlag == 0 {
|
||||
accessRecord++
|
||||
glog.V(1).Infof("accessRecord++")
|
||||
if accessRecord >= m.ldbTimeout {
|
||||
unloadLdb(m)
|
||||
}
|
||||
} else {
|
||||
glog.V(1).Infof("reset accessRecord %s", m.dbFileName)
|
||||
// reset accessRecord
|
||||
accessRecord = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user