github.com/klaytn/klaytn@v1.12.1/storage/database/badger_database.go (about)

     1  // Copyright 2018 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package database
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"time"
    23  
    24  	"github.com/dgraph-io/badger"
    25  	"github.com/klaytn/klaytn/log"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  const (
    30  	gcThreshold      = int64(1 << 30) // GB
    31  	sizeGCTickerTime = 1 * time.Minute
    32  )
    33  
    34  type badgerDB struct {
    35  	fn string // filename for reporting
    36  	db *badger.DB
    37  
    38  	gcTicker *time.Ticker  // runs periodically and runs gc if db size exceeds the threshold.
    39  	closeCh  chan struct{} // stops gc go routine when db closes.
    40  
    41  	logger log.Logger // Contextual logger tracking the database path
    42  }
    43  
    44  func getBadgerDBOptions(dbDir string) badger.Options {
    45  	opts := badger.DefaultOptions(dbDir)
    46  	return opts
    47  }
    48  
    49  func NewBadgerDB(dbDir string) (*badgerDB, error) {
    50  	localLogger := logger.NewWith("dbDir", dbDir)
    51  
    52  	if fi, err := os.Stat(dbDir); err == nil {
    53  		if !fi.IsDir() {
    54  			return nil, fmt.Errorf("failed to make badgerDB while checking dbDir. Given dbDir is not a directory. dbDir: %v", dbDir)
    55  		}
    56  	} else if os.IsNotExist(err) {
    57  		if err := os.MkdirAll(dbDir, 0o755); err != nil {
    58  			return nil, fmt.Errorf("failed to make badgerDB while making dbDir. dbDir: %v, err: %v", dbDir, err)
    59  		}
    60  	} else {
    61  		return nil, fmt.Errorf("failed to make badgerDB while checking dbDir. dbDir: %v, err: %v", dbDir, err)
    62  	}
    63  
    64  	opts := getBadgerDBOptions(dbDir)
    65  	db, err := badger.Open(opts)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("failed to make badgerDB while opening the DB. dbDir: %v, err: %v", dbDir, err)
    68  	}
    69  
    70  	badger := &badgerDB{
    71  		fn:       dbDir,
    72  		db:       db,
    73  		logger:   localLogger,
    74  		gcTicker: time.NewTicker(sizeGCTickerTime),
    75  		closeCh:  make(chan struct{}),
    76  	}
    77  
    78  	go badger.runValueLogGC()
    79  
    80  	return badger, nil
    81  }
    82  
    83  // runValueLogGC runs gc for two cases.
    84  // It periodically checks the size of value log and runs gc if it exceeds gcThreshold.
    85  func (bg *badgerDB) runValueLogGC() {
    86  	_, lastValueLogSize := bg.db.Size()
    87  
    88  	for {
    89  		select {
    90  		case <-bg.closeCh:
    91  			bg.logger.Debug("Stopped value log GC", "dbDir", bg.fn)
    92  			return
    93  		case <-bg.gcTicker.C:
    94  			_, currValueLogSize := bg.db.Size()
    95  			if currValueLogSize-lastValueLogSize < gcThreshold {
    96  				continue
    97  			}
    98  
    99  			err := bg.db.RunValueLogGC(0.5)
   100  			if err != nil {
   101  				bg.logger.Error("Error while runValueLogGC()", "err", err)
   102  				continue
   103  			}
   104  
   105  			_, lastValueLogSize = bg.db.Size()
   106  		}
   107  	}
   108  }
   109  
   110  func (bg *badgerDB) Type() DBType {
   111  	return BadgerDB
   112  }
   113  
   114  // Path returns the path to the database directory.
   115  func (bg *badgerDB) Path() string {
   116  	return bg.fn
   117  }
   118  
   119  // Put inserts the given key and value pair to the database.
   120  func (bg *badgerDB) Put(key []byte, value []byte) error {
   121  	txn := bg.db.NewTransaction(true)
   122  	defer txn.Discard()
   123  	err := txn.Set(key, value)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	return txn.Commit()
   128  }
   129  
   130  // Has returns true if the corresponding value to the given key exists.
   131  func (bg *badgerDB) Has(key []byte) (bool, error) {
   132  	txn := bg.db.NewTransaction(false)
   133  	defer txn.Discard()
   134  	item, err := txn.Get(key)
   135  	if err != nil {
   136  		if err == badger.ErrKeyNotFound {
   137  			return false, nil
   138  		}
   139  		return false, err
   140  	}
   141  	err = item.Value(nil)
   142  	return err == nil, err
   143  }
   144  
   145  // Get returns the corresponding value to the given key if exists.
   146  func (bg *badgerDB) Get(key []byte) ([]byte, error) {
   147  	txn := bg.db.NewTransaction(false)
   148  	defer txn.Discard()
   149  	item, err := txn.Get(key)
   150  	if err != nil {
   151  		if err == badger.ErrKeyNotFound {
   152  			return nil, dataNotFoundErr
   153  		}
   154  		return nil, err
   155  	}
   156  
   157  	var valCopy []byte
   158  	err = item.Value(func(val []byte) error {
   159  		valCopy = append([]byte{}, val...)
   160  		return nil
   161  	})
   162  	return valCopy, err
   163  }
   164  
   165  // Delete deletes the key from the queue and database
   166  func (bg *badgerDB) Delete(key []byte) error {
   167  	txn := bg.db.NewTransaction(true)
   168  	defer txn.Discard()
   169  	err := txn.Delete(key)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	return txn.Commit()
   174  }
   175  
   176  func (bg *badgerDB) NewIterator(prefix []byte, start []byte) Iterator {
   177  	// txn := bg.db.NewTransaction(false)
   178  	// return txn.NewIterator(badger.DefaultIteratorOptions)
   179  	logger.CritWithStack("badgerDB doesn't support NewIterator")
   180  	return nil
   181  }
   182  
   183  func (bg *badgerDB) Close() {
   184  	close(bg.closeCh)
   185  	err := bg.db.Close()
   186  	if err == nil {
   187  		bg.logger.Info("Database closed")
   188  	} else {
   189  		bg.logger.Error("Failed to close database", "err", err)
   190  	}
   191  }
   192  
   193  func (bg *badgerDB) LDB() *badger.DB {
   194  	return bg.db
   195  }
   196  
   197  func (bg *badgerDB) NewBatch() Batch {
   198  	txn := bg.db.NewTransaction(true)
   199  	return &badgerBatch{db: bg.db, txn: txn}
   200  }
   201  
   202  func (bg *badgerDB) Meter(prefix string) {
   203  	logger.Warn("badgerDB does not support metrics!")
   204  }
   205  
   206  func (bg *badgerDB) TryCatchUpWithPrimary() error {
   207  	return nil
   208  }
   209  
   210  type badgerBatch struct {
   211  	db   *badger.DB
   212  	txn  *badger.Txn
   213  	size int
   214  }
   215  
   216  // Put inserts the given value into the batch for later committing.
   217  func (b *badgerBatch) Put(key, value []byte) error {
   218  	err := b.txn.Set(key, value)
   219  	b.size += len(value)
   220  	return err
   221  }
   222  
   223  // Delete inserts the a key removal into the batch for later committing.
   224  func (b *badgerBatch) Delete(key []byte) error {
   225  	if err := b.txn.Delete(key); err != nil {
   226  		return err
   227  	}
   228  	b.size += 1
   229  	return nil
   230  }
   231  
   232  // Write flushes any accumulated data to disk.
   233  func (b *badgerBatch) Write() error {
   234  	return b.txn.Commit()
   235  }
   236  
   237  // ValueSize retrieves the amount of data queued up for writing.
   238  func (b *badgerBatch) ValueSize() int {
   239  	return b.size
   240  }
   241  
   242  // Replay replays the batch contents.
   243  func (b *badgerBatch) Reset() {
   244  	b.txn = b.db.NewTransaction(true)
   245  	b.size = 0
   246  }
   247  
   248  func (b *badgerBatch) Release() {
   249  	// nothing to do with badgerBatch
   250  }
   251  
   252  // Replay replays the batch contents.
   253  func (b *badgerBatch) Replay(w KeyValueWriter) error {
   254  	logger.CritWithStack("Replay is not implemented in badgerBatch!")
   255  	return nil
   256  }
   257  
   258  func (bg *badgerDB) Stat(property string) (string, error) {
   259  	return "", errors.New("unknown property")
   260  }
   261  
   262  func (bg *badgerDB) Compact(start []byte, limit []byte) error {
   263  	return nil
   264  }