github.com/scottcagno/storage@v1.8.0/pkg/lsmtree/lsmtree.go (about) 1 package lsmtree 2 3 import ( 4 "os" 5 "path/filepath" 6 "sync" 7 ) 8 9 type LSMTree struct { 10 lock sync.RWMutex 11 opt *Options 12 logDir string 13 sstDir string 14 wacl *commitLog 15 memt *rbTree 16 sstm *ssTableManager 17 logger *Logger 18 } 19 20 // OpenLSMTree opens or creates an LSMTree instance 21 func OpenLSMTree(options *Options) (*LSMTree, error) { 22 // check lsm config 23 opt := checkOptions(options) 24 // initialize base path 25 base, err := initBasePath(opt.BaseDir) 26 if err != nil { 27 return nil, err 28 } 29 // create commit log base directory 30 logdir := filepath.Join(base, defaultWalDir) 31 err = os.MkdirAll(logdir, os.ModeDir) 32 if err != nil { 33 return nil, err 34 } 35 // open commit log 36 wacl, err := openCommitLog(logdir, opt.SyncOnWrite) 37 if err != nil { 38 return nil, err 39 } 40 // create ss-table data base directory 41 sstdir := filepath.Join(base, defaultSstDir) 42 err = os.MkdirAll(sstdir, os.ModeDir) 43 if err != nil { 44 return nil, err 45 } 46 // open ss-table-manager 47 sstm, err := openSSTableManager(sstdir) 48 if err != nil { 49 return nil, err 50 } 51 // create lsm-tree instance 52 lsmt := &LSMTree{ 53 opt: opt, 54 logDir: logdir, 55 sstDir: sstdir, 56 wacl: wacl, 57 memt: newRBTree(), 58 sstm: sstm, 59 logger: newLogger(opt.LoggingLevel), 60 } 61 // load mem-table with commit log data 62 err = lsmt.loadDataFromCommitLog() 63 if err != nil { 64 return nil, err 65 } 66 // return lsm-tree 67 return lsmt, nil 68 } 69 70 // Has returns a boolean signaling weather or not the key 71 // is in the LSMTree. It should be noted that in some cases 72 // this may return a false positive, but it should never 73 // return a false negative. 74 func (lsm *LSMTree) Has(k []byte) (bool, error) { 75 // read lock 76 lsm.lock.RLock() 77 defer lsm.lock.RUnlock() 78 // make entry for key 79 e := &Entry{Key: k} 80 // check the entry 81 err := checkKey(e) 82 if err != nil { 83 return false, err 84 } 85 // call internal get method 86 e, err = lsm.getEntry(e) 87 if err != nil { 88 return false, err 89 } 90 // otherwise, we got it 91 return e != nil, err 92 } 93 94 // Get takes a key and attempts to find a match in the LSMTree. If 95 // a match cannot be found Get returns a nil value and ErrNotFound. 96 // Get first checks the bloom filter, then the mem-table. If it is 97 // still not found it attempts to do a binary search on the for the 98 // key in the ss-index and if that yields no result it will try to 99 // find the entry by doing a linear search of the ss-table itself. 100 func (lsm *LSMTree) Get(k []byte) ([]byte, error) { 101 // read lock 102 lsm.lock.RLock() 103 defer lsm.lock.RUnlock() 104 // make entry for key 105 e := &Entry{Key: k} 106 // check the entry 107 err := checkKey(e) 108 if err != nil { 109 return nil, err 110 } 111 // call internal get method 112 ent, err := lsm.getEntry(e) 113 if err != nil { 114 return nil, err 115 } 116 // otherwise, we got it! 117 return ent.Value, nil 118 } 119 120 // Put takes a key and a value and adds them to the LSMTree. If 121 // the entry already exists, it should overwrite the old entry. 122 func (lsm *LSMTree) Put(k, v []byte) error { 123 // write lock 124 lsm.lock.Lock() 125 defer lsm.lock.Unlock() 126 // make entry 127 e := &Entry{ 128 Key: k, 129 Value: v, 130 CRC: checksum(append(k, v...)), 131 } 132 // check entry 133 err := checkEntry(e) 134 if err != nil { 135 return err 136 } 137 // call internal put method 138 err = lsm.putEntry(e) 139 if err != nil { 140 return err 141 } 142 return nil 143 } 144 145 // Del takes a key and overwrites the record with a Tombstone or 146 // a 'deleted' or nil entry. It leaves the key in the LSMTree 147 // so that future table versions can properly merge. 148 func (lsm *LSMTree) Del(k []byte) error { 149 // write lock 150 lsm.lock.Lock() 151 defer lsm.lock.Unlock() 152 // make entry 153 e := &Entry{ 154 Key: k, 155 Value: makeTombstone(), 156 CRC: checksum(append(k, Tombstone...)), 157 } 158 // check entry 159 err := checkEntry(e) 160 if err != nil { 161 return err 162 } 163 // call internal delete method 164 err = lsm.delEntry(e) 165 if err != nil { 166 return err 167 } 168 return nil 169 } 170 171 // PutBatch takes a batch of entries and adds all of them at 172 // one time. It acts a bit like a transaction. If you have a 173 // configuration option of SyncOnWrite: true it will be disabled 174 // temporarily and the batch will sync at the end of all the 175 // writes. This is to give a slight performance advantage. It 176 // should be worth noting that very large batches may have an 177 // impact on performance and may also cause frequent ss-table 178 // flushes which may result in fragmentation. 179 func (lsm *LSMTree) PutBatch(b *Batch) error { 180 // lock 181 lsm.lock.Lock() 182 defer lsm.lock.Unlock() 183 // iterate batch entries 184 for _, e := range b.Entries { 185 // call internal putEntry 186 err := lsm.putEntry(e) 187 if err != nil { 188 return err 189 } 190 } 191 // we're done 192 return nil 193 } 194 195 // GetBatch attempts to find entries matching the keys provided. If a matching 196 // entry is found, it is added to the batch that is returned. If a matching 197 // entry cannot be found it is simply skipped and not added to the batch. GetBatch 198 // will return a nil error if all the matching entries were found. If it found 199 // some but not all, GetBatch will return ErrIncompleteSet along with the batch 200 // of entries that it could find. If it could not find any matches at all, the 201 // batch will be nil and GetBatch will return an ErrNotFound 202 func (lsm *LSMTree) GetBatch(keys ...[]byte) (*Batch, error) { 203 // read lock 204 lsm.lock.RLock() 205 defer lsm.lock.RUnlock() 206 // create new batch to return 207 batch := NewBatch() 208 // iterate over keys 209 for _, k := range keys { 210 // make entry and check it 211 e := &Entry{Key: k} 212 err := checkKey(e) 213 if err != nil { 214 return nil, err 215 } 216 // call internal getEntry 217 ent, err := lsm.getEntry(e) 218 if err != nil { 219 if err == ErrFoundTombstone { 220 // found tombstone entry (means this entry was 221 // deleted) so we can try to find the next one 222 continue 223 } 224 // if not tombstone, then it may be a bad entry, or a 225 // bad checksum, or not found! either way, return err 226 return nil, err 227 } 228 // otherwise, we got it! add to batch 229 _ = batch.writeEntry(ent) 230 continue 231 } 232 // check the batch 233 if batch.Len() == 0 { 234 // nothing at all was found 235 return nil, ErrNotFound 236 } 237 if batch.Len() == len(keys) { 238 // we found all the potential matches! 239 return batch, nil 240 } 241 // otherwise, we found some but not all 242 return batch, ErrIncompleteSet 243 } 244 245 // Sync forces a sync on all underlying structures no matter what the configuration 246 func (lsm *LSMTree) Sync() error { 247 // write lock 248 lsm.lock.Lock() 249 defer lsm.lock.Unlock() 250 // sync commit log 251 err := lsm.wacl.sync() 252 if err != nil { 253 return err 254 } 255 return nil 256 } 257 258 // Close syncs and closes the LSMTree 259 func (lsm *LSMTree) Close() error { 260 // close commit log 261 err := lsm.wacl.close() 262 if err != nil { 263 return err 264 } 265 return nil 266 } 267 268 // getEntry is the internal "get" implementation 269 func (lsm *LSMTree) getEntry(e *Entry) (*Entry, error) { 270 // check to make sure there is data 271 if lsm.memt.sizeOfEntries() < 1 { 272 return nil, ErrNoDataFound 273 } 274 // look in mem-table 275 ent, found := lsm.memt.getEntry(e) 276 if found { 277 // found it 278 return ent, nil 279 } 280 // look in ss-tables 281 ent, err := lsm.sstm.get(e) 282 if err == nil { 283 // found it 284 return ent, nil 285 } 286 return nil, ErrNotFound 287 } 288 289 // putEntry is the internal "get" implementation 290 func (lsm *LSMTree) putEntry(e *Entry) error { 291 // write entry to the commit log 292 _, err := lsm.wacl.put(e) 293 if err != nil { 294 return err 295 } 296 // write entry to the mem-table 297 _, needToFlush := lsm.memt.upsertAndCheckSize(e, lsm.opt.flushThreshold) 298 // check if we should do a flush 299 if needToFlush { 300 // attempt to flush 301 err = lsm.flushToSSTable() 302 if err != nil { 303 return err 304 } 305 // cycle the commit log 306 err = lsm.wacl.cycle() 307 if err != nil { 308 return err 309 } 310 } 311 return nil 312 } 313 314 // delEntry is the internal "get" implementation 315 func (lsm *LSMTree) delEntry(e *Entry) error { 316 // write entry to the commit log 317 _, err := lsm.wacl.put(e) 318 if err != nil { 319 return err 320 } 321 // write entry to the mem-table 322 _, needToFlush := lsm.memt.upsertAndCheckSize(e, lsm.opt.flushThreshold) 323 // check if we should do a flush 324 if needToFlush { 325 // attempt to flush 326 err = lsm.flushToSSTable() 327 if err != nil { 328 return err 329 } 330 // cycle the commit log 331 err = lsm.wacl.cycle() 332 if err != nil { 333 return err 334 } 335 } 336 return nil 337 } 338 339 // loadDataFromCommitLog looks for any commit logs on disk 340 // and reads the contents of the commit log in order to 341 // re-populate the MemTable on a restart 342 func (lsm *LSMTree) loadDataFromCommitLog() error { 343 // write lock 344 lsm.lock.Lock() 345 defer lsm.lock.Unlock() 346 // iterate through the commit log... 347 err := lsm.wacl.scan(func(e *Entry) bool { 348 // ...and insert entries back into mem-table 349 if e.hasTombstone() { 350 // skip tombstone entry 351 return true 352 } 353 // blind insert of entry 354 lsm.memt.putEntry(e) 355 return true 356 }) 357 if err != nil { 358 return err 359 } 360 return nil 361 } 362 363 // flushToSSTable flushes the current mem-table to a level-0 ss-table 364 func (lsm *LSMTree) flushToSSTable() error { 365 // create a new table files on disk 366 err := lsm.sstm.createSSAndIndexTables(lsm.memt) 367 if err != nil { 368 return err 369 } 370 // reset mem-table 371 lsm.memt.reset() 372 return nil 373 }