github.com/scottcagno/storage@v1.8.0/pkg/lsmt/sstable/ss-table-mgr.go (about)

     1  package sstable
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/scottcagno/storage/pkg/lsmt/binary"
     7  	"github.com/scottcagno/storage/pkg/lsmt/mtbl"
     8  	"github.com/scottcagno/storage/pkg/lsmt/trees/rbtree"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  )
    16  
    17  const (
    18  	filePrefix      = "sst-"
    19  	dataFileSuffix  = ".dat"
    20  	indexFileSuffix = ".idx"
    21  )
    22  
    23  var Tombstone = []byte(nil)
    24  
    25  type spiEntry struct {
    26  	Key        string
    27  	SSTIndex   int64
    28  	IndexEntry *binary.Index
    29  }
    30  
    31  func (r spiEntry) Compare(that rbtree.RBEntry) int {
    32  	return strings.Compare(r.Key, that.(spiEntry).Key)
    33  }
    34  
    35  func (r spiEntry) Size() int {
    36  	return len(r.Key) + 16
    37  }
    38  
    39  func (r spiEntry) String() string {
    40  	return fmt.Sprintf("entry.LastKey=%q", r.Key)
    41  }
    42  
    43  type Int64Slice []int64
    44  
    45  func (x Int64Slice) Len() int           { return len(x) }
    46  func (x Int64Slice) Less(i, j int) bool { return x[i] < x[j] }
    47  func (x Int64Slice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
    48  
    49  type SSTManager struct {
    50  	lock        sync.RWMutex
    51  	base        string
    52  	sequence    int64
    53  	sparseIndex *rbtree.RBTree
    54  	fileIndexes []int64
    55  }
    56  
    57  func OpenSSTManager(base string) (*SSTManager, error) {
    58  	// make sure we are working with absolute paths
    59  	base, err := filepath.Abs(base)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	// sanitize any path separators
    64  	base = filepath.ToSlash(base)
    65  	// create base directory
    66  	err = os.MkdirAll(base, os.ModeDir)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	// create ss-table-manager instance
    71  	sstm := &SSTManager{
    72  		base:        base,
    73  		sequence:    0,
    74  		sparseIndex: rbtree.NewRBTree(),
    75  	}
    76  	// read the ss-table directory
    77  	files, err := os.ReadDir(base)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	// lock
    82  	sstm.lock.RLock()
    83  	defer sstm.lock.RUnlock()
    84  	// iterate over all the ss-index files
    85  	for _, file := range files {
    86  		// skip all non ss-index files
    87  		if file.IsDir() || !strings.HasSuffix(file.Name(), indexFileSuffix) {
    88  			continue
    89  		}
    90  		// get ss-index index number from file name
    91  		index, err := IndexFromDataFileName(file.Name())
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  		// open the ss-index file
    96  		ssi, err := OpenSSTIndex(sstm.base, index)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  		// generate and populate sparse index
   101  		err = sstm.AddSparseIndex(ssi)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		// close ss-index
   106  		err = ssi.Close()
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  		// update the last sequence number
   111  		if index > sstm.sequence {
   112  			sstm.sequence = index
   113  		}
   114  		// add to list of file indexes
   115  		sstm.fileIndexes = append(sstm.fileIndexes, index)
   116  	}
   117  	return sstm, nil
   118  }
   119  
   120  func (sstm *SSTManager) FlushToSSTable(mt *mtbl.RBTree) error {
   121  	// lock
   122  	sstm.lock.Lock()
   123  	defer sstm.lock.Unlock()
   124  	// open new ss-table
   125  	sst, err := OpenSSTable(sstm.base, sstm.sequence+1)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	// iterate mem-table entries
   130  	mt.Scan(func(e *binary.Entry) bool {
   131  		// and write each entry to the ss-table
   132  		err = sst.Write(e)
   133  		if err != nil {
   134  			log.Println(err)
   135  			return false
   136  		}
   137  		return true
   138  	})
   139  	// reset mem-table
   140  	mt.Reset()
   141  	// add new entries to sparse index
   142  	err = sstm.AddSparseIndex(sst.index)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	// flush and close ss-table
   147  	err = sst.Close()
   148  	if err != nil {
   149  		return err
   150  	}
   151  	// in the clear, increment sequence number
   152  	sstm.sequence++
   153  	// return
   154  	return nil
   155  }
   156  
   157  func (sstm *SSTManager) flushBatchToSSTable(batch *binary.Batch) error {
   158  	// lock
   159  	sstm.lock.Lock()
   160  	defer sstm.lock.Unlock()
   161  	// open new ss-table
   162  	sst, err := OpenSSTable(sstm.base, sstm.sequence+1)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	// write batch to ss-table
   167  	err = sst.WriteBatch(batch)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	// add new entries to sparse index
   172  	err = sstm.AddSparseIndex(sst.index)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	// flush and close ss-table
   177  	err = sst.Close()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	// in the clear, increment sequence
   182  	sstm.sequence++
   183  	// return, dummy
   184  	return nil
   185  }
   186  
   187  func (sstm *SSTManager) AddSparseIndex(ssi *SSTIndex) error {
   188  	// generate sparse index and fill out/add to the supplied sparseIndex
   189  	err := ssi.GenerateAndPutSparseIndex(sstm.sparseIndex)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	return nil
   194  }
   195  
   196  func (sstm *SSTManager) SearchSparseIndex(k string) (int64, error) {
   197  	e, err := sstm.searchSparseIndex(k)
   198  	if err != nil {
   199  		return e.SSTIndex, err
   200  	}
   201  	return e.SSTIndex, nil
   202  }
   203  
   204  func (sstm *SSTManager) searchSparseIndex(k string) (spiEntry, error) {
   205  	// search "sparse index"
   206  	e, nearMin, nearMax, exact := sstm.sparseIndex.GetApproxPrevNext(spiEntry{Key: k})
   207  	if exact {
   208  		// found exact entry
   209  		return e.(spiEntry), nil
   210  	}
   211  	// check to see if key is greater than near max
   212  	if nearMax == nil || k > nearMax.(spiEntry).Key {
   213  		// note: nearMax should be nil if the key is out of range
   214  		// key is greater than the near max, which
   215  		// means it is not located in this table
   216  		return spiEntry{SSTIndex: -1}, binary.ErrBadEntry
   217  	}
   218  	// if we get here, key is less than near max
   219  	if nearMin != nil && k >= nearMin.(spiEntry).Key {
   220  		// and key is greater than the near min which
   221  		// means that it is most likely in this table
   222  		//util.DEBUG("[nearMin] searchSparseIndex(%q) returning: entry found in near min\n", k)
   223  		return nearMin.(spiEntry), nil
   224  	}
   225  	//util.DEBUG("[weird end stage] searchSparseIndex(%q) returning: error bad entry\n", k)
   226  	// if we get here something bad happened?? this uaully
   227  	// means a key was searched for that is less than the near
   228  	// min or something that just doesn't compute well
   229  	return spiEntry{SSTIndex: -1}, binary.ErrBadEntry
   230  }
   231  
   232  type ScanDirection int
   233  
   234  const (
   235  	ScanOldToNew = 0
   236  	ScanNewToOld = 1
   237  )
   238  
   239  func (sstm *SSTManager) Scan(direction ScanDirection, iter func(e *binary.Entry) bool) error {
   240  	if direction != ScanOldToNew && direction != ScanNewToOld {
   241  		return ErrInvalidScanDirection
   242  	}
   243  	if direction == ScanNewToOld {
   244  		// sort the ss-index files so the most recent ones are first
   245  		sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes)))
   246  	}
   247  	if direction == ScanOldToNew {
   248  		// sort the ss-index files so the least recent ones are first
   249  		sort.Sort(Int64Slice(sstm.fileIndexes))
   250  	}
   251  	// start iterating
   252  	for _, index := range sstm.fileIndexes {
   253  		// open the ss-table
   254  		sst, err := OpenSSTable(sstm.base, index)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		// scan the ss-table
   259  		err = sst.Scan(iter)
   260  		if err != nil {
   261  			return err
   262  		}
   263  		// close the ss-table
   264  		err = sst.Close()
   265  		if err != nil {
   266  			return nil
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  func (sstm *SSTManager) Search0(k string) (*binary.Entry, error) {
   273  	// read lock
   274  	sstm.lock.RLock()
   275  	defer sstm.lock.RUnlock()
   276  	// sort the ss-index files so the most recent ones are first
   277  	sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes)))
   278  	// start iterating
   279  	for _, index := range sstm.fileIndexes {
   280  		// open the ss-table
   281  		sst, err := OpenSSTable(sstm.base, index)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		// check to see if the key is most likely
   286  		// located in the range of this table...
   287  		ok := sst.KeyInTableRange(k)
   288  		if !ok {
   289  			// close the ss-table
   290  			err = sst.Close()
   291  			if err != nil {
   292  				return nil, nil
   293  			}
   294  			continue // skip to next table...
   295  		}
   296  		// key is most likely in range of this table...
   297  		// perform binary search, attempt to
   298  		// locate a matching entry
   299  		de, err := sst.Read(k)
   300  		if err != nil {
   301  			return nil, err
   302  		}
   303  		// close the ss-table
   304  		err = sst.Close()
   305  		if err != nil {
   306  			return nil, nil
   307  		}
   308  		// double check entry
   309  		if de == nil {
   310  			continue
   311  		}
   312  		// otherwise, return
   313  		return de, nil
   314  	}
   315  	return nil, binary.ErrEntryNotFound
   316  }
   317  
   318  func (sstm *SSTManager) LinearSearch(k string) (*binary.Entry, error) {
   319  	// read lock
   320  	sstm.lock.RLock()
   321  	defer sstm.lock.RUnlock()
   322  	// sort the ss-index files so the most recent ones are first
   323  	sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes)))
   324  	// iterate the ss-index files (backward)
   325  	for _, index := range sstm.fileIndexes {
   326  		// open the ss-table
   327  		sst, err := OpenSSTable(sstm.base, index)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  		// perform binary search, attempt to
   332  		// locate a matching entry
   333  		de, err := sst.Read(k)
   334  		if err != nil {
   335  			return nil, err
   336  		}
   337  		// do not forget to close the ss-table
   338  		err = sst.Close()
   339  		if err != nil {
   340  			return nil, err
   341  		}
   342  		// double check entry
   343  		if de == nil {
   344  			continue
   345  		}
   346  		// otherwise, return
   347  		return de, nil
   348  	}
   349  	return nil, binary.ErrEntryNotFound
   350  }
   351  
   352  func (sstm *SSTManager) CheckDeleteInSparseIndex(k string) {
   353  	// lock
   354  	sstm.lock.Lock()
   355  	defer sstm.lock.Unlock()
   356  	// make sparse index entry
   357  	sie := spiEntry{Key: k}
   358  	// search for exact key in sparse index
   359  	if sstm.sparseIndex.Has(sie) {
   360  		// remove key from sparse index
   361  		sstm.sparseIndex.Del(sie)
   362  	}
   363  }
   364  
   365  func (sstm *SSTManager) Search(k string) (*binary.Entry, error) {
   366  	// read lock
   367  	sstm.lock.RLock()
   368  	defer sstm.lock.RUnlock()
   369  	// search "sparse index"
   370  	sie, err := sstm.searchSparseIndex(k)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  	// open ss-table
   375  	sst, err := OpenSSTable(sstm.base, sie.SSTIndex)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	// create an entry to return if we find a match
   380  	matchedEntry := new(binary.Entry)
   381  	// for key match at offset in spiEntry
   382  	err = sst.ScanAt(sie.IndexEntry.Offset, func(e *binary.Entry) bool {
   383  		if string(e.Key) == k {
   384  			// we found our match, write data into matchedEntry
   385  			matchedEntry = e
   386  			return false // to stop scanning
   387  		}
   388  		return true // to keep scanning
   389  	})
   390  	// make sure to error check the scanner
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  	// close ss-table
   395  	err = sst.Close()
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	// double check matched entry
   400  	if matchedEntry == nil {
   401  		return nil, binary.ErrBadEntry
   402  	}
   403  	// entry might be tombstone?? maybe we should return anyway
   404  	if matchedEntry.Value == nil {
   405  		return nil, binary.ErrBadEntry
   406  	}
   407  	return matchedEntry, nil
   408  }
   409  
   410  func (sstm *SSTManager) CompactAllSSTables() error {
   411  	for _, index := range sstm.fileIndexes {
   412  		err := sstm.CompactSSTable(index)
   413  		if err != nil {
   414  			return err
   415  		}
   416  	}
   417  	return nil
   418  }
   419  
   420  func (sstm *SSTManager) CompactSSTable(index int64) error {
   421  	// lock
   422  	sstm.lock.Lock()
   423  	defer sstm.lock.Unlock()
   424  	// load sstable
   425  	sst, err := OpenSSTable(sstm.base, index)
   426  	if err != nil {
   427  		return err
   428  	}
   429  	// make batch
   430  	batch := binary.NewBatch()
   431  	// iterate
   432  	err = sst.Scan(func(e *binary.Entry) bool {
   433  		// add any data entries that are not tombstones to batch
   434  		if e.Value != nil && !bytes.Equal(e.Value, Tombstone) {
   435  			batch.WriteEntry(e)
   436  		}
   437  		return true
   438  	})
   439  	if err != nil {
   440  		return err
   441  	}
   442  	// get path
   443  	tpath, ipath := sst.path, sst.index.path
   444  	// close sstable
   445  	err = sst.Close()
   446  	if err != nil {
   447  		return err
   448  	}
   449  	// remove old ss-table
   450  	err = os.Remove(tpath)
   451  	if err != nil {
   452  		return err
   453  	}
   454  	// remove old ss-index
   455  	err = os.Remove(ipath)
   456  	if err != nil {
   457  		return err
   458  	}
   459  	// open new ss-table to write to
   460  	sst, err = OpenSSTable(sstm.base, index)
   461  	if err != nil {
   462  		return err
   463  	}
   464  	// write batch to table
   465  	err = sst.WriteBatch(batch)
   466  	// flush and close sstable
   467  	err = sst.Close()
   468  	if err != nil {
   469  		return err
   470  	}
   471  	return nil
   472  }
   473  
   474  func (sstm *SSTManager) MergeAllSSTables(mergeThreshold int) error {
   475  	// ensure there is an even number of tables
   476  	// before attempting to merge--if not, just
   477  	// silently return a nil error....
   478  	if len(sstm.fileIndexes)%2 != 0 {
   479  		// odd number of tables, difficult to merge
   480  		return nil
   481  	}
   482  	// otherwise, we have an even number of tables
   483  	// and could merge in theory--check threshold
   484  	if len(sstm.fileIndexes) < mergeThreshold {
   485  		// haven't reached the merge threshold
   486  		// so, silently return a nil error
   487  		return nil
   488  	}
   489  	// otherwise, start by sorting...
   490  	sort.Sort(Int64Slice(sstm.fileIndexes))
   491  	// then iterate and attempt to merge...
   492  	for i := int(sstm.fileIndexes[0]); i < len(sstm.fileIndexes); i += 2 {
   493  		// merging pair
   494  		err := sstm.MergeSSTables(int64(i), int64(i+1))
   495  		if err != nil {
   496  			return err
   497  		}
   498  	}
   499  	// everything merge successfully
   500  	return nil
   501  }
   502  
   503  func (sstm *SSTManager) MergeSSTables(iA, iB int64) error {
   504  	// lock
   505  	sstm.lock.Lock()
   506  	defer sstm.lock.Unlock()
   507  	// load sstable A
   508  	sstA, err := OpenSSTable(sstm.base, iA)
   509  	if err != nil {
   510  		return err
   511  	}
   512  	// and sstable B
   513  	sstB, err := OpenSSTable(sstm.base, iB)
   514  	if err != nil {
   515  		return err
   516  	}
   517  	// make batch to write data to
   518  	batch := binary.NewBatch()
   519  	// pass tables to the merge writer
   520  	err = mergeTablesAndWriteToBatch(sstA, sstB, batch)
   521  	if err != nil {
   522  		return err
   523  	}
   524  	// close table A
   525  	err = sstA.Close()
   526  	if err != nil {
   527  		return err
   528  	}
   529  	// close table B
   530  	err = sstB.Close()
   531  	if err != nil {
   532  		return err
   533  	}
   534  	// open new sstable to write to
   535  	sstC, err := OpenSSTable(sstm.base, iB+1)
   536  	if err != nil {
   537  		return err
   538  	}
   539  	// write batch to table
   540  	err = sstC.WriteBatch(batch)
   541  	// flush and close sstable
   542  	err = sstC.Close()
   543  	if err != nil {
   544  		return err
   545  	}
   546  	return nil
   547  }
   548  
   549  func (sstm *SSTManager) Close() error {
   550  	// TODO: implement me
   551  	return nil
   552  }
   553  
   554  func mergeTablesAndWriteToBatch(sstA, sstB *SSTable, batch *binary.Batch) error {
   555  
   556  	i, j := 0, 0
   557  	n1, n2 := sstA.index.Len(), sstB.index.Len()
   558  
   559  	var err error
   560  	var de *binary.Entry
   561  	for i < n1 && j < n2 {
   562  		if bytes.Compare(sstA.index.data[i].Key, sstB.index.data[j].Key) == 0 {
   563  			// read entry from sstB
   564  			de, err = sstB.ReadAt(sstB.index.data[j].Offset)
   565  			if err != nil {
   566  				return err
   567  			}
   568  			// write entry to batch
   569  			batch.WriteEntry(de)
   570  			i++
   571  			j++
   572  			continue
   573  		}
   574  		if bytes.Compare(sstA.index.data[i].Key, sstB.index.data[j].Key) == -1 {
   575  			// read entry from sstA
   576  			de, err = sstA.ReadAt(sstA.index.data[i].Offset)
   577  			if err != nil {
   578  				return err
   579  			}
   580  			// write entry to batch
   581  			batch.WriteEntry(de)
   582  			i++
   583  			continue
   584  		}
   585  		if bytes.Compare(sstB.index.data[j].Key, sstA.index.data[i].Key) == -1 {
   586  			// read entry from sstB
   587  			de, err = sstB.ReadAt(sstB.index.data[j].Offset)
   588  			if err != nil {
   589  				return err
   590  			}
   591  			// write entry to batch
   592  			batch.WriteEntry(de)
   593  			j++
   594  			continue
   595  		}
   596  	}
   597  
   598  	// print remaining
   599  	for i < n1 {
   600  		// read entry from sstA
   601  		de, err = sstA.ReadAt(sstA.index.data[i].Offset)
   602  		if err != nil {
   603  			return err
   604  		}
   605  		// write entry to batch
   606  		batch.WriteEntry(de)
   607  		i++
   608  	}
   609  
   610  	// print remaining
   611  	for j < n2 {
   612  		// read entry from sstB
   613  		de, err = sstB.ReadAt(sstB.index.data[j].Offset)
   614  		if err != nil {
   615  			return err
   616  		}
   617  		// write entry to batch
   618  		batch.WriteEntry(de)
   619  		j++
   620  	}
   621  
   622  	// return error free
   623  	return nil
   624  }