github.com/scottcagno/storage@v1.8.0/pkg/lsmtree/lsmtree.go (about)

     1  package lsmtree
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"sync"
     7  )
     8  
     9  type LSMTree struct {
    10  	lock   sync.RWMutex
    11  	opt    *Options
    12  	logDir string
    13  	sstDir string
    14  	wacl   *commitLog
    15  	memt   *rbTree
    16  	sstm   *ssTableManager
    17  	logger *Logger
    18  }
    19  
    20  // OpenLSMTree opens or creates an LSMTree instance
    21  func OpenLSMTree(options *Options) (*LSMTree, error) {
    22  	// check lsm config
    23  	opt := checkOptions(options)
    24  	// initialize base path
    25  	base, err := initBasePath(opt.BaseDir)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	// create commit log base directory
    30  	logdir := filepath.Join(base, defaultWalDir)
    31  	err = os.MkdirAll(logdir, os.ModeDir)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	// open commit log
    36  	wacl, err := openCommitLog(logdir, opt.SyncOnWrite)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	// create ss-table data base directory
    41  	sstdir := filepath.Join(base, defaultSstDir)
    42  	err = os.MkdirAll(sstdir, os.ModeDir)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	// open ss-table-manager
    47  	sstm, err := openSSTableManager(sstdir)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	// create lsm-tree instance
    52  	lsmt := &LSMTree{
    53  		opt:    opt,
    54  		logDir: logdir,
    55  		sstDir: sstdir,
    56  		wacl:   wacl,
    57  		memt:   newRBTree(),
    58  		sstm:   sstm,
    59  		logger: newLogger(opt.LoggingLevel),
    60  	}
    61  	// load mem-table with commit log data
    62  	err = lsmt.loadDataFromCommitLog()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	// return lsm-tree
    67  	return lsmt, nil
    68  }
    69  
    70  // Has returns a boolean signaling weather or not the key
    71  // is in the LSMTree. It should be noted that in some cases
    72  // this may return a false positive, but it should never
    73  // return a false negative.
    74  func (lsm *LSMTree) Has(k []byte) (bool, error) {
    75  	// read lock
    76  	lsm.lock.RLock()
    77  	defer lsm.lock.RUnlock()
    78  	// make entry for key
    79  	e := &Entry{Key: k}
    80  	// check the entry
    81  	err := checkKey(e)
    82  	if err != nil {
    83  		return false, err
    84  	}
    85  	// call internal get method
    86  	e, err = lsm.getEntry(e)
    87  	if err != nil {
    88  		return false, err
    89  	}
    90  	// otherwise, we got it
    91  	return e != nil, err
    92  }
    93  
    94  // Get takes a key and attempts to find a match in the LSMTree. If
    95  // a match cannot be found Get returns a nil value and ErrNotFound.
    96  // Get first checks the bloom filter, then the mem-table. If it is
    97  // still not found it attempts to do a binary search on the for the
    98  // key in the ss-index and if that yields no result it will try to
    99  // find the entry by doing a linear search of the ss-table itself.
   100  func (lsm *LSMTree) Get(k []byte) ([]byte, error) {
   101  	// read lock
   102  	lsm.lock.RLock()
   103  	defer lsm.lock.RUnlock()
   104  	// make entry for key
   105  	e := &Entry{Key: k}
   106  	// check the entry
   107  	err := checkKey(e)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	// call internal get method
   112  	ent, err := lsm.getEntry(e)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	// otherwise, we got it!
   117  	return ent.Value, nil
   118  }
   119  
   120  // Put takes a key and a value and adds them to the LSMTree. If
   121  // the entry already exists, it should overwrite the old entry.
   122  func (lsm *LSMTree) Put(k, v []byte) error {
   123  	// write lock
   124  	lsm.lock.Lock()
   125  	defer lsm.lock.Unlock()
   126  	// make entry
   127  	e := &Entry{
   128  		Key:   k,
   129  		Value: v,
   130  		CRC:   checksum(append(k, v...)),
   131  	}
   132  	// check entry
   133  	err := checkEntry(e)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	// call internal put method
   138  	err = lsm.putEntry(e)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  // Del takes a key and overwrites the record with a Tombstone or
   146  // a 'deleted' or nil entry. It leaves the key in the LSMTree
   147  // so that future table versions can properly merge.
   148  func (lsm *LSMTree) Del(k []byte) error {
   149  	// write lock
   150  	lsm.lock.Lock()
   151  	defer lsm.lock.Unlock()
   152  	// make entry
   153  	e := &Entry{
   154  		Key:   k,
   155  		Value: makeTombstone(),
   156  		CRC:   checksum(append(k, Tombstone...)),
   157  	}
   158  	// check entry
   159  	err := checkEntry(e)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	// call internal delete method
   164  	err = lsm.delEntry(e)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	return nil
   169  }
   170  
   171  // PutBatch takes a batch of entries and adds all of them at
   172  // one time. It acts a bit like a transaction. If you have a
   173  // configuration option of SyncOnWrite: true it will be disabled
   174  // temporarily and the batch will sync at the end of all the
   175  // writes. This is to give a slight performance advantage. It
   176  // should be worth noting that very large batches may have an
   177  // impact on performance and may also cause frequent ss-table
   178  // flushes which may result in fragmentation.
   179  func (lsm *LSMTree) PutBatch(b *Batch) error {
   180  	// lock
   181  	lsm.lock.Lock()
   182  	defer lsm.lock.Unlock()
   183  	// iterate batch entries
   184  	for _, e := range b.Entries {
   185  		// call internal putEntry
   186  		err := lsm.putEntry(e)
   187  		if err != nil {
   188  			return err
   189  		}
   190  	}
   191  	// we're done
   192  	return nil
   193  }
   194  
   195  // GetBatch attempts to find entries matching the keys provided. If a matching
   196  // entry is found, it is added to the batch that is returned. If a matching
   197  // entry cannot be found it is simply skipped and not added to the batch. GetBatch
   198  // will return a nil error if all the matching entries were found. If it found
   199  // some but not all, GetBatch will return ErrIncompleteSet along with the batch
   200  // of entries that it could find. If it could not find any matches at all, the
   201  // batch will be nil and GetBatch will return an ErrNotFound
   202  func (lsm *LSMTree) GetBatch(keys ...[]byte) (*Batch, error) {
   203  	// read lock
   204  	lsm.lock.RLock()
   205  	defer lsm.lock.RUnlock()
   206  	// create new batch to return
   207  	batch := NewBatch()
   208  	// iterate over keys
   209  	for _, k := range keys {
   210  		// make entry and check it
   211  		e := &Entry{Key: k}
   212  		err := checkKey(e)
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		// call internal getEntry
   217  		ent, err := lsm.getEntry(e)
   218  		if err != nil {
   219  			if err == ErrFoundTombstone {
   220  				// found tombstone entry (means this entry was
   221  				// deleted) so we can try to find the next one
   222  				continue
   223  			}
   224  			// if not tombstone, then it may be a bad entry, or a
   225  			// bad checksum, or not found! either way, return err
   226  			return nil, err
   227  		}
   228  		// otherwise, we got it! add to batch
   229  		_ = batch.writeEntry(ent)
   230  		continue
   231  	}
   232  	// check the batch
   233  	if batch.Len() == 0 {
   234  		// nothing at all was found
   235  		return nil, ErrNotFound
   236  	}
   237  	if batch.Len() == len(keys) {
   238  		// we found all the potential matches!
   239  		return batch, nil
   240  	}
   241  	// otherwise, we found some but not all
   242  	return batch, ErrIncompleteSet
   243  }
   244  
   245  // Sync forces a sync on all underlying structures no matter what the configuration
   246  func (lsm *LSMTree) Sync() error {
   247  	// write lock
   248  	lsm.lock.Lock()
   249  	defer lsm.lock.Unlock()
   250  	// sync commit log
   251  	err := lsm.wacl.sync()
   252  	if err != nil {
   253  		return err
   254  	}
   255  	return nil
   256  }
   257  
   258  // Close syncs and closes the LSMTree
   259  func (lsm *LSMTree) Close() error {
   260  	// close commit log
   261  	err := lsm.wacl.close()
   262  	if err != nil {
   263  		return err
   264  	}
   265  	return nil
   266  }
   267  
   268  // getEntry is the internal "get" implementation
   269  func (lsm *LSMTree) getEntry(e *Entry) (*Entry, error) {
   270  	// check to make sure there is data
   271  	if lsm.memt.sizeOfEntries() < 1 {
   272  		return nil, ErrNoDataFound
   273  	}
   274  	// look in mem-table
   275  	ent, found := lsm.memt.getEntry(e)
   276  	if found {
   277  		// found it
   278  		return ent, nil
   279  	}
   280  	// look in ss-tables
   281  	ent, err := lsm.sstm.get(e)
   282  	if err == nil {
   283  		// found it
   284  		return ent, nil
   285  	}
   286  	return nil, ErrNotFound
   287  }
   288  
   289  // putEntry is the internal "get" implementation
   290  func (lsm *LSMTree) putEntry(e *Entry) error {
   291  	// write entry to the commit log
   292  	_, err := lsm.wacl.put(e)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	// write entry to the mem-table
   297  	_, needToFlush := lsm.memt.upsertAndCheckSize(e, lsm.opt.flushThreshold)
   298  	// check if we should do a flush
   299  	if needToFlush {
   300  		// attempt to flush
   301  		err = lsm.flushToSSTable()
   302  		if err != nil {
   303  			return err
   304  		}
   305  		// cycle the commit log
   306  		err = lsm.wacl.cycle()
   307  		if err != nil {
   308  			return err
   309  		}
   310  	}
   311  	return nil
   312  }
   313  
   314  // delEntry is the internal "get" implementation
   315  func (lsm *LSMTree) delEntry(e *Entry) error {
   316  	// write entry to the commit log
   317  	_, err := lsm.wacl.put(e)
   318  	if err != nil {
   319  		return err
   320  	}
   321  	// write entry to the mem-table
   322  	_, needToFlush := lsm.memt.upsertAndCheckSize(e, lsm.opt.flushThreshold)
   323  	// check if we should do a flush
   324  	if needToFlush {
   325  		// attempt to flush
   326  		err = lsm.flushToSSTable()
   327  		if err != nil {
   328  			return err
   329  		}
   330  		// cycle the commit log
   331  		err = lsm.wacl.cycle()
   332  		if err != nil {
   333  			return err
   334  		}
   335  	}
   336  	return nil
   337  }
   338  
   339  // loadDataFromCommitLog looks for any commit logs on disk
   340  // and reads the contents of the commit log in order to
   341  // re-populate the MemTable on a restart
   342  func (lsm *LSMTree) loadDataFromCommitLog() error {
   343  	// write lock
   344  	lsm.lock.Lock()
   345  	defer lsm.lock.Unlock()
   346  	// iterate through the commit log...
   347  	err := lsm.wacl.scan(func(e *Entry) bool {
   348  		// ...and insert entries back into mem-table
   349  		if e.hasTombstone() {
   350  			// skip tombstone entry
   351  			return true
   352  		}
   353  		// blind insert of entry
   354  		lsm.memt.putEntry(e)
   355  		return true
   356  	})
   357  	if err != nil {
   358  		return err
   359  	}
   360  	return nil
   361  }
   362  
   363  // flushToSSTable flushes the current mem-table to a level-0 ss-table
   364  func (lsm *LSMTree) flushToSSTable() error {
   365  	// create a new table files on disk
   366  	err := lsm.sstm.createSSAndIndexTables(lsm.memt)
   367  	if err != nil {
   368  		return err
   369  	}
   370  	// reset mem-table
   371  	lsm.memt.reset()
   372  	return nil
   373  }