github.com/klaytn/klaytn@v1.10.2/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  )
    27  
    28  const (
    29  	gcThreshold      = int64(1 << 30) // GB
    30  	sizeGCTickerTime = 1 * time.Minute
    31  )
    32  
    33  type badgerDB struct {
    34  	fn string // filename for reporting
    35  	db *badger.DB
    36  
    37  	gcTicker *time.Ticker  // runs periodically and runs gc if db size exceeds the threshold.
    38  	closeCh  chan struct{} // stops gc go routine when db closes.
    39  
    40  	logger log.Logger // Contextual logger tracking the database path
    41  }
    42  
    43  func getBadgerDBOptions(dbDir string) badger.Options {
    44  	opts := badger.DefaultOptions(dbDir)
    45  	return opts
    46  }
    47  
    48  func NewBadgerDB(dbDir string) (*badgerDB, error) {
    49  	localLogger := logger.NewWith("dbDir", dbDir)
    50  
    51  	if fi, err := os.Stat(dbDir); err == nil {
    52  		if !fi.IsDir() {
    53  			return nil, fmt.Errorf("failed to make badgerDB while checking dbDir. Given dbDir is not a directory. dbDir: %v", dbDir)
    54  		}
    55  	} else if os.IsNotExist(err) {
    56  		if err := os.MkdirAll(dbDir, 0o755); err != nil {
    57  			return nil, fmt.Errorf("failed to make badgerDB while making dbDir. dbDir: %v, err: %v", dbDir, err)
    58  		}
    59  	} else {
    60  		return nil, fmt.Errorf("failed to make badgerDB while checking dbDir. dbDir: %v, err: %v", dbDir, err)
    61  	}
    62  
    63  	opts := getBadgerDBOptions(dbDir)
    64  	db, err := badger.Open(opts)
    65  	if err != nil {
    66  		return nil, fmt.Errorf("failed to make badgerDB while opening the DB. dbDir: %v, err: %v", dbDir, err)
    67  	}
    68  
    69  	badger := &badgerDB{
    70  		fn:       dbDir,
    71  		db:       db,
    72  		logger:   localLogger,
    73  		gcTicker: time.NewTicker(sizeGCTickerTime),
    74  		closeCh:  make(chan struct{}),
    75  	}
    76  
    77  	go badger.runValueLogGC()
    78  
    79  	return badger, nil
    80  }
    81  
    82  // runValueLogGC runs gc for two cases.
    83  // It periodically checks the size of value log and runs gc if it exceeds gcThreshold.
    84  func (bg *badgerDB) runValueLogGC() {
    85  	_, lastValueLogSize := bg.db.Size()
    86  
    87  	for {
    88  		select {
    89  		case <-bg.closeCh:
    90  			bg.logger.Debug("Stopped value log GC", "dbDir", bg.fn)
    91  			return
    92  		case <-bg.gcTicker.C:
    93  			_, currValueLogSize := bg.db.Size()
    94  			if currValueLogSize-lastValueLogSize < gcThreshold {
    95  				continue
    96  			}
    97  
    98  			err := bg.db.RunValueLogGC(0.5)
    99  			if err != nil {
   100  				bg.logger.Error("Error while runValueLogGC()", "err", err)
   101  				continue
   102  			}
   103  
   104  			_, lastValueLogSize = bg.db.Size()
   105  		}
   106  	}
   107  }
   108  
   109  func (bg *badgerDB) Type() DBType {
   110  	return BadgerDB
   111  }
   112  
   113  // Path returns the path to the database directory.
   114  func (bg *badgerDB) Path() string {
   115  	return bg.fn
   116  }
   117  
   118  // Put inserts the given key and value pair to the database.
   119  func (bg *badgerDB) Put(key []byte, value []byte) error {
   120  	txn := bg.db.NewTransaction(true)
   121  	defer txn.Discard()
   122  	err := txn.Set(key, value)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	return txn.Commit()
   127  }
   128  
   129  // Has returns true if the corresponding value to the given key exists.
   130  func (bg *badgerDB) Has(key []byte) (bool, error) {
   131  	txn := bg.db.NewTransaction(false)
   132  	defer txn.Discard()
   133  	item, err := txn.Get(key)
   134  	if err != nil {
   135  		return false, err
   136  	}
   137  	err = item.Value(nil)
   138  	return err == nil, err
   139  }
   140  
   141  // Get returns the corresponding value to the given key if exists.
   142  func (bg *badgerDB) Get(key []byte) ([]byte, error) {
   143  	txn := bg.db.NewTransaction(false)
   144  	defer txn.Discard()
   145  	item, err := txn.Get(key)
   146  	if err != nil {
   147  		if err == badger.ErrKeyNotFound {
   148  			return nil, dataNotFoundErr
   149  		}
   150  		return nil, err
   151  	}
   152  
   153  	var valCopy []byte
   154  	err = item.Value(func(val []byte) error {
   155  		valCopy = append([]byte{}, val...)
   156  		return nil
   157  	})
   158  	return valCopy, err
   159  }
   160  
   161  // Delete deletes the key from the queue and database
   162  func (bg *badgerDB) Delete(key []byte) error {
   163  	txn := bg.db.NewTransaction(true)
   164  	defer txn.Discard()
   165  	err := txn.Delete(key)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	return txn.Commit()
   170  }
   171  
   172  func (bg *badgerDB) NewIterator(prefix []byte, start []byte) Iterator {
   173  	// txn := bg.db.NewTransaction(false)
   174  	// return txn.NewIterator(badger.DefaultIteratorOptions)
   175  	logger.CritWithStack("badgerDB doesn't support NewIterator")
   176  	return nil
   177  }
   178  
   179  func (bg *badgerDB) Close() {
   180  	close(bg.closeCh)
   181  	err := bg.db.Close()
   182  	if err == nil {
   183  		bg.logger.Info("Database closed")
   184  	} else {
   185  		bg.logger.Error("Failed to close database", "err", err)
   186  	}
   187  }
   188  
   189  func (bg *badgerDB) LDB() *badger.DB {
   190  	return bg.db
   191  }
   192  
   193  func (bg *badgerDB) NewBatch() Batch {
   194  	txn := bg.db.NewTransaction(true)
   195  	return &badgerBatch{db: bg.db, txn: txn}
   196  }
   197  
   198  func (bg *badgerDB) Meter(prefix string) {
   199  	logger.Warn("badgerDB does not support metrics!")
   200  }
   201  
   202  type badgerBatch struct {
   203  	db   *badger.DB
   204  	txn  *badger.Txn
   205  	size int
   206  }
   207  
   208  // Put inserts the given value into the batch for later committing.
   209  func (b *badgerBatch) Put(key, value []byte) error {
   210  	err := b.txn.Set(key, value)
   211  	b.size += len(value)
   212  	return err
   213  }
   214  
   215  // Delete inserts the a key removal into the batch for later committing.
   216  func (b *badgerBatch) Delete(key []byte) error {
   217  	if err := b.txn.Delete(key); err != nil {
   218  		return err
   219  	}
   220  	b.size += 1
   221  	return nil
   222  }
   223  
   224  // Write flushes any accumulated data to disk.
   225  func (b *badgerBatch) Write() error {
   226  	return b.txn.Commit()
   227  }
   228  
   229  // ValueSize retrieves the amount of data queued up for writing.
   230  func (b *badgerBatch) ValueSize() int {
   231  	return b.size
   232  }
   233  
   234  // Replay replays the batch contents.
   235  func (b *badgerBatch) Reset() {
   236  	b.txn = b.db.NewTransaction(true)
   237  	b.size = 0
   238  }
   239  
   240  // Replay replays the batch contents.
   241  func (b *badgerBatch) Replay(w KeyValueWriter) error {
   242  	logger.CritWithStack("Replay is not implemented in badgerBatch!")
   243  	return nil
   244  }