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 }