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 }