github.com/ethersphere/bee/v2@v2.2.0/pkg/shed/db.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package shed provides a simple abstraction components to compose
    18  // more complex operations on storage data organized in fields and indexes.
    19  //
    20  // Only type which holds logical information about swarm storage chunks data
    21  // and metadata is Item. This part is not generalized mostly for
    22  // performance reasons.
    23  package shed
    24  
    25  import (
    26  	"errors"
    27  
    28  	"github.com/syndtr/goleveldb/leveldb"
    29  	"github.com/syndtr/goleveldb/leveldb/iterator"
    30  	"github.com/syndtr/goleveldb/leveldb/opt"
    31  	"github.com/syndtr/goleveldb/leveldb/storage"
    32  	"github.com/syndtr/goleveldb/leveldb/util"
    33  )
    34  
    35  var (
    36  	defaultOpenFilesLimit         = uint64(256)
    37  	defaultBlockCacheCapacity     = uint64(1 * 1024 * 1024)
    38  	defaultWriteBufferSize        = uint64(1 * 1024 * 1024)
    39  	defaultDisableSeeksCompaction = false
    40  )
    41  
    42  type Options struct {
    43  	BlockCacheCapacity     uint64
    44  	WriteBufferSize        uint64
    45  	OpenFilesLimit         uint64
    46  	DisableSeeksCompaction bool
    47  }
    48  
    49  // DB provides abstractions over LevelDB in order to
    50  // implement complex structures using fields and ordered indexes.
    51  // It provides a schema functionality to store fields and indexes
    52  // information about naming and types.
    53  type DB struct {
    54  	ldb     *leveldb.DB
    55  	metrics metrics
    56  	quit    chan struct{} // Quit channel to stop the metrics collection before closing the database
    57  }
    58  
    59  // NewDB constructs a new DB and validates the schema
    60  // if it exists in database on the given path.
    61  // metricsPrefix is used for metrics collection for the given DB.
    62  func NewDB(path string, o *Options) (db *DB, err error) {
    63  	if o == nil {
    64  		o = &Options{
    65  			OpenFilesLimit:         defaultOpenFilesLimit,
    66  			BlockCacheCapacity:     defaultBlockCacheCapacity,
    67  			WriteBufferSize:        defaultWriteBufferSize,
    68  			DisableSeeksCompaction: defaultDisableSeeksCompaction,
    69  		}
    70  	}
    71  	var ldb *leveldb.DB
    72  	if path == "" {
    73  		ldb, err = leveldb.Open(storage.NewMemStorage(), nil)
    74  	} else {
    75  		ldb, err = leveldb.OpenFile(path, &opt.Options{
    76  			OpenFilesCacheCapacity: int(o.OpenFilesLimit),
    77  			BlockCacheCapacity:     int(o.BlockCacheCapacity),
    78  			WriteBuffer:            int(o.WriteBufferSize),
    79  			DisableSeeksCompaction: o.DisableSeeksCompaction,
    80  		})
    81  	}
    82  
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	return NewDBWrap(ldb)
    88  }
    89  
    90  // NewDBWrap returns new DB which uses the given ldb as its underlying storage.
    91  // The function will panics if the given ldb is nil.
    92  func NewDBWrap(ldb *leveldb.DB) (db *DB, err error) {
    93  	if ldb == nil {
    94  		panic(errors.New("shed: NewDBWrap: nil ldb"))
    95  	}
    96  
    97  	db = &DB{
    98  		ldb:     ldb,
    99  		metrics: newMetrics(),
   100  	}
   101  
   102  	if _, err = db.getSchema(); err != nil {
   103  		if errors.Is(err, leveldb.ErrNotFound) {
   104  			// Save schema with initialized default fields.
   105  			if err = db.putSchema(schema{
   106  				Fields:  make(map[string]fieldSpec),
   107  				Indexes: make(map[byte]indexSpec),
   108  			}); err != nil {
   109  				return nil, err
   110  			}
   111  		} else {
   112  			return nil, err
   113  		}
   114  	}
   115  
   116  	// Create a quit channel for the periodic metrics collector and run it.
   117  	db.quit = make(chan struct{})
   118  
   119  	return db, nil
   120  }
   121  
   122  // Put wraps LevelDB Put method to increment metrics counter.
   123  func (db *DB) Put(key, value []byte) (err error) {
   124  	err = db.ldb.Put(key, value, nil)
   125  	if err != nil {
   126  		db.metrics.PutFailCounter.Inc()
   127  		return err
   128  	}
   129  	db.metrics.PutCounter.Inc()
   130  	return nil
   131  }
   132  
   133  // Get wraps LevelDB Get method to increment metrics counter.
   134  func (db *DB) Get(key []byte) (value []byte, err error) {
   135  	value, err = db.ldb.Get(key, nil)
   136  	if err != nil {
   137  		if errors.Is(err, leveldb.ErrNotFound) {
   138  			db.metrics.GetNotFoundCounter.Inc()
   139  		} else {
   140  			db.metrics.GetFailCounter.Inc()
   141  		}
   142  		return nil, err
   143  	}
   144  	db.metrics.GetCounter.Inc()
   145  	return value, nil
   146  }
   147  
   148  // Has wraps LevelDB Has method to increment metrics counter.
   149  func (db *DB) Has(key []byte) (yes bool, err error) {
   150  	yes, err = db.ldb.Has(key, nil)
   151  	if err != nil {
   152  		db.metrics.HasFailCounter.Inc()
   153  		return false, err
   154  	}
   155  	db.metrics.HasCounter.Inc()
   156  	return yes, nil
   157  }
   158  
   159  // Delete wraps LevelDB Delete method to increment metrics counter.
   160  func (db *DB) Delete(key []byte) (err error) {
   161  	err = db.ldb.Delete(key, nil)
   162  	if err != nil {
   163  		db.metrics.DeleteFailCounter.Inc()
   164  		return err
   165  	}
   166  	db.metrics.DeleteCounter.Inc()
   167  	return nil
   168  }
   169  
   170  // NewIterator wraps LevelDB NewIterator method to increment metrics counter.
   171  func (db *DB) NewIterator() iterator.Iterator {
   172  	db.metrics.IteratorCounter.Inc()
   173  	return db.ldb.NewIterator(nil, nil)
   174  }
   175  
   176  // WriteBatch wraps LevelDB Write method to increment metrics counter.
   177  func (db *DB) WriteBatch(batch *leveldb.Batch) (err error) {
   178  	err = db.ldb.Write(batch, nil)
   179  	if err != nil {
   180  		db.metrics.WriteBatchFailCounter.Inc()
   181  		return err
   182  	}
   183  	db.metrics.WriteBatchCounter.Inc()
   184  	return nil
   185  }
   186  
   187  // Compact triggers a full database compaction on the underlying
   188  // LevelDB instance. Use with care! This can be very expensive!
   189  func (db *DB) Compact(start, end []byte) error {
   190  	r := util.Range{Start: start, Limit: end}
   191  	return db.ldb.CompactRange(r)
   192  }
   193  
   194  // Close closes LevelDB database.
   195  func (db *DB) Close() (err error) {
   196  	close(db.quit)
   197  	return db.ldb.Close()
   198  }