github.com/braveheart12/insolar-09-08-19@v0.8.7/ledger/storage/replica_iter.go (about)

     1  /*
     2   *    Copyright 2019 Insolar Technologies
     3   *
     4   *    Licensed under the Apache License, Version 2.0 (the "License");
     5   *    you may not use this file except in compliance with the License.
     6   *    You may obtain a copy of the License at
     7   *
     8   *        http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   *    Unless required by applicable law or agreed to in writing, software
    11   *    distributed under the License is distributed on an "AS IS" BASIS,
    12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *    See the License for the specific language governing permissions and
    14   *    limitations under the License.
    15   */
    16  
    17  package storage
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  
    24  	"github.com/dgraph-io/badger"
    25  	"github.com/insolar/insolar/core"
    26  	"github.com/insolar/insolar/ledger/storage/jet"
    27  )
    28  
    29  // iterstate stores iterator state
    30  type iterstate struct {
    31  	prefix []byte
    32  	start  []byte
    33  	end    []byte
    34  }
    35  
    36  // ReplicaIter provides partial iterator over BadgerDB key/value pairs
    37  // required for replication to Heavy Material node in provided pulses range.
    38  //
    39  // "Required KV pairs" are all keys with namespace 'scopeIDRecord' (TODO: 'add scopeIDBlob')
    40  // in provided pulses range and all indexes from zero pulse to the end of provided range.
    41  //
    42  // "Partial" means it fetches data in chunks of the specified size.
    43  // After a chunk has been fetched, an iterator saves current position.
    44  //
    45  // NOTE: This is not an "honest" alogrithm, because the last record size can exceed the limit.
    46  // Better implementation is for the future work.
    47  type ReplicaIter struct {
    48  	ctx        context.Context
    49  	dbContext  DBContext
    50  	limitBytes int
    51  	istates    []*iterstate
    52  	lastpulse  core.PulseNumber
    53  }
    54  
    55  // NewReplicaIter creates ReplicaIter what iterates over records on jet,
    56  // required for heavy material replication.
    57  //
    58  // Params 'start' and 'end' defines pulses from which scan should happen,
    59  // and on which it should be stopped, but indexes scan are always started
    60  // from core.FirstPulseNumber.
    61  //
    62  // Param 'limit' sets per message limit.
    63  func NewReplicaIter(
    64  	ctx context.Context,
    65  	dbContext DBContext,
    66  	jetID core.RecordID,
    67  	start core.PulseNumber,
    68  	end core.PulseNumber,
    69  	limit int,
    70  ) *ReplicaIter {
    71  	// fmt.Printf("CALL NewReplicaIter [%v:%v] (jet=%v)\n", start, end, jetID)
    72  	newit := func(prefixbyte byte, jetID core.RecordID, start, end core.PulseNumber) *iterstate {
    73  		prefix := []byte{prefixbyte}
    74  		_, jetPrefix := jet.Jet(jetID)
    75  		iter := &iterstate{prefix: prefix}
    76  		iter.start = bytes.Join([][]byte{prefix, jetPrefix[:], start.Bytes()}, nil)
    77  		iter.end = bytes.Join([][]byte{prefix, jetPrefix[:], end.Bytes()}, nil)
    78  		return iter
    79  	}
    80  
    81  	return &ReplicaIter{
    82  		ctx:        ctx,
    83  		dbContext:  dbContext,
    84  		limitBytes: limit,
    85  		// record iterators (order matters for heavy node consistency)
    86  		istates: []*iterstate{
    87  			newit(scopeIDRecord, jetID, start, end),
    88  			newit(scopeIDBlob, jetID, start, end),
    89  			newit(scopeIDLifeline, jetID, core.FirstPulseNumber, end),
    90  			newit(scopeIDJetDrop, jetID, start, end),
    91  		},
    92  	}
    93  }
    94  
    95  // NextRecords fetches next part of key value pairs.
    96  func (r *ReplicaIter) NextRecords() ([]core.KV, error) {
    97  	if r.isDone() {
    98  		return nil, ErrReplicatorDone
    99  	}
   100  	fc := &fetchchunk{
   101  		db:    r.dbContext.GetBadgerDB(),
   102  		limit: r.limitBytes,
   103  	}
   104  	for _, is := range r.istates {
   105  		if is.start == nil {
   106  			continue
   107  		}
   108  		var fetcherr error
   109  		var lastpulse core.PulseNumber
   110  		is.start, lastpulse, fetcherr = fc.fetch(r.ctx, is.prefix, is.start, is.end)
   111  		if fetcherr != nil {
   112  			return nil, fetcherr
   113  		}
   114  		if lastpulse > r.lastpulse {
   115  			r.lastpulse = lastpulse
   116  		}
   117  	}
   118  	return fc.records, nil
   119  }
   120  
   121  // LastPulse returns maximum pulse number of returned keys after each fetch.
   122  func (r *ReplicaIter) LastSeenPulse() core.PulseNumber {
   123  	return r.lastpulse
   124  }
   125  
   126  // ErrReplicatorDone is returned by an Replicator NextRecords method when the iteration is complete.
   127  var ErrReplicatorDone = errors.New("no more items in iterator")
   128  
   129  func (r *ReplicaIter) isDone() bool {
   130  	for _, is := range r.istates {
   131  		if is.start != nil {
   132  			return false
   133  		}
   134  	}
   135  	return true
   136  }
   137  
   138  type fetchchunk struct {
   139  	db      *badger.DB
   140  	records []core.KV
   141  	size    int
   142  	limit   int
   143  }
   144  
   145  func (fc *fetchchunk) fetch(
   146  	ctx context.Context,
   147  	prefix []byte,
   148  	start []byte,
   149  	end []byte,
   150  ) ([]byte, core.PulseNumber, error) {
   151  	if fc.size > fc.limit {
   152  		return start, 0, nil
   153  	}
   154  
   155  	var nextstart []byte
   156  	var lastpulse core.PulseNumber
   157  	err := fc.db.View(func(txn *badger.Txn) error {
   158  		it := txn.NewIterator(badger.DefaultIteratorOptions)
   159  		defer it.Close()
   160  
   161  		for it.Seek(start); it.ValidForPrefix(prefix); it.Next() {
   162  			item := it.Item()
   163  			if item == nil {
   164  				break
   165  			}
   166  			// key prefix < end
   167  			if bytes.Compare(item.Key()[:len(end)], end) != -1 {
   168  				break
   169  			}
   170  
   171  			key := item.KeyCopy(nil)
   172  			if fc.size > fc.limit {
   173  				nextstart = key
   174  				// inslogger.FromContext(ctx).Warnf("size > r.limit: %v > %v (nextstart=%v)",
   175  				// 	fc.size, fc.limit, hex.EncodeToString(key))
   176  				return nil
   177  			}
   178  
   179  			lastpulse = pulseFromKey(key)
   180  			// fmt.Printf("Replica> key: %v (pulse=%v)\n", hex.EncodeToString(key), lastpulse)
   181  
   182  			value, err := it.Item().ValueCopy(nil)
   183  			if err != nil {
   184  				return err
   185  			}
   186  
   187  			NullifyJetInKey(key)
   188  			fc.records = append(fc.records, core.KV{K: key, V: value})
   189  			fc.size += len(key) + len(value)
   190  		}
   191  		nextstart = nil
   192  		return nil
   193  	})
   194  	return nextstart, lastpulse, err
   195  }
   196  
   197  // NullifyJetInKey nullify jet part in record.
   198  func NullifyJetInKey(key []byte) {
   199  	// if we remove jet part from drop, different drops from same pulses collapsed
   200  	// TODO: figure out how we want to send jet drops on heavy nodes - @Alexander Orlovsky 18.01.2019
   201  	if key[0] == scopeIDJetDrop {
   202  		return
   203  	}
   204  	for i := 1; i < core.RecordHashSize; i++ {
   205  		key[i] = 0
   206  	}
   207  }