github.com/scottcagno/storage@v1.8.0/pkg/lsmt/sstable/ss-table-mgr.go (about) 1 package sstable 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/scottcagno/storage/pkg/lsmt/binary" 7 "github.com/scottcagno/storage/pkg/lsmt/mtbl" 8 "github.com/scottcagno/storage/pkg/lsmt/trees/rbtree" 9 "log" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 "sync" 15 ) 16 17 const ( 18 filePrefix = "sst-" 19 dataFileSuffix = ".dat" 20 indexFileSuffix = ".idx" 21 ) 22 23 var Tombstone = []byte(nil) 24 25 type spiEntry struct { 26 Key string 27 SSTIndex int64 28 IndexEntry *binary.Index 29 } 30 31 func (r spiEntry) Compare(that rbtree.RBEntry) int { 32 return strings.Compare(r.Key, that.(spiEntry).Key) 33 } 34 35 func (r spiEntry) Size() int { 36 return len(r.Key) + 16 37 } 38 39 func (r spiEntry) String() string { 40 return fmt.Sprintf("entry.LastKey=%q", r.Key) 41 } 42 43 type Int64Slice []int64 44 45 func (x Int64Slice) Len() int { return len(x) } 46 func (x Int64Slice) Less(i, j int) bool { return x[i] < x[j] } 47 func (x Int64Slice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 48 49 type SSTManager struct { 50 lock sync.RWMutex 51 base string 52 sequence int64 53 sparseIndex *rbtree.RBTree 54 fileIndexes []int64 55 } 56 57 func OpenSSTManager(base string) (*SSTManager, error) { 58 // make sure we are working with absolute paths 59 base, err := filepath.Abs(base) 60 if err != nil { 61 return nil, err 62 } 63 // sanitize any path separators 64 base = filepath.ToSlash(base) 65 // create base directory 66 err = os.MkdirAll(base, os.ModeDir) 67 if err != nil { 68 return nil, err 69 } 70 // create ss-table-manager instance 71 sstm := &SSTManager{ 72 base: base, 73 sequence: 0, 74 sparseIndex: rbtree.NewRBTree(), 75 } 76 // read the ss-table directory 77 files, err := os.ReadDir(base) 78 if err != nil { 79 return nil, err 80 } 81 // lock 82 sstm.lock.RLock() 83 defer sstm.lock.RUnlock() 84 // iterate over all the ss-index files 85 for _, file := range files { 86 // skip all non ss-index files 87 if file.IsDir() || !strings.HasSuffix(file.Name(), indexFileSuffix) { 88 continue 89 } 90 // get ss-index index number from file name 91 index, err := IndexFromDataFileName(file.Name()) 92 if err != nil { 93 return nil, err 94 } 95 // open the ss-index file 96 ssi, err := OpenSSTIndex(sstm.base, index) 97 if err != nil { 98 return nil, err 99 } 100 // generate and populate sparse index 101 err = sstm.AddSparseIndex(ssi) 102 if err != nil { 103 return nil, err 104 } 105 // close ss-index 106 err = ssi.Close() 107 if err != nil { 108 return nil, err 109 } 110 // update the last sequence number 111 if index > sstm.sequence { 112 sstm.sequence = index 113 } 114 // add to list of file indexes 115 sstm.fileIndexes = append(sstm.fileIndexes, index) 116 } 117 return sstm, nil 118 } 119 120 func (sstm *SSTManager) FlushToSSTable(mt *mtbl.RBTree) error { 121 // lock 122 sstm.lock.Lock() 123 defer sstm.lock.Unlock() 124 // open new ss-table 125 sst, err := OpenSSTable(sstm.base, sstm.sequence+1) 126 if err != nil { 127 return err 128 } 129 // iterate mem-table entries 130 mt.Scan(func(e *binary.Entry) bool { 131 // and write each entry to the ss-table 132 err = sst.Write(e) 133 if err != nil { 134 log.Println(err) 135 return false 136 } 137 return true 138 }) 139 // reset mem-table 140 mt.Reset() 141 // add new entries to sparse index 142 err = sstm.AddSparseIndex(sst.index) 143 if err != nil { 144 return err 145 } 146 // flush and close ss-table 147 err = sst.Close() 148 if err != nil { 149 return err 150 } 151 // in the clear, increment sequence number 152 sstm.sequence++ 153 // return 154 return nil 155 } 156 157 func (sstm *SSTManager) flushBatchToSSTable(batch *binary.Batch) error { 158 // lock 159 sstm.lock.Lock() 160 defer sstm.lock.Unlock() 161 // open new ss-table 162 sst, err := OpenSSTable(sstm.base, sstm.sequence+1) 163 if err != nil { 164 return err 165 } 166 // write batch to ss-table 167 err = sst.WriteBatch(batch) 168 if err != nil { 169 return err 170 } 171 // add new entries to sparse index 172 err = sstm.AddSparseIndex(sst.index) 173 if err != nil { 174 return err 175 } 176 // flush and close ss-table 177 err = sst.Close() 178 if err != nil { 179 return err 180 } 181 // in the clear, increment sequence 182 sstm.sequence++ 183 // return, dummy 184 return nil 185 } 186 187 func (sstm *SSTManager) AddSparseIndex(ssi *SSTIndex) error { 188 // generate sparse index and fill out/add to the supplied sparseIndex 189 err := ssi.GenerateAndPutSparseIndex(sstm.sparseIndex) 190 if err != nil { 191 return err 192 } 193 return nil 194 } 195 196 func (sstm *SSTManager) SearchSparseIndex(k string) (int64, error) { 197 e, err := sstm.searchSparseIndex(k) 198 if err != nil { 199 return e.SSTIndex, err 200 } 201 return e.SSTIndex, nil 202 } 203 204 func (sstm *SSTManager) searchSparseIndex(k string) (spiEntry, error) { 205 // search "sparse index" 206 e, nearMin, nearMax, exact := sstm.sparseIndex.GetApproxPrevNext(spiEntry{Key: k}) 207 if exact { 208 // found exact entry 209 return e.(spiEntry), nil 210 } 211 // check to see if key is greater than near max 212 if nearMax == nil || k > nearMax.(spiEntry).Key { 213 // note: nearMax should be nil if the key is out of range 214 // key is greater than the near max, which 215 // means it is not located in this table 216 return spiEntry{SSTIndex: -1}, binary.ErrBadEntry 217 } 218 // if we get here, key is less than near max 219 if nearMin != nil && k >= nearMin.(spiEntry).Key { 220 // and key is greater than the near min which 221 // means that it is most likely in this table 222 //util.DEBUG("[nearMin] searchSparseIndex(%q) returning: entry found in near min\n", k) 223 return nearMin.(spiEntry), nil 224 } 225 //util.DEBUG("[weird end stage] searchSparseIndex(%q) returning: error bad entry\n", k) 226 // if we get here something bad happened?? this uaully 227 // means a key was searched for that is less than the near 228 // min or something that just doesn't compute well 229 return spiEntry{SSTIndex: -1}, binary.ErrBadEntry 230 } 231 232 type ScanDirection int 233 234 const ( 235 ScanOldToNew = 0 236 ScanNewToOld = 1 237 ) 238 239 func (sstm *SSTManager) Scan(direction ScanDirection, iter func(e *binary.Entry) bool) error { 240 if direction != ScanOldToNew && direction != ScanNewToOld { 241 return ErrInvalidScanDirection 242 } 243 if direction == ScanNewToOld { 244 // sort the ss-index files so the most recent ones are first 245 sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes))) 246 } 247 if direction == ScanOldToNew { 248 // sort the ss-index files so the least recent ones are first 249 sort.Sort(Int64Slice(sstm.fileIndexes)) 250 } 251 // start iterating 252 for _, index := range sstm.fileIndexes { 253 // open the ss-table 254 sst, err := OpenSSTable(sstm.base, index) 255 if err != nil { 256 return err 257 } 258 // scan the ss-table 259 err = sst.Scan(iter) 260 if err != nil { 261 return err 262 } 263 // close the ss-table 264 err = sst.Close() 265 if err != nil { 266 return nil 267 } 268 } 269 return nil 270 } 271 272 func (sstm *SSTManager) Search0(k string) (*binary.Entry, error) { 273 // read lock 274 sstm.lock.RLock() 275 defer sstm.lock.RUnlock() 276 // sort the ss-index files so the most recent ones are first 277 sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes))) 278 // start iterating 279 for _, index := range sstm.fileIndexes { 280 // open the ss-table 281 sst, err := OpenSSTable(sstm.base, index) 282 if err != nil { 283 return nil, err 284 } 285 // check to see if the key is most likely 286 // located in the range of this table... 287 ok := sst.KeyInTableRange(k) 288 if !ok { 289 // close the ss-table 290 err = sst.Close() 291 if err != nil { 292 return nil, nil 293 } 294 continue // skip to next table... 295 } 296 // key is most likely in range of this table... 297 // perform binary search, attempt to 298 // locate a matching entry 299 de, err := sst.Read(k) 300 if err != nil { 301 return nil, err 302 } 303 // close the ss-table 304 err = sst.Close() 305 if err != nil { 306 return nil, nil 307 } 308 // double check entry 309 if de == nil { 310 continue 311 } 312 // otherwise, return 313 return de, nil 314 } 315 return nil, binary.ErrEntryNotFound 316 } 317 318 func (sstm *SSTManager) LinearSearch(k string) (*binary.Entry, error) { 319 // read lock 320 sstm.lock.RLock() 321 defer sstm.lock.RUnlock() 322 // sort the ss-index files so the most recent ones are first 323 sort.Sort(sort.Reverse(Int64Slice(sstm.fileIndexes))) 324 // iterate the ss-index files (backward) 325 for _, index := range sstm.fileIndexes { 326 // open the ss-table 327 sst, err := OpenSSTable(sstm.base, index) 328 if err != nil { 329 return nil, err 330 } 331 // perform binary search, attempt to 332 // locate a matching entry 333 de, err := sst.Read(k) 334 if err != nil { 335 return nil, err 336 } 337 // do not forget to close the ss-table 338 err = sst.Close() 339 if err != nil { 340 return nil, err 341 } 342 // double check entry 343 if de == nil { 344 continue 345 } 346 // otherwise, return 347 return de, nil 348 } 349 return nil, binary.ErrEntryNotFound 350 } 351 352 func (sstm *SSTManager) CheckDeleteInSparseIndex(k string) { 353 // lock 354 sstm.lock.Lock() 355 defer sstm.lock.Unlock() 356 // make sparse index entry 357 sie := spiEntry{Key: k} 358 // search for exact key in sparse index 359 if sstm.sparseIndex.Has(sie) { 360 // remove key from sparse index 361 sstm.sparseIndex.Del(sie) 362 } 363 } 364 365 func (sstm *SSTManager) Search(k string) (*binary.Entry, error) { 366 // read lock 367 sstm.lock.RLock() 368 defer sstm.lock.RUnlock() 369 // search "sparse index" 370 sie, err := sstm.searchSparseIndex(k) 371 if err != nil { 372 return nil, err 373 } 374 // open ss-table 375 sst, err := OpenSSTable(sstm.base, sie.SSTIndex) 376 if err != nil { 377 return nil, err 378 } 379 // create an entry to return if we find a match 380 matchedEntry := new(binary.Entry) 381 // for key match at offset in spiEntry 382 err = sst.ScanAt(sie.IndexEntry.Offset, func(e *binary.Entry) bool { 383 if string(e.Key) == k { 384 // we found our match, write data into matchedEntry 385 matchedEntry = e 386 return false // to stop scanning 387 } 388 return true // to keep scanning 389 }) 390 // make sure to error check the scanner 391 if err != nil { 392 return nil, err 393 } 394 // close ss-table 395 err = sst.Close() 396 if err != nil { 397 return nil, err 398 } 399 // double check matched entry 400 if matchedEntry == nil { 401 return nil, binary.ErrBadEntry 402 } 403 // entry might be tombstone?? maybe we should return anyway 404 if matchedEntry.Value == nil { 405 return nil, binary.ErrBadEntry 406 } 407 return matchedEntry, nil 408 } 409 410 func (sstm *SSTManager) CompactAllSSTables() error { 411 for _, index := range sstm.fileIndexes { 412 err := sstm.CompactSSTable(index) 413 if err != nil { 414 return err 415 } 416 } 417 return nil 418 } 419 420 func (sstm *SSTManager) CompactSSTable(index int64) error { 421 // lock 422 sstm.lock.Lock() 423 defer sstm.lock.Unlock() 424 // load sstable 425 sst, err := OpenSSTable(sstm.base, index) 426 if err != nil { 427 return err 428 } 429 // make batch 430 batch := binary.NewBatch() 431 // iterate 432 err = sst.Scan(func(e *binary.Entry) bool { 433 // add any data entries that are not tombstones to batch 434 if e.Value != nil && !bytes.Equal(e.Value, Tombstone) { 435 batch.WriteEntry(e) 436 } 437 return true 438 }) 439 if err != nil { 440 return err 441 } 442 // get path 443 tpath, ipath := sst.path, sst.index.path 444 // close sstable 445 err = sst.Close() 446 if err != nil { 447 return err 448 } 449 // remove old ss-table 450 err = os.Remove(tpath) 451 if err != nil { 452 return err 453 } 454 // remove old ss-index 455 err = os.Remove(ipath) 456 if err != nil { 457 return err 458 } 459 // open new ss-table to write to 460 sst, err = OpenSSTable(sstm.base, index) 461 if err != nil { 462 return err 463 } 464 // write batch to table 465 err = sst.WriteBatch(batch) 466 // flush and close sstable 467 err = sst.Close() 468 if err != nil { 469 return err 470 } 471 return nil 472 } 473 474 func (sstm *SSTManager) MergeAllSSTables(mergeThreshold int) error { 475 // ensure there is an even number of tables 476 // before attempting to merge--if not, just 477 // silently return a nil error.... 478 if len(sstm.fileIndexes)%2 != 0 { 479 // odd number of tables, difficult to merge 480 return nil 481 } 482 // otherwise, we have an even number of tables 483 // and could merge in theory--check threshold 484 if len(sstm.fileIndexes) < mergeThreshold { 485 // haven't reached the merge threshold 486 // so, silently return a nil error 487 return nil 488 } 489 // otherwise, start by sorting... 490 sort.Sort(Int64Slice(sstm.fileIndexes)) 491 // then iterate and attempt to merge... 492 for i := int(sstm.fileIndexes[0]); i < len(sstm.fileIndexes); i += 2 { 493 // merging pair 494 err := sstm.MergeSSTables(int64(i), int64(i+1)) 495 if err != nil { 496 return err 497 } 498 } 499 // everything merge successfully 500 return nil 501 } 502 503 func (sstm *SSTManager) MergeSSTables(iA, iB int64) error { 504 // lock 505 sstm.lock.Lock() 506 defer sstm.lock.Unlock() 507 // load sstable A 508 sstA, err := OpenSSTable(sstm.base, iA) 509 if err != nil { 510 return err 511 } 512 // and sstable B 513 sstB, err := OpenSSTable(sstm.base, iB) 514 if err != nil { 515 return err 516 } 517 // make batch to write data to 518 batch := binary.NewBatch() 519 // pass tables to the merge writer 520 err = mergeTablesAndWriteToBatch(sstA, sstB, batch) 521 if err != nil { 522 return err 523 } 524 // close table A 525 err = sstA.Close() 526 if err != nil { 527 return err 528 } 529 // close table B 530 err = sstB.Close() 531 if err != nil { 532 return err 533 } 534 // open new sstable to write to 535 sstC, err := OpenSSTable(sstm.base, iB+1) 536 if err != nil { 537 return err 538 } 539 // write batch to table 540 err = sstC.WriteBatch(batch) 541 // flush and close sstable 542 err = sstC.Close() 543 if err != nil { 544 return err 545 } 546 return nil 547 } 548 549 func (sstm *SSTManager) Close() error { 550 // TODO: implement me 551 return nil 552 } 553 554 func mergeTablesAndWriteToBatch(sstA, sstB *SSTable, batch *binary.Batch) error { 555 556 i, j := 0, 0 557 n1, n2 := sstA.index.Len(), sstB.index.Len() 558 559 var err error 560 var de *binary.Entry 561 for i < n1 && j < n2 { 562 if bytes.Compare(sstA.index.data[i].Key, sstB.index.data[j].Key) == 0 { 563 // read entry from sstB 564 de, err = sstB.ReadAt(sstB.index.data[j].Offset) 565 if err != nil { 566 return err 567 } 568 // write entry to batch 569 batch.WriteEntry(de) 570 i++ 571 j++ 572 continue 573 } 574 if bytes.Compare(sstA.index.data[i].Key, sstB.index.data[j].Key) == -1 { 575 // read entry from sstA 576 de, err = sstA.ReadAt(sstA.index.data[i].Offset) 577 if err != nil { 578 return err 579 } 580 // write entry to batch 581 batch.WriteEntry(de) 582 i++ 583 continue 584 } 585 if bytes.Compare(sstB.index.data[j].Key, sstA.index.data[i].Key) == -1 { 586 // read entry from sstB 587 de, err = sstB.ReadAt(sstB.index.data[j].Offset) 588 if err != nil { 589 return err 590 } 591 // write entry to batch 592 batch.WriteEntry(de) 593 j++ 594 continue 595 } 596 } 597 598 // print remaining 599 for i < n1 { 600 // read entry from sstA 601 de, err = sstA.ReadAt(sstA.index.data[i].Offset) 602 if err != nil { 603 return err 604 } 605 // write entry to batch 606 batch.WriteEntry(de) 607 i++ 608 } 609 610 // print remaining 611 for j < n2 { 612 // read entry from sstB 613 de, err = sstB.ReadAt(sstB.index.data[j].Offset) 614 if err != nil { 615 return err 616 } 617 // write entry to batch 618 batch.WriteEntry(de) 619 j++ 620 } 621 622 // return error free 623 return nil 624 }