github.com/lbryio/lbcd@v0.22.119/claimtrie/node/noderepo/pebble.go (about)

     1  package noderepo
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"sort"
     7  	"sync"
     8  
     9  	"github.com/cockroachdb/pebble"
    10  	"github.com/lbryio/lbcd/claimtrie/change"
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  type Pebble struct {
    15  	db *pebble.DB
    16  }
    17  
    18  type pooledMerger struct {
    19  	values [][]byte
    20  	index  []int
    21  	pool   *sync.Pool
    22  	buffer []byte
    23  }
    24  
    25  func (a *pooledMerger) Len() int           { return len(a.index) }
    26  func (a *pooledMerger) Less(i, j int) bool { return a.index[i] < a.index[j] }
    27  func (a *pooledMerger) Swap(i, j int) {
    28  	a.index[i], a.index[j] = a.index[j], a.index[i]
    29  	a.values[i], a.values[j] = a.values[j], a.values[i]
    30  }
    31  
    32  func (a *pooledMerger) MergeNewer(value []byte) error {
    33  	vc := a.pool.Get().([]byte)[:0]
    34  	vc = append(vc, value...)
    35  	a.values = append(a.values, vc)
    36  	a.index = append(a.index, len(a.values))
    37  	return nil
    38  }
    39  
    40  func (a *pooledMerger) MergeOlder(value []byte) error {
    41  	vc := a.pool.Get().([]byte)[:0]
    42  	vc = append(vc, value...)
    43  	a.values = append(a.values, vc)
    44  	a.index = append(a.index, -len(a.values))
    45  	return nil
    46  }
    47  
    48  func (a *pooledMerger) Finish(includesBase bool) ([]byte, io.Closer, error) {
    49  	sort.Sort(a)
    50  
    51  	a.buffer = a.pool.Get().([]byte)[:0]
    52  	for i := range a.values {
    53  		a.buffer = append(a.buffer, a.values[i]...)
    54  	}
    55  
    56  	return a.buffer, a, nil
    57  }
    58  
    59  func (a *pooledMerger) Close() error {
    60  	for i := range a.values {
    61  		a.pool.Put(a.values[i])
    62  	}
    63  	a.pool.Put(a.buffer)
    64  	return nil
    65  }
    66  
    67  func NewPebble(path string) (*Pebble, error) {
    68  
    69  	mp := &sync.Pool{
    70  		New: func() interface{} {
    71  			return make([]byte, 0, 256)
    72  		},
    73  	}
    74  
    75  	db, err := pebble.Open(path, &pebble.Options{
    76  		Merger: &pebble.Merger{
    77  			Merge: func(key, value []byte) (pebble.ValueMerger, error) {
    78  				p := &pooledMerger{pool: mp}
    79  				return p, p.MergeNewer(value)
    80  			},
    81  			Name: pebble.DefaultMerger.Name, // yes, it's a lie
    82  		},
    83  		Cache:        pebble.NewCache(64 << 20),
    84  		BytesPerSync: 8 << 20,
    85  		MaxOpenFiles: 2000,
    86  	})
    87  
    88  	repo := &Pebble{db: db}
    89  
    90  	return repo, errors.Wrapf(err, "unable to open %s", path)
    91  }
    92  
    93  func (repo *Pebble) AppendChanges(changes []change.Change) error {
    94  
    95  	batch := repo.db.NewBatch()
    96  	defer batch.Close()
    97  
    98  	buffer := bytes.NewBuffer(nil)
    99  
   100  	for _, chg := range changes {
   101  		buffer.Reset()
   102  		err := chg.Marshal(buffer)
   103  		if err != nil {
   104  			return errors.Wrap(err, "in marshaller")
   105  		}
   106  
   107  		err = batch.Merge(chg.Name, buffer.Bytes(), pebble.NoSync)
   108  		if err != nil {
   109  			return errors.Wrap(err, "in merge")
   110  		}
   111  	}
   112  	return errors.Wrap(batch.Commit(pebble.NoSync), "in commit")
   113  }
   114  
   115  func (repo *Pebble) LoadChanges(name []byte) ([]change.Change, error) {
   116  
   117  	data, closer, err := repo.db.Get(name)
   118  	if err != nil && err != pebble.ErrNotFound {
   119  		return nil, errors.Wrapf(err, "in get %s", name) // does returning a name in an error expose too much?
   120  	}
   121  	if closer != nil {
   122  		defer closer.Close()
   123  	}
   124  
   125  	return unmarshalChanges(name, data)
   126  }
   127  
   128  func unmarshalChanges(name, data []byte) ([]change.Change, error) {
   129  	// data is 84bytes+ per change
   130  	changes := make([]change.Change, 0, len(data)/84+1) // average is 5.1 changes
   131  
   132  	buffer := bytes.NewBuffer(data)
   133  	sortNeeded := false
   134  	for buffer.Len() > 0 {
   135  		var chg change.Change
   136  		err := chg.Unmarshal(buffer)
   137  		if err != nil {
   138  			return nil, errors.Wrap(err, "in decode")
   139  		}
   140  		chg.Name = name
   141  		if len(changes) > 0 && chg.Height < changes[len(changes)-1].Height {
   142  			sortNeeded = true // alternately: sortNeeded || chg.Height != chg.VisibleHeight
   143  		}
   144  		changes = append(changes, chg)
   145  	}
   146  
   147  	if sortNeeded {
   148  		// this was required for the normalization stuff:
   149  		sort.SliceStable(changes, func(i, j int) bool {
   150  			return changes[i].Height < changes[j].Height
   151  		})
   152  	}
   153  	return changes, nil
   154  }
   155  
   156  func (repo *Pebble) DropChanges(name []byte, finalHeight int32) error {
   157  	changes, err := repo.LoadChanges(name)
   158  	if err != nil {
   159  		return errors.Wrapf(err, "in load changes for %s", name)
   160  	}
   161  	buffer := bytes.NewBuffer(nil)
   162  	for i := 0; i < len(changes); i++ { // assuming changes are ordered by height
   163  		if changes[i].Height > finalHeight {
   164  			break
   165  		}
   166  		if changes[i].VisibleHeight > finalHeight { // created after this height has to be skipped
   167  			continue
   168  		}
   169  		// having to sort the changes really messes up performance here. It would be better to not remarshal
   170  		err := changes[i].Marshal(buffer)
   171  		if err != nil {
   172  			return errors.Wrap(err, "in marshaller")
   173  		}
   174  	}
   175  
   176  	// making a performance assumption that DropChanges won't happen often:
   177  	err = repo.db.Set(name, buffer.Bytes(), pebble.NoSync)
   178  	return errors.Wrapf(err, "in set at %s", name)
   179  }
   180  
   181  func (repo *Pebble) IterateChildren(name []byte, f func(changes []change.Change) bool) error {
   182  	start := make([]byte, len(name)+1) // zeros that last byte; need a constant len for stack alloc?
   183  	copy(start, name)
   184  
   185  	end := make([]byte, len(name)) // max name length is 255
   186  	copy(end, name)
   187  	validEnd := false
   188  	for i := len(name) - 1; i >= 0; i-- {
   189  		end[i]++
   190  		if end[i] != 0 {
   191  			validEnd = true
   192  			break
   193  		}
   194  	}
   195  	if !validEnd {
   196  		end = nil // uh, we think this means run to the end of the table
   197  	}
   198  
   199  	prefixIterOptions := &pebble.IterOptions{
   200  		LowerBound: start,
   201  		UpperBound: end,
   202  	}
   203  
   204  	iter := repo.db.NewIter(prefixIterOptions)
   205  	defer iter.Close()
   206  
   207  	for iter.First(); iter.Valid(); iter.Next() {
   208  		// NOTE! iter.Key() is ephemeral!
   209  		changes, err := unmarshalChanges(iter.Key(), iter.Value())
   210  		if err != nil {
   211  			return errors.Wrapf(err, "from unmarshaller at %s", iter.Key())
   212  		}
   213  		if !f(changes) {
   214  			break
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func (repo *Pebble) IterateAll(predicate func(name []byte) bool) {
   221  	iter := repo.db.NewIter(nil)
   222  	defer iter.Close()
   223  
   224  	for iter.First(); iter.Valid(); iter.Next() {
   225  		if !predicate(iter.Key()) {
   226  			break
   227  		}
   228  	}
   229  }
   230  
   231  func (repo *Pebble) Close() error {
   232  
   233  	err := repo.db.Flush()
   234  	if err != nil {
   235  		// if we fail to close are we going to try again later?
   236  		return errors.Wrap(err, "on flush")
   237  	}
   238  
   239  	err = repo.db.Close()
   240  	return errors.Wrap(err, "on close")
   241  }
   242  
   243  func (repo *Pebble) Flush() error {
   244  	_, err := repo.db.AsyncFlush()
   245  	return err
   246  }