From 8901f4090ec40218f3511b239d01804940328f8d Mon Sep 17 00:00:00 2001 From: Bilal Akhtar Date: Tue, 23 Nov 2021 17:29:43 -0500 Subject: [PATCH 1/6] *: disaggregated storage WIP internal/cache: Implement secondary cache --- compaction.go | 105 +++++-- db.go | 6 + ingest.go | 17 ++ internal/base/filenames.go | 7 + internal/cache/clockpro.go | 53 +++- internal/cache/entry.go | 5 +- internal/cache/secondary_cache.go | 472 ++++++++++++++++++++++++++++++ internal/manifest/version.go | 17 +- internal/manifest/version_edit.go | 23 +- internal/manifest/version_test.go | 2 +- open.go | 29 +- options.go | 32 +- persistent_cache.go | 282 ++++++++++++++++++ sstable/reader.go | 42 ++- table_cache.go | 39 ++- vfs/preallocate_generic.go | 8 + vfs/preallocate_linux.go | 7 + vfs/vfs.go | 45 +++ 18 files changed, 1140 insertions(+), 51 deletions(-) create mode 100644 internal/cache/secondary_cache.go create mode 100644 persistent_cache.go diff --git a/compaction.go b/compaction.go index c046192561d..6f01bc4a7e4 100644 --- a/compaction.go +++ b/compaction.go @@ -13,6 +13,7 @@ import ( "runtime/pprof" "sort" "strings" + "sync" "sync/atomic" "time" @@ -2149,6 +2150,39 @@ func (d *DB) compact1(c *compaction, errChannel chan error) (err error) { return err } +func moveFileToSharedFS(filepath string, fs vfs.FS, sharedPath string, sharedFS vfs.FS) error { + file, err := fs.Open(filepath, vfs.SequentialReadsOption) + if err != nil { + return err + } + defer func() { + if file != nil { + _ = file.Close() + } + }() + destFile, err := sharedFS.Create(sharedPath) + if err != nil { + return err + } + defer func() { + _ = destFile.Close() + }() + + _, err = io.Copy(destFile, file) + if err != nil { + return err + } + + if err := file.Close(); err != nil { + return err + } + file = nil + if err := fs.Remove(filepath); err != nil { + return err + } + return nil +} + // runCompactions runs a compaction that produces new on-disk tables from // memtables or old on-disk tables. // @@ -2251,6 +2285,7 @@ func (d *DB) runCompaction( var ( filenames []string tw *sstable.Writer + movers sync.WaitGroup ) defer func() { if iter != nil { @@ -2534,6 +2569,18 @@ func (d *DB) runCompaction( meta.ExtendRangeKeyBounds(d.cmp, writerMeta.SmallestRangeKey, writerMeta.LargestRangeKey) } + if c.outputLevel.level >= 5 && d.opts.SharedFS != nil { + oldFilename := base.MakeFilepath(d.opts.FS, d.dirname, fileTypeTable, meta.FileNum) + sharedFilename := base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, d.opts.UniqueID, meta.FileNum) + movers.Add(1) + go func() { + defer movers.Done() + if err := moveFileToSharedFS(oldFilename, d.opts.FS, sharedFilename, d.opts.SharedFS); err == nil { + meta.UsesSharedFS = true + } + }() + } + // Verify that the sstable bounds fall within the compaction input // bounds. This is a sanity check that we don't have a logic error // elsewhere that causes the sstable bounds to accidentally expand past the @@ -2723,6 +2770,7 @@ func (d *DB) runCompaction( }] = f } } + movers.Wait() if err := d.dataDir.Sync(); err != nil { return nil, pendingOutputs, err @@ -2918,15 +2966,18 @@ func (d *DB) deleteObsoleteFiles(jobID int, waitForOngoing bool) { // obsoleteFile holds information about a file that needs to be deleted soon. type obsoleteFile struct { - dir string - fileNum base.FileNum - fileType fileType - fileSize uint64 + dir string + fileNum base.FileNum + fileType fileType + fileSize uint64 + usesSharedFS bool + skipMetrics bool } type fileInfo struct { - fileNum FileNum - fileSize uint64 + fileNum FileNum + fileSize uint64 + usesSharedFS bool } // d.mu must be held when calling this, but the mutex may be dropped and @@ -2956,8 +3007,9 @@ func (d *DB) doDeleteObsoleteFiles(jobID int) { for _, table := range d.mu.versions.obsoleteTables { obsoleteTables = append(obsoleteTables, fileInfo{ - fileNum: table.FileNum, - fileSize: table.Size, + fileNum: table.FileNum, + fileSize: table.Size, + usesSharedFS: table.UsesSharedFS, }) } d.mu.versions.obsoleteTables = nil @@ -3017,10 +3069,11 @@ func (d *DB) doDeleteObsoleteFiles(jobID int) { } filesToDelete = append(filesToDelete, obsoleteFile{ - dir: dir, - fileNum: fi.fileNum, - fileType: f.fileType, - fileSize: fi.fileSize, + dir: dir, + fileNum: fi.fileNum, + fileType: f.fileType, + fileSize: fi.fileSize, + usesSharedFS: fi.usesSharedFS, }) } } @@ -3047,13 +3100,21 @@ func (d *DB) paceAndDeleteObsoleteFiles(jobID int, files []obsoleteFile) { for _, of := range files { path := base.MakeFilepath(d.opts.FS, of.dir, of.fileType, of.fileNum) if of.fileType == fileTypeTable { + if of.usesSharedFS { + path = base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, d.opts.UniqueID, of.fileNum) + if d.persistentCache != nil { + d.persistentCache.MarkDeleted(of.fileNum) + } + } _ = pacer.maybeThrottle(of.fileSize) - d.mu.Lock() - d.mu.versions.metrics.Table.ObsoleteCount-- - d.mu.versions.metrics.Table.ObsoleteSize -= of.fileSize - d.mu.Unlock() + if !of.skipMetrics { + d.mu.Lock() + d.mu.versions.metrics.Table.ObsoleteCount-- + d.mu.versions.metrics.Table.ObsoleteSize -= of.fileSize + d.mu.Unlock() + } } - d.deleteObsoleteFile(of.fileType, jobID, path, of.fileNum) + d.deleteObsoleteFile(of.fileType, jobID, path, of.fileNum, of.usesSharedFS) } } @@ -3082,10 +3143,16 @@ func (d *DB) maybeScheduleObsoleteTableDeletion() { } // deleteObsoleteFile deletes file that is no longer needed. -func (d *DB) deleteObsoleteFile(fileType fileType, jobID int, path string, fileNum FileNum) { +func (d *DB) deleteObsoleteFile( + fileType fileType, jobID int, path string, fileNum FileNum, usesSharedFS bool, +) { // TODO(peter): need to handle this error, probably by re-adding the // file that couldn't be deleted to one of the obsolete slices map. - err := d.opts.Cleaner.Clean(d.opts.FS, fileType, path) + fs := d.opts.FS + if usesSharedFS { + fs = d.opts.SharedFS + } + err := d.opts.Cleaner.Clean(fs, fileType, path) if oserror.IsNotExist(err) { return } diff --git a/db.go b/db.go index 3deac014d79..fa0fb5add65 100644 --- a/db.go +++ b/db.go @@ -283,6 +283,8 @@ type DB struct { // compactionShedulers.Wait() should not be called while the DB.mu is held. compactionSchedulers sync.WaitGroup + persistentCache *persistentCache + // The main mutex protecting internal DB state. This mutex encompasses many // fields because those fields need to be accessed and updated atomically. In // particular, the current version, log.*, mem.*, and snapshot list need to @@ -1228,6 +1230,10 @@ func (d *DB) Close() error { d.mu.tableValidation.cond.Wait() } + if d.persistentCache != nil { + d.persistentCache.Close() + } + var err error if n := len(d.mu.compact.inProgress); n > 0 { err = errors.Errorf("pebble: %d unexpected in-progress compactions", errors.Safe(n)) diff --git a/ingest.go b/ingest.go index 20ca53b2c3c..e14706b6120 100644 --- a/ingest.go +++ b/ingest.go @@ -298,6 +298,10 @@ func ingestLink( } else { err = vfs.LinkOrCopy(fs, paths[i], target) } + if err == nil && opts.SharedFS != nil { + sharedTarget := base.MakeSharedSSTPath(opts.SharedFS, opts.SharedDir, opts.UniqueID, meta[i].FileNum) + err = vfs.CopyAcrossFS(fs, paths[i], opts.SharedFS, sharedTarget) + } if err != nil { if err2 := ingestCleanup(fs, dirname, meta[:i]); err2 != nil { opts.Logger.Infof("ingest cleanup failed: %v", err2) @@ -822,6 +826,7 @@ func (d *DB) ingestApply( current := d.mu.versions.currentVersion() baseLevel := d.mu.versions.picker.getBaseLevel() iterOps := IterOptions{logger: d.opts.Logger} + obsoleteFiles := make([]obsoleteFile, 0) for i := range meta { // Determine the lowest level in the LSM for which the sstable doesn't // overlap any existing files in the level. @@ -833,6 +838,16 @@ func (d *DB) ingestApply( d.mu.versions.logUnlock() return nil, err } + if f.Level >= 5 && d.opts.SharedFS != nil { + m.UsesSharedFS = true + obsoleteFiles = append(obsoleteFiles, obsoleteFile{ + dir: d.dirname, + fileNum: m.FileNum, + fileType: fileTypeTable, + fileSize: m.Size, + skipMetrics: true, + }) + } f.Meta = m levelMetrics := metrics[f.Level] if levelMetrics == nil { @@ -851,6 +866,8 @@ func (d *DB) ingestApply( } d.updateReadStateLocked(d.opts.DebugCheck) d.updateTableStatsLocked(ve.NewFiles) + d.deleters.Add(1) + go d.paceAndDeleteObsoleteFiles(jobID, obsoleteFiles) d.deleteObsoleteFiles(jobID, false /* waitForOngoing */) // The ingestion may have pushed a level over the threshold for compaction, // so check to see if one is necessary and schedule it. diff --git a/internal/base/filenames.go b/internal/base/filenames.go index 269d14b7d16..b5d71a47efe 100644 --- a/internal/base/filenames.go +++ b/internal/base/filenames.go @@ -63,6 +63,13 @@ func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, fileNum FileNum) return fs.PathJoin(dirname, MakeFilename(fileType, fileNum)) } +// MakeSharedSSTPath builds a filepath for a shared SST. +func MakeSharedSSTPath(fs vfs.FS, dirname string, uniqueID uint16, fileNum FileNum) string { + const numBuckets = 10 + bucket := (uint64(fileNum) * (uint64(uniqueID) + 1)) % numBuckets + return fs.PathJoin(dirname, fmt.Sprintf("%d/%d/%s", uniqueID, bucket, MakeFilename(FileTypeTable, fileNum))) +} + // ParseFilename parses the components from a filename. func ParseFilename(fs vfs.FS, filename string) (fileType FileType, fileNum FileNum, ok bool) { filename = fs.PathBase(filename) diff --git a/internal/cache/clockpro.go b/internal/cache/clockpro.go index a5f8fd0a76b..75e346b41ea 100644 --- a/internal/cache/clockpro.go +++ b/internal/cache/clockpro.go @@ -28,6 +28,7 @@ import ( "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/invariants" + "github.com/cockroachdb/pebble/vfs" ) type fileKey struct { @@ -111,9 +112,16 @@ type shard struct { countHot int64 countCold int64 countTest int64 + + sc SecondaryCache + parent *Cache +} + +func (c *shard) setSecondaryCache(sc SecondaryCache) { + c.sc = sc } -func (c *shard) Get(id uint64, fileNum base.FileNum, offset uint64) Handle { +func (c *shard) Get(id uint64, fileNum base.FileNum, offset uint64, secondary bool) Handle { c.mu.RLock() var value *Value if e := c.blocks.Get(key{fileKey{id, fileNum}, offset}); e != nil { @@ -125,13 +133,24 @@ func (c *shard) Get(id uint64, fileNum base.FileNum, offset uint64) Handle { c.mu.RUnlock() if value == nil { atomic.AddInt64(&c.misses, 1) + if c.sc != nil && secondary { + if secondaryVal := c.sc.GetAndEvict(id, fileNum, offset); len(secondaryVal) > 0 { + v := c.parent.Alloc(len(secondaryVal)) + copy(v.Buf(), secondaryVal) + c.Set(id, fileNum, offset, v, true) + atomic.AddInt64(&c.hits, 1) + return Handle{value: value} + } + } return Handle{} } atomic.AddInt64(&c.hits, 1) return Handle{value: value} } -func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value) Handle { +func (c *shard) Set( + id uint64, fileNum base.FileNum, offset uint64, value *Value, eligibleForSecondary bool, +) Handle { if n := value.refs(); n != 1 { panic(fmt.Sprintf("pebble: Value has already been added to the cache: refs=%d", n)) } @@ -147,6 +166,7 @@ func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value // no cache entry? add it e = newEntry(c, k, int64(len(value.buf))) e.setValue(value) + e.eligibleForSecondary = eligibleForSecondary if c.metaAdd(k, e) { value.ref.trace("add-cold") c.sizeCold += e.size @@ -177,6 +197,9 @@ func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value c.sizeTest -= e.size c.countTest-- c.metaDel(e) + if c.sc != nil && e.eligibleForSecondary { + c.sc.Set(e.key.id, e.key.fileNum, e.key.offset, e.val.Buf()) + } c.metaCheck(e) e.size = int64(len(value.buf)) @@ -262,6 +285,9 @@ func (c *shard) EvictFile(id uint64, fileNum base.FileNum) { break } } + if c.sc != nil { + c.sc.DeleteFile(id, fileNum) + } c.checkConsistency() } @@ -565,6 +591,9 @@ func (c *shard) runHandTest() { c.coldTarget = 0 } c.metaDel(e) + if c.sc != nil && e.eligibleForSecondary { + c.sc.Set(e.key.id, e.key.fileNum, e.key.offset, e.val.Buf()) + } c.metaCheck(e) e.free() } @@ -658,6 +687,7 @@ func newShards(size int64, shards int) *Cache { c.shards[i] = shard{ maxSize: size / int64(len(c.shards)), coldTarget: size / int64(len(c.shards)), + parent: c, } if entriesGoAllocated { c.shards[i].entries = make(map[*entry]struct{}) @@ -736,18 +766,29 @@ func (c *Cache) Unref() { } } +func (c *Cache) AddSecondaryCache(dir string, fs vfs.FSWithOpenForWrites, capacity uint64) { + capacity = capacity / uint64(len(c.shards)) + for i := range c.shards { + path := fs.PathJoin(dir, fmt.Sprintf("shard-%d", i+1)) + fs.MkdirAll(path, 0755) + c.shards[i].setSecondaryCache(NewPersistentCache(path, fs, capacity)) + } +} + // Get retrieves the cache value for the specified file and offset, returning // nil if no value is present. -func (c *Cache) Get(id uint64, fileNum base.FileNum, offset uint64) Handle { - return c.getShard(id, fileNum, offset).Get(id, fileNum, offset) +func (c *Cache) Get(id uint64, fileNum base.FileNum, offset uint64, secondary bool) Handle { + return c.getShard(id, fileNum, offset).Get(id, fileNum, offset, secondary) } // Set sets the cache value for the specified file and offset, overwriting an // existing value if present. A Handle is returned which provides faster // retrieval of the cached value than Get (lock-free and avoidance of the map // lookup). The value must have been allocated by Cache.Alloc. -func (c *Cache) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value) Handle { - return c.getShard(id, fileNum, offset).Set(id, fileNum, offset, value) +func (c *Cache) Set( + id uint64, fileNum base.FileNum, offset uint64, value *Value, eligibleForSecondary bool, +) Handle { + return c.getShard(id, fileNum, offset).Set(id, fileNum, offset, value, eligibleForSecondary) } // Delete deletes the cached value for the specified file and offset. diff --git a/internal/cache/entry.go b/internal/cache/entry.go index 1b96e679e21..26db112fffa 100644 --- a/internal/cache/entry.go +++ b/internal/cache/entry.go @@ -56,8 +56,9 @@ type entry struct { ptype entryType // referenced is atomically set to indicate that this entry has been accessed // since the last time one of the clock hands swept it. - referenced int32 - shard *shard + referenced int32 + shard *shard + eligibleForSecondary bool // Reference count for the entry. The entry is freed when the reference count // drops to zero. ref refcnt diff --git a/internal/cache/secondary_cache.go b/internal/cache/secondary_cache.go new file mode 100644 index 00000000000..392a157d478 --- /dev/null +++ b/internal/cache/secondary_cache.go @@ -0,0 +1,472 @@ +// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package cache + +import ( + "encoding/binary" + "fmt" + "sync" + "sync/atomic" + + "github.com/cockroachdb/errors" + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/vfs" +) + +type SecondaryCache interface { + GetAndEvict(id uint64, fileNum base.FileNum, offset uint64) []byte + Set(id uint64, fileNum base.FileNum, offset uint64, block []byte) + DeleteFile(id uint64, fileNum base.FileNum) +} + +// Design of secondary cache: +// +// 0) Block key = {id, filenum, offset uint64} +// 1) Slab files of max size 16mb that contain block key, size (8 bytes), +// unused (8 bytes), followed by block contents. +// 2) In-memory map containing key (above), plus cache slab file number and +// offset in file. +// 3) On node restart, we read all slab files to repopulate in-memory map. +// +// On eviction from primary block cache: +// 1) Check if file is not deleted, and has UsesSharedFS = true. If yes, file +// blocks are eligible for secondary cache. +// 1.5) Check if block is already in the cache (possible if it was promoted +// to primary cache but not evicted from secondary yet). If it is, take +// it out of block eviction queue. +// 2) If cache usage is below capacity (fast path), append to end of current +// slab file. If current slab file has no room, preallocate new slab file. +// 3) If cache usage is above capacity, we have two options: +// a) Look at head of block eviction queue. If size of that block is higher +// than that of new block, replace that block with this block. +// b) Failing that, delete least recently accessed slab file, remove relevant +// entries from it from block eviction queue (this can happen async), +// and do 2. +// +// When a file is marked as deleted in the primary cache: +// 1) Enqueue all blocks from it into the block eviction queue. +// +// On a "Get": +// 1) Check in-memory map for block, if it exists, do a random read from that +// slab file and return block contents. +// 2) Move up slab file in LRU queue. +// 3) Add to block eviction queue +// +// On block eviction for any reason: +// 1) Remove self from original file's list +// 2) Remove self from slab file's list (not necessary if part of slab file +// deletion). +// +// Doubly linked lists: +// 1) LRU of slab files (doubly linked list with ability to move things around) +// 2) Block eviction queue (can come later) +// 3) List of all cached blocks from a given original sst file +// operations: add to tail, remove at any point +// 4) List of all cached blocks from a given slab file +// operations: add to tail, remove at any point + +type blockKey struct { + fileKey + + offset uint64 +} + +type lruFileList struct { + head, tail *secondaryCacheFile +} + +func (l *lruFileList) moveToFront(h *secondaryCacheFile) { + if l.tail == h && l.head != h { + l.tail = h.prev + } + if h.prev != nil { + h.prev.next = h.next + } + if h.next != nil && l.head != h { + h.next.prev = h.prev + } + h.prev = nil + if l.head != h { + h.next = l.head + l.head.prev = h + l.head = h + } +} + +func (l *lruFileList) add(h *secondaryCacheFile) { + h.prev = nil + h.next = l.head + l.head = h + if l.tail == nil { + l.tail = h + } else { + h.next.prev = h + } +} + +func (l *lruFileList) evict() *secondaryCacheFile { + if l.tail == nil { + return nil + } + h := l.tail + l.tail = h.prev + if l.head != h { + h.prev.next = nil + } else { + l.head = nil + } + h.next = nil + h.prev = nil + return h +} + +type blockListType int + +const ( + blockListOrigFile blockListType = iota + blockListSlabFile +) + +type blockLinkedList struct { + head, tail *secondaryCacheValue +} + +type blockListLinks struct { + prev, next *secondaryCacheValue +} + +func (b *blockLinkedList) add(s *secondaryCacheValue, listType blockListType) { + switch listType { + case blockListOrigFile: + s.origFileLink.prev = b.tail + s.origFileLink.next = nil + case blockListSlabFile: + s.slabFileLink.prev = b.tail + s.slabFileLink.next = nil + } + if b.tail == nil { + b.head = s + } else { + switch listType { + case blockListOrigFile: + b.tail.origFileLink.next = s + case blockListSlabFile: + b.tail.slabFileLink.next = s + } + } + b.tail = s +} + +func (b *blockLinkedList) remove(s *secondaryCacheValue, listType blockListType) { + var leftNode, rightNode *secondaryCacheValue + switch listType { + case blockListOrigFile: + leftNode = s.origFileLink.prev + rightNode = s.origFileLink.next + s.origFileLink.prev = nil + s.origFileLink.next = nil + case blockListSlabFile: + leftNode = s.slabFileLink.prev + rightNode = s.slabFileLink.next + s.slabFileLink.prev = nil + s.slabFileLink.next = nil + } + + if leftNode == nil { + b.head = rightNode + } else { + switch listType { + case blockListOrigFile: + leftNode.origFileLink.next = rightNode + case blockListSlabFile: + leftNode.slabFileLink.next = rightNode + } + } + if rightNode == nil { + b.tail = leftNode + } else { + switch listType { + case blockListOrigFile: + rightNode.origFileLink.prev = leftNode + case blockListSlabFile: + rightNode.slabFileLink.prev = leftNode + } + } +} + +const secondaryCacheHeaderSize = 5 * 8 // 5 uint64s +const targetSlabFileSize = 16 << 20 // 16MB + +type secondaryCache struct { + mu struct { + sync.Mutex + + blocks map[blockKey]*secondaryCacheValue + files map[int]*secondaryCacheFile + origFiles map[fileKey]*secondaryCacheOrigFile + list lruFileList + usedCapacity uint64 + + currentFileNum int + currentFile *secondaryCacheFile + } + + capacity uint64 + wg sync.WaitGroup + cacheDir string + fs vfs.FSWithOpenForWrites +} + +type secondaryCacheFile struct { + name int + handle vfs.RandomWriteFile + writerMu struct { + sync.Mutex + usedSize uint64 + } + + // protected by secondaryCache.mu + size uint64 + prev, next *secondaryCacheFile + blocks blockLinkedList + + refs refcnt +} + +func (f *secondaryCacheFile) writeBlock( + val *secondaryCacheValue, block []byte, fs vfs.FSWithOpenForWrites, dir string, +) { + f.writerMu.Lock() + defer f.writerMu.Unlock() + + if f.handle == nil { + var err error + f.handle, err = fs.OpenForWrites(fs.PathJoin(dir, fmt.Sprintf("%d.slab", f.name))) + vfs.Preallocate(f.handle, 0, targetSlabFileSize) + if err != nil { + panic(err.Error()) + } + } + + buf := make([]byte, 0, secondaryCacheHeaderSize+len(block)) + val.offset = f.writerMu.usedSize + secondaryCacheHeaderSize + buf = val.Encode(buf, block) + n, err := f.handle.WriteAt(buf, int64(f.writerMu.usedSize)) + if err != nil { + panic(err.Error()) + } + f.writerMu.usedSize += uint64(n) + return +} + +type secondaryCacheOrigFile struct { + filenum base.FileNum + blocks blockLinkedList +} + +type secondaryCacheValue struct { + atomic struct { + ready int64 + } + cacheID uint64 + origFile base.FileNum + origOffset uint64 + cacheFile *secondaryCacheFile + offset uint64 + size uint64 + unused uint64 + + // Protected by secondaryCache.mu. + origFileLink blockListLinks + slabFileLink blockListLinks +} + +func (s *secondaryCacheValue) Encode(dst []byte, blockData []byte) []byte { + newCap := cap(dst) + if newCap == 0 { + newCap = 1 + } + for newCap < len(dst)+secondaryCacheHeaderSize+len(blockData) { + newCap *= 2 + } + if newCap > cap(dst) { + oldDst := dst + dst = make([]byte, len(dst), newCap) + copy(dst, oldDst) + } + origLen := len(dst) + dst = dst[:len(dst)+secondaryCacheHeaderSize+len(blockData)] + binary.LittleEndian.PutUint64(dst[origLen:], s.cacheID) + binary.LittleEndian.PutUint64(dst[origLen+8:], uint64(s.origFile)) + binary.LittleEndian.PutUint64(dst[origLen+16:], s.origOffset) + binary.LittleEndian.PutUint64(dst[origLen+24:], s.size) + binary.LittleEndian.PutUint64(dst[origLen+32:], s.unused) + copy(dst[origLen+secondaryCacheHeaderSize:], blockData) + return dst +} + +func (s *secondaryCacheValue) Decode(src []byte) (rem []byte, block []byte, err error) { + if len(src) < secondaryCacheHeaderSize { + return nil, nil, errors.New("source slice too small") + } + s.cacheID = binary.LittleEndian.Uint64(src[:8]) + s.origFile = base.FileNum(binary.LittleEndian.Uint64(src[8:16])) + s.origOffset = binary.LittleEndian.Uint64(src[16:24]) + s.size = binary.LittleEndian.Uint64(src[24:32]) + s.unused = binary.LittleEndian.Uint64(src[24:32]) + block = src[secondaryCacheHeaderSize : secondaryCacheHeaderSize+s.size] + return src[secondaryCacheHeaderSize+s.size+s.unused:], block, nil +} + +func NewPersistentCache(dir string, fs vfs.FSWithOpenForWrites, capacity uint64) SecondaryCache { + psc := &secondaryCache{ + capacity: capacity, + cacheDir: dir, + fs: fs, + } + psc.mu.files = make(map[int]*secondaryCacheFile) + psc.mu.origFiles = make(map[fileKey]*secondaryCacheOrigFile) + psc.mu.blocks = make(map[blockKey]*secondaryCacheValue) + return psc +} + +func (l *secondaryCache) GetAndEvict(id uint64, fileNum base.FileNum, offset uint64) []byte { + key := blockKey{fileKey{id, fileNum}, offset} + l.mu.Lock() + val, ok := l.mu.blocks[key] + if !ok { + l.mu.Unlock() + return nil + } + // TODO(bilal): Add block to eviction queue. + f := val.cacheFile + f.refs.acquire() + defer f.refs.release() + l.mu.list.moveToFront(f) + l.mu.Unlock() + + buf := make([]byte, val.size) + for atomic.LoadInt64(&val.atomic.ready) == 0 { + // spin + } + n, err := f.handle.ReadAt(buf, int64(val.offset)) + if err != nil || n != int(val.size) { + return nil + } + + return buf +} + +// mu must be held while calling this function. +func (l *secondaryCache) rotateFile() { + l.mu.currentFileNum++ + l.mu.currentFile = &secondaryCacheFile{ + name: l.mu.currentFileNum, + handle: nil, + size: 0, + prev: nil, + next: nil, + blocks: blockLinkedList{}, + } + l.mu.currentFile.refs.init(1) + l.mu.list.add(l.mu.currentFile) +} + +// Set pseudocode: +// 1) Check if file is not deleted, and has UsesSharedFS = true. If yes, file +// blocks are eligible for secondary cache. +// 1.5) Check if block is already in the cache (possible if it was promoted +// to primary cache but not evicted from secondary yet). If it is, take +// it out of block eviction queue. +// 2) If cache usage is below capacity (fast path), append to end of current +// slab file. If current slab file has no room, preallocate new slab file. +// 3) If cache usage is above capacity, we have two options: +// a) Look at head of block eviction queue. If size of that block is higher +// than that of new block, replace that block with this block. +// b) Failing that, delete least recently accessed slab file, remove relevant +// entries from it from block eviction queue (this can happen async), +// and do 2. +func (l *secondaryCache) Set(id uint64, fileNum base.FileNum, offset uint64, block []byte) { + key := blockKey{fileKey{id, fileNum}, offset} + l.mu.Lock() + val, ok := l.mu.blocks[key] + if ok && val != nil { + l.mu.Unlock() + return + } + // TODO(bilal): Once block eviction queue is in, check if val is in it, and if + // it is, take it out of it. + + l.mu.usedCapacity += uint64(len(block) + secondaryCacheHeaderSize) + filesToEvict := make([]*secondaryCacheFile, 0, 1) + for l.mu.usedCapacity > l.capacity { + f := l.mu.list.evict() + l.mu.usedCapacity -= f.size + for ptr := f.blocks.head; ptr != nil; ptr = ptr.slabFileLink.next { + fk := fileKey{ptr.cacheID, ptr.origFile} + delete(l.mu.blocks, blockKey{fk, ptr.origOffset}) + l.mu.origFiles[fk].blocks.remove(ptr, blockListOrigFile) + } + delete(l.mu.files, f.name) + filesToEvict = append(filesToEvict, f) + } + if l.mu.currentFile == nil || l.mu.currentFile.size > targetSlabFileSize { + l.rotateFile() + } + currentFile := l.mu.currentFile + currentFile.refs.acquire() + currentFile.size += uint64(secondaryCacheHeaderSize + len(block)) + + val = &secondaryCacheValue{ + cacheID: id, + origFile: fileNum, + origOffset: offset, + cacheFile: currentFile, + size: uint64(len(block)), + } + l.mu.blocks[key] = val + currentFile.blocks.add(val, blockListSlabFile) + origFile := l.mu.origFiles[key.fileKey] + if origFile == nil { + origFile = &secondaryCacheOrigFile{ + filenum: fileNum, + blocks: blockLinkedList{}, + } + l.mu.origFiles[key.fileKey] = origFile + } + origFile.blocks.add(val, blockListOrigFile) + l.mu.Unlock() + + currentFile.writeBlock(val, block, l.fs, l.cacheDir) + atomic.StoreInt64(&val.atomic.ready, 1) + currentFile.refs.release() + + // Delete evicted files. + for _, f := range filesToEvict { + refsClosed := f.refs.release() + for !refsClosed && f.refs.refs() != 0 { + // spin + } + f.handle.Close() + l.fs.Remove(l.fs.PathJoin(l.cacheDir, fmt.Sprintf("%d.slab", f.name))) + } +} + +func (l *secondaryCache) DeleteFile(id uint64, fileNum base.FileNum) { + // TODO(bilal) Once block eviction queue is in, add these blocks there instead. + l.mu.Lock() + defer l.mu.Unlock() + fk := fileKey{id, fileNum} + origFile, ok := l.mu.origFiles[fk] + if !ok { + return + } + + for ptr := origFile.blocks.head; ptr != nil; ptr = ptr.origFileLink.next { + ptr.cacheFile.blocks.remove(ptr, blockListSlabFile) + delete(l.mu.blocks, blockKey{fk, ptr.origOffset}) + } + delete(l.mu.origFiles, fk) +} diff --git a/internal/manifest/version.go b/internal/manifest/version.go index a3f0d0d8988..6583b4db255 100644 --- a/internal/manifest/version.go +++ b/internal/manifest/version.go @@ -131,6 +131,11 @@ type FileMetadata struct { // that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc). AllowedSeeks int64 + // BytesBeforeLocalCache is used to determine how many bytes should + // be read from this file before it's cached in the persistent cache. + // Only used when UsesSharedFS = true. + BytesBeforeLocalCache int64 + // statsValid is 1 if stats have been loaded for the table. The // TableStats structure is populated only if valid is 1. statsValid uint32 @@ -140,6 +145,10 @@ type FileMetadata struct { // to re-set allowed seeks on a file once it hits 0. InitAllowedSeeks int64 + // InitBytesBeforeLocalCache is the initial value of BytesBeforeLocalCache. + // It is used to reset BytesBeforeLocalCache on a file once it hits 0. + InitBytesBeforeLocalCache int64 + // Reference count for the file: incremented when a file is added to a // version and decremented when the version is unreferenced. The file is // obsolete when the reference count falls to zero. @@ -152,6 +161,9 @@ type FileMetadata struct { // UTC). For ingested sstables, this corresponds to the time the file was // ingested. CreationTime int64 + // UsesSharedFS is true if this file is stored in the shared FS. A copy + // of it could also be cached on the local FS. + UsesSharedFS bool // Smallest and largest sequence numbers in the table, across both point and // range keys. SmallestSeqNum uint64 @@ -1037,13 +1049,16 @@ func (v *Version) CheckOrdering(cmp Compare, format base.FormatKey) error { // CheckConsistency checks that all of the files listed in the version exist // and their on-disk sizes match the sizes listed in the version. -func (v *Version) CheckConsistency(dirname string, fs vfs.FS) error { +func (v *Version) CheckConsistency(dirname string, fs vfs.FS, sharedFS vfs.FS) error { var buf bytes.Buffer var args []interface{} for level, files := range v.Levels { iter := files.Iter() for f := iter.First(); f != nil; f = iter.Next() { + if f.UsesSharedFS && sharedFS != nil { + continue + } path := base.MakeFilepath(fs, dirname, base.FileTypeTable, f.FileNum) info, err := fs.Stat(path) if err != nil { diff --git a/internal/manifest/version_edit.go b/internal/manifest/version_edit.go index 75509ab1714..aa99d46330e 100644 --- a/internal/manifest/version_edit.go +++ b/internal/manifest/version_edit.go @@ -54,6 +54,7 @@ const ( customTagTerminate = 1 customTagNeedsCompaction = 2 customTagCreationTime = 6 + customTagSharedFS = 7 customTagPathID = 65 customTagNonSafeIgnoreMask = 1 << 6 ) @@ -265,7 +266,7 @@ func (v *VersionEdit) Decode(r io.Reader) error { return err } } - var markedForCompaction bool + var markedForCompaction, usesSharedFS bool var creationTime uint64 if tag == tagNewFile4 || tag == tagNewFile5 { for { @@ -297,6 +298,12 @@ func (v *VersionEdit) Decode(r io.Reader) error { case customTagPathID: return base.CorruptionErrorf("new-file4: path-id field not supported") + case customTagSharedFS: + if len(field) != 1 { + return base.CorruptionErrorf("new-file4: need-compaction field wrong size") + } + usesSharedFS = (field[0] == 1) + default: if (customTag & customTagNonSafeIgnoreMask) != 0 { return base.CorruptionErrorf("new-file4: custom field not supported: %d", customTag) @@ -342,6 +349,7 @@ func (v *VersionEdit) Decode(r io.Reader) error { } } m.boundsSet = true + m.UsesSharedFS = usesSharedFS v.NewFiles = append(v.NewFiles, NewFileEntry{ Level: level, Meta: m, @@ -397,7 +405,7 @@ func (v *VersionEdit) Encode(w io.Writer) error { e.writeUvarint(uint64(x.FileNum)) } for _, x := range v.NewFiles { - customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 + customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.UsesSharedFS var tag uint64 switch { case x.Meta.HasRangeKeys: @@ -450,6 +458,10 @@ func (v *VersionEdit) Encode(w io.Writer) error { e.writeUvarint(customTagNeedsCompaction) e.writeBytes([]byte{1}) } + if x.Meta.UsesSharedFS { + e.writeUvarint(customTagSharedFS) + e.writeBytes([]byte{1}) + } e.writeUvarint(customTagTerminate) } } @@ -726,6 +738,13 @@ func (b *BulkVersionEdit) Apply( atomic.StoreInt64(&f.Atomic.AllowedSeeks, allowedSeeks) f.InitAllowedSeeks = allowedSeeks + bytesBeforeLocalCache := int64(f.Size) / 10 + if bytesBeforeLocalCache < 1024*1024 { + bytesBeforeLocalCache = 1024 * 1024 + } + atomic.StoreInt64(&f.Atomic.BytesBeforeLocalCache, bytesBeforeLocalCache) + f.InitBytesBeforeLocalCache = bytesBeforeLocalCache + err := lm.tree.insert(f) if err != nil { return nil, nil, errors.Wrap(err, "pebble") diff --git a/internal/manifest/version_test.go b/internal/manifest/version_test.go index bc471adaa32..59dc2dc49f1 100644 --- a/internal/manifest/version_test.go +++ b/internal/manifest/version_test.go @@ -378,7 +378,7 @@ func TestCheckConsistency(t *testing.T) { } v := NewVersion(cmp, fmtKey, 0, filesByLevel) - err := v.CheckConsistency(dir, mem) + err := v.CheckConsistency(dir, mem, nil) if err != nil { if redactErr { redacted := redact.Sprint(err).Redact() diff --git a/open.go b/open.go index ed217ce9316..0ce6a55efe7 100644 --- a/open.go +++ b/open.go @@ -113,11 +113,6 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { } }() - tableCacheSize := TableCacheSize(opts.MaxOpenFiles) - d.tableCache = newTableCacheContainer(opts.TableCache, d.cacheID, dirname, opts.FS, d.opts, tableCacheSize) - d.newIters = d.tableCache.newIters - d.tableNewRangeKeyIter = d.tableCache.newRangeKeyIter - d.commit = newCommitPipeline(commitEnv{ logSeqNum: &d.mu.versions.atomic.logSeqNum, visibleSeqNum: &d.mu.versions.atomic.visibleSeqNum, @@ -255,7 +250,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { if err := d.mu.versions.load(dirname, opts, manifestFileNum, manifestMarker, setCurrent, &d.mu.Mutex); err != nil { return nil, err } - if err := d.mu.versions.currentVersion().CheckConsistency(dirname, opts.FS); err != nil { + if err := d.mu.versions.currentVersion().CheckConsistency(dirname, opts.FS, opts.SharedFS); err != nil { return nil, err } } @@ -349,13 +344,27 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { // Validate the most-recent OPTIONS file, if there is one. var strictWALTail bool + var uniqueID uint16 if previousOptionsFilename != "" { path := opts.FS.PathJoin(dirname, previousOptionsFilename) - strictWALTail, err = checkOptions(opts, path) + strictWALTail, uniqueID, err = checkOptions(opts, path) if err != nil { return nil, err } } + if uniqueID != 0 { + // Overwrite the uniqueID in opts with this one. + opts.UniqueID = uniqueID + } + + //if opts.SharedFS != nil { + // d.persistentCache = newPersistentCache(opts.FS, dirname, opts.SharedFS, opts.SharedDir, opts.UniqueID) + // d.persistentCache.Start() + //} + tableCacheSize := TableCacheSize(opts.MaxOpenFiles) + d.tableCache = newTableCacheContainer(opts.TableCache, d.cacheID, dirname, opts.FS, opts.SharedDir, opts.SharedFS, d.persistentCache, d.opts, tableCacheSize) + d.newIters = d.tableCache.newIters + d.tableNewRangeKeyIter = d.tableCache.newRangeKeyIter sort.Slice(logFiles, func(i, j int) bool { return logFiles[i].num < logFiles[j].num @@ -713,16 +722,16 @@ func (d *DB) replayWAL( return maxSeqNum, err } -func checkOptions(opts *Options, path string) (strictWALTail bool, err error) { +func checkOptions(opts *Options, path string) (strictWALTail bool, uniqueID uint16, err error) { f, err := opts.FS.Open(path) if err != nil { - return false, err + return false, 0, err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { - return false, err + return false, 0, err } return opts.checkOptions(string(data)) } diff --git a/options.go b/options.go index 24ba14a7355..66efafff62d 100644 --- a/options.go +++ b/options.go @@ -701,6 +701,20 @@ type Options struct { // disabled. ReadOnly bool + // SharedDir is the base directory name in SharedFS. + SharedDir string + + // SharedFS is a second file system that could contain files that are + // not fully owned by this Pebble instance, necessitating more coordination + // for deletes. It is also expected to have slower read/write performance + // than FS. + SharedFS vfs.FS + + // UniqueID is a unique ID that's generated for new Pebble instances and + // serialized into the Options file. Used to disambiguate this instance's + // files from that of others in SharedFS. + UniqueID uint16 + // TableCache is an initialized TableCache which should be set as an // option if the DB needs to be initialized with a pre-existing table cache. // If TableCache is nil, then a table cache which is unique to the DB instance @@ -1013,6 +1027,7 @@ func (o *Options) String() string { fmt.Fprintf(&buf, "%s", o.TablePropertyCollectors[i]().Name()) } fmt.Fprintf(&buf, "]\n") + fmt.Fprintf(&buf, " unique_id=%d\n", o.UniqueID) fmt.Fprintf(&buf, " validate_on_ingest=%t\n", o.Experimental.ValidateOnIngest) fmt.Fprintf(&buf, " wal_dir=%s\n", o.WALDir) fmt.Fprintf(&buf, " wal_bytes_per_sync=%d\n", o.WALBytesPerSync) @@ -1238,6 +1253,10 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error { } case "table_property_collectors": // TODO(peter): set o.TablePropertyCollectors + case "unique_id": + var uniqueID uint64 + uniqueID, err = strconv.ParseUint(value, 10, 16) + o.UniqueID = uint16(uniqueID) case "validate_on_ingest": o.Experimental.ValidateOnIngest, err = strconv.ParseBool(value) case "wal_dir": @@ -1324,9 +1343,9 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error { }) } -func (o *Options) checkOptions(s string) (strictWALTail bool, err error) { +func (o *Options) checkOptions(s string) (strictWALTail bool, uniqueID uint16, err error) { // TODO(jackson): Refactor to avoid awkwardness of the strictWALTail return value. - return strictWALTail, parseOptions(s, func(section, key, value string) error { + return strictWALTail, uniqueID, parseOptions(s, func(section, key, value string) error { switch section + "." + key { case "Options.comparer": if value != o.Comparer.Name { @@ -1345,6 +1364,13 @@ func (o *Options) checkOptions(s string) (strictWALTail bool, err error) { if err != nil { return errors.Errorf("pebble: error parsing strict_wal_tail value %q: %w", value, err) } + case "Options.unique_id": + var uniqueIDint uint64 + uniqueIDint, err = strconv.ParseUint(value, 10, 16) + if err != nil { + return errors.Errorf("pebble: error parsing unique_id value %q: %w", value, err) + } + uniqueID = uint16(uniqueIDint) } return nil }) @@ -1354,7 +1380,7 @@ func (o *Options) checkOptions(s string) (strictWALTail bool, err error) { // serialized by Options.String(). For example, the Comparer and Merger must be // the same, or data will not be able to be properly read from the DB. func (o *Options) Check(s string) error { - _, err := o.checkOptions(s) + _, _, err := o.checkOptions(s) return err } diff --git a/persistent_cache.go b/persistent_cache.go new file mode 100644 index 00000000000..287c8258450 --- /dev/null +++ b/persistent_cache.go @@ -0,0 +1,282 @@ +// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package pebble + +import ( + "fmt" + "sync" + "sync/atomic" + + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/internal/manifest" + "github.com/cockroachdb/pebble/sstable" + "github.com/cockroachdb/pebble/vfs" +) + +type persistentCache struct { + mu struct { + sync.Mutex + + files map[base.FileNum]*persistentCacheValue + head, tail *persistentCacheValue + usedCapacity uint64 + } + + files chan *persistentCacheValue + capacity uint64 + + localFS, sharedFS vfs.FS + localDir, sharedDir string + uniqueID uint16 + wg sync.WaitGroup +} + +type persistentCacheValue struct { + atomic struct { + initialized uint64 + } + mu struct { + sync.Mutex + + refs int64 + evicting sync.Cond + } + localFile vfs.File + fileNum base.FileNum + dir string + localFS vfs.FS + size uint64 + + // Protected by persistentCache.mu. + prev, next *persistentCacheValue +} + +func (p *persistentCacheValue) File() vfs.File { + return p.localFile +} + +func (p *persistentCacheValue) Ref() { + p.mu.Lock() + p.mu.refs++ + p.mu.Unlock() +} + +func (p *persistentCacheValue) unrefInternal(waitForEviction bool) { + p.mu.Lock() + defer p.mu.Unlock() + p.mu.refs-- + if p.mu.refs > 0 { + if waitForEviction { + p.mu.evicting.Wait() + } + return + } else if p.mu.refs < 0 { + panic("inconsistent ref count") + } + // p.mu.refs == 0 + fmt.Printf("persistent cache: evicted %s (size = %d)\n", p.fileNum, p.size) + if atomic.LoadUint64(&p.atomic.initialized) != 0 { + _ = p.localFile.Close() + _ = p.localFS.Remove(base.MakeFilepath(p.localFS, p.dir, fileTypeTable, p.fileNum)) + } + p.mu.evicting.Broadcast() +} + +func (p *persistentCacheValue) Unref() { + p.unrefInternal(false /* waitForEviction */) +} + +func newPersistentCache( + localFS vfs.FS, localDir string, sharedFS vfs.FS, sharedDir string, uniqueID uint16, +) *persistentCache { + psc := &persistentCache{ + files: make(chan *persistentCacheValue, 500), + capacity: 10 << 30, /* 10 GB */ + localFS: localFS, + sharedFS: sharedFS, + localDir: localDir, + sharedDir: sharedDir, + uniqueID: uniqueID, + } + psc.mu.files = make(map[base.FileNum]*persistentCacheValue) + return psc +} + +func (l *persistentCache) MarkDeleted(fileNum base.FileNum) { + l.mu.Lock() + defer l.mu.Unlock() + cached := l.mu.files[fileNum] + if cached == nil { + return + } + + delete(l.mu.files, fileNum) +} + +func (l *persistentCache) Get(fileNum base.FileNum) sstable.PersistentCacheValue { + l.mu.Lock() + cached := l.mu.files[fileNum] + + if cached == nil { + l.mu.Unlock() + return nil + } + + cached.Ref() + + // Move up to head. + if l.mu.tail == cached && l.mu.head != cached { + l.mu.tail = cached.prev + } + if cached.prev != nil { + cached.prev.next = cached.next + } + if cached.next != nil && l.mu.head != cached { + cached.next.prev = cached.prev + } + cached.prev = nil + if l.mu.head != cached { + cached.next = l.mu.head + l.mu.head.prev = cached + l.mu.head = cached + } + + l.mu.Unlock() + + if atomic.LoadUint64(&cached.atomic.initialized) == 0 { + cached.Unref() + return nil + } + return cached +} + +func (l *persistentCache) MaybeCache(meta *manifest.FileMetadata, amountRead int64) { + newVal := atomic.AddInt64(&meta.Atomic.BytesBeforeLocalCache, -1*amountRead) + if newVal > 0 { + return + } else { + atomic.StoreInt64(&meta.Atomic.BytesBeforeLocalCache, meta.InitBytesBeforeLocalCache) + } + + l.mu.Lock() + cached := l.mu.files[meta.FileNum] + if cached != nil { + l.mu.Unlock() + return + } + pcValue := &persistentCacheValue{ + localFile: nil, + fileNum: meta.FileNum, + size: meta.Size, + dir: l.localDir, + localFS: l.localFS, + } + pcValue.atomic.initialized = 0 + pcValue.mu.refs = 1 + pcValue.mu.evicting.L = &pcValue.mu + l.mu.files[meta.FileNum] = pcValue + + pcValue.prev = nil + pcValue.next = l.mu.head + l.mu.head = pcValue + if l.mu.tail == nil { + l.mu.tail = pcValue + } else { + pcValue.next.prev = pcValue + } + l.mu.usedCapacity += pcValue.size + l.mu.Unlock() + + l.files <- pcValue +} + +func (l *persistentCache) copyAsync( + localPath string, sharedPath string, val *persistentCacheValue, +) { + defer l.wg.Done() + defer val.Unref() + + err := vfs.CopyAcrossFS(l.sharedFS, sharedPath, l.localFS, localPath) + if err != nil { + // TODO: handle. + fmt.Printf("error when copying across fs: %s\n", err) + return + } + + val.localFile, err = l.localFS.Open(localPath) + if err != nil { + // TODO: handle. + fmt.Printf("error when opening file after copy: %s\n", err) + return + } + atomic.StoreUint64(&val.atomic.initialized, 1) + fmt.Printf("persistent cache: saved %s (size = %d)\n", val.fileNum, val.size) +} + +func (l *persistentCache) runCacher() { + defer l.wg.Done() + + for { + file, ok := <-l.files + if !ok { + return + } + l.mu.Lock() + skip := false + for l.mu.usedCapacity > l.capacity { + tail := l.mu.tail + if tail == file { + // Evicting ourselves. Don't cache. + skip = true + } else if tail == nil { + // Empty cache. + break + } + l.mu.tail = tail.prev + if tail.prev == nil { + l.mu.head = nil + } else { + tail.prev.next = nil + } + + if !skip { + l.mu.usedCapacity -= tail.size + } + tail.next = nil + tail.prev = nil + delete(l.mu.files, tail.fileNum) + if skip { + break + } + l.mu.Unlock() + + tail.unrefInternal(true) + l.mu.Lock() + } + if skip || (file.next == nil && file.prev == nil) { + // We were evicted. + l.mu.Unlock() + continue + } + file.Ref() + l.mu.Unlock() + fmt.Printf("persistent cache: added %s (size = %d)\n", file.fileNum, file.size) + + localPath := base.MakeFilepath(l.localFS, l.localDir, fileTypeTable, file.fileNum) + sharedPath := base.MakeSharedSSTPath(l.sharedFS, l.sharedDir, l.uniqueID, file.fileNum) + l.wg.Add(1) + go l.copyAsync(localPath, sharedPath, file) + } +} + +func (l *persistentCache) Close() { + close(l.files) + l.wg.Wait() +} + +func (l *persistentCache) Start() { + l.wg.Add(1) + go l.runCacher() +} diff --git a/sstable/reader.go b/sstable/reader.go index 22e135d55f0..06b8a118814 100644 --- a/sstable/reader.go +++ b/sstable/reader.go @@ -21,6 +21,7 @@ import ( "github.com/cockroachdb/pebble/internal/crc" "github.com/cockroachdb/pebble/internal/invariants" "github.com/cockroachdb/pebble/internal/keyspan" + "github.com/cockroachdb/pebble/internal/manifest" "github.com/cockroachdb/pebble/internal/private" "github.com/cockroachdb/pebble/vfs" ) @@ -39,6 +40,17 @@ const ( maxReadaheadSize = 256 << 10 /* 256KB */ ) +type PersistentCacheValue interface { + File() vfs.File + Ref() + Unref() +} + +type PersistentCache interface { + Get(fileNum base.FileNum) PersistentCacheValue + MaybeCache(meta *manifest.FileMetadata, amountRead int64) +} + // decodeBlockHandle returns the block handle encoded at the start of src, as // well as the number of bytes it occupies. It returns zero if given invalid // input. A block handle for a data block or a first/lower level index block @@ -2326,6 +2338,16 @@ func (f FileReopenOpt) readerApply(r *Reader) { } } +type PersistentCacheOpt struct { + PsCache PersistentCache + Meta *manifest.FileMetadata +} + +func (p PersistentCacheOpt) readerApply(r *Reader) { + //r.psCache = p.PsCache + r.meta = p.Meta +} + // rawTombstonesOpt is a Reader open option for specifying that range // tombstones returned by Reader.NewRangeDelIter() should not be // fragmented. Used by debug tools to get a raw view of the tombstones @@ -2371,6 +2393,8 @@ type Reader struct { tableFilter *tableFilterReader tableFormat TableFormat Properties Properties + psCache PersistentCache + meta *manifest.FileMetadata } // Close implements DB.Close, as documented in the pebble package. @@ -2542,7 +2566,11 @@ func checkChecksum( func (r *Reader) readBlock( bh BlockHandle, transform blockTransform, raState *readaheadState, ) (_ cache.Handle, cacheHit bool, _ error) { - if h := r.opts.Cache.Get(r.cacheID, r.fileNum, bh.Offset); h.Get() != nil { + usesSharedFS := false + if r.meta != nil { + usesSharedFS = r.meta.UsesSharedFS + } + if h := r.opts.Cache.Get(r.cacheID, r.fileNum, bh.Offset, usesSharedFS); h.Get() != nil { if raState != nil { raState.recordCacheHit(int64(bh.Offset), int64(bh.Length+blockTrailerLen)) } @@ -2550,6 +2578,13 @@ func (r *Reader) readBlock( } file := r.file + if r.psCache != nil { + if val := r.psCache.Get(r.fileNum); val != nil { + file = val.File() + defer val.Unref() + } + } + if raState != nil { if raState.sequentialFile != nil { file = raState.sequentialFile @@ -2628,7 +2663,10 @@ func (r *Reader) readBlock( v = newV } - h := r.opts.Cache.Set(r.cacheID, r.fileNum, bh.Offset, v) + h := r.opts.Cache.Set(r.cacheID, r.fileNum, bh.Offset, v, usesSharedFS) + if r.psCache != nil { + r.psCache.MaybeCache(r.meta, int64(bh.Length)) + } return h, false, nil } diff --git a/table_cache.go b/table_cache.go index 93872f12f8b..d8fff0e4ac1 100644 --- a/table_cache.go +++ b/table_cache.go @@ -69,6 +69,10 @@ type tableCacheOpts struct { cacheID uint64 dirname string fs vfs.FS + sharedDir string + sharedFS vfs.FS + uniqueID uint16 + psCache *persistentCache opts sstable.ReaderOptions filterMetrics *FilterMetrics } @@ -86,7 +90,15 @@ type tableCacheContainer struct { // newTableCacheContainer will panic if the underlying cache in the table cache // doesn't match Options.Cache. func newTableCacheContainer( - tc *TableCache, cacheID uint64, dirname string, fs vfs.FS, opts *Options, size int, + tc *TableCache, + cacheID uint64, + dirname string, + fs vfs.FS, + sharedDir string, + sharedFS vfs.FS, + psCache *persistentCache, + opts *Options, + size int, ) *tableCacheContainer { // We will release a ref to table cache acquired here when tableCacheContainer.close is called. if tc != nil { @@ -106,6 +118,10 @@ func newTableCacheContainer( t.dbOpts.cacheID = cacheID t.dbOpts.dirname = dirname t.dbOpts.fs = fs + t.dbOpts.sharedDir = sharedDir + t.dbOpts.sharedFS = sharedFS + t.dbOpts.uniqueID = opts.UniqueID + t.dbOpts.psCache = psCache t.dbOpts.opts = opts.MakeReaderOptions() t.dbOpts.filterMetrics = &FilterMetrics{} t.dbOpts.atomic.iterCount = new(int32) @@ -874,12 +890,25 @@ type tableCacheValue struct { func (v *tableCacheValue) load(meta *fileMetadata, c *tableCacheShard, dbOpts *tableCacheOpts) { // Try opening the fileTypeTable first. var f vfs.File - v.filename = base.MakeFilepath(dbOpts.fs, dbOpts.dirname, fileTypeTable, meta.FileNum) - f, v.err = dbOpts.fs.Open(v.filename, vfs.RandomReadsOption) + fs := dbOpts.fs + dirname := dbOpts.dirname + if meta.UsesSharedFS { + fs = dbOpts.sharedFS + dirname = dbOpts.sharedDir + v.filename = base.MakeSharedSSTPath(fs, dirname, dbOpts.uniqueID, meta.FileNum) + } else { + v.filename = base.MakeFilepath(fs, dirname, fileTypeTable, meta.FileNum) + } + f, v.err = fs.Open(v.filename, vfs.RandomReadsOption) if v.err == nil { cacheOpts := private.SSTableCacheOpts(dbOpts.cacheID, meta.FileNum).(sstable.ReaderOption) - reopenOpt := sstable.FileReopenOpt{FS: dbOpts.fs, Filename: v.filename} - v.reader, v.err = sstable.NewReader(f, dbOpts.opts, cacheOpts, dbOpts.filterMetrics, reopenOpt) + extraOpts := []sstable.ReaderOption{cacheOpts, dbOpts.filterMetrics} + if !meta.UsesSharedFS { + extraOpts = append(extraOpts, sstable.FileReopenOpt{FS: dbOpts.fs, Filename: v.filename}) + } else { + extraOpts = append(extraOpts, sstable.PersistentCacheOpt{PsCache: dbOpts.psCache, Meta: meta}) + } + v.reader, v.err = sstable.NewReader(f, dbOpts.opts, extraOpts...) } if v.err == nil { if meta.SmallestSeqNum == meta.LargestSeqNum { diff --git a/vfs/preallocate_generic.go b/vfs/preallocate_generic.go index 4b86afb4bbc..bafa9c5611e 100644 --- a/vfs/preallocate_generic.go +++ b/vfs/preallocate_generic.go @@ -23,3 +23,11 @@ func preallocExtend(fd uintptr, offset, length int64) error { // fallocate or posix_fallocate in order to be effective. return nil } + +// Preallocate TODO +func Preallocate(f File, offset, length int64) error { + if fdFile, ok := f.(fdGetter); ok { + return preallocExtend(fdFile.Fd(), offset, length) + } + return nil +} diff --git a/vfs/preallocate_linux.go b/vfs/preallocate_linux.go index b6b36eb2e2a..b323b306085 100644 --- a/vfs/preallocate_linux.go +++ b/vfs/preallocate_linux.go @@ -26,3 +26,10 @@ import ( func preallocExtend(fd uintptr, offset, length int64) error { return syscall.Fallocate(int(fd), unix.FALLOC_FL_KEEP_SIZE, offset, length) } + +func Preallocate(f File, offset, length int64) error { + if fdFile, ok := f.(fdGetter); ok { + return preallocExtend(fdFile.Fd(), offset, length) + } + return nil +} diff --git a/vfs/vfs.go b/vfs/vfs.go index c816a8aaa03..27f40eeb13d 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -33,6 +33,11 @@ type File interface { Sync() error } +type RandomWriteFile interface { + File + io.WriterAt +} + // OpenOption provide an interface to do work on file handles in the Open() // call. type OpenOption interface { @@ -128,6 +133,12 @@ type FS interface { GetDiskUsage(path string) (DiskUsage, error) } +type FSWithOpenForWrites interface { + FS + + OpenForWrites(name string, opts ...OpenOption) (RandomWriteFile, error) +} + // DiskUsage summarizes disk space usage on a filesystem. type DiskUsage struct { // Total disk space available to the current process in bytes. @@ -180,6 +191,19 @@ func (defaultFS) Open(name string, opts ...OpenOption) (File, error) { return file, nil } +func (defaultFS) OpenForWrites(name string, opts ...OpenOption) (RandomWriteFile, error) { + // TODO(bilal): Once secondary cache supports reopening existing files, remove + // O_TRUNC. + file, err := os.OpenFile(name, os.O_RDWR|syscall.O_CLOEXEC|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return nil, errors.WithStack(err) + } + for _, opt := range opts { + opt.Apply(file) + } + return file, nil +} + func (defaultFS) Remove(name string) error { return errors.WithStack(os.Remove(name)) } @@ -286,6 +310,27 @@ func Copy(fs FS, oldname, newname string) error { return dst.Sync() } +// CopyAcrossFS copies the contents of oldname to newname. If newname exists, it will +// be overwritten. +func CopyAcrossFS(fs FS, oldname string, newFS FS, newname string) error { + src, err := fs.Open(oldname) + if err != nil { + return err + } + defer src.Close() + + dst, err := newFS.Create(newname) + if err != nil { + return err + } + defer dst.Close() + + if _, err := io.Copy(dst, src); err != nil { + return err + } + return dst.Sync() +} + // LimitedCopy copies up to maxBytes from oldname to newname. If newname // exists, it will be overwritten. func LimitedCopy(fs FS, oldname, newname string, maxBytes int64) error { From 96ed6b7bd2b9679ce7f594886f4f690ac6414cc9 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Wed, 22 Jun 2022 11:19:32 -0500 Subject: [PATCH 2/6] *: fix test arguments to make the project compile --- .github/workflows/ci.yaml | 14 +------ db_test.go | 4 +- internal/cache/clockpro.go | 3 +- internal/cache/clockpro_test.go | 62 +++++++++++++++---------------- internal/cache/secondary_cache.go | 6 +-- log_recycler_test.go | 20 +++++----- options_test.go | 1 + persistent_cache.go | 3 +- sstable/reader.go | 5 ++- sstable/writer_test.go | 4 +- table_cache_test.go | 2 +- testdata/event_listener | 2 +- testdata/ingest | 2 +- testdata/metrics | 8 ++-- vfs/preallocate_linux.go | 1 + vfs/vfs.go | 2 + 16 files changed, 67 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5f0c8753e3d..cbe8a905ea4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,6 +5,7 @@ on: branches: - master - crl-release-* + - disagg pull_request: branches: - master @@ -32,19 +33,6 @@ jobs: - run: make test generate - linux-crossversion: - name: go-linux-crossversion - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: "1.18" - - - run: make crossversion-meta - linux-race: name: go-linux-race runs-on: ubuntu-latest diff --git a/db_test.go b/db_test.go index f16f395c220..929cce179ee 100644 --- a/db_test.go +++ b/db_test.go @@ -804,7 +804,7 @@ func TestMemTableReservation(t *testing.T) { helloWorld := []byte("hello world") value := opts.Cache.Alloc(len(helloWorld)) copy(value.Buf(), helloWorld) - opts.Cache.Set(tmpID, 0, 0, value).Release() + opts.Cache.Set(tmpID, 0, 0, value, false).Release() d, err := Open("", opts) require.NoError(t, err) @@ -821,7 +821,7 @@ func TestMemTableReservation(t *testing.T) { t.Fatalf("expected 2 refs, but found %d", refs) } // Verify the memtable reservation has caused our test block to be evicted. - if h := opts.Cache.Get(tmpID, 0, 0); h.Get() != nil { + if h := opts.Cache.Get(tmpID, 0, 0, false); h.Get() != nil { t.Fatalf("expected failure, but found success: %s", h.Get()) } diff --git a/internal/cache/clockpro.go b/internal/cache/clockpro.go index 75e346b41ea..dad7ad5ae87 100644 --- a/internal/cache/clockpro.go +++ b/internal/cache/clockpro.go @@ -766,12 +766,13 @@ func (c *Cache) Unref() { } } +// AddSecondaryCache creates and add a secondary cache to a Cache func (c *Cache) AddSecondaryCache(dir string, fs vfs.FSWithOpenForWrites, capacity uint64) { capacity = capacity / uint64(len(c.shards)) for i := range c.shards { path := fs.PathJoin(dir, fmt.Sprintf("shard-%d", i+1)) fs.MkdirAll(path, 0755) - c.shards[i].setSecondaryCache(NewPersistentCache(path, fs, capacity)) + c.shards[i].setSecondaryCache(newPersistentCache(path, fs, capacity)) } } diff --git a/internal/cache/clockpro_test.go b/internal/cache/clockpro_test.go index 93a2a87efa0..97c1cdb4632 100644 --- a/internal/cache/clockpro_test.go +++ b/internal/cache/clockpro_test.go @@ -39,11 +39,11 @@ func TestCache(t *testing.T) { wantHit := fields[1][0] == 'h' var hit bool - h := cache.Get(1, base.FileNum(key), 0) + h := cache.Get(1, base.FileNum(key), 0, false) if v := h.Get(); v == nil { value := cache.Alloc(1) value.Buf()[0] = fields[0][0] - cache.Set(1, base.FileNum(key), 0, value).Release() + cache.Set(1, base.FileNum(key), 0, value, false).Release() } else { hit = true if !bytes.Equal(v, fields[0][:1]) { @@ -69,9 +69,9 @@ func TestCacheDelete(t *testing.T) { cache := newShards(100, 1) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 5)).Release() - cache.Set(1, 1, 0, testValue(cache, "a", 5)).Release() - cache.Set(1, 2, 0, testValue(cache, "a", 5)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 5), false).Release() + cache.Set(1, 1, 0, testValue(cache, "a", 5), false).Release() + cache.Set(1, 2, 0, testValue(cache, "a", 5), false).Release() if expected, size := int64(15), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } @@ -79,12 +79,12 @@ func TestCacheDelete(t *testing.T) { if expected, size := int64(10), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } - if h := cache.Get(1, 0, 0); h.Get() == nil { + if h := cache.Get(1, 0, 0, false); h.Get() == nil { t.Fatalf("expected to find block 0/0") } else { h.Release() } - if h := cache.Get(1, 1, 0); h.Get() != nil { + if h := cache.Get(1, 1, 0, false); h.Get() != nil { t.Fatalf("expected to not find block 1/0") } else { h.Release() @@ -100,11 +100,11 @@ func TestEvictFile(t *testing.T) { cache := newShards(100, 1) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 5)).Release() - cache.Set(1, 1, 0, testValue(cache, "a", 5)).Release() - cache.Set(1, 2, 0, testValue(cache, "a", 5)).Release() - cache.Set(1, 2, 1, testValue(cache, "a", 5)).Release() - cache.Set(1, 2, 2, testValue(cache, "a", 5)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 5), false).Release() + cache.Set(1, 1, 0, testValue(cache, "a", 5), false).Release() + cache.Set(1, 2, 0, testValue(cache, "a", 5), false).Release() + cache.Set(1, 2, 1, testValue(cache, "a", 5), false).Release() + cache.Set(1, 2, 2, testValue(cache, "a", 5), false).Release() if expected, size := int64(25), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } @@ -128,16 +128,16 @@ func TestEvictAll(t *testing.T) { cache := newShards(100, 1) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 101)).Release() - cache.Set(1, 1, 0, testValue(cache, "a", 101)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 101), false).Release() + cache.Set(1, 1, 0, testValue(cache, "a", 101), false).Release() } func TestMultipleDBs(t *testing.T) { cache := newShards(100, 1) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 5)).Release() - cache.Set(2, 0, 0, testValue(cache, "b", 5)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 5), false).Release() + cache.Set(2, 0, 0, testValue(cache, "b", 5), false).Release() if expected, size := int64(10), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } @@ -145,11 +145,11 @@ func TestMultipleDBs(t *testing.T) { if expected, size := int64(5), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } - h := cache.Get(1, 0, 0) + h := cache.Get(1, 0, 0, false) if v := h.Get(); v != nil { t.Fatalf("expected not present, but found %s", v) } - h = cache.Get(2, 0, 0) + h = cache.Get(2, 0, 0, false) if v := h.Get(); string(v) != "bbbbb" { t.Fatalf("expected bbbbb, but found %s", v) } else { @@ -161,27 +161,27 @@ func TestZeroSize(t *testing.T) { cache := newShards(0, 1) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 5)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 5), false).Release() } func TestReserve(t *testing.T) { cache := newShards(4, 2) defer cache.Unref() - cache.Set(1, 0, 0, testValue(cache, "a", 1)).Release() - cache.Set(2, 0, 0, testValue(cache, "a", 1)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 1), false).Release() + cache.Set(2, 0, 0, testValue(cache, "a", 1), false).Release() require.EqualValues(t, 2, cache.Size()) r := cache.Reserve(1) require.EqualValues(t, 0, cache.Size()) - cache.Set(1, 0, 0, testValue(cache, "a", 1)).Release() - cache.Set(2, 0, 0, testValue(cache, "a", 1)).Release() - cache.Set(3, 0, 0, testValue(cache, "a", 1)).Release() - cache.Set(4, 0, 0, testValue(cache, "a", 1)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 1), false).Release() + cache.Set(2, 0, 0, testValue(cache, "a", 1), false).Release() + cache.Set(3, 0, 0, testValue(cache, "a", 1), false).Release() + cache.Set(4, 0, 0, testValue(cache, "a", 1), false).Release() require.EqualValues(t, 2, cache.Size()) r() require.EqualValues(t, 2, cache.Size()) - cache.Set(1, 0, 0, testValue(cache, "a", 1)).Release() - cache.Set(2, 0, 0, testValue(cache, "a", 1)).Release() + cache.Set(1, 0, 0, testValue(cache, "a", 1), false).Release() + cache.Set(2, 0, 0, testValue(cache, "a", 1), false).Release() require.EqualValues(t, 4, cache.Size()) } @@ -217,7 +217,7 @@ func TestCacheStressSetExisting(t *testing.T) { go func(i int) { defer wg.Done() for j := 0; j < 10000; j++ { - cache.Set(1, 0, uint64(i), testValue(cache, "a", 1)).Release() + cache.Set(1, 0, uint64(i), testValue(cache, "a", 1), false).Release() runtime.Gosched() } }(i) @@ -233,7 +233,7 @@ func BenchmarkCacheGet(b *testing.B) { for i := 0; i < size; i++ { v := testValue(cache, "a", 1) - cache.Set(1, 0, uint64(i), v).Release() + cache.Set(1, 0, uint64(i), v, false).Release() } b.ResetTimer() @@ -241,7 +241,7 @@ func BenchmarkCacheGet(b *testing.B) { rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) for pb.Next() { - h := cache.Get(1, 0, uint64(rng.Intn(size))) + h := cache.Get(1, 0, uint64(rng.Intn(size)), false) if h.Get() == nil { b.Fatal("failed to lookup value") } @@ -259,7 +259,7 @@ func TestReserveColdTarget(t *testing.T) { defer cache.Unref() for i := 0; i < 50; i++ { - cache.Set(uint64(i+1), 0, 0, testValue(cache, "a", 1)).Release() + cache.Set(uint64(i+1), 0, 0, testValue(cache, "a", 1), false).Release() } if cache.Size() != 50 { diff --git a/internal/cache/secondary_cache.go b/internal/cache/secondary_cache.go index 392a157d478..8931925c465 100644 --- a/internal/cache/secondary_cache.go +++ b/internal/cache/secondary_cache.go @@ -15,6 +15,7 @@ import ( "github.com/cockroachdb/pebble/vfs" ) +// SecondaryCache interface type SecondaryCache interface { GetAndEvict(id uint64, fileNum base.FileNum, offset uint64) []byte Set(id uint64, fileNum base.FileNum, offset uint64, block []byte) @@ -214,7 +215,7 @@ type secondaryCache struct { } capacity uint64 - wg sync.WaitGroup + // wg sync.WaitGroup cacheDir string fs vfs.FSWithOpenForWrites } @@ -258,7 +259,6 @@ func (f *secondaryCacheFile) writeBlock( panic(err.Error()) } f.writerMu.usedSize += uint64(n) - return } type secondaryCacheOrigFile struct { @@ -320,7 +320,7 @@ func (s *secondaryCacheValue) Decode(src []byte) (rem []byte, block []byte, err return src[secondaryCacheHeaderSize+s.size+s.unused:], block, nil } -func NewPersistentCache(dir string, fs vfs.FSWithOpenForWrites, capacity uint64) SecondaryCache { +func newPersistentCache(dir string, fs vfs.FSWithOpenForWrites, capacity uint64) SecondaryCache { psc := &secondaryCache{ capacity: capacity, cacheDir: dir, diff --git a/log_recycler_test.go b/log_recycler_test.go index 075394b7091..f0c95f3de7f 100644 --- a/log_recycler_test.go +++ b/log_recycler_test.go @@ -27,32 +27,32 @@ func TestLogRecycler(t *testing.T) { r := logRecycler{limit: 3, minRecycleLogNum: 4} // Logs below the min-recycle number are not recycled. - require.False(t, r.add(fileInfo{1, 0})) - require.False(t, r.add(fileInfo{2, 0})) - require.False(t, r.add(fileInfo{3, 0})) + require.False(t, r.add(fileInfo{1, 0, false})) + require.False(t, r.add(fileInfo{2, 0, false})) + require.False(t, r.add(fileInfo{3, 0, false})) // Logs are recycled up to the limit. - require.True(t, r.add(fileInfo{4, 0})) + require.True(t, r.add(fileInfo{4, 0, false})) require.EqualValues(t, []FileNum{4}, r.logNums()) require.EqualValues(t, 4, r.maxLogNum()) fi, ok := r.peek() require.True(t, ok) require.EqualValues(t, 4, fi.fileNum) - require.True(t, r.add(fileInfo{5, 0})) + require.True(t, r.add(fileInfo{5, 0, false})) require.EqualValues(t, []FileNum{4, 5}, r.logNums()) require.EqualValues(t, 5, r.maxLogNum()) - require.True(t, r.add(fileInfo{6, 0})) + require.True(t, r.add(fileInfo{6, 0, false})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 6, r.maxLogNum()) // Trying to add a file past the limit fails. - require.False(t, r.add(fileInfo{7, 0})) + require.False(t, r.add(fileInfo{7, 0, false})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 7, r.maxLogNum()) // Trying to add a previously recycled file returns success, but the internal // state is unchanged. - require.True(t, r.add(fileInfo{4, 0})) + require.True(t, r.add(fileInfo{4, 0, false})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 7, r.maxLogNum()) @@ -63,10 +63,10 @@ func TestLogRecycler(t *testing.T) { require.EqualValues(t, []FileNum{5, 6}, r.logNums()) // Log number 7 was already considered, so it won't be recycled. - require.True(t, r.add(fileInfo{7, 0})) + require.True(t, r.add(fileInfo{7, 0, false})) require.EqualValues(t, []FileNum{5, 6}, r.logNums()) - require.True(t, r.add(fileInfo{8, 0})) + require.True(t, r.add(fileInfo{8, 0, false})) require.EqualValues(t, []FileNum{5, 6, 8}, r.logNums()) require.EqualValues(t, 8, r.maxLogNum()) diff --git a/options_test.go b/options_test.go index d907fa59fe2..e39f52a3b5a 100644 --- a/options_test.go +++ b/options_test.go @@ -95,6 +95,7 @@ func TestOptionsString(t *testing.T) { strict_wal_tail=true table_cache_shards=8 table_property_collectors=[] + unique_id=0 validate_on_ingest=false wal_dir= wal_bytes_per_sync=0 diff --git a/persistent_cache.go b/persistent_cache.go index 287c8258450..3bfdc0be969 100644 --- a/persistent_cache.go +++ b/persistent_cache.go @@ -156,9 +156,8 @@ func (l *persistentCache) MaybeCache(meta *manifest.FileMetadata, amountRead int newVal := atomic.AddInt64(&meta.Atomic.BytesBeforeLocalCache, -1*amountRead) if newVal > 0 { return - } else { - atomic.StoreInt64(&meta.Atomic.BytesBeforeLocalCache, meta.InitBytesBeforeLocalCache) } + atomic.StoreInt64(&meta.Atomic.BytesBeforeLocalCache, meta.InitBytesBeforeLocalCache) l.mu.Lock() cached := l.mu.files[meta.FileNum] diff --git a/sstable/reader.go b/sstable/reader.go index 06b8a118814..106bff9e267 100644 --- a/sstable/reader.go +++ b/sstable/reader.go @@ -40,12 +40,14 @@ const ( maxReadaheadSize = 256 << 10 /* 256KB */ ) +// PersistentCacheValue is the value stored in a persistent cache type PersistentCacheValue interface { File() vfs.File Ref() Unref() } +// PersistentCache is a file-backed cache type PersistentCache interface { Get(fileNum base.FileNum) PersistentCacheValue MaybeCache(meta *manifest.FileMetadata, amountRead int64) @@ -2338,13 +2340,14 @@ func (f FileReopenOpt) readerApply(r *Reader) { } } +// PersistentCacheOpt specifies the cache options type PersistentCacheOpt struct { PsCache PersistentCache Meta *manifest.FileMetadata } func (p PersistentCacheOpt) readerApply(r *Reader) { - //r.psCache = p.PsCache + // r.psCache = p.PsCache r.meta = p.Meta } diff --git a/sstable/writer_test.go b/sstable/writer_test.go index 862a131a573..cac3193528c 100644 --- a/sstable/writer_test.go +++ b/sstable/writer_test.go @@ -407,7 +407,7 @@ func TestWriterClearCache(t *testing.T) { // Poison the cache for each of the blocks. poison := func(bh BlockHandle) { - opts.Cache.Set(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset, invalidData()).Release() + opts.Cache.Set(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset, invalidData(), false).Release() } foreachBH(layout, poison) @@ -417,7 +417,7 @@ func TestWriterClearCache(t *testing.T) { // Verify that the written blocks have been cleared from the cache. check := func(bh BlockHandle) { - h := opts.Cache.Get(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset) + h := opts.Cache.Get(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset, false) if h.Get() != nil { t.Fatalf("%d: expected cache to be cleared, but found %q", bh.Offset, h.Get()) } diff --git a/table_cache_test.go b/table_cache_test.go index eb43a76ea23..93bcdcfe8fd 100644 --- a/table_cache_test.go +++ b/table_cache_test.go @@ -191,7 +191,7 @@ func newTableCacheContainerTest( opts.Cache = tc.cache } - c := newTableCacheContainer(tc, opts.Cache.NewID(), dirname, fs, opts, tableCacheTestCacheSize) + c := newTableCacheContainer(tc, opts.Cache.NewID(), dirname, fs, "", nil, nil, opts, tableCacheTestCacheSize) return c, fs, nil } diff --git a/testdata/event_listener b/testdata/event_listener index b2e3baf1690..e728012cb8a 100644 --- a/testdata/event_listener +++ b/testdata/event_listener @@ -199,7 +199,7 @@ compact 1 2.3 K 0 B 0 (size == estimated-debt, scor zmemtbl 0 0 B ztbl 0 0 B bcache 8 1.4 K 11.1% (score == hit-rate) - tcache 1 680 B 40.0% (score == hit-rate) + tcache 1 704 B 40.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 0 filter - - 0.0% (score == utility) diff --git a/testdata/ingest b/testdata/ingest index 031ffeb97fe..b14ad40fb62 100644 --- a/testdata/ingest +++ b/testdata/ingest @@ -48,7 +48,7 @@ compact 0 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 0 0 B ztbl 0 0 B bcache 8 1.5 K 42.9% (score == hit-rate) - tcache 1 680 B 50.0% (score == hit-rate) + tcache 1 704 B 50.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 0 filter - - 0.0% (score == utility) diff --git a/testdata/metrics b/testdata/metrics index 1e4f891c41c..4355bf77f51 100644 --- a/testdata/metrics +++ b/testdata/metrics @@ -34,7 +34,7 @@ compact 0 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 1 256 K ztbl 0 0 B bcache 4 698 B 0.0% (score == hit-rate) - tcache 1 680 B 0.0% (score == hit-rate) + tcache 1 704 B 0.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 1 filter - - 0.0% (score == utility) @@ -82,7 +82,7 @@ compact 1 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 2 512 K ztbl 2 1.5 K bcache 8 1.4 K 42.9% (score == hit-rate) - tcache 2 1.3 K 66.7% (score == hit-rate) + tcache 2 1.4 K 66.7% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 2 filter - - 0.0% (score == utility) @@ -115,7 +115,7 @@ compact 1 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 1 256 K ztbl 2 1.5 K bcache 8 1.4 K 42.9% (score == hit-rate) - tcache 2 1.3 K 66.7% (score == hit-rate) + tcache 2 1.4 K 66.7% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 2 filter - - 0.0% (score == utility) @@ -145,7 +145,7 @@ compact 1 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 1 256 K ztbl 1 771 B bcache 4 698 B 42.9% (score == hit-rate) - tcache 1 680 B 66.7% (score == hit-rate) + tcache 1 704 B 66.7% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 1 filter - - 0.0% (score == utility) diff --git a/vfs/preallocate_linux.go b/vfs/preallocate_linux.go index b323b306085..93f0624a748 100644 --- a/vfs/preallocate_linux.go +++ b/vfs/preallocate_linux.go @@ -27,6 +27,7 @@ func preallocExtend(fd uintptr, offset, length int64) error { return syscall.Fallocate(int(fd), unix.FALLOC_FL_KEEP_SIZE, offset, length) } +// Preallocate wraps the Fallocate syscall over a vfs.File func Preallocate(f File, offset, length int64) error { if fdFile, ok := f.(fdGetter); ok { return preallocExtend(fdFile.Fd(), offset, length) diff --git a/vfs/vfs.go b/vfs/vfs.go index 27f40eeb13d..9b5a257014c 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -33,6 +33,7 @@ type File interface { Sync() error } +// RandomWriteFile is a file that supports writing at designated offsets type RandomWriteFile interface { File io.WriterAt @@ -133,6 +134,7 @@ type FS interface { GetDiskUsage(path string) (DiskUsage, error) } +// FSWithOpenForWrites wraps a FS that supports OpenForWrites type FSWithOpenForWrites interface { FS From deb4c58cb18364ff08d00d58f60868d093419237 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Thu, 23 Jun 2022 10:47:25 -0500 Subject: [PATCH 3/6] *: tableIterator and rangeDelIter --- compaction.go | 106 ++++-- disagg_test.go | 171 ++++++++++ ingest.go | 6 +- internal/base/filenames.go | 2 +- internal/manifest/version.go | 23 +- internal/manifest/version_edit.go | 54 +++- log_recycler_test.go | 20 +- open.go | 17 +- options.go | 18 +- persistent_cache.go | 6 +- sstable/reader.go | 187 ++++++----- sstable/reader_test.go | 4 +- sstable/shared.go | 519 ++++++++++++++++++++++++++++++ sstable/shared_test.go | 171 ++++++++++ table_cache.go | 23 +- testdata/metrics | 2 +- 16 files changed, 1158 insertions(+), 171 deletions(-) create mode 100644 disagg_test.go create mode 100644 sstable/shared.go create mode 100644 sstable/shared_test.go diff --git a/compaction.go b/compaction.go index 6f01bc4a7e4..abf95c9149c 100644 --- a/compaction.go +++ b/compaction.go @@ -29,6 +29,8 @@ import ( "github.com/cockroachdb/pebble/vfs" ) +const sharedLevel = 5 + var errEmptyTable = errors.New("pebble: empty table") var errFlushInvariant = errors.New("pebble: flush next log number is unset") @@ -2183,6 +2185,10 @@ func moveFileToSharedFS(filepath string, fs vfs.FS, sharedPath string, sharedFS return nil } +// this function just copies the overall boundaries of keys but +// it can be injected by tests +var setSharedSSTMetadata func(meta *manifest.FileMetadata, creatorID uint32) + // runCompactions runs a compaction that produces new on-disk tables from // memtables or old on-disk tables. // @@ -2569,14 +2575,22 @@ func (d *DB) runCompaction( meta.ExtendRangeKeyBounds(d.cmp, writerMeta.SmallestRangeKey, writerMeta.LargestRangeKey) } - if c.outputLevel.level >= 5 && d.opts.SharedFS != nil { + // If the output SSTable falls in lower levels than sharedLevel, it will be moved to the shared + // file system asynchronously + if d.opts.SharedFS != nil && c.outputLevel.level >= sharedLevel { + if writerMeta.HasRangeKeys { + panic("runCompaction: shared sst does not support range keys") + } + + setSharedSSTMetadata(meta, d.opts.UniqueID) + oldFilename := base.MakeFilepath(d.opts.FS, d.dirname, fileTypeTable, meta.FileNum) - sharedFilename := base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, d.opts.UniqueID, meta.FileNum) + sharedFilename := base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, meta.CreatorUniqueID, meta.PhysicalFileNum) movers.Add(1) go func() { defer movers.Done() if err := moveFileToSharedFS(oldFilename, d.opts.FS, sharedFilename, d.opts.SharedFS); err == nil { - meta.UsesSharedFS = true + meta.IsShared = true } }() } @@ -2966,18 +2980,22 @@ func (d *DB) deleteObsoleteFiles(jobID int, waitForOngoing bool) { // obsoleteFile holds information about a file that needs to be deleted soon. type obsoleteFile struct { - dir string - fileNum base.FileNum - fileType fileType - fileSize uint64 - usesSharedFS bool - skipMetrics bool + dir string + fileNum base.FileNum + fileType fileType + fileSize uint64 + skipMetrics bool + isShared bool + creatorUniqueID uint32 + physicalFileNum base.FileNum } type fileInfo struct { - fileNum FileNum - fileSize uint64 - usesSharedFS bool + fileNum FileNum + fileSize uint64 + isShared bool + creatorUniqueID uint32 + physicalFileNum base.FileNum } // d.mu must be held when calling this, but the mutex may be dropped and @@ -3006,10 +3024,21 @@ func (d *DB) doDeleteObsoleteFiles(jobID int) { } for _, table := range d.mu.versions.obsoleteTables { + // consider the file was created locally by default + physicalFileNum := table.FileNum + creatorUniqueID := d.opts.UniqueID + if table.IsShared { + // if the file is shared, then use its metadata regardless of it + // is local or foreign + physicalFileNum = table.PhysicalFileNum + creatorUniqueID = table.CreatorUniqueID + } obsoleteTables = append(obsoleteTables, fileInfo{ - fileNum: table.FileNum, - fileSize: table.Size, - usesSharedFS: table.UsesSharedFS, + fileNum: table.FileNum, + fileSize: table.Size, + isShared: table.IsShared, + creatorUniqueID: creatorUniqueID, + physicalFileNum: physicalFileNum, }) } d.mu.versions.obsoleteTables = nil @@ -3069,11 +3098,13 @@ func (d *DB) doDeleteObsoleteFiles(jobID int) { } filesToDelete = append(filesToDelete, obsoleteFile{ - dir: dir, - fileNum: fi.fileNum, - fileType: f.fileType, - fileSize: fi.fileSize, - usesSharedFS: fi.usesSharedFS, + dir: dir, + fileNum: fi.fileNum, + fileType: f.fileType, + fileSize: fi.fileSize, + isShared: fi.isShared, + creatorUniqueID: fi.creatorUniqueID, + physicalFileNum: fi.physicalFileNum, }) } } @@ -3100,8 +3131,8 @@ func (d *DB) paceAndDeleteObsoleteFiles(jobID int, files []obsoleteFile) { for _, of := range files { path := base.MakeFilepath(d.opts.FS, of.dir, of.fileType, of.fileNum) if of.fileType == fileTypeTable { - if of.usesSharedFS { - path = base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, d.opts.UniqueID, of.fileNum) + if of.isShared { + path = base.MakeSharedSSTPath(d.opts.SharedFS, d.opts.SharedDir, of.creatorUniqueID, of.physicalFileNum) if d.persistentCache != nil { d.persistentCache.MarkDeleted(of.fileNum) } @@ -3114,7 +3145,7 @@ func (d *DB) paceAndDeleteObsoleteFiles(jobID int, files []obsoleteFile) { d.mu.Unlock() } } - d.deleteObsoleteFile(of.fileType, jobID, path, of.fileNum, of.usesSharedFS) + d.deleteObsoleteFile(of.fileType, jobID, path, of.fileNum, of.isShared) } } @@ -3144,15 +3175,20 @@ func (d *DB) maybeScheduleObsoleteTableDeletion() { // deleteObsoleteFile deletes file that is no longer needed. func (d *DB) deleteObsoleteFile( - fileType fileType, jobID int, path string, fileNum FileNum, usesSharedFS bool, + fileType fileType, jobID int, path string, fileNum FileNum, isShared bool, ) { // TODO(peter): need to handle this error, probably by re-adding the // file that couldn't be deleted to one of the obsolete slices map. fs := d.opts.FS - if usesSharedFS { + if isShared { fs = d.opts.SharedFS } - err := d.opts.Cleaner.Clean(fs, fileType, path) + var err error + err = nil + //TODO(chen): really delete obsolete files in shared fs after refcnt is implemented + if !isShared { + err = d.opts.Cleaner.Clean(fs, fileType, path) + } if oserror.IsNotExist(err) { return } @@ -3221,3 +3257,21 @@ func mergeFileMetas(a, b []*fileMetadata) []*fileMetadata { } return a[:n] } + +func init() { + // Inject the shared sst metadata setting function here. + // Since the result is created by the current Pebble instance, + // it can access all the data in the table + setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { + // The output sst is shared so update its boundaries + meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest + + // assign virtual boundaries for all boundary properties + lb, ub := meta.Smallest, meta.Largest + meta.Smallest, meta.Largest = lb, ub + meta.SmallestPointKey, meta.LargestPointKey = lb, ub + + meta.CreatorUniqueID = creatorUniqueID + meta.PhysicalFileNum = meta.FileNum + } +} diff --git a/disagg_test.go b/disagg_test.go new file mode 100644 index 00000000000..a8956849d19 --- /dev/null +++ b/disagg_test.go @@ -0,0 +1,171 @@ +package pebble + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/cockroachdb/pebble/internal/manifest" + "github.com/cockroachdb/pebble/vfs" + "github.com/stretchr/testify/require" +) + +func TestDBWithSharedSST(t *testing.T) { + fs := vfs.NewMem() + sharedfs := vfs.NewMem() + + uid := uint32(rand.Uint32()) + t.Log("Opening ...") + d, err := Open("", &Options{ + FS: fs, + SharedFS: sharedfs, + SharedDir: "", + UniqueID: uid, + }) + require.NoError(t, err) + + // this is tricky, we need to make the directory for testing + const buckets = 10 + for i := 0; i < buckets; i++ { + // create local and fake foreign buckets + require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid, i), 0755)) + require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid+1, i), 0755)) + } + + rand.Seed(time.Now().UnixNano()) + const letters = "abcdefghijklmnopqrstuvwxyz" + const N = 1000000 + const K = 8 + var keys [N][]byte + for i := 0; i < N; i++ { + keys[i] = make([]byte, K) + for j := range keys[i] { + keys[i][j] = letters[rand.Intn(len(letters))] + } + } + value := bytes.Repeat([]byte("x"), 4096) + + // record all visible keys (1 per table in the test) + visible := make(map[string]bool) + + // inject key boundaries function to filter out the upper + setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { + // The output sst is shared so update its boundaries + meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest + + // Only one key for each table for testing + lb, ub := meta.Smallest, meta.Smallest + meta.Smallest, meta.Largest = lb, ub + meta.SmallestPointKey, meta.LargestPointKey = lb, ub + + meta.CreatorUniqueID = creatorUniqueID + 1 // fake foreign sst + meta.PhysicalFileNum = meta.FileNum + + t.Logf(" -- new shared sst with virtual bound (%s %s) with file bound (%s %s)", + lb.UserKey, ub.UserKey, meta.FileSmallest.UserKey, meta.FileLargest.UserKey) + } + + // repeatly inserting/updating a random key + t.Log("Inserting ...") + for i := 0; i < N; i++ { + if i%100000 == 0 && i != 0 { + t.Logf("set %d keys", i) + } + key := &keys[i] + if err := d.Set(*key, value, nil); err != nil { + t.Fatalf("set key %s error %v", key, err) + } + } + + printlevels := func() { + visible = make(map[string]bool) + // check the number of table files in each level + t.Logf("Level metadata") + var nTables [manifest.NumLevels]int + readState := d.loadReadState() + for i := 0; i < manifest.NumLevels; i++ { + nTables[i] = readState.current.Levels[i].Len() + t.Logf(" -- level %d has %d tables", i, nTables[i]) + iter := readState.current.Levels[i].Iter() + fm := iter.First() + for fm != nil { + if fm.IsShared { + t.Logf(" -- sst %d is shared with virtual bound (%s %s) file bound (%s %s)\n", + fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey, fm.FileSmallest.UserKey, fm.FileLargest.UserKey) + visible[string(fm.Smallest.UserKey)] = true + visible[string(fm.FileLargest.UserKey)] = false + } else { + t.Logf(" -- sst %d is local with bound (%s %s)\n", fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey) + //visible[string(fm.Smallest.UserKey)] = true + //visible[string(fm.FileLargest.UserKey)] = true + } + fm = iter.Next() + } + } + readState.unref() + } + + printlevels() + // read some keys to trigger new compactions + rand.Shuffle(N, func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) + t.Logf("Touching ...") + for i := 0; i < N; i++ { + _, closer, err := d.Get(keys[i]) + if err == nil { + require.NoError(t, closer.Close()) + } + } + printlevels() + + // check the number of files in the shared fs bucket + // t.Logf("Shared fs metadata") + // for i := 0; i < buckets; i++ { + // lst, err := sharedfs.List(fmt.Sprintf("%d/%d", uid, i)) + // require.NoError(t, err) + // t.Logf(" -- shared fs bucket %d has %d files: %s", i, len(lst), strings.Join(lst, " ")) + // } + + // validating visible and invisible keys + validateboundaries := func() { + // no compaction should have happened, take a snapshot + snapshot := d.NewSnapshot() + t.Logf("Validating ...") + for key, iv := range visible { + v, closer, err := snapshot.Get([]byte(key)) + if iv { + if err != nil { + t.Fatalf(" -- get: visible key %s get error (%v)", key, err) + } + if !bytes.Equal(v, value) { + t.Fatalf(" -- get: visible key %s get successfully but returned wrong value", key) + } + t.Logf(" -- get: visible key %s found ", key) + require.NoError(t, closer.Close()) + } else { + if err != ErrNotFound { + t.Fatalf(" -- get: invisible key %s get error (%v)", key, err) + } + t.Logf(" -- get: invisible key %s not found ", key) + } + } + require.NoError(t, snapshot.Close()) + } + + validateboundaries() + require.NoError(t, d.Close()) + + t.Log("Reopening ...") + d, err = Open("", &Options{ + FS: fs, + SharedFS: sharedfs, + SharedDir: "", + }) + require.NoError(t, err) + + printlevels() + validateboundaries() + + require.NoError(t, d.Close()) +} diff --git a/ingest.go b/ingest.go index e14706b6120..6bb514346d2 100644 --- a/ingest.go +++ b/ingest.go @@ -299,7 +299,7 @@ func ingestLink( err = vfs.LinkOrCopy(fs, paths[i], target) } if err == nil && opts.SharedFS != nil { - sharedTarget := base.MakeSharedSSTPath(opts.SharedFS, opts.SharedDir, opts.UniqueID, meta[i].FileNum) + sharedTarget := base.MakeSharedSSTPath(opts.SharedFS, opts.SharedDir, opts.UniqueID, meta[i].PhysicalFileNum) err = vfs.CopyAcrossFS(fs, paths[i], opts.SharedFS, sharedTarget) } if err != nil { @@ -838,8 +838,8 @@ func (d *DB) ingestApply( d.mu.versions.logUnlock() return nil, err } - if f.Level >= 5 && d.opts.SharedFS != nil { - m.UsesSharedFS = true + if f.Level >= sharedLevel && d.opts.SharedFS != nil { + m.IsShared = true obsoleteFiles = append(obsoleteFiles, obsoleteFile{ dir: d.dirname, fileNum: m.FileNum, diff --git a/internal/base/filenames.go b/internal/base/filenames.go index b5d71a47efe..c15ec48ae15 100644 --- a/internal/base/filenames.go +++ b/internal/base/filenames.go @@ -64,7 +64,7 @@ func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, fileNum FileNum) } // MakeSharedSSTPath builds a filepath for a shared SST. -func MakeSharedSSTPath(fs vfs.FS, dirname string, uniqueID uint16, fileNum FileNum) string { +func MakeSharedSSTPath(fs vfs.FS, dirname string, uniqueID uint32, fileNum FileNum) string { const numBuckets = 10 bucket := (uint64(fileNum) * (uint64(uniqueID) + 1)) % numBuckets return fs.PathJoin(dirname, fmt.Sprintf("%d/%d/%s", uniqueID, bucket, MakeFilename(FileTypeTable, fileNum))) diff --git a/internal/manifest/version.go b/internal/manifest/version.go index 6583b4db255..b0843147b89 100644 --- a/internal/manifest/version.go +++ b/internal/manifest/version.go @@ -161,9 +161,6 @@ type FileMetadata struct { // UTC). For ingested sstables, this corresponds to the time the file was // ingested. CreationTime int64 - // UsesSharedFS is true if this file is stored in the shared FS. A copy - // of it could also be cached on the local FS. - UsesSharedFS bool // Smallest and largest sequence numbers in the table, across both point and // range keys. SmallestSeqNum uint64 @@ -236,6 +233,24 @@ type FileMetadata struct { // key type (point or range) corresponds to the smallest and largest overall // table bounds. boundTypeSmallest, boundTypeLargest boundType + + // IsShared indicates whether the file is local-only or shared + // among Pebble instances + IsShared bool + + // CreatorUniqueID is the sst creator's UniqueID. + // This is used in MakeSharedSSTPath + CreatorUniqueID uint32 + + // PhysicalFileNum is the file's file num when it is created + // This is used in MakeSharedSSTPath + PhysicalFileNum base.FileNum + + // FileSmallest and FileLargest record the key boundaries of the file + // instead of the virtual boundaries that the current Pebble instance + // can read + FileSmallest InternalKey + FileLargest InternalKey } // SetCompactionState transitions this file's compaction state to the given @@ -1056,7 +1071,7 @@ func (v *Version) CheckConsistency(dirname string, fs vfs.FS, sharedFS vfs.FS) e for level, files := range v.Levels { iter := files.Iter() for f := iter.First(); f != nil; f = iter.Next() { - if f.UsesSharedFS && sharedFS != nil { + if f.IsShared && sharedFS != nil { continue } path := base.MakeFilepath(fs, dirname, base.FileTypeTable, f.FileNum) diff --git a/internal/manifest/version_edit.go b/internal/manifest/version_edit.go index aa99d46330e..bcbd9ffb6e6 100644 --- a/internal/manifest/version_edit.go +++ b/internal/manifest/version_edit.go @@ -54,7 +54,7 @@ const ( customTagTerminate = 1 customTagNeedsCompaction = 2 customTagCreationTime = 6 - customTagSharedFS = 7 + customTagIsShared = 7 customTagPathID = 65 customTagNonSafeIgnoreMask = 1 << 6 ) @@ -205,6 +205,7 @@ func (v *VersionEdit) Decode(r io.Reader) error { smallestRangeKey, largestRangeKey []byte parsedPointBounds bool boundsMarker byte + fileSmallest, fileLargest []byte ) if tag != tagNewFile5 { // Range keys not present in the table. Parse the point key bounds. @@ -266,8 +267,11 @@ func (v *VersionEdit) Decode(r io.Reader) error { return err } } - var markedForCompaction, usesSharedFS bool + var markedForCompaction bool + var isShared bool var creationTime uint64 + var creatorUniqueID uint64 + var physicalFileNum uint64 if tag == tagNewFile4 || tag == tagNewFile5 { for { customTag, err := d.readUvarint() @@ -298,11 +302,33 @@ func (v *VersionEdit) Decode(r io.Reader) error { case customTagPathID: return base.CorruptionErrorf("new-file4: path-id field not supported") - case customTagSharedFS: + case customTagIsShared: if len(field) != 1 { - return base.CorruptionErrorf("new-file4: need-compaction field wrong size") + return base.CorruptionErrorf("new-file4: is-shared field wrong size") + } + if field[0] != 1 { + return base.CorruptionErrorf("new-file4: is-shared field tag found but value is not true") + } + isShared = true + + creatorUniqueID, err = d.readUvarint() + if err != nil { + return err + } + + physicalFileNum, err = d.readUvarint() + if err != nil { + return err + } + + fileSmallest, err = d.readBytes() + if err != nil { + return err + } + fileLargest, err = d.readBytes() + if err != nil { + return err } - usesSharedFS = (field[0] == 1) default: if (customTag & customTagNonSafeIgnoreMask) != 0 { @@ -349,7 +375,13 @@ func (v *VersionEdit) Decode(r io.Reader) error { } } m.boundsSet = true - m.UsesSharedFS = usesSharedFS + m.IsShared = isShared + if isShared { + m.CreatorUniqueID = uint32(creatorUniqueID) + m.PhysicalFileNum = base.FileNum(physicalFileNum) + m.FileSmallest = base.DecodeInternalKey(fileSmallest) + m.FileLargest = base.DecodeInternalKey(fileLargest) + } v.NewFiles = append(v.NewFiles, NewFileEntry{ Level: level, Meta: m, @@ -405,7 +437,7 @@ func (v *VersionEdit) Encode(w io.Writer) error { e.writeUvarint(uint64(x.FileNum)) } for _, x := range v.NewFiles { - customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.UsesSharedFS + customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.IsShared var tag uint64 switch { case x.Meta.HasRangeKeys: @@ -458,9 +490,13 @@ func (v *VersionEdit) Encode(w io.Writer) error { e.writeUvarint(customTagNeedsCompaction) e.writeBytes([]byte{1}) } - if x.Meta.UsesSharedFS { - e.writeUvarint(customTagSharedFS) + if x.Meta.IsShared { + e.writeUvarint(customTagIsShared) e.writeBytes([]byte{1}) + e.writeUvarint(uint64(x.Meta.CreatorUniqueID)) + e.writeUvarint(uint64(x.Meta.PhysicalFileNum)) + e.writeKey(x.Meta.FileSmallest) + e.writeKey(x.Meta.FileLargest) } e.writeUvarint(customTagTerminate) } diff --git a/log_recycler_test.go b/log_recycler_test.go index f0c95f3de7f..86744cf7306 100644 --- a/log_recycler_test.go +++ b/log_recycler_test.go @@ -27,32 +27,32 @@ func TestLogRecycler(t *testing.T) { r := logRecycler{limit: 3, minRecycleLogNum: 4} // Logs below the min-recycle number are not recycled. - require.False(t, r.add(fileInfo{1, 0, false})) - require.False(t, r.add(fileInfo{2, 0, false})) - require.False(t, r.add(fileInfo{3, 0, false})) + require.False(t, r.add(fileInfo{1, 0, false, 0, 1})) + require.False(t, r.add(fileInfo{2, 0, false, 0, 2})) + require.False(t, r.add(fileInfo{3, 0, false, 0, 3})) // Logs are recycled up to the limit. - require.True(t, r.add(fileInfo{4, 0, false})) + require.True(t, r.add(fileInfo{4, 0, false, 0, 4})) require.EqualValues(t, []FileNum{4}, r.logNums()) require.EqualValues(t, 4, r.maxLogNum()) fi, ok := r.peek() require.True(t, ok) require.EqualValues(t, 4, fi.fileNum) - require.True(t, r.add(fileInfo{5, 0, false})) + require.True(t, r.add(fileInfo{5, 0, false, 0, 5})) require.EqualValues(t, []FileNum{4, 5}, r.logNums()) require.EqualValues(t, 5, r.maxLogNum()) - require.True(t, r.add(fileInfo{6, 0, false})) + require.True(t, r.add(fileInfo{6, 0, false, 0, 6})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 6, r.maxLogNum()) // Trying to add a file past the limit fails. - require.False(t, r.add(fileInfo{7, 0, false})) + require.False(t, r.add(fileInfo{7, 0, false, 0, 7})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 7, r.maxLogNum()) // Trying to add a previously recycled file returns success, but the internal // state is unchanged. - require.True(t, r.add(fileInfo{4, 0, false})) + require.True(t, r.add(fileInfo{4, 0, false, 0, 4})) require.EqualValues(t, []FileNum{4, 5, 6}, r.logNums()) require.EqualValues(t, 7, r.maxLogNum()) @@ -63,10 +63,10 @@ func TestLogRecycler(t *testing.T) { require.EqualValues(t, []FileNum{5, 6}, r.logNums()) // Log number 7 was already considered, so it won't be recycled. - require.True(t, r.add(fileInfo{7, 0, false})) + require.True(t, r.add(fileInfo{7, 0, false, 0, 7})) require.EqualValues(t, []FileNum{5, 6}, r.logNums()) - require.True(t, r.add(fileInfo{8, 0, false})) + require.True(t, r.add(fileInfo{8, 0, false, 0, 8})) require.EqualValues(t, []FileNum{5, 6, 8}, r.logNums()) require.EqualValues(t, 8, r.maxLogNum()) diff --git a/open.go b/open.go index 0ce6a55efe7..e8f1896d149 100644 --- a/open.go +++ b/open.go @@ -23,6 +23,7 @@ import ( "github.com/cockroachdb/pebble/internal/manual" "github.com/cockroachdb/pebble/internal/rate" "github.com/cockroachdb/pebble/record" + "github.com/cockroachdb/pebble/sstable" "github.com/cockroachdb/pebble/vfs" ) @@ -344,7 +345,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { // Validate the most-recent OPTIONS file, if there is one. var strictWALTail bool - var uniqueID uint16 + var uniqueID uint32 if previousOptionsFilename != "" { path := opts.FS.PathJoin(dirname, previousOptionsFilename) strictWALTail, uniqueID, err = checkOptions(opts, path) @@ -357,10 +358,14 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { opts.UniqueID = uniqueID } - //if opts.SharedFS != nil { - // d.persistentCache = newPersistentCache(opts.FS, dirname, opts.SharedFS, opts.SharedDir, opts.UniqueID) - // d.persistentCache.Start() - //} + // Inject UniqueID to sstable package + sstable.DBUniqueID = opts.UniqueID + + if opts.SharedFS != nil && opts.PersistentCacheSize != 0 { + d.persistentCache = newPersistentCache(opts.FS, dirname, opts.SharedFS, opts.SharedDir, opts.UniqueID, opts.PersistentCacheSize) + d.persistentCache.Start() + } + tableCacheSize := TableCacheSize(opts.MaxOpenFiles) d.tableCache = newTableCacheContainer(opts.TableCache, d.cacheID, dirname, opts.FS, opts.SharedDir, opts.SharedFS, d.persistentCache, d.opts, tableCacheSize) d.newIters = d.tableCache.newIters @@ -722,7 +727,7 @@ func (d *DB) replayWAL( return maxSeqNum, err } -func checkOptions(opts *Options, path string) (strictWALTail bool, uniqueID uint16, err error) { +func checkOptions(opts *Options, path string) (strictWALTail bool, uniqueID uint32, err error) { f, err := opts.FS.Open(path) if err != nil { return false, 0, err diff --git a/options.go b/options.go index 66efafff62d..f6e9cc72a18 100644 --- a/options.go +++ b/options.go @@ -166,7 +166,7 @@ type IterOptions struct { // Internal options. logger Logger // Level corresponding to this file. Only passed in if constructed by a - // levelIter. + // levelIter (including regular reads and compaction inputs). level manifest.Level // NB: If adding new Options, you must account for them in iterator @@ -713,7 +713,11 @@ type Options struct { // UniqueID is a unique ID that's generated for new Pebble instances and // serialized into the Options file. Used to disambiguate this instance's // files from that of others in SharedFS. - UniqueID uint16 + UniqueID uint32 + + // PersistentCacheSize is the size of persistent cache in bytes. + // If it is zero, no persistent case will be created. + PersistentCacheSize uint64 // TableCache is an initialized TableCache which should be set as an // option if the DB needs to be initialized with a pre-existing table cache. @@ -1255,8 +1259,8 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error { // TODO(peter): set o.TablePropertyCollectors case "unique_id": var uniqueID uint64 - uniqueID, err = strconv.ParseUint(value, 10, 16) - o.UniqueID = uint16(uniqueID) + uniqueID, err = strconv.ParseUint(value, 10, 32) + o.UniqueID = uint32(uniqueID) case "validate_on_ingest": o.Experimental.ValidateOnIngest, err = strconv.ParseBool(value) case "wal_dir": @@ -1343,7 +1347,7 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error { }) } -func (o *Options) checkOptions(s string) (strictWALTail bool, uniqueID uint16, err error) { +func (o *Options) checkOptions(s string) (strictWALTail bool, uniqueID uint32, err error) { // TODO(jackson): Refactor to avoid awkwardness of the strictWALTail return value. return strictWALTail, uniqueID, parseOptions(s, func(section, key, value string) error { switch section + "." + key { @@ -1366,11 +1370,11 @@ func (o *Options) checkOptions(s string) (strictWALTail bool, uniqueID uint16, e } case "Options.unique_id": var uniqueIDint uint64 - uniqueIDint, err = strconv.ParseUint(value, 10, 16) + uniqueIDint, err = strconv.ParseUint(value, 10, 32) if err != nil { return errors.Errorf("pebble: error parsing unique_id value %q: %w", value, err) } - uniqueID = uint16(uniqueIDint) + uniqueID = uint32(uniqueIDint) } return nil }) diff --git a/persistent_cache.go b/persistent_cache.go index 3bfdc0be969..079193037c7 100644 --- a/persistent_cache.go +++ b/persistent_cache.go @@ -29,7 +29,7 @@ type persistentCache struct { localFS, sharedFS vfs.FS localDir, sharedDir string - uniqueID uint16 + uniqueID uint32 wg sync.WaitGroup } @@ -89,11 +89,11 @@ func (p *persistentCacheValue) Unref() { } func newPersistentCache( - localFS vfs.FS, localDir string, sharedFS vfs.FS, sharedDir string, uniqueID uint16, + localFS vfs.FS, localDir string, sharedFS vfs.FS, sharedDir string, uniqueID uint32, size uint64, ) *persistentCache { psc := &persistentCache{ files: make(chan *persistentCacheValue, 500), - capacity: 10 << 30, /* 10 GB */ + capacity: size, localFS: localFS, sharedFS: sharedFS, localDir: localDir, diff --git a/sstable/reader.go b/sstable/reader.go index 106bff9e267..ee750bb0bb0 100644 --- a/sstable/reader.go +++ b/sstable/reader.go @@ -26,6 +26,9 @@ import ( "github.com/cockroachdb/pebble/vfs" ) +// DBUniqueID is injected by pebble during Open() +var DBUniqueID uint32 = 0 + var errCorruptIndexEntry = base.CorruptionErrorf("pebble/table: corrupt index entry") var errReaderClosed = errors.New("pebble/table: reader is closed") @@ -116,6 +119,9 @@ type Iterator interface { MaybeFilteredKeys() bool SetCloseHook(fn func(i Iterator) error) + + SetLevel(level int) + GetLevel() int } // singleLevelIterator iterates over an entire table of data. To seek for a given @@ -237,6 +243,9 @@ type singleLevelIterator struct { // is high). useFilter bool lastBloomFilterMatched bool + + level int + levelSet bool } // singleLevelIterator implements the base.InternalIterator interface. @@ -1161,6 +1170,20 @@ func (i *singleLevelIterator) SetCloseHook(fn func(i Iterator) error) { i.closeHook = fn } +// SetLevel implementes Iterator.SetLevel() +func (i *singleLevelIterator) SetLevel(level int) { + i.levelSet = true + i.level = level +} + +// GetLevel implements Iterator.GetLevel() +func (i *singleLevelIterator) GetLevel() int { + if !i.levelSet { + return -1 + } + return i.level +} + func firstError(err0, err1 error) error { if err0 != nil { return err0 @@ -1243,7 +1266,7 @@ func (i *singleLevelIterator) ResetStats() { // compactionIterator is similar to Iterator but it increments the number of // bytes that have been iterated through. type compactionIterator struct { - *singleLevelIterator + *tableIterator bytesIterated *uint64 prevOffset uint64 } @@ -1252,7 +1275,7 @@ type compactionIterator struct { var _ base.InternalIterator = (*compactionIterator)(nil) func (i *compactionIterator) String() string { - return i.reader.fileNum.String() + return i.tableIterator.getReader().fileNum.String() } func (i *compactionIterator) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []byte) { @@ -1270,8 +1293,13 @@ func (i *compactionIterator) SeekLT(key []byte, flags base.SeekLTFlags) (*Intern } func (i *compactionIterator) First() (*InternalKey, []byte) { - i.err = nil // clear cached iteration error - return i.skipForward(i.singleLevelIterator.First()) + i.Iterator.(*singleLevelIterator).err = nil // clear cached iteration error + // wrap for shared sst + k, v := i.tableIterator.First() + curOffset := i.Iterator.(*singleLevelIterator).recordOffset() + *i.bytesIterated += uint64(curOffset - i.prevOffset) + i.prevOffset = curOffset + return k, v } func (i *compactionIterator) Last() (*InternalKey, []byte) { @@ -1281,52 +1309,21 @@ func (i *compactionIterator) Last() (*InternalKey, []byte) { // Note: compactionIterator.Next mirrors the implementation of Iterator.Next // due to performance. Keep the two in sync. func (i *compactionIterator) Next() (*InternalKey, []byte) { - if i.err != nil { + if i.Iterator.(*singleLevelIterator).err != nil { return nil, nil } - return i.skipForward(i.data.Next()) + // wrap for shared sst + k, v := i.tableIterator.Next() + curOffset := i.Iterator.(*singleLevelIterator).recordOffset() + *i.bytesIterated += uint64(curOffset - i.prevOffset) + i.prevOffset = curOffset + return k, v } func (i *compactionIterator) Prev() (*InternalKey, []byte) { panic("pebble: Prev unimplemented") } -func (i *compactionIterator) skipForward(key *InternalKey, val []byte) (*InternalKey, []byte) { - if key == nil { - for { - if key, _ := i.index.Next(); key == nil { - break - } - result := i.loadBlock(+1) - if result != loadBlockOK { - if i.err != nil { - break - } - switch result { - case loadBlockFailed: - // We checked that i.index was at a valid entry, so - // loadBlockFailed could not have happened due to to i.index - // being exhausted, and must be due to an error. - panic("loadBlock should not have failed with no error") - case loadBlockIrrelevant: - panic("compactionIter should not be using block intervals for skipping") - default: - panic(fmt.Sprintf("unexpected case %d", result)) - } - } - // result == loadBlockOK - if key, val = i.data.First(); key != nil { - break - } - } - } - - curOffset := i.recordOffset() - *i.bytesIterated += uint64(curOffset - i.prevOffset) - i.prevOffset = curOffset - return key, val -} - type twoLevelIterator struct { singleLevelIterator // maybeFilteredKeysSingleLevel indicates whether the last iterator @@ -1970,7 +1967,7 @@ func (i *twoLevelIterator) Close() error { // Note: twoLevelCompactionIterator and compactionIterator are very similar but // were separated due to performance. type twoLevelCompactionIterator struct { - *twoLevelIterator + *tableIterator bytesIterated *uint64 prevOffset uint64 } @@ -1979,7 +1976,7 @@ type twoLevelCompactionIterator struct { var _ base.InternalIterator = (*twoLevelCompactionIterator)(nil) func (i *twoLevelCompactionIterator) Close() error { - return i.twoLevelIterator.Close() + return i.tableIterator.Close() } func (i *twoLevelCompactionIterator) SeekGE( @@ -2001,8 +1998,13 @@ func (i *twoLevelCompactionIterator) SeekLT( } func (i *twoLevelCompactionIterator) First() (*InternalKey, []byte) { - i.err = nil // clear cached iteration error - return i.skipForward(i.twoLevelIterator.First()) + i.Iterator.(*twoLevelIterator).err = nil // clear cached iteration error + // wrap for shared sst + k, v := i.tableIterator.First() + curOffset := i.Iterator.(*twoLevelIterator).recordOffset() + *i.bytesIterated += uint64(curOffset - i.prevOffset) + i.prevOffset = curOffset + return k, v } func (i *twoLevelCompactionIterator) Last() (*InternalKey, []byte) { @@ -2012,10 +2014,15 @@ func (i *twoLevelCompactionIterator) Last() (*InternalKey, []byte) { // Note: twoLevelCompactionIterator.Next mirrors the implementation of // twoLevelIterator.Next due to performance. Keep the two in sync. func (i *twoLevelCompactionIterator) Next() (*InternalKey, []byte) { - if i.err != nil { + if i.Iterator.(*twoLevelIterator).err != nil { return nil, nil } - return i.skipForward(i.singleLevelIterator.Next()) + // wrap for shared sst + k, v := i.tableIterator.Next() + curOffset := i.Iterator.(*twoLevelIterator).recordOffset() + *i.bytesIterated += uint64(curOffset - i.prevOffset) + i.prevOffset = curOffset + return k, v } func (i *twoLevelCompactionIterator) Prev() (*InternalKey, []byte) { @@ -2023,45 +2030,7 @@ func (i *twoLevelCompactionIterator) Prev() (*InternalKey, []byte) { } func (i *twoLevelCompactionIterator) String() string { - return i.reader.fileNum.String() -} - -func (i *twoLevelCompactionIterator) skipForward( - key *InternalKey, val []byte, -) (*InternalKey, []byte) { - if key == nil { - for { - if key, _ := i.topLevelIndex.Next(); key == nil { - break - } - result := i.loadIndex(+1) - if result != loadBlockOK { - if i.err != nil { - break - } - switch result { - case loadBlockFailed: - // We checked that i.index was at a valid entry, so - // loadBlockFailed could not have happened due to to i.index - // being exhausted, and must be due to an error. - panic("loadBlock should not have failed with no error") - case loadBlockIrrelevant: - panic("compactionIter should not be using block intervals for skipping") - default: - panic(fmt.Sprintf("unexpected case %d", result)) - } - } - // result == loadBlockOK - if key, val = i.singleLevelIterator.First(); key != nil { - break - } - } - } - - curOffset := i.recordOffset() - *i.bytesIterated += uint64(curOffset - i.prevOffset) - i.prevOffset = curOffset - return key, val + return i.tableIterator.getReader().fileNum.String() } type blockTransform func([]byte) ([]byte, error) @@ -2438,7 +2407,14 @@ func (r *Reader) NewIterWithBlockPropertyFilters( if err != nil { return nil, err } - return i, nil + rangeDelIter, err := r.newInternalRangeDelIter() + if err != nil { + if i.Close() != nil { + panic("NewIter: cannot create rangeDelIter for tableIterator") + } + return nil, err + } + return &tableIterator{i, rangeDelIter}, nil } i := singleLevelIterPool.Get().(*singleLevelIterator) @@ -2446,7 +2422,14 @@ func (r *Reader) NewIterWithBlockPropertyFilters( if err != nil { return nil, err } - return i, nil + rangeDelIter, err := r.newInternalRangeDelIter() + if err != nil { + if i.Close() != nil { + panic("NewIter: cannot create rangeDelIter for tableIterator") + } + return nil, err + } + return &tableIterator{i, rangeDelIter}, nil } // NewIter returns an iterator for the contents of the table. If an error @@ -2466,9 +2449,16 @@ func (r *Reader) NewCompactionIter(bytesIterated *uint64) (Iterator, error) { return nil, err } i.setupForCompaction() + rangeDelIter, err := r.newInternalRangeDelIter() + if err != nil { + if i.Close() != nil { + panic("NewIter: cannot create rangeDelIter for tableIterator") + } + return nil, err + } return &twoLevelCompactionIterator{ - twoLevelIterator: i, - bytesIterated: bytesIterated, + tableIterator: &tableIterator{i, rangeDelIter}, + bytesIterated: bytesIterated, }, nil } i := singleLevelIterPool.Get().(*singleLevelIterator) @@ -2477,9 +2467,16 @@ func (r *Reader) NewCompactionIter(bytesIterated *uint64) (Iterator, error) { return nil, err } i.setupForCompaction() + rangeDelIter, err := r.newInternalRangeDelIter() + if err != nil { + if i.Close() != nil { + panic("NewIter: cannot create rangeDelIter for tableIterator") + } + return nil, err + } return &compactionIterator{ - singleLevelIterator: i, - bytesIterated: bytesIterated, + tableIterator: &tableIterator{i, rangeDelIter}, + bytesIterated: bytesIterated, }, nil } @@ -2494,7 +2491,7 @@ func (r *Reader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) { if err != nil { return nil, err } - i := &fragmentBlockIter{} + i := &rangeDelIter{reader: r} if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil { return nil, err } @@ -2571,7 +2568,7 @@ func (r *Reader) readBlock( ) (_ cache.Handle, cacheHit bool, _ error) { usesSharedFS := false if r.meta != nil { - usesSharedFS = r.meta.UsesSharedFS + usesSharedFS = r.meta.IsShared } if h := r.opts.Cache.Get(r.cacheID, r.fileNum, bh.Offset, usesSharedFS); h.Get() != nil { if raState != nil { diff --git a/sstable/reader_test.go b/sstable/reader_test.go index 4d721f53db7..497541cac20 100644 --- a/sstable/reader_test.go +++ b/sstable/reader_test.go @@ -552,9 +552,9 @@ func TestCompactionIteratorSetupForCompaction(t *testing.T) { require.NoError(t, err) switch i := citer.(type) { case *compactionIterator: - require.NotNil(t, i.dataRS.sequentialFile) + require.NotNil(t, i.Iterator.(*singleLevelIterator).dataRS.sequentialFile) case *twoLevelCompactionIterator: - require.NotNil(t, i.dataRS.sequentialFile) + require.NotNil(t, i.Iterator.(*twoLevelIterator).dataRS.sequentialFile) default: require.Failf(t, fmt.Sprintf("unknown compaction iterator type: %T", citer), "") } diff --git a/sstable/shared.go b/sstable/shared.go new file mode 100644 index 00000000000..65d47d74172 --- /dev/null +++ b/sstable/shared.go @@ -0,0 +1,519 @@ +// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package sstable + +import ( + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/internal/keyspan" +) + +const ( + seqNumL5PointKey = 2 + seqNumL5RangeDel = 1 + seqNumL6All = 0 +) + +type tableIterator struct { + Iterator + rangeDelIter keyspan.FragmentIterator +} + +// NOTE: The physical layout of user keys follows the descending order of freshness +// (e.g., newer versions precede older versions) + +func (i *tableIterator) getReader() *Reader { + var r *Reader + switch i.Iterator.(type) { + case *twoLevelIterator: + r = i.Iterator.(*twoLevelIterator).reader + case *singleLevelIterator: + r = i.Iterator.(*singleLevelIterator).reader + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } + return r +} + +func (i *tableIterator) getCmp() Compare { + var cmp Compare + switch i.Iterator.(type) { + case *twoLevelIterator: + cmp = i.Iterator.(*twoLevelIterator).cmp + case *singleLevelIterator: + cmp = i.Iterator.(*singleLevelIterator).cmp + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } + return cmp +} + +func (i *tableIterator) isShared() bool { + r := i.getReader() + if r.meta != nil && r.meta.IsShared { + return true + } + return false +} + +// cmpSharedBound returns -1 if key < smallest, 1 if key > largest, +// or 0 otherwise +func (i *tableIterator) cmpSharedBound(key []byte) int { + if key == nil { + return 0 + } + r, cmp := i.getReader(), i.getCmp() + lower := r.meta.Smallest.UserKey + upper := r.meta.Largest.UserKey + if cmp(key, lower) < 0 { + return -1 + } else if cmp(key, upper) > 0 { + return 1 + } + return 0 +} + +// Note: the current implementation decouples isLocallyCreated() and isShared() for testing +// purposes. If a table is locally created, the reader should follow the read path of a regular +// iterator despite it is shared or not. Similarly, if a table is not locally created, it is +// inherently shared. This is kinda redundant but in some tests the file metadata is not complete +// which exposes a default value (e.g. 0) and it might direct the code to a wrong path. +// So for now I will just leave these two flags as is.. + +func (i *tableIterator) isLocallyCreated() bool { + var r *Reader + switch i.Iterator.(type) { + case *twoLevelIterator: + r = i.Iterator.(*twoLevelIterator).reader + case *singleLevelIterator: + r = i.Iterator.(*singleLevelIterator).reader + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } + return i.isShared() && r.meta.CreatorUniqueID == DBUniqueID +} + +func (i *tableIterator) setExhaustedBounds(e int8) { + switch i.Iterator.(type) { + case *twoLevelIterator: + i.Iterator.(*twoLevelIterator).exhaustedBounds = e + case *singleLevelIterator: + i.Iterator.(*singleLevelIterator).exhaustedBounds = e + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } +} + +func (i *tableIterator) getCurrUserKey() InternalKey { + var k InternalKey + switch i.Iterator.(type) { + case *twoLevelIterator: + k = i.Iterator.(*twoLevelIterator).data.ikey + case *singleLevelIterator: + k = i.Iterator.(*singleLevelIterator).data.ikey + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } + return k +} + +func (i *tableIterator) isKeyDeleted(k *InternalKey) bool { + if k.Kind() == InternalKeyKindMerge { + panic("tableIterator: found InternalKeyKindMerge when evaluating key deletion") + } + if k.Kind() == InternalKeyKindDelete { + return true + } + // check rangeDel in the same sstable + if i.rangeDelIter != nil { + cmp := i.getCmp() + span := keyspan.SeekGE(cmp, i.rangeDelIter, k.UserKey) + if span != nil && span.Contains(cmp, k.UserKey) && span.Covers(k.SeqNum()) { + return true + } + } + return false +} + +func setKeySeqNum(key *InternalKey, level int) { + if level == 5 { + key.SetSeqNum(seqNumL5PointKey) + } else if level == 6 { + key.SetSeqNum(seqNumL6All) + } else { + panic("sharedTableIterator: a table with shared flag must have its level at 5 or 6") + } +} + +func (i *tableIterator) seekGEShared( + prefix, key []byte, trySeekUsingNext bool, +) (*InternalKey, []byte) { + r := i.getReader() + ib := i.cmpSharedBound(key) + if ib > 0 { + // The search key overflows + i.setExhaustedBounds(+1) + return nil, nil + } else if ib < 0 { + // The search key underflows, substitute it with the lower shared bound + key = r.meta.Smallest.UserKey + } + var k *InternalKey + var v []byte + if prefix == nil { + k, v = i.Iterator.SeekGE(key, trySeekUsingNext) + } else { + k, v = i.Iterator.SeekPrefixGE(prefix, key, trySeekUsingNext) + } + if k == nil { + i.setExhaustedBounds(+1) + return nil, nil + } + // If the table is not locally created (i.e., purely foreign table), update + // the SeqNum accordingly. Note that we don't need to perform any extra movement + // here because if k != nil then we are guaranteed to be positioned at the first + // user key that satisfies the condition, which is the latest version. + if !i.isLocallyCreated() { + // if the latest key is a tombstone, omit the current key + // Note that we don't need to set the SeqNum in this case because the key + // returned from the last level is either nil or has its SeqNum set correctly + if i.isKeyDeleted(k) { + k, v = i.nextShared() + } else { + setKeySeqNum(k, i.GetLevel()) + } + } + // finally, check upper bound + if k == nil || i.cmpSharedBound(k.UserKey) > 0 { + i.setExhaustedBounds(+1) + return nil, nil + } + return k, v +} + +func (i *tableIterator) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) { + // shared path + if i.isShared() { + return i.seekGEShared(nil, key, trySeekUsingNext) + } + // non-shared path + return i.Iterator.SeekGE(key, trySeekUsingNext) +} + +func (i *tableIterator) SeekPrefixGE( + prefix, key []byte, trySeekUsingNext bool, +) (*InternalKey, []byte) { + if i.isShared() { + return i.seekGEShared(prefix, key, trySeekUsingNext) + } + // non-shared path + return i.Iterator.SeekPrefixGE(prefix, key, trySeekUsingNext) +} + +func (i *tableIterator) seekLTShared(key []byte) (*InternalKey, []byte) { + r, cmp := i.getReader(), i.getCmp() + ib := i.cmpSharedBound(key) + if ib < 0 { + i.setExhaustedBounds(-1) + return nil, nil + } else if ib > 0 { + key = r.meta.Largest.UserKey + } + k, v := i.Iterator.SeekLT(key) + if k == nil { + i.setExhaustedBounds(-1) + return nil, nil + } + // SeekLT is different from SeekGE as we are at the oldest version for the user key + // and we need to move to the newest version + if !i.isLocallyCreated() { + ik := i.getCurrUserKey() + k, _ = i.Iterator.Prev() + for k != nil && cmp(k.UserKey, ik.UserKey) == 0 { + k, _ = i.Iterator.Prev() + } + // now, either k == nil or k < ik, so k is just one slot over + k, v = i.Iterator.Next() + // if the latest key is a tombstone, omit the current key + if i.isKeyDeleted(k) { + k, v = i.prevShared() + } else { + setKeySeqNum(k, i.GetLevel()) + } + } + // check lower bound + if i.cmpSharedBound(k.UserKey) < 0 { + i.setExhaustedBounds(-1) + return nil, nil + } + return k, v +} + +func (i *tableIterator) SeekLT(key []byte) (*InternalKey, []byte) { + // shared path + if i.isShared() { + return i.seekLTShared(key) + } + return i.Iterator.SeekLT(key) +} + +// First() and Last() are just two synonyms of SeekGE and SeekLT + +func (i *tableIterator) First() (*InternalKey, []byte) { + if i.isShared() { + // in this case the table must have a smallest key + return i.seekGEShared(nil, i.getReader().meta.Smallest.UserKey, false) + } + return i.Iterator.First() +} + +func (i *tableIterator) Last() (*InternalKey, []byte) { + if i.isShared() { + // in this case the table must have a smallest key + return i.seekLTShared(i.getReader().meta.Largest.UserKey) + } + return i.Iterator.Last() +} + +func (i *tableIterator) nextShared() (*InternalKey, []byte) { + cmp := i.getCmp() + // Next() is not a simple case, as a valid position of an iterator + // for a purely foreign table always points to the latest version of a user key, + // and all the other versions are not exposed. Therefore, when we move forward, + // it is highly possible that we encounter these history versions which we should omit, + // and we can not easily determine when we crossed the key boundaries. + // To this end, we let tmpIter go first. + ik := i.getCurrUserKey() + k, v := i.Iterator.Next() + if k == nil { + i.setExhaustedBounds(+1) + return nil, nil + } + if !i.isLocallyCreated() { + // k is not nil, so it might position to a different key or a invisible history version + for k != nil && cmp(k.UserKey, ik.UserKey) == 0 { + k, v = i.Iterator.Next() + } + // now one of the following conditions stands: + // k == nil, we just return nil, or + // k > ik, we found a new key and it is the newest version + if k == nil { + i.setExhaustedBounds(+1) + return nil, nil + } + // if the latest key is a tombstone, omit the current key + if i.isKeyDeleted(k) { + k, v = i.nextShared() + } else { + setKeySeqNum(k, i.GetLevel()) + } + } + // check upper bound + if k == nil || i.cmpSharedBound(k.UserKey) > 0 { + i.setExhaustedBounds(+1) + return nil, nil + } + return k, v +} + +func (i *tableIterator) Next() (*InternalKey, []byte) { + if i.isShared() { + return i.nextShared() + } + return i.Iterator.Next() +} + +func (i *tableIterator) prevShared() (*InternalKey, []byte) { + cmp := i.getCmp() + // First move to the previous position, as we must move at least once. + // Note that if the iterator operates correctly, this Prev() must set the position + // of the iterator to a different key, as we were exposing the latest point version + // of a user key, i.e., the first slot. + k, v := i.Iterator.Prev() + if k == nil { + i.setExhaustedBounds(-1) + return nil, nil + } + // if the table is not locally created (i.e., purely foreign table), make sure exactly + // one version (the latest) of a user key is exposed. The SeqNum needs to be updated accordingly. + if !i.isLocallyCreated() { + ik := i.getCurrUserKey() + // find duplicated keys, or nil, whichever comes first + for k != nil && cmp(k.UserKey, ik.UserKey) == 0 { + k, _ = i.Iterator.Prev() + } + // At the current moment, either k < ik, or k == nil. So we rewind iter once. + k, v = i.Iterator.Next() + if i.isKeyDeleted(k) { + k, v = i.prevShared() + } else { + setKeySeqNum(k, i.GetLevel()) + } + } + // check lower bound + if k == nil || i.cmpSharedBound(k.UserKey) > 0 { + i.setExhaustedBounds(-1) + return nil, nil + } + return k, v +} + +func (i *tableIterator) Prev() (*InternalKey, []byte) { + if i.isShared() { + return i.prevShared() + } + return i.Iterator.Prev() +} + +func (i *tableIterator) Close() error { + if i.rangeDelIter != nil { + err := i.rangeDelIter.Close() + if err != nil { + panic("tableIterator: internal fragmentBlockIter close() error") + } + } + return i.Iterator.Close() +} + +func (i tableIterator) Stats() base.InternalIteratorStats { + var stats base.InternalIteratorStats + switch i.Iterator.(type) { + case *twoLevelIterator: + stats = i.Iterator.(*twoLevelIterator).stats + case *singleLevelIterator: + stats = i.Iterator.(*singleLevelIterator).stats + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } + return stats +} + +// ResetStats implements InternalIteratorWithStats. +func (i *tableIterator) ResetStats() { + switch i.Iterator.(type) { + case *twoLevelIterator: + i.Iterator.(*twoLevelIterator).stats = base.InternalIteratorStats{} + case *singleLevelIterator: + i.Iterator.(*singleLevelIterator).stats = base.InternalIteratorStats{} + default: + panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") + } +} + +// Implemented interfaces. All things goes to the internal Interator. +var _ base.InternalIterator = (*tableIterator)(nil) +var _ base.InternalIteratorWithStats = (*tableIterator)(nil) +var _ Iterator = (*tableIterator)(nil) + +// This is a copy of the vanilla NewRawRangeDelIter function. +// This function is used when creating a tableIterator because it needs a +// fragmentBlockIter that exposes the real SeqNUms. +// The NewRawRangeDelIter will be wrapped by the rangeDelIter type below +// and the SeqNum for shared L5/L6 will be manipulated. +func (r *Reader) newInternalRangeDelIter() (keyspan.FragmentIterator, error) { + if r.rangeDelBH.Length == 0 { + return nil, nil + } + h, err := r.readRangeDel() + if err != nil { + return nil, err + } + i := &fragmentBlockIter{} + if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil { + return nil, err + } + return i, nil +} + +type rangeDelIter struct { + fragmentBlockIter + reader *Reader + level int + levelSet bool +} + +func (i *rangeDelIter) SetLevel(level int) { + i.levelSet = true + i.level = level +} + +func (i *rangeDelIter) GetLevel() int { + if !i.levelSet { + return -1 + } + return i.level +} + +func (i *rangeDelIter) isShared() bool { + r := i.reader + if r.meta != nil && r.meta.IsShared { + return true + } + return false +} + +func (i *rangeDelIter) isLocallyCreated() bool { + r := i.reader + return i.isShared() && r.meta.CreatorUniqueID == DBUniqueID +} + +func setSpanSeqNum(s *keyspan.Span, seqnum uint64) { + for i := range s.Keys { + trailer := (s.Keys[i].Trailer & 0xff) | (seqnum << 8) + s.Keys[i].Trailer = trailer + } +} + +func (i *rangeDelIter) filterSpan(s *keyspan.Span) *keyspan.Span { + if i.isShared() && !i.isLocallyCreated() { + level := i.GetLevel() + if level == 5 { + setSpanSeqNum(s, seqNumL5RangeDel) + } else if level == 6 { + setSpanSeqNum(s, seqNumL6All) + } else { + panic("rangeDelIter: a table with shared flag must have its level at 5 or 6") + } + } + return s +} + +// The following functions only need to have a different behavior if +// the table is not locally created (so it is also shared), which is different +// from tableIterator. + +func (i *rangeDelIter) SeekGE(key []byte) *keyspan.Span { + s := i.fragmentBlockIter.SeekGE(key) + return i.filterSpan(s) +} + +func (i *rangeDelIter) SeekLT(key []byte) *keyspan.Span { + s := i.fragmentBlockIter.SeekLT(key) + return i.filterSpan(s) +} + +func (i *rangeDelIter) First() *keyspan.Span { + s := i.fragmentBlockIter.First() + return i.filterSpan(s) +} + +func (i *rangeDelIter) Last() *keyspan.Span { + s := i.fragmentBlockIter.Last() + return i.filterSpan(s) +} + +func (i *rangeDelIter) Next() *keyspan.Span { + s := i.fragmentBlockIter.Next() + return i.filterSpan(s) +} + +func (i *rangeDelIter) Prev() *keyspan.Span { + s := i.fragmentBlockIter.Prev() + return i.filterSpan(s) +} + +var _ keyspan.FragmentIterator = (*rangeDelIter)(nil) + +// RangeDelIter exposes the internal rangeDelIter for setting levels +type RangeDelIter = rangeDelIter diff --git a/sstable/shared_test.go b/sstable/shared_test.go new file mode 100644 index 00000000000..1cf597602df --- /dev/null +++ b/sstable/shared_test.go @@ -0,0 +1,171 @@ +// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package sstable + +import ( + "os" + "testing" + + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/internal/cache" + "github.com/cockroachdb/pebble/internal/manifest" + "github.com/cockroachdb/pebble/vfs" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + // check DBUniqueID == 0, otherwise the testing will fail + if DBUniqueID != 0 { + panic("shared_test.go: DBUniqueID != 0. It might be injected by a db.Open()") + } + code := m.Run() + os.Exit(code) +} + +func TestSharedSST(t *testing.T) { + t.Logf("Start TestSharedSST") + mem := vfs.NewMem() + f0, err := mem.Create("test") + require.NoError(t, err) + + w := NewWriter(f0, WriterOptions{}) + + // Insert the following kv: + // a#1,DEL + // a#0,SET - foo + // b#0,SET - foo + // c#1,SET - foo + // c#0,SET - bar + // d#0,SET - foo + // e#0,SET - foo + // e-f#2,RANGEDEL + // If the reader treats this table as purely foreign, it can only see keys + // b, c and d with value "foo" (only latest version, and also considering rangedels) + // For the creator, it can see all the keys + + kvPairs := []struct { + k []byte + v []byte + seqNum uint64 + kind InternalKeyKind + }{ + {[]byte("a"), []byte{}, 1, InternalKeyKindDelete}, + {[]byte("a"), []byte("foo"), 0, InternalKeyKindSet}, + {[]byte("b"), []byte("foo"), 0, InternalKeyKindSet}, + {[]byte("c"), []byte("foo"), 1, InternalKeyKindSet}, + {[]byte("c"), []byte("bar"), 0, InternalKeyKindSet}, + {[]byte("d"), []byte("foo"), 0, InternalKeyKindSet}, + {[]byte("e"), []byte("foo"), 0, InternalKeyKindSet}, + {[]byte("e"), []byte("f"), 2, InternalKeyKindRangeDelete}, + } + + for i := range kvPairs { + kv := kvPairs[i] + if kv.kind != InternalKeyKindRangeDelete { + w.addPoint(base.MakeInternalKey(kv.k, kv.seqNum, kv.kind), kv.v) + } else { + w.addTombstone(base.MakeInternalKey(kv.k, kv.seqNum, kv.kind), kv.v) + } + } + + require.NoError(t, w.Close()) + t.Logf("Table writing finished") + + // Reopen the file for further reading tests + + f1, err := mem.Open("test") + require.NoError(t, err) + + c := cache.New(128 << 10) + defer c.Unref() + r, err := NewReader(f1, ReaderOptions{ + Cache: c, + }, FileReopenOpt{ + FS: mem, + Filename: "test", + }) + require.NoError(t, err) + + // local table + t.Logf("Read as locally created shared table") + + meta := &manifest.FileMetadata{ + IsShared: true, + CreatorUniqueID: 0, + Smallest: InternalKey{UserKey: []byte("a"), Trailer: 0}, + Largest: InternalKey{UserKey: []byte("e"), Trailer: 0}, + } + r.meta = meta + require.Equal(t, uint32(0), DBUniqueID) + + iter, err := r.NewIter(nil, nil) + require.NoError(t, err) + require.NotEqual(t, iter, nil) + iter.SetLevel(5) + + i := 0 + for k, v := iter.First(); k != nil; k, v = iter.Next() { + t.Logf(" - %s %s", k, v) + require.Equal(t, base.MakeInternalKey(kvPairs[i].k, kvPairs[i].seqNum, kvPairs[i].kind), *k) + require.Equal(t, kvPairs[i].v, v) + i++ + } + require.NoError(t, iter.Close()) + + fragmentIter, err := r.NewRawRangeDelIter() + require.NoError(t, err) + require.NotEqual(t, fragmentIter, nil) + rDelIter, ok := fragmentIter.(*rangeDelIter) + require.Equal(t, true, ok) + rDelIter.SetLevel(5) + + s := rDelIter.First() + require.Equal(t, []byte("e"), s.Start) + require.Equal(t, []byte("f"), s.End) + for i := range s.Keys { + // here we should be able to see 2 as SeqNum + require.Equal(t, uint64(2), s.Keys[i].SeqNum()) + } + require.NoError(t, rDelIter.Close()) + + // foreign table + t.Logf("Read as remotely created shared table") + r.meta.CreatorUniqueID = 1 + iter, err = r.NewIter(nil, nil) + require.NoError(t, err) + require.NotEqual(t, iter, nil) + require.NotEqual(t, iter.(*tableIterator).rangeDelIter, nil) + iter.SetLevel(5) + + // Visible keys: b#2,SET, c#2,SET, d#2,SET + visible := []int{2, 3, 5} + i = 0 + for k, v := iter.First(); k != nil; k, v = iter.Next() { + require.Less(t, i, len(visible)) + t.Logf(" - %s %s", k, v) + require.Equal(t, base.MakeInternalKey(kvPairs[visible[i]].k, seqNumL5PointKey, kvPairs[visible[i]].kind), *k) + require.Equal(t, kvPairs[visible[i]].v, v) + i++ + } + + require.NoError(t, iter.Close()) + + fragmentIter, err = r.NewRawRangeDelIter() + require.NoError(t, err) + require.NotEqual(t, fragmentIter, nil) + rDelIter, ok = fragmentIter.(*rangeDelIter) + require.Equal(t, true, ok) + rDelIter.SetLevel(6) + + s = rDelIter.First() + require.Equal(t, []byte("e"), s.Start) + require.Equal(t, []byte("f"), s.End) + for i := range s.Keys { + require.Equal(t, uint64(seqNumL6All), s.Keys[i].SeqNum()) + } + require.NoError(t, rDelIter.Close()) + + require.NoError(t, r.Close()) +} diff --git a/table_cache.go b/table_cache.go index d8fff0e4ac1..1497ef79eb2 100644 --- a/table_cache.go +++ b/table_cache.go @@ -71,7 +71,7 @@ type tableCacheOpts struct { fs vfs.FS sharedDir string sharedFS vfs.FS - uniqueID uint16 + uniqueID uint32 psCache *persistentCache opts sstable.ReaderOptions filterMetrics *FilterMetrics @@ -423,7 +423,9 @@ func (c *tableCacheShard) newIters( var iter sstable.Iterator useFilter := true + level := -1 if opts != nil { + level = manifest.LevelToInt(opts.level) useFilter = manifest.LevelToInt(opts.level) != 6 || opts.UseL6Filters } if internalOpts.bytesIterated != nil { @@ -439,6 +441,9 @@ func (c *tableCacheShard) newIters( c.unrefValue(v) return nil, nil, err } + // Set the level here for internal use by sstable package (now only for shared sst) + iter.SetLevel(level) + // NB: v.closeHook takes responsibility for calling unrefValue(v) here. Take // care to avoid introduceingan allocation here by adding a closure. iter.SetCloseHook(v.closeHook) @@ -450,6 +455,16 @@ func (c *tableCacheShard) newIters( c.mu.iters[iter] = debug.Stack() c.mu.Unlock() } + + if rangeDelIter != nil { + sstRangeDelIter, ok := rangeDelIter.(*sstable.RangeDelIter) + if !ok { + panic("table_cache.go: rangeDelIter returned is not sstable.RangeDelIter") + } + // Set the level here for internal use by sstable package (now only for shared sst) + sstRangeDelIter.SetLevel(level) + } + return iter, rangeDelIter, nil } @@ -892,10 +907,10 @@ func (v *tableCacheValue) load(meta *fileMetadata, c *tableCacheShard, dbOpts *t var f vfs.File fs := dbOpts.fs dirname := dbOpts.dirname - if meta.UsesSharedFS { + if meta.IsShared { fs = dbOpts.sharedFS dirname = dbOpts.sharedDir - v.filename = base.MakeSharedSSTPath(fs, dirname, dbOpts.uniqueID, meta.FileNum) + v.filename = base.MakeSharedSSTPath(fs, dirname, meta.CreatorUniqueID, meta.PhysicalFileNum) } else { v.filename = base.MakeFilepath(fs, dirname, fileTypeTable, meta.FileNum) } @@ -903,7 +918,7 @@ func (v *tableCacheValue) load(meta *fileMetadata, c *tableCacheShard, dbOpts *t if v.err == nil { cacheOpts := private.SSTableCacheOpts(dbOpts.cacheID, meta.FileNum).(sstable.ReaderOption) extraOpts := []sstable.ReaderOption{cacheOpts, dbOpts.filterMetrics} - if !meta.UsesSharedFS { + if !meta.IsShared { extraOpts = append(extraOpts, sstable.FileReopenOpt{FS: dbOpts.fs, Filename: v.filename}) } else { extraOpts = append(extraOpts, sstable.PersistentCacheOpt{PsCache: dbOpts.psCache, Meta: meta}) diff --git a/testdata/metrics b/testdata/metrics index 4355bf77f51..49e95e11e7d 100644 --- a/testdata/metrics +++ b/testdata/metrics @@ -41,7 +41,7 @@ zmemtbl 1 256 K disk-usage ---- -1.9 K +2.0 K batch set b 2 From dd075875137dd1456d8b54ed4e17b52dd8a579e7 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Wed, 20 Jul 2022 11:38:14 -0500 Subject: [PATCH 4/6] *: exporting iterator and ingestion of shared sstable Note: this commit changes the first SeqNum from 1 to 4 (sstable.SeqNumStart) and the zero SeqNum from 0 to 3 (sstable.SeqNumZero). The zero SeqNum is used in compactions where a key has no underneath keys, for better compression (see maybeZeroSeqnum). A special case is ingestion where the input table still needs to have SeqNum = 0. --- Makefile | 11 +- compaction_iter.go | 4 +- compaction_test.go | 6 +- data_test.go | 14 +- db_test.go | 8 +- disagg_test.go | 271 ++++++++---- error_test.go | 16 +- event_listener_test.go | 2 +- flush_external.go | 2 +- ingest.go | 203 +++++++-- ingest_test.go | 61 +-- internal/base/internal.go | 24 ++ internal/keyspan/span.go | 49 +++ internal/metamorphic/ops.go | 2 +- internal/replay/replay.go | 2 +- iterator_test.go | 7 +- level_iter.go | 20 + open.go | 8 +- open_test.go | 2 +- options.go | 6 + range_del_test.go | 49 +-- sstable/options.go | 3 + sstable/reader.go | 18 +- sstable/shared.go | 48 ++- sstable/shared_test.go | 12 +- table_cache.go | 4 +- testdata/compaction_allow_zero_seqnum | 4 +- testdata/compaction_delete_only_hints | 88 ++-- testdata/compaction_iter | 6 +- testdata/compaction_iter_set_with_del | 10 +- testdata/compaction_read_triggered | 36 +- testdata/compaction_tombstones | 38 +- testdata/event_listener | 2 +- testdata/ingest | 226 +++++----- testdata/ingest_target_level | 32 +- testdata/iterator_block_interval_filter | 4 +- testdata/iterator_next_prev | 26 +- testdata/iterator_read_sampling | 40 +- testdata/iterator_seek_opt | 8 +- testdata/iterator_table_filter | 8 +- testdata/manual_compaction | 404 +++++++++--------- testdata/manual_compaction_range_keys | 28 +- testdata/manual_compaction_set_with_del | 332 +++++++------- testdata/manual_flush | 20 +- testdata/marked_for_compaction | 10 +- testdata/metrics | 28 +- testdata/range_del | 164 +++---- testdata/rangekeys | 16 +- testdata/read_compaction_queue | 2 +- testdata/singledel_manual_compaction | 30 +- .../singledel_manual_compaction_set_with_del | 104 ++--- testdata/split_user_key_migration | 38 +- testdata/table_stats | 120 +++--- tool/lsm_data.go | 6 +- tool/make_test_find_db.go | 2 +- tool/testdata/find | 110 ++--- tool/testdata/find-db/MANIFEST-000001 | Bin 422 -> 424 bytes tool/testdata/find-db/OPTIONS-000003 | 13 +- tool/testdata/find-db/archive/000002.log | Bin 161 -> 161 bytes tool/testdata/find-db/archive/000004.log | Bin 99 -> 99 bytes tool/testdata/find-db/archive/000005.sst | Bin 784 -> 784 bytes tool/testdata/find-db/archive/000008.sst | Bin 791 -> 792 bytes tool/testdata/find-db/archive/000010.sst | Bin 834 -> 834 bytes tool/testdata/find-db/archive/000011.sst | Bin 898 -> 900 bytes tool/testdata/manifest_dump | 47 +- tool/testdata/manifest_summarize | 12 +- tool/testdata/sstable_properties | 2 +- version_set_test.go | 6 +- 68 files changed, 1607 insertions(+), 1267 deletions(-) diff --git a/Makefile b/Makefile index 4c9bd9235ab..505b7f9dd9e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ GOFLAGS := STRESSFLAGS := TAGS := invariants TESTS := . -LATEST_RELEASE := $(shell git fetch origin && git branch -r --list '*/crl-release-*' | grep -o 'crl-release-.*$$' | sort | tail -1) .PHONY: all all: @@ -24,7 +23,7 @@ test: ${GO} test -mod=vendor -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG} .PHONY: testrace -testrace: testflags += -race -timeout 20m +testrace: testflags += -v -race -timeout 20m testrace: test .PHONY: stress stressrace @@ -38,14 +37,6 @@ stressmeta: override STRESSFLAGS += -p 1 stressmeta: override TESTS = TestMeta$$ stressmeta: stress -.PHONY: crossversion-meta -crossversion-meta: - git checkout ${LATEST_RELEASE}; \ - ${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${LATEST_RELEASE}.test'; \ - git checkout -; \ - ${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/head.test'; \ - ${GO} test -tags '$(TAGS)' ${testflags} -v -run 'TestMetaCrossVersion' ./internal/metamorphic/crossversion --version '${LATEST_RELEASE},${LATEST_RELEASE},${LATEST_RELEASE}.test' --version 'HEAD,HEAD,./head.test' - .PHONY: generate generate: ${GO} generate -mod=vendor ${PKG} diff --git a/compaction_iter.go b/compaction_iter.go index ae67457cbef..dbeff60c623 100644 --- a/compaction_iter.go +++ b/compaction_iter.go @@ -16,6 +16,7 @@ import ( "github.com/cockroachdb/pebble/internal/invariants" "github.com/cockroachdb/pebble/internal/keyspan" "github.com/cockroachdb/pebble/internal/rangekey" + "github.com/cockroachdb/pebble/sstable" ) // compactionIter provides a forward-only iterator that encapsulates the logic @@ -886,5 +887,6 @@ func (i *compactionIter) maybeZeroSeqnum(snapshotIdx int) { // This is not the last snapshot return } - i.key.SetSeqNum(0) + // Not really zeroing out the SeqNum but set it to the smallest possible one + i.key.SetSeqNum(sstable.SeqNumZero) } diff --git a/compaction_test.go b/compaction_test.go index 8b09f867f7b..90d9d7182ef 100644 --- a/compaction_test.go +++ b/compaction_test.go @@ -2016,6 +2016,7 @@ func TestCompactionDeleteOnlyHints(t *testing.T) { if err != nil { return err.Error() } + seqNum += sstable.SeqNumZero d.mu.Lock() var s *Snapshot l := &d.mu.snapshots @@ -2150,6 +2151,7 @@ func TestCompactionTombstones(t *testing.T) { if err != nil { return err.Error() } + seqNum += sstable.SeqNumZero d.mu.Lock() var s *Snapshot l := &d.mu.snapshots @@ -2802,7 +2804,7 @@ func TestCompactionErrorCleanup(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{"ext"})) + require.NoError(t, d.Ingest([]string{"ext"}, nil)) } ingest("a", "c") ingest("b") @@ -3713,7 +3715,7 @@ func TestCompaction_LogAndApplyFails(t *testing.T) { require.NoError(t, w.Set(key, nil)) require.NoError(t, w.Close()) // Ingest the SST. - return db.Ingest([]string{fName}) + return db.Ingest([]string{fName}, nil) } testCases := []struct { diff --git a/data_test.go b/data_test.go index cb5c7e97057..33bf4a1921d 100644 --- a/data_test.go +++ b/data_test.go @@ -50,6 +50,7 @@ func runGetCmd(td *datadriven.TestData, d *DB) string { if err != nil { return err.Error() } + snap.seqNum += sstable.SeqNumZero default: return fmt.Sprintf("%s: unknown arg: %s", td.Cmd, arg.Key) } @@ -664,6 +665,7 @@ func runDBDefineCmd(td *datadriven.TestData, opts *Options) (*DB, error) { if err != nil { return nil, err } + seqNum += sstable.SeqNumZero snapshots[i] = seqNum if i > 0 && snapshots[i] < snapshots[i-1] { return nil, errors.New("Snapshots must be in ascending order") @@ -834,11 +836,11 @@ func runDBDefineCmd(td *datadriven.TestData, opts *Options) (*DB, error) { toBreak := false switch { case strings.HasPrefix(field, "start="): - ikey := base.ParseInternalKey(strings.TrimPrefix(field, "start=")) + ikey := base.ParseInternalKeyWithSeqNumOffset(strings.TrimPrefix(field, "start="), sstable.SeqNumZero) start = &ikey boundFields++ case strings.HasPrefix(field, "end="): - ikey := base.ParseInternalKey(strings.TrimPrefix(field, "end=")) + ikey := base.ParseInternalKeyWithSeqNumOffset(strings.TrimPrefix(field, "end="), sstable.SeqNumZero) end = &ikey boundFields++ default: @@ -865,7 +867,7 @@ func runDBDefineCmd(td *datadriven.TestData, opts *Options) (*DB, error) { continue } if data[:i] == "rangekey" { - span := keyspan.ParseSpan(data[i:]) + span := keyspan.ParseSpanWithSeqNumOffset(data[i:], sstable.SeqNumZero) err := rangekey.Encode(&span, func(k base.InternalKey, v []byte) error { return mem.set(k, v) }) @@ -874,7 +876,7 @@ func runDBDefineCmd(td *datadriven.TestData, opts *Options) (*DB, error) { } continue } - key := base.ParseInternalKey(data[:i]) + key := base.ParseInternalKeyWithSeqNumOffset(data[:i], sstable.SeqNumZero) valueStr := data[i+1:] value := []byte(valueStr) if valueStr == "" { @@ -986,7 +988,7 @@ func runIngestCmd(td *datadriven.TestData, d *DB, fs vfs.FS) error { paths = append(paths, arg.String()) } - if err := d.Ingest(paths); err != nil { + if err := d.Ingest(paths, nil); err != nil { return err } return nil @@ -1007,7 +1009,7 @@ func runForceIngestCmd(td *datadriven.TestData, d *DB) error { } } } - _, err := d.ingest(paths, func( + _, err := d.ingest(paths, nil, func( tableNewIters, IterOptions, Compare, diff --git a/db_test.go b/db_test.go index 929cce179ee..2fc9488c8e0 100644 --- a/db_test.go +++ b/db_test.go @@ -414,13 +414,13 @@ func TestLargeBatch(t *testing.T) { // Verify this results in one L0 table being created. require.NoError(t, try(100*time.Microsecond, 20*time.Second, - verifyLSM("0.0:\n 000005:[a#1,SET-a#1,SET]\n"))) + verifyLSM("0.0:\n 000005:[a#4,SET-a#4,SET]\n"))) require.NoError(t, d.Set([]byte("b"), bytes.Repeat([]byte("b"), 512), nil)) // Verify this results in a second L0 table being created. require.NoError(t, try(100*time.Microsecond, 20*time.Second, - verifyLSM("0.0:\n 000005:[a#1,SET-a#1,SET]\n 000007:[b#2,SET-b#2,SET]\n"))) + verifyLSM("0.0:\n 000005:[a#4,SET-a#4,SET]\n 000007:[b#5,SET-b#5,SET]\n"))) // Allocate a bunch of batches to exhaust the batchPool. None of these // batches should have a non-zero count. @@ -1011,7 +1011,7 @@ func TestDBClosed(t *testing.T) { require.True(t, errors.Is(catch(func() { _, _, _ = d.Get(nil) }), ErrClosed)) require.True(t, errors.Is(catch(func() { _ = d.Delete(nil, nil) }), ErrClosed)) require.True(t, errors.Is(catch(func() { _ = d.DeleteRange(nil, nil, nil) }), ErrClosed)) - require.True(t, errors.Is(catch(func() { _ = d.Ingest(nil) }), ErrClosed)) + require.True(t, errors.Is(catch(func() { _ = d.Ingest(nil, nil) }), ErrClosed)) require.True(t, errors.Is(catch(func() { _ = d.LogData(nil, nil) }), ErrClosed)) require.True(t, errors.Is(catch(func() { _ = d.Merge(nil, nil, nil) }), ErrClosed)) require.True(t, errors.Is(catch(func() { _ = d.RatchetFormatMajorVersion(FormatNewest) }), ErrClosed)) @@ -1083,7 +1083,7 @@ func TestDBConcurrentCompactClose(t *testing.T) { }) require.NoError(t, w.Set([]byte(fmt.Sprint(j)), nil)) require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{path})) + require.NoError(t, d.Ingest([]string{path}, nil)) } require.NoError(t, d.Close()) diff --git a/disagg_test.go b/disagg_test.go index a8956849d19..80f1578db62 100644 --- a/disagg_test.go +++ b/disagg_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "math/rand" + "os" "testing" "time" @@ -12,6 +13,79 @@ import ( "github.com/stretchr/testify/require" ) +func generateRandomKeys(klen uint64, num uint64) [][]byte { + const letters = "abcdefghijklmnopqrstuvwxyz" + rand.Seed(time.Now().UnixNano()) + + keys := make([][]byte, num) + for i := uint64(0); i < num; i++ { + keys[i] = make([]byte, klen) + for j := range keys[i] { + keys[i][j] = letters[rand.Intn(len(letters))] + } + } + + return keys +} + +func printLevels(t *testing.T, d *DB) map[string]bool { + visible := make(map[string]bool) + // check the number of table files in each level + t.Logf("Level metadata") + var nTables [manifest.NumLevels]int + readState := d.loadReadState() + for i := 0; i < manifest.NumLevels; i++ { + nTables[i] = readState.current.Levels[i].Len() + t.Logf(" -- level %d has %d tables", i, nTables[i]) + iter := readState.current.Levels[i].Iter() + fm := iter.First() + for fm != nil { + if fm.IsShared { + t.Logf(" -- sst %d is shared with virtual bound (%s %s) file bound (%s %s)\n", + fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey, fm.FileSmallest.UserKey, fm.FileLargest.UserKey) + visible[string(fm.Smallest.UserKey)] = true + visible[string(fm.FileLargest.UserKey)] = false + } else { + t.Logf(" -- sst %d is local with bound (%s %s)\n", fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey) + //visible[string(fm.Smallest.UserKey)] = true + //visible[string(fm.FileLargest.UserKey)] = true + } + fm = iter.Next() + } + } + readState.unref() + return visible +} + +func validateBoundaries(t *testing.T, d *DB, visible *map[string]bool) { + // no compaction should have happened, take a snapshot + snapshot := d.NewSnapshot() + t.Logf("Validating ...") + for key, iv := range *visible { + _, closer, err := snapshot.Get([]byte(key)) + if iv { + if err != nil { + t.Fatalf(" -- get: visible key %s get error (%v)", key, err) + } + t.Logf(" -- get: visible key %s found ", key) + require.NoError(t, closer.Close()) + } else { + if err != ErrNotFound { + t.Fatalf(" -- get: invisible key %s get error (%v)", key, err) + } + t.Logf(" -- get: invisible key %s not found ", key) + } + } + require.NoError(t, snapshot.Close()) +} + +func TestMain(m *testing.M) { + code := m.Run() + // reset to default function + + os.Exit(code) +} + func TestDBWithSharedSST(t *testing.T) { fs := vfs.NewMem() sharedfs := vfs.NewMem() @@ -34,22 +108,10 @@ func TestDBWithSharedSST(t *testing.T) { require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid+1, i), 0755)) } - rand.Seed(time.Now().UnixNano()) - const letters = "abcdefghijklmnopqrstuvwxyz" const N = 1000000 - const K = 8 - var keys [N][]byte - for i := 0; i < N; i++ { - keys[i] = make([]byte, K) - for j := range keys[i] { - keys[i][j] = letters[rand.Intn(len(letters))] - } - } + keys := generateRandomKeys(8, N) value := bytes.Repeat([]byte("x"), 4096) - // record all visible keys (1 per table in the test) - visible := make(map[string]bool) - // inject key boundaries function to filter out the upper setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { // The output sst is shared so update its boundaries @@ -79,81 +141,9 @@ func TestDBWithSharedSST(t *testing.T) { } } - printlevels := func() { - visible = make(map[string]bool) - // check the number of table files in each level - t.Logf("Level metadata") - var nTables [manifest.NumLevels]int - readState := d.loadReadState() - for i := 0; i < manifest.NumLevels; i++ { - nTables[i] = readState.current.Levels[i].Len() - t.Logf(" -- level %d has %d tables", i, nTables[i]) - iter := readState.current.Levels[i].Iter() - fm := iter.First() - for fm != nil { - if fm.IsShared { - t.Logf(" -- sst %d is shared with virtual bound (%s %s) file bound (%s %s)\n", - fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey, fm.FileSmallest.UserKey, fm.FileLargest.UserKey) - visible[string(fm.Smallest.UserKey)] = true - visible[string(fm.FileLargest.UserKey)] = false - } else { - t.Logf(" -- sst %d is local with bound (%s %s)\n", fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey) - //visible[string(fm.Smallest.UserKey)] = true - //visible[string(fm.FileLargest.UserKey)] = true - } - fm = iter.Next() - } - } - readState.unref() - } - - printlevels() - // read some keys to trigger new compactions - rand.Shuffle(N, func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) - t.Logf("Touching ...") - for i := 0; i < N; i++ { - _, closer, err := d.Get(keys[i]) - if err == nil { - require.NoError(t, closer.Close()) - } - } - printlevels() - - // check the number of files in the shared fs bucket - // t.Logf("Shared fs metadata") - // for i := 0; i < buckets; i++ { - // lst, err := sharedfs.List(fmt.Sprintf("%d/%d", uid, i)) - // require.NoError(t, err) - // t.Logf(" -- shared fs bucket %d has %d files: %s", i, len(lst), strings.Join(lst, " ")) - // } - - // validating visible and invisible keys - validateboundaries := func() { - // no compaction should have happened, take a snapshot - snapshot := d.NewSnapshot() - t.Logf("Validating ...") - for key, iv := range visible { - v, closer, err := snapshot.Get([]byte(key)) - if iv { - if err != nil { - t.Fatalf(" -- get: visible key %s get error (%v)", key, err) - } - if !bytes.Equal(v, value) { - t.Fatalf(" -- get: visible key %s get successfully but returned wrong value", key) - } - t.Logf(" -- get: visible key %s found ", key) - require.NoError(t, closer.Close()) - } else { - if err != ErrNotFound { - t.Fatalf(" -- get: invisible key %s get error (%v)", key, err) - } - t.Logf(" -- get: invisible key %s not found ", key) - } - } - require.NoError(t, snapshot.Close()) - } + visible := printLevels(t, d) - validateboundaries() + validateBoundaries(t, d, &visible) require.NoError(t, d.Close()) t.Log("Reopening ...") @@ -164,8 +154,111 @@ func TestDBWithSharedSST(t *testing.T) { }) require.NoError(t, err) - printlevels() - validateboundaries() + visible = printLevels(t, d) + validateBoundaries(t, d, &visible) require.NoError(t, d.Close()) + + // revert + setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { + meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest + lb, ub := meta.Smallest, meta.Largest + meta.Smallest, meta.Largest = lb, ub + meta.SmallestPointKey, meta.LargestPointKey = lb, ub + meta.CreatorUniqueID = creatorUniqueID + meta.PhysicalFileNum = meta.FileNum + } +} + +func TestIngestSharedSST(t *testing.T) { + + fs1, fs2 := vfs.NewMem(), vfs.NewMem() + uid1, uid2 := uint32(rand.Uint32()), uint32(rand.Uint32()) + sharedfs := vfs.NewMem() + + t.Log("Opening ...") + d1, err := Open("", &Options{ + FS: fs1, + SharedFS: sharedfs, + SharedDir: "", + UniqueID: uid1, + }) + require.NoError(t, err) + d2, err := Open("", &Options{ + FS: fs2, + SharedFS: sharedfs, + SharedDir: "", + UniqueID: uid2, + }) + require.NoError(t, err) + + const buckets = 10 + for i := 0; i < buckets; i++ { + require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid1, i), 0755)) + require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid2, i), 0755)) + } + + const N = 100000 + keys := generateRandomKeys(8, N) + value := bytes.Repeat([]byte("x"), 4096) + + // repeatly inserting/updating a random key + t.Log("Inserting ...") + for i := 0; i < N; i++ { + if i%10000 == 0 && i != 0 { + t.Logf("set %d keys", i) + } + key := &keys[i] + if err := d1.Set(*key, value, nil); err != nil { + t.Fatalf("set key %s error %v", key, err) + } + } + + t.Logf("Printing d1 LSM") + printLevels(t, d1) + + // test skipshared callback + t.Logf("Iterating and creating sharedmeta...") + var smeta []SharedSSTMeta + + rand.Seed(time.Now().Unix()) + cb := func(meta *manifest.FileMetadata) { + // Randomly pick a table here.. + if rand.Uint32()%2 == 1 { + return + } + m := SharedSSTMeta{ + CreatorUniqueID: meta.CreatorUniqueID, + PhysicalFileNum: meta.PhysicalFileNum, + Smallest: meta.Smallest, + Largest: meta.Smallest, // Only copy one key + FileSmallest: meta.FileSmallest, + FileLargest: meta.FileLargest, + } + smeta = append(smeta, m) + } + iterOpts := &IterOptions{SkipSharedFile: true, SharedFileCallback: cb} + iter := d1.NewIter(iterOpts) + require.NotEqual(t, nil, iter) + require.Equal(t, true, iter.First()) + for iter.Next() { + // do nothing + } + require.NoError(t, iter.Close()) + + require.NoError(t, d2.Ingest(nil, smeta)) + + t.Logf("Printing d2 LSM") + printLevels(t, d2) + + t.Logf("Iterating over d2's key space") + iter = d2.NewIter(&IterOptions{}) + require.NotEqual(t, nil, iter) + for i := iter.First(); i; i = iter.Next() { + t.Logf(" - key: %s", iter.Key()) + } + require.NoError(t, iter.Close()) + + require.NoError(t, d1.Close()) + require.NoError(t, d2.Close()) } diff --git a/error_test.go b/error_test.go index 16d1e628a6b..dbacb2be554 100644 --- a/error_test.go +++ b/error_test.go @@ -188,16 +188,16 @@ func TestRequireReadError(t *testing.T) { if formatVersion < FormatSetWithDelete { expectLSM(` 0.0: - 000007:[a1#4,SET-a2#72057594037927935,RANGEDEL] + 000007:[a1#7,SET-a2#72057594037927935,RANGEDEL] 6: - 000005:[a1#1,SET-a2#2,SET] + 000005:[a1#4,SET-a2#5,SET] `, d, t) } else { expectLSM(` 0.0: - 000007:[a1#4,SETWITHDEL-a2#72057594037927935,RANGEDEL] + 000007:[a1#7,SETWITHDEL-a2#72057594037927935,RANGEDEL] 6: - 000005:[a1#1,SET-a2#2,SET] + 000005:[a1#4,SET-a2#5,SET] `, d, t) } @@ -290,17 +290,17 @@ func TestCorruptReadError(t *testing.T) { if formatVersion < FormatSetWithDelete { expectLSM(` 0.0: - 000007:[a1#4,SET-a2#72057594037927935,RANGEDEL] + 000007:[a1#7,SET-a2#72057594037927935,RANGEDEL] 6: - 000005:[a1#1,SET-a2#2,SET] + 000005:[a1#4,SET-a2#5,SET] `, d, t) } else { expectLSM(` 0.0: - 000007:[a1#4,SETWITHDEL-a2#72057594037927935,RANGEDEL] + 000007:[a1#7,SETWITHDEL-a2#72057594037927935,RANGEDEL] 6: - 000005:[a1#1,SET-a2#2,SET] + 000005:[a1#4,SET-a2#5,SET] `, d, t) } diff --git a/event_listener_test.go b/event_listener_test.go index 2fe38162a4b..94ec8329cae 100644 --- a/event_listener_test.go +++ b/event_listener_test.go @@ -237,7 +237,7 @@ func TestEventListener(t *testing.T) { if err := w.Close(); err != nil { return err.Error() } - if err := d.Ingest([]string{"ext/0"}); err != nil { + if err := d.Ingest([]string{"ext/0"}, nil); err != nil { return err.Error() } return buf.String() diff --git a/flush_external.go b/flush_external.go index 179beb2127a..e8b0def1c14 100644 --- a/flush_external.go +++ b/flush_external.go @@ -44,7 +44,7 @@ func flushExternalTable(untypedDB interface{}, path string, originalMeta *fileMe } // Hard link the sstable into the DB directory. - if err := ingestLink(jobID, d.opts, d.dirname, []string{path}, []*fileMetadata{m}); err != nil { + if err := ingestLink(jobID, d.opts, d.dirname, []string{path}, []*fileMetadata{m}, []bool{false}); err != nil { return err } if err := d.dataDir.Sync(); err != nil { diff --git a/ingest.go b/ingest.go index 6bb514346d2..326eac16fd7 100644 --- a/ingest.go +++ b/ingest.go @@ -32,12 +32,22 @@ func sstableKeyCompare(userCmp Compare, a, b InternalKey) int { return 0 } -func ingestValidateKey(opts *Options, key *InternalKey) error { +func ingestValidateKey(opts *Options, key *InternalKey, isShared bool) error { if key.Kind() == InternalKeyKindInvalid { return base.CorruptionErrorf("pebble: external sstable has corrupted key: %s", key.Pretty(opts.Comparer.FormatKey)) } - if key.SeqNum() != 0 { + // Default case: current db has no shared fs + expectedSeqNum := uint64(0) + // If the current DB has shared fs + if opts.SharedFS != nil { + // Imported sst is local + if !isShared { + expectedSeqNum = sstable.SeqNumZero + } + // Note: if the imported sst is shared, we expect 0 which is sstable.seqNumL6All + } + if key.SeqNum() != expectedSeqNum { return base.CorruptionErrorf("pebble: external sstable has non-zero seqnum: %s", key.Pretty(opts.Comparer.FormatKey)) } @@ -45,20 +55,37 @@ func ingestValidateKey(opts *Options, key *InternalKey) error { } func ingestLoad1( - opts *Options, fmv FormatMajorVersion, path string, cacheID uint64, fileNum FileNum, + opts *Options, + fmv FormatMajorVersion, + path string, + smeta SharedSSTMeta, + isShared bool, + cacheID uint64, + fileNum FileNum, ) (*fileMetadata, error) { - stat, err := opts.FS.Stat(path) + if isShared && opts.SharedFS == nil { + panic("ingestLoad1: function called with shared meta but DB does not have shared fs") + } + + fs := opts.FS + if isShared { + fs = opts.SharedFS + } + + stat, err := fs.Stat(path) if err != nil { return nil, err } - f, err := opts.FS.Open(path) + f, err := fs.Open(path) if err != nil { return nil, err } cacheOpts := private.SSTableCacheOpts(cacheID, fileNum).(sstable.ReaderOption) - r, err := sstable.NewReader(f, opts.MakeReaderOptions(), cacheOpts) + // Create meta earlier and attach it to reader (does not affect non-shared sst) + meta := &fileMetadata{} + r, err := sstable.NewReader(f, opts.MakeReaderOptions(), cacheOpts, &sstable.FileMetadataOpt{Meta: meta, DBUniqueID: opts.UniqueID}) if err != nil { return nil, err } @@ -76,11 +103,20 @@ func ingestLoad1( ) } - meta := &fileMetadata{} meta.FileNum = fileNum meta.Size = uint64(stat.Size()) meta.CreationTime = time.Now().Unix() + if isShared { + meta.IsShared = true + meta.CreatorUniqueID = smeta.CreatorUniqueID + meta.PhysicalFileNum = smeta.PhysicalFileNum + meta.Smallest = smeta.Smallest + meta.Largest = smeta.Largest + meta.FileSmallest = smeta.FileSmallest + meta.FileLargest = smeta.FileLargest + } + // Avoid loading into the table cache for collecting stats if we // don't need to. If there are no range deletions, we have all the // information to compute the stats here. @@ -92,15 +128,21 @@ func ingestLoad1( // calculating stats before we can remove the original link. maybeSetStatsFromProperties(meta, &r.Properties) + // XXX(chen): I think the following logic also applies to shared ssts but + // we must first "mount" the meta to the reader.. (this has been done above) { iter, err := r.NewIter(nil /* lower */, nil /* upper */) if err != nil { return nil, err } + if isShared { + // This is tricky because L6 shared sst will expose SeqNum = 0 + iter.SetLevel(6) + } defer iter.Close() var smallest InternalKey if key, _ := iter.First(); key != nil { - if err := ingestValidateKey(opts, key); err != nil { + if err := ingestValidateKey(opts, key, isShared); err != nil { return nil, err } smallest = (*key).Clone() @@ -109,7 +151,7 @@ func ingestLoad1( return nil, err } if key, _ := iter.Last(); key != nil { - if err := ingestValidateKey(opts, key); err != nil { + if err := ingestValidateKey(opts, key, isShared); err != nil { return nil, err } meta.ExtendPointKeyBounds(opts.Comparer.Compare, smallest, key.Clone()) @@ -124,11 +166,18 @@ func ingestLoad1( return nil, err } if iter != nil { + if isShared { + sstRangeDelIter, ok := iter.(*sstable.RangeDelIter) + if !ok { + panic("ingestLoad1: rangeDelIter returned is not sstable.RangeDelIter") + } + sstRangeDelIter.SetLevel(6) + } defer iter.Close() var smallest InternalKey if s := iter.First(); s != nil { key := s.SmallestKey() - if err := ingestValidateKey(opts, &key); err != nil { + if err := ingestValidateKey(opts, &key, isShared); err != nil { return nil, err } smallest = key.Clone() @@ -138,7 +187,7 @@ func ingestLoad1( } if s := iter.Last(); s != nil { k := s.SmallestKey() - if err := ingestValidateKey(opts, &k); err != nil { + if err := ingestValidateKey(opts, &k, isShared); err != nil { return nil, err } largest := s.LargestKey().Clone() @@ -147,7 +196,8 @@ func ingestLoad1( } // Update the range-key bounds for the table. - { + // XXX(chen): only for local ssts as shared sst does not support rangekeys + if !isShared { iter, err := r.NewRawRangeKeyIter() if err != nil { return nil, err @@ -157,7 +207,7 @@ func ingestLoad1( var smallest InternalKey if s := iter.First(); s != nil { key := s.SmallestKey() - if err := ingestValidateKey(opts, &key); err != nil { + if err := ingestValidateKey(opts, &key, isShared); err != nil { return nil, err } smallest = key.Clone() @@ -167,7 +217,7 @@ func ingestLoad1( } if s := iter.Last(); s != nil { k := s.SmallestKey() - if err := ingestValidateKey(opts, &k); err != nil { + if err := ingestValidateKey(opts, &k, isShared); err != nil { return nil, err } // As range keys are fragmented, the end key of the last range key in @@ -181,6 +231,10 @@ func ingestLoad1( } } + if isShared && meta.HasRangeKeys { + panic("ingestLoad1: shared sst should not have range keys") + } + if !meta.HasPointKeys && !meta.HasRangeKeys { return nil, nil } @@ -194,30 +248,61 @@ func ingestLoad1( } func ingestLoad( - opts *Options, fmv FormatMajorVersion, paths []string, cacheID uint64, pending []FileNum, -) ([]*fileMetadata, []string, error) { - meta := make([]*fileMetadata, 0, len(paths)) - newPaths := make([]string, 0, len(paths)) + opts *Options, + fmv FormatMajorVersion, + paths []string, + smeta []SharedSSTMeta, + cacheID uint64, + pending []FileNum, +) ([]*fileMetadata, []string, []bool, error) { + nTables := len(paths) + if opts.SharedFS != nil && smeta != nil { + nTables += len(smeta) + } + meta := make([]*fileMetadata, 0, nTables) + newPaths := make([]string, 0, nTables) + shared := make([]bool, 0, nTables) for i := range paths { - m, err := ingestLoad1(opts, fmv, paths[i], cacheID, pending[i]) + m, err := ingestLoad1(opts, fmv, paths[i], SharedSSTMeta{}, false, cacheID, pending[i]) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if m != nil { meta = append(meta, m) newPaths = append(newPaths, paths[i]) + shared = append(shared, false) } } - return meta, newPaths, nil + // Handle shared sstable for the len(paths)+1-th to the end of the slice + if opts.SharedFS != nil && smeta != nil { + for i := range smeta { + j := i + len(paths) + spath := base.MakeSharedSSTPath(opts.SharedFS, opts.SharedDir, smeta[i].CreatorUniqueID, smeta[i].PhysicalFileNum) + m, err := ingestLoad1(opts, fmv, spath, smeta[i], true, cacheID, pending[j]) + if err != nil { + return nil, nil, nil, err + } + if m == nil { + panic("ingestLoad: shared sst is empty which is not handled now") + } + if m != nil { + meta = append(meta, m) + newPaths = append(newPaths, spath) + shared = append(shared, true) + } + } + } + return meta, newPaths, shared, nil } // Struct for sorting metadatas by smallest user keys, while ensuring the // matching path also gets swapped to the same index. For use in // ingestSortAndVerify. type metaAndPaths struct { - meta []*fileMetadata - paths []string - cmp Compare + meta []*fileMetadata + paths []string + shared []bool + cmp Compare } func (m metaAndPaths) Len() int { @@ -231,17 +316,19 @@ func (m metaAndPaths) Less(i, j int) bool { func (m metaAndPaths) Swap(i, j int) { m.meta[i], m.meta[j] = m.meta[j], m.meta[i] m.paths[i], m.paths[j] = m.paths[j], m.paths[i] + m.shared[i], m.shared[j] = m.shared[j], m.shared[i] } -func ingestSortAndVerify(cmp Compare, meta []*fileMetadata, paths []string) error { +func ingestSortAndVerify(cmp Compare, meta []*fileMetadata, paths []string, shared []bool) error { if len(meta) <= 1 { return nil } sort.Sort(&metaAndPaths{ - meta: meta, - paths: paths, - cmp: cmp, + meta: meta, + paths: paths, + shared: shared, + cmp: cmp, }) for i := 1; i < len(meta); i++ { @@ -264,7 +351,7 @@ func ingestCleanup(fs vfs.FS, dirname string, meta []*fileMetadata) error { } func ingestLink( - jobID int, opts *Options, dirname string, paths []string, meta []*fileMetadata, + jobID int, opts *Options, dirname string, paths []string, meta []*fileMetadata, shared []bool, ) error { // Wrap the normal filesystem with one which wraps newly created files with // vfs.NewSyncingFile. @@ -277,6 +364,10 @@ func ingestLink( } for i := range paths { + // Nothing to do for shared ssts + if shared[i] { + continue + } target := base.MakeFilepath(fs, dirname, fileTypeTable, meta[i].FileNum) var err error if _, ok := opts.FS.(*vfs.MemFS); ok && opts.DebugCheck != nil { @@ -525,11 +616,17 @@ func ingestTargetLevel( rangeDelIter.Close() } if overlap { + if meta.IsShared { + panic("ingestTargetLevel: shared sst has overlaps with L0 which is not allowed") + } return targetLevel, nil } } level := baseLevel + if meta.IsShared { + level = 5 + } for ; level < numLevels; level++ { levelIter := newLevelIter(iterOps, cmp, nil /* split */, newIters, v.Levels[level].Iter(), manifest.Level(level), nil) @@ -575,6 +672,16 @@ func ingestTargetLevel( return targetLevel, nil } +// SharedSSTMeta records the necessary information when ingesting a shared sstable +type SharedSSTMeta struct { + CreatorUniqueID uint32 + PhysicalFileNum base.FileNum + Smallest InternalKey + Largest InternalKey + FileSmallest InternalKey + FileLargest InternalKey +} + // Ingest ingests a set of sstables into the DB. Ingestion of the files is // atomic and semantically equivalent to creating a single batch containing all // of the mutations in the sstables. Ingestion may require the memtable to be @@ -617,14 +724,14 @@ func ingestTargetLevel( // can produce a noticeable hiccup in performance. See // https://github.com/cockroachdb/pebble/issues/25 for an idea for how to fix // this hiccup. -func (d *DB) Ingest(paths []string) error { +func (d *DB) Ingest(paths []string, smeta []SharedSSTMeta) error { if err := d.closed.Load(); err != nil { panic(err) } if d.opts.ReadOnly { return ErrReadOnly } - _, err := d.ingest(paths, ingestTargetLevel) + _, err := d.ingest(paths, smeta, ingestTargetLevel) return err } @@ -643,18 +750,18 @@ type IngestOperationStats struct { // IngestWithStats does the same as Ingest, and additionally returns // IngestOperationStats. -func (d *DB) IngestWithStats(paths []string) (IngestOperationStats, error) { +func (d *DB) IngestWithStats(paths []string, smeta []SharedSSTMeta) (IngestOperationStats, error) { if err := d.closed.Load(); err != nil { panic(err) } if d.opts.ReadOnly { return IngestOperationStats{}, ErrReadOnly } - return d.ingest(paths, ingestTargetLevel) + return d.ingest(paths, smeta, ingestTargetLevel) } func (d *DB) ingest( - paths []string, targetLevelFunc ingestTargetLevelFunc, + paths []string, smeta []SharedSSTMeta, targetLevelFunc ingestTargetLevelFunc, ) (IngestOperationStats, error) { // Allocate file numbers for all of the files being ingested and mark them as // pending in order to prevent them from being deleted. Note that this causes @@ -662,8 +769,14 @@ func (d *DB) ingest( // ordering. The sorting of L0 tables by sequence number avoids relying on // that (busted) invariant. d.mu.Lock() - pendingOutputs := make([]FileNum, len(paths)) - for i := range paths { + // Reserve slots for both local and shared sstables + nTables := len(paths) + if d.opts.SharedFS != nil && smeta != nil { + nTables += len(smeta) + } + pendingOutputs := make([]FileNum, nTables) + // pending[0:len(paths)] are for local sstables and the remaining are for shared tables + for i := 0; i < nTables; i++ { pendingOutputs[i] = d.mu.versions.getNextFileNum() } jobID := d.mu.nextJobID @@ -672,7 +785,7 @@ func (d *DB) ingest( // Load the metadata for all of the files being ingested. This step detects // and elides empty sstables. - meta, paths, err := ingestLoad(d.opts, d.FormatMajorVersion(), paths, d.cacheID, pendingOutputs) + meta, paths, shared, err := ingestLoad(d.opts, d.FormatMajorVersion(), paths, smeta, d.cacheID, pendingOutputs) if err != nil { return IngestOperationStats{}, err } @@ -681,8 +794,13 @@ func (d *DB) ingest( return IngestOperationStats{}, nil } + // Just to make sure + if len(meta) != len(paths) || len(meta) != len(shared) { + panic("ingest: meta, paths and shared have different length") + } + // Verify the sstables do not overlap. - if err := ingestSortAndVerify(d.cmp, meta, paths); err != nil { + if err := ingestSortAndVerify(d.cmp, meta, paths, shared); err != nil { return IngestOperationStats{}, err } @@ -691,7 +809,7 @@ func (d *DB) ingest( // (e.g. because the files reside on a different filesystem), ingestLink will // fall back to copying, and if that fails we undo our work and return an // error. - if err := ingestLink(jobID, d.opts, d.dirname, paths, meta); err != nil { + if err := ingestLink(jobID, d.opts, d.dirname, paths, meta, shared); err != nil { return IngestOperationStats{}, err } // Fsync the directory we added the tables to. We need to do this at some @@ -762,7 +880,12 @@ func (d *DB) ingest( d.opts.Logger.Infof("ingest cleanup failed: %v", err2) } } else { - for _, path := range paths { + for i, path := range paths { + // No removal for shared ssts + // Here the items in paths and meta are matched + if shared[i] { + continue + } if err2 := d.opts.FS.Remove(path); err2 != nil { d.opts.Logger.Infof("ingest failed to remove original file: %s", err2) } diff --git a/ingest_test.go b/ingest_test.go index dd7dc0b19d5..fde30730f48 100644 --- a/ingest_test.go +++ b/ingest_test.go @@ -85,7 +85,7 @@ func TestIngestLoad(t *testing.T) { Comparer: DefaultComparer, FS: mem, } - meta, _, err := ingestLoad(opts, dbVersion, []string{"ext"}, 0, []FileNum{1}) + meta, _, _, err := ingestLoad(opts, dbVersion, []string{"ext"}, nil, 0, []FileNum{1}) if err != nil { return err.Error() } @@ -171,7 +171,7 @@ func TestIngestLoadRand(t *testing.T) { Comparer: DefaultComparer, FS: mem, } - meta, _, err := ingestLoad(opts, version, paths, 0, pending) + meta, _, _, err := ingestLoad(opts, FormatNewest, paths, nil, 0, pending) require.NoError(t, err) for _, m := range meta { @@ -192,7 +192,7 @@ func TestIngestLoadInvalid(t *testing.T) { Comparer: DefaultComparer, FS: mem, } - if _, _, err := ingestLoad(opts, FormatNewest, []string{"invalid"}, 0, []FileNum{1}); err == nil { + if _, _, _, err := ingestLoad(opts, FormatNewest, []string{"invalid"}, nil, 0, []FileNum{1}); err == nil { t.Fatalf("expected error, but found success") } } @@ -212,6 +212,7 @@ func TestIngestSortAndVerify(t *testing.T) { var buf bytes.Buffer var meta []*fileMetadata var paths []string + var shared []bool var cmpName string d.ScanArgs(t, "cmp", &cmpName) cmp := comparers[cmpName] @@ -231,8 +232,9 @@ func TestIngestSortAndVerify(t *testing.T) { m := (&fileMetadata{}).ExtendPointKeyBounds(cmp, smallest, largest) meta = append(meta, m) paths = append(paths, strconv.Itoa(i)) + shared = append(shared, false) } - err := ingestSortAndVerify(cmp, meta, paths) + err := ingestSortAndVerify(cmp, meta, paths, shared) if err != nil { return fmt.Sprintf("%v\n", err) } @@ -264,6 +266,7 @@ func TestIngestLink(t *testing.T) { paths := make([]string, 10) meta := make([]*fileMetadata, len(paths)) contents := make([][]byte, len(paths)) + shared := make([]bool, len(paths)) for j := range paths { paths[j] = fmt.Sprintf("external%d", j) meta[j] = &fileMetadata{} @@ -277,13 +280,15 @@ func TestIngestLink(t *testing.T) { _, err = f.Write(append([]byte(nil), contents[j]...)) require.NoError(t, err) require.NoError(t, f.Close()) + + shared[j] = false } if i < count { mem.Remove(paths[i]) } - err := ingestLink(0 /* jobID */, opts, dir, paths, meta) + err := ingestLink(0 /* jobID */, opts, dir, paths, meta, shared) if i < count { if err == nil { t.Fatalf("expected error, but found success") @@ -343,7 +348,7 @@ func TestIngestLinkFallback(t *testing.T) { opts.EnsureDefaults() meta := []*fileMetadata{{FileNum: 1}} - require.NoError(t, ingestLink(0, opts, "", []string{"source"}, meta)) + require.NoError(t, ingestLink(0, opts, "", []string{"source"}, meta, []bool{false})) dest, err := mem.Open("000001.sst") require.NoError(t, err) @@ -486,7 +491,7 @@ func BenchmarkIngestOverlappingMemtable(b *testing.B) { assertNoError(w.Close()) b.StartTimer() - assertNoError(d.Ingest([]string{"ext"})) + assertNoError(d.Ingest([]string{"ext"}, nil)) } }) } @@ -713,8 +718,8 @@ func TestIngestError(t *testing.T) { }() inj.SetIndex(i) - err1 := d.Ingest([]string{"ext0"}) - err2 := d.Ingest([]string{"ext1"}) + err1 := d.Ingest([]string{"ext0"}, nil) + err2 := d.Ingest([]string{"ext1"}, nil) err := firstError(err1, err2) if err != nil && !errors.Is(err, errorfs.ErrInjected) { t.Fatal(err) @@ -755,7 +760,7 @@ func TestIngestIdempotence(t *testing.T) { for i := 0; i < count; i++ { ingestPath := fs.PathJoin(dir, fmt.Sprintf("ext%d", i)) require.NoError(t, fs.Link(path, ingestPath)) - require.NoError(t, d.Ingest([]string{ingestPath})) + require.NoError(t, d.Ingest([]string{ingestPath}, nil)) } require.NoError(t, d.Close()) } @@ -797,7 +802,7 @@ func TestIngestCompact(t *testing.T) { // flushed. require.NoError(t, d.Set(key, nil, nil)) } - require.NoError(t, d.Ingest([]string{src(i)})) + require.NoError(t, d.Ingest([]string{src(i)}, nil)) } require.NoError(t, d.Close()) @@ -834,7 +839,7 @@ func TestConcurrentIngest(t *testing.T) { // Perform N ingestions concurrently. for i := 0; i < cap(errCh); i++ { go func(i int) { - err := d.Ingest([]string{src(i)}) + err := d.Ingest([]string{src(i)}, nil) if err == nil { if _, err = d.opts.FS.Stat(src(i)); oserror.IsNotExist(err) { err = nil @@ -879,7 +884,7 @@ func TestConcurrentIngestCompact(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{"ext"})) + require.NoError(t, d.Ingest([]string{"ext"}, nil)) } compact := func(start, end string) { @@ -910,11 +915,11 @@ func TestConcurrentIngestCompact(t *testing.T) { expectLSM(` 0.0: - 000005:[a#2,SET-a#2,SET] - 000007:[c#4,SET-c#4,SET] + 000005:[a#5,SET-a#5,SET] + 000007:[c#7,SET-c#7,SET] 6: - 000004:[a#1,SET-a#1,SET] - 000006:[c#3,SET-c#3,SET] + 000004:[a#4,SET-a#4,SET] + 000006:[c#6,SET-c#6,SET] `) // At this point ingestion of an sstable containing only key "b" will be @@ -937,9 +942,9 @@ func TestConcurrentIngestCompact(t *testing.T) { expectLSM(` 0.0: - 000009:[b#5,SET-b#5,SET] + 000009:[b#8,SET-b#8,SET] 6: - 000008:[a#0,SET-c#0,SET] + 000008:[a#3,SET-c#3,SET] `) case 1: @@ -999,7 +1004,7 @@ func TestIngestFlushQueuedMemTable(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - stats, err := d.IngestWithStats([]string{"ext"}) + stats, err := d.IngestWithStats([]string{"ext"}, nil) require.NoError(t, err) require.Equal(t, stats.ApproxIngestedIntoL0Bytes, stats.Bytes) require.Less(t, uint64(0), stats.Bytes) @@ -1027,7 +1032,7 @@ func TestIngestStats(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - stats, err := d.IngestWithStats([]string{"ext"}) + stats, err := d.IngestWithStats([]string{"ext"}, nil) require.NoError(t, err) if expectedLevel == 0 { require.Equal(t, stats.ApproxIngestedIntoL0Bytes, stats.Bytes) @@ -1075,7 +1080,7 @@ func TestIngestFlushQueuedLargeBatch(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{"ext"})) + require.NoError(t, d.Ingest([]string{"ext"}, nil)) } ingest("a") @@ -1113,7 +1118,7 @@ func TestIngestMemtablePendingOverlap(t *testing.T) { require.NoError(t, w.Set([]byte(k), nil)) } require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{"ext"})) + require.NoError(t, d.Ingest([]string{"ext"}, nil)) } var wg sync.WaitGroup @@ -1230,7 +1235,7 @@ func TestIngestFileNumReuseCrash(t *testing.T) { for _, f := range files { func() { defer func() { err = recover().(error) }() - err = d.Ingest([]string{fs.PathJoin(dir, f)}) + err = d.Ingest([]string{fs.PathJoin(dir, f)}, nil) }() if err == nil || !errors.Is(err, errorfs.ErrInjected) { t.Fatalf("expected injected error, got %v", err) @@ -1626,7 +1631,7 @@ func TestIngestValidation(t *testing.T) { require.NoError(t, err) // Ingest the external table. - err = d.Ingest([]string{ingestTableName}) + err = d.Ingest([]string{ingestTableName}, nil) if err != nil { et.errLoc = errLocationIngest et.err = err @@ -1712,7 +1717,7 @@ func BenchmarkManySSTables(b *testing.B) { require.NoError(b, w.Close()) paths = append(paths, n) } - require.NoError(b, d.Ingest(paths)) + require.NoError(b, d.Ingest(paths, nil)) { const broadIngest = "broad.sst" @@ -1722,7 +1727,7 @@ func BenchmarkManySSTables(b *testing.B) { require.NoError(b, w.Set([]byte("0"), nil)) require.NoError(b, w.Set([]byte("Z"), nil)) require.NoError(b, w.Close()) - require.NoError(b, d.Ingest([]string{broadIngest})) + require.NoError(b, d.Ingest([]string{broadIngest}, nil)) } switch op { @@ -1747,7 +1752,7 @@ func runBenchmarkManySSTablesIngest(b *testing.B, d *DB, fs vfs.FS, count int) { w := sstable.NewWriter(f, sstable.WriterOptions{}) require.NoError(b, w.Set([]byte(n), nil)) require.NoError(b, w.Close()) - require.NoError(b, d.Ingest([]string{n})) + require.NoError(b, d.Ingest([]string{n}, nil)) } } diff --git a/internal/base/internal.go b/internal/base/internal.go index 6953de9710c..a566405e083 100644 --- a/internal/base/internal.go +++ b/internal/base/internal.go @@ -414,3 +414,27 @@ func ParsePrettyInternalKey(s string) InternalKey { seqNum, _ := strconv.ParseUint(x[1], 10, 64) return MakeInternalKey([]byte(ukey), seqNum, kind) } + +// ParseInternalKeyWithSeqNumOffset adds a sstable.SeqNumZero to all keys in the test +// (for shared sst compatibility) +func ParseInternalKeyWithSeqNumOffset(s string, offset uint64) InternalKey { + x := strings.Split(s, ".") + ukey := x[0] + kind, ok := kindsMap[x[1]] + if !ok { + panic(fmt.Sprintf("unknown kind: %q", x[1])) + } + j := 0 + if x[2][0] == 'b' { + j = 1 + } + seqNum, _ := strconv.ParseUint(x[2][j:], 10, 64) + // If the key is not sential key for RANGEDEL + if seqNum != InternalKeySeqNumMax { + seqNum += offset + } + if x[2][0] == 'b' { + seqNum |= InternalKeySeqNumBatch + } + return MakeInternalKey([]byte(ukey), seqNum, kind) +} diff --git a/internal/keyspan/span.go b/internal/keyspan/span.go index ee31df1fe39..ac9ae8b8a9d 100644 --- a/internal/keyspan/span.go +++ b/internal/keyspan/span.go @@ -443,3 +443,52 @@ func ParseSpan(input string) Span { } return s } + +// ParseSpanWithSeqNumOffset adds the adds a sstable.SeqNumZero to all keys in the test +// (for shared sst compatibility) +func ParseSpanWithSeqNumOffset(input string, offset uint64) Span { + var s Span + parts := strings.FieldsFunc(input, func(r rune) bool { + switch r { + case '-', ':', '{', '}': + return true + default: + return unicode.IsSpace(r) + } + }) + s.Start, s.End = []byte(parts[0]), []byte(parts[1]) + + // Each of the remaining parts represents a single Key. + s.Keys = make([]Key, 0, len(parts)-2) + for _, p := range parts[2:] { + keyFields := strings.FieldsFunc(p, func(r rune) bool { + switch r { + case '#', ',', '(', ')': + return true + default: + return unicode.IsSpace(r) + } + }) + + var k Key + // Parse the sequence number. + seqNum, err := strconv.ParseUint(keyFields[0], 10, 64) + if err != nil { + panic(fmt.Sprintf("invalid sequence number: %q: %s", keyFields[0], err)) + } + seqNum += offset + // Parse the key kind. + kind := base.ParseKind(keyFields[1]) + k.Trailer = base.MakeTrailer(seqNum, kind) + // Parse the optional suffix. + if len(keyFields) >= 3 { + k.Suffix = []byte(keyFields[2]) + } + // Parse the optional value. + if len(keyFields) >= 4 { + k.Value = []byte(keyFields[3]) + } + s.Keys = append(s.Keys, k) + } + return s +} diff --git a/internal/metamorphic/ops.go b/internal/metamorphic/ops.go index a7f29d5cc45..39b22d9625a 100644 --- a/internal/metamorphic/ops.go +++ b/internal/metamorphic/ops.go @@ -371,7 +371,7 @@ func (o *ingestOp) run(t *test, h *history) { } err = firstError(err, withRetries(func() error { - return t.db.Ingest(paths) + return t.db.Ingest(paths, nil) })) h.Recordf("%s // %v", o, err) diff --git a/internal/replay/replay.go b/internal/replay/replay.go index d0906c220ea..e78409f57f4 100644 --- a/internal/replay/replay.go +++ b/internal/replay/replay.go @@ -73,7 +73,7 @@ func (d *DB) Ingest(tables []Table) error { for i, tbl := range tables { paths[i] = tbl.Path } - return d.d.Ingest(paths) + return d.d.Ingest(paths, nil) } // FlushExternal simulates a flush of the table, linking it directly diff --git a/iterator_test.go b/iterator_test.go index 46354304e18..ff0d8a016c2 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -530,7 +530,7 @@ func TestIterator(t *testing.T) { vals = vals[:0] for _, key := range strings.Split(d.Input, "\n") { j := strings.Index(key, ":") - keys = append(keys, base.ParseInternalKey(key[:j])) + keys = append(keys, base.ParseInternalKeyWithSeqNumOffset(key[:j], sstable.SeqNumZero)) vals = append(vals, []byte(key[j+1:])) } return "" @@ -550,6 +550,7 @@ func TestIterator(t *testing.T) { if err != nil { return err.Error() } + seqNum += sstable.SeqNumZero case "lower": opts.LowerBound = []byte(arg.Vals[0]) case "upper": @@ -813,6 +814,7 @@ func TestIteratorTableFilter(t *testing.T) { if err != nil { return err.Error() } + seqNum += sstable.SeqNumZero iterOpts.TableFilter = func(userProps map[string]string) bool { minSeqNum, err := strconv.ParseUint(userProps["test.min-seq-num"], 10, 64) if err != nil { @@ -896,6 +898,7 @@ func TestIteratorNextPrev(t *testing.T) { if err != nil { return err.Error() } + seqNum += sstable.SeqNumZero default: return fmt.Sprintf("%s: unknown arg: %s", td.Cmd, arg.Key) } @@ -1207,7 +1210,7 @@ func TestIteratorSeekOptErrors(t *testing.T) { vals = vals[:0] for _, key := range strings.Split(d.Input, "\n") { j := strings.Index(key, ":") - keys = append(keys, base.ParseInternalKey(key[:j])) + keys = append(keys, base.ParseInternalKeyWithSeqNumOffset(key[:j], sstable.SeqNumZero)) vals = append(vals, []byte(key[j+1:])) } return "" diff --git a/level_iter.go b/level_iter.go index 7083bc705f2..3aefce1c17e 100644 --- a/level_iter.go +++ b/level_iter.go @@ -236,6 +236,8 @@ func (l *levelIter) init( l.tableOpts.PointKeyFilters = opts.PointKeyFilters l.tableOpts.UseL6Filters = opts.UseL6Filters l.tableOpts.level = l.level + l.tableOpts.SkipSharedFile = opts.SkipSharedFile + l.tableOpts.SharedFileCallback = opts.SharedFileCallback l.cmp = cmp l.split = split l.iterFile = nil @@ -542,6 +544,7 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato return noFileLoaded } + // This is the main loop that seeks for the target file to be loaded for { l.iterFile = file if file == nil { @@ -578,6 +581,23 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato continue } + // We have targeted a file but we need to further check the shared sst related options + if l.tableOpts.SkipSharedFile && file.IsShared { + // No need to check if the file is locally created or not as this is mostly for exporting + if l.tableOpts.SharedFileCallback != nil { + l.tableOpts.SharedFileCallback(file) + } + if dir < 0 { + file = l.files.Prev() + continue + } else if dir > 0 { + file = l.files.Next() + continue + } else { + panic("level_iter.go: dir == 0") + } + } + var rangeDelIter keyspan.FragmentIterator var iter internalIterator iter, rangeDelIter, l.err = l.newIters(l.files.Current(), &l.tableOpts, l.internalOpts) diff --git a/open.go b/open.go index e8f1896d149..d5ef295ecf5 100644 --- a/open.go +++ b/open.go @@ -134,9 +134,12 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { d.mu.compact.inProgress = make(map[*compaction]struct{}) d.mu.compact.noOngoingFlushStartTime = time.Now() d.mu.snapshots.init() + // logSeqNum is the next sequence number that will be assigned. Start // assigning sequence numbers from 1 to match rocksdb. - d.mu.versions.atomic.logSeqNum = 1 + // d.mu.versions.atomic.logSeqNum = 1 + // Note: for shared sst, only start from 4 here + d.mu.versions.atomic.logSeqNum = sstable.SeqNumStart d.timeNow = time.Now @@ -358,9 +361,6 @@ func Open(dirname string, opts *Options) (db *DB, _ error) { opts.UniqueID = uniqueID } - // Inject UniqueID to sstable package - sstable.DBUniqueID = opts.UniqueID - if opts.SharedFS != nil && opts.PersistentCacheSize != 0 { d.persistentCache = newPersistentCache(opts.FS, dirname, opts.SharedFS, opts.SharedDir, opts.UniqueID, opts.PersistentCacheSize) d.persistentCache.Start() diff --git a/open_test.go b/open_test.go index cbef01d9881..3188d4b4f9a 100644 --- a/open_test.go +++ b/open_test.go @@ -433,7 +433,7 @@ func TestOpenReadOnly(t *testing.T) { require.EqualValues(t, ErrReadOnly, d.Delete(nil, nil)) require.EqualValues(t, ErrReadOnly, d.DeleteRange(nil, nil, nil)) - require.EqualValues(t, ErrReadOnly, d.Ingest(nil)) + require.EqualValues(t, ErrReadOnly, d.Ingest(nil, nil)) require.EqualValues(t, ErrReadOnly, d.LogData(nil, nil)) require.EqualValues(t, ErrReadOnly, d.Merge(nil, nil, nil)) require.EqualValues(t, ErrReadOnly, d.Set(nil, nil, nil)) diff --git a/options.go b/options.go index f6e9cc72a18..fa66445025a 100644 --- a/options.go +++ b/options.go @@ -169,6 +169,12 @@ type IterOptions struct { // levelIter (including regular reads and compaction inputs). level manifest.Level + // SkipSharedFile determines whether the iterator will read shared sstables during + // its iteration. If the callback function is not nil, it will pass in the + // FileMetadata of the shared table for internal usage (e.g., constructing msg) + SkipSharedFile bool + SharedFileCallback func(*manifest.FileMetadata) + // NB: If adding new Options, you must account for them in iterator // construction and Iterator.SetOptions. } diff --git a/range_del_test.go b/range_del_test.go index ffadeb987d4..6b975f80e03 100644 --- a/range_del_test.go +++ b/range_del_test.go @@ -84,6 +84,7 @@ func TestRangeDel(t *testing.T) { if err != nil { return err.Error() } + snap.seqNum += sstable.SeqNumZero default: return fmt.Sprintf("%s: unknown arg: %s", td.Cmd, arg.Key) } @@ -207,17 +208,17 @@ func TestRangeDelCompactionTruncation(t *testing.T) { require.NoError(t, d.Compact([]byte("c"), []byte("c\x00"), false)) expectLSM(` 1: - 000008:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] - 000009:[b#3,RANGEDEL-d#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] + 000009:[b#6,RANGEDEL-d#72057594037927935,RANGEDEL] `) // Compact again to move one of the tables to L2. require.NoError(t, d.Compact([]byte("c"), []byte("c\x00"), false)) expectLSM(` 1: - 000008:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] 2: - 000009:[b#3,RANGEDEL-d#72057594037927935,RANGEDEL] + 000009:[b#6,RANGEDEL-d#72057594037927935,RANGEDEL] `) // Write "b" and "c" to a new table. @@ -226,11 +227,11 @@ func TestRangeDelCompactionTruncation(t *testing.T) { require.NoError(t, d.Flush()) expectLSM(` 0.0: - 000011:[b#4,SET-c#5,SET] + 000011:[b#7,SET-c#8,SET] 1: - 000008:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] 2: - 000009:[b#3,RANGEDEL-d#72057594037927935,RANGEDEL] + 000009:[b#6,RANGEDEL-d#72057594037927935,RANGEDEL] `) // "b" is still visible at this point as it should be. @@ -265,20 +266,20 @@ func TestRangeDelCompactionTruncation(t *testing.T) { if formatVersion < FormatSetWithDelete { expectLSM(` 1: - 000008:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] 2: - 000012:[b#4,SET-c#72057594037927935,RANGEDEL] + 000012:[b#7,SET-c#72057594037927935,RANGEDEL] 3: - 000013:[c#5,SET-d#72057594037927935,RANGEDEL] + 000013:[c#8,SET-d#72057594037927935,RANGEDEL] `) } else { expectLSM(` 1: - 000008:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] 2: - 000012:[b#4,SETWITHDEL-c#72057594037927935,RANGEDEL] + 000012:[b#7,SETWITHDEL-c#72057594037927935,RANGEDEL] 3: - 000013:[c#5,SET-d#72057594037927935,RANGEDEL] + 000013:[c#8,SET-d#72057594037927935,RANGEDEL] `) } @@ -357,15 +358,15 @@ func TestRangeDelCompactionTruncation2(t *testing.T) { require.NoError(t, d.Compact([]byte("b"), []byte("b\x00"), false)) expectLSM(` 6: - 000009:[a#3,RANGEDEL-d#72057594037927935,RANGEDEL] + 000009:[a#6,RANGEDEL-d#72057594037927935,RANGEDEL] `) require.NoError(t, d.Set([]byte("c"), bytes.Repeat([]byte("d"), 100), nil)) require.NoError(t, d.Compact([]byte("c"), []byte("c\x00"), false)) expectLSM(` 6: - 000012:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] - 000013:[c#4,SET-d#72057594037927935,RANGEDEL] + 000012:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[c#7,SET-d#72057594037927935,RANGEDEL] `) } @@ -431,7 +432,7 @@ func TestRangeDelCompactionTruncation3(t *testing.T) { } expectLSM(` 3: - 000009:[a#3,RANGEDEL-d#72057594037927935,RANGEDEL] + 000009:[a#6,RANGEDEL-d#72057594037927935,RANGEDEL] `) require.NoError(t, d.Set([]byte("c"), bytes.Repeat([]byte("d"), 100), nil)) @@ -439,17 +440,17 @@ func TestRangeDelCompactionTruncation3(t *testing.T) { require.NoError(t, d.Compact([]byte("c"), []byte("c\x00"), false)) expectLSM(` 3: - 000013:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] 4: - 000014:[c#4,SET-d#72057594037927935,RANGEDEL] + 000014:[c#7,SET-d#72057594037927935,RANGEDEL] `) require.NoError(t, d.Compact([]byte("c"), []byte("c\x00"), false)) expectLSM(` 3: - 000013:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] 5: - 000014:[c#4,SET-d#72057594037927935,RANGEDEL] + 000014:[c#7,SET-d#72057594037927935,RANGEDEL] `) if _, _, err := d.Get([]byte("b")); err != ErrNotFound { @@ -459,9 +460,9 @@ func TestRangeDelCompactionTruncation3(t *testing.T) { require.NoError(t, d.Compact([]byte("a"), []byte("a\x00"), false)) expectLSM(` 4: - 000013:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] 5: - 000014:[c#4,SET-d#72057594037927935,RANGEDEL] + 000014:[c#7,SET-d#72057594037927935,RANGEDEL] `) if v, _, err := d.Get([]byte("b")); err != ErrNotFound { @@ -522,7 +523,7 @@ func benchmarkRangeDelIterate(b *testing.B, entries, deleted int, snapshotCompac if err := w.Close(); err != nil { b.Fatal(err) } - if err := d.Ingest([]string{"ext"}); err != nil { + if err := d.Ingest([]string{"ext"}, nil); err != nil { b.Fatal(err) } diff --git a/sstable/options.go b/sstable/options.go index d94a1e4f9cd..8383c179dc9 100644 --- a/sstable/options.go +++ b/sstable/options.go @@ -117,6 +117,9 @@ type ReaderOptions struct { // written with {Batch,DB}.Merge. The MergerName is checked for consistency // with the value stored in the sstable when it was written. MergerName string + + // DBUniqueID is the pebble instance's unique ID of this reader + DBUniqueID uint32 } func (o ReaderOptions) ensureDefaults() ReaderOptions { diff --git a/sstable/reader.go b/sstable/reader.go index ee750bb0bb0..ba2fe061f59 100644 --- a/sstable/reader.go +++ b/sstable/reader.go @@ -26,9 +26,6 @@ import ( "github.com/cockroachdb/pebble/vfs" ) -// DBUniqueID is injected by pebble during Open() -var DBUniqueID uint32 = 0 - var errCorruptIndexEntry = base.CorruptionErrorf("pebble/table: corrupt index entry") var errReaderClosed = errors.New("pebble/table: reader is closed") @@ -2312,12 +2309,22 @@ func (f FileReopenOpt) readerApply(r *Reader) { // PersistentCacheOpt specifies the cache options type PersistentCacheOpt struct { PsCache PersistentCache - Meta *manifest.FileMetadata } func (p PersistentCacheOpt) readerApply(r *Reader) { // r.psCache = p.PsCache - r.meta = p.Meta + r.psCache = nil +} + +// FileMetadataOpt specifies the reader's meta field +type FileMetadataOpt struct { + Meta *manifest.FileMetadata + DBUniqueID uint32 +} + +func (f FileMetadataOpt) readerApply(r *Reader) { + r.meta = f.Meta + r.dbUniqueID = f.DBUniqueID } // rawTombstonesOpt is a Reader open option for specifying that range @@ -2367,6 +2374,7 @@ type Reader struct { Properties Properties psCache PersistentCache meta *manifest.FileMetadata + dbUniqueID uint32 } // Close implements DB.Close, as documented in the pebble package. diff --git a/sstable/shared.go b/sstable/shared.go index 65d47d74172..0636058fa39 100644 --- a/sstable/shared.go +++ b/sstable/shared.go @@ -10,6 +10,10 @@ import ( ) const ( + // SeqNumStart is the first SeqNum used by a Pebble instance (originally 1) + SeqNumStart = 4 + // SeqNumZero is the original 0 + SeqNumZero = 3 seqNumL5PointKey = 2 seqNumL5RangeDel = 1 seqNumL6All = 0 @@ -91,7 +95,7 @@ func (i *tableIterator) isLocallyCreated() bool { default: panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") } - return i.isShared() && r.meta.CreatorUniqueID == DBUniqueID + return i.isShared() && r.meta.CreatorUniqueID == r.dbUniqueID } func (i *tableIterator) setExhaustedBounds(e int8) { @@ -147,7 +151,7 @@ func setKeySeqNum(key *InternalKey, level int) { } func (i *tableIterator) seekGEShared( - prefix, key []byte, trySeekUsingNext bool, + prefix, key []byte, flags base.SeekGEFlags, ) (*InternalKey, []byte) { r := i.getReader() ib := i.cmpSharedBound(key) @@ -162,9 +166,9 @@ func (i *tableIterator) seekGEShared( var k *InternalKey var v []byte if prefix == nil { - k, v = i.Iterator.SeekGE(key, trySeekUsingNext) + k, v = i.Iterator.SeekGE(key, flags) } else { - k, v = i.Iterator.SeekPrefixGE(prefix, key, trySeekUsingNext) + k, v = i.Iterator.SeekPrefixGE(prefix, key, flags) } if k == nil { i.setExhaustedBounds(+1) @@ -192,35 +196,38 @@ func (i *tableIterator) seekGEShared( return k, v } -func (i *tableIterator) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) { +func (i *tableIterator) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []byte) { // shared path if i.isShared() { - return i.seekGEShared(nil, key, trySeekUsingNext) + return i.seekGEShared(nil, key, flags) } // non-shared path - return i.Iterator.SeekGE(key, trySeekUsingNext) + return i.Iterator.SeekGE(key, flags) } func (i *tableIterator) SeekPrefixGE( - prefix, key []byte, trySeekUsingNext bool, + prefix, key []byte, flags base.SeekGEFlags, ) (*InternalKey, []byte) { if i.isShared() { - return i.seekGEShared(prefix, key, trySeekUsingNext) + return i.seekGEShared(prefix, key, flags) } // non-shared path - return i.Iterator.SeekPrefixGE(prefix, key, trySeekUsingNext) + return i.Iterator.SeekPrefixGE(prefix, key, flags) } -func (i *tableIterator) seekLTShared(key []byte) (*InternalKey, []byte) { +func (i *tableIterator) seekLTShared(key []byte, flags base.SeekLTFlags) (*InternalKey, []byte) { r, cmp := i.getReader(), i.getCmp() ib := i.cmpSharedBound(key) if ib < 0 { i.setExhaustedBounds(-1) return nil, nil } else if ib > 0 { + // Here since ib > 0, we must specify a tightly larger key than Largest to make sure + // the upper bound is visible key = r.meta.Largest.UserKey + key = append(key, byte(0)) } - k, v := i.Iterator.SeekLT(key) + k, v := i.Iterator.SeekLT(key, flags) if k == nil { i.setExhaustedBounds(-1) return nil, nil @@ -250,12 +257,12 @@ func (i *tableIterator) seekLTShared(key []byte) (*InternalKey, []byte) { return k, v } -func (i *tableIterator) SeekLT(key []byte) (*InternalKey, []byte) { +func (i *tableIterator) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []byte) { // shared path if i.isShared() { - return i.seekLTShared(key) + return i.seekLTShared(key, flags) } - return i.Iterator.SeekLT(key) + return i.Iterator.SeekLT(key, flags) } // First() and Last() are just two synonyms of SeekGE and SeekLT @@ -263,15 +270,18 @@ func (i *tableIterator) SeekLT(key []byte) (*InternalKey, []byte) { func (i *tableIterator) First() (*InternalKey, []byte) { if i.isShared() { // in this case the table must have a smallest key - return i.seekGEShared(nil, i.getReader().meta.Smallest.UserKey, false) + return i.seekGEShared(nil, i.getReader().meta.Smallest.UserKey, base.SeekGEFlagsNone) } return i.Iterator.First() } func (i *tableIterator) Last() (*InternalKey, []byte) { if i.isShared() { - // in this case the table must have a smallest key - return i.seekLTShared(i.getReader().meta.Largest.UserKey) + // This case is also tricky.. we should pass in the key which is tightly larger + // than the table's largest key to reuse seekLT.. + k := i.getReader().meta.Largest.UserKey + k = append(k, byte(0)) + return i.seekLTShared(k, base.SeekLTFlagsNone) } return i.Iterator.Last() } @@ -455,7 +465,7 @@ func (i *rangeDelIter) isShared() bool { func (i *rangeDelIter) isLocallyCreated() bool { r := i.reader - return i.isShared() && r.meta.CreatorUniqueID == DBUniqueID + return i.isShared() && r.meta.CreatorUniqueID == r.dbUniqueID } func setSpanSeqNum(s *keyspan.Span, seqnum uint64) { diff --git a/sstable/shared_test.go b/sstable/shared_test.go index 1cf597602df..407e1d6edd7 100644 --- a/sstable/shared_test.go +++ b/sstable/shared_test.go @@ -5,7 +5,6 @@ package sstable import ( - "os" "testing" "github.com/cockroachdb/pebble/internal/base" @@ -15,15 +14,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestMain(m *testing.M) { - // check DBUniqueID == 0, otherwise the testing will fail - if DBUniqueID != 0 { - panic("shared_test.go: DBUniqueID != 0. It might be injected by a db.Open()") - } - code := m.Run() - os.Exit(code) -} - func TestSharedSST(t *testing.T) { t.Logf("Start TestSharedSST") mem := vfs.NewMem() @@ -98,7 +88,7 @@ func TestSharedSST(t *testing.T) { Largest: InternalKey{UserKey: []byte("e"), Trailer: 0}, } r.meta = meta - require.Equal(t, uint32(0), DBUniqueID) + require.Equal(t, uint32(0), r.dbUniqueID) iter, err := r.NewIter(nil, nil) require.NoError(t, err) diff --git a/table_cache.go b/table_cache.go index 1497ef79eb2..d3e72d6a852 100644 --- a/table_cache.go +++ b/table_cache.go @@ -921,8 +921,10 @@ func (v *tableCacheValue) load(meta *fileMetadata, c *tableCacheShard, dbOpts *t if !meta.IsShared { extraOpts = append(extraOpts, sstable.FileReopenOpt{FS: dbOpts.fs, Filename: v.filename}) } else { - extraOpts = append(extraOpts, sstable.PersistentCacheOpt{PsCache: dbOpts.psCache, Meta: meta}) + extraOpts = append(extraOpts, sstable.PersistentCacheOpt{PsCache: dbOpts.psCache}) } + // No matter what file type it is, attach metadata and db's unique ID here + extraOpts = append(extraOpts, sstable.FileMetadataOpt{Meta: meta, DBUniqueID: dbOpts.uniqueID}) v.reader, v.err = sstable.NewReader(f, dbOpts.opts, extraOpts...) } if v.err == nil { diff --git a/testdata/compaction_allow_zero_seqnum b/testdata/compaction_allow_zero_seqnum index efe62abbd32..d81b8f4babd 100644 --- a/testdata/compaction_allow_zero_seqnum +++ b/testdata/compaction_allow_zero_seqnum @@ -3,7 +3,7 @@ L2 c.SET.2:2 ---- 2: - 000004:[c#2,SET-c#2,SET] + 000004:[c#5,SET-c#5,SET] allow-zero-seqnum L0:b-b @@ -47,7 +47,7 @@ L1 a.SET.0:0 ---- 1: - 000004:[a#0,SET-a#0,SET] + 000004:[a#3,SET-a#3,SET] allow-zero-seqnum flush diff --git a/testdata/compaction_delete_only_hints b/testdata/compaction_delete_only_hints index db909e703db..5a68106168c 100644 --- a/testdata/compaction_delete_only_hints +++ b/testdata/compaction_delete_only_hints @@ -36,25 +36,25 @@ L4 m.SET.30:m u.SET.60:u ---- 0.0: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[d#110,SET-i#140,SET] + 000005:[d#113,SET-i#143,SET] 3: - 000006:[k#90,SET-o#150,SET] + 000006:[k#93,SET-o#153,SET] 4: - 000007:[m#30,SET-u#60,SET] + 000007:[m#33,SET-u#63,SET] # Test a hint that is blocked by open snapshots. No compaction should occur # and the hint should not be removed. get-hints ---- -L0.000004 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) +L0.000004 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) maybe-compact ---- Deletion hints: - L0.000004 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) + L0.000004 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) Compactions: (none) @@ -71,17 +71,17 @@ L4 m.SET.30:m u.SET.60:u ---- 0.0: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[d#110,SET-i#140,SET] + 000005:[d#113,SET-i#143,SET] 3: - 000006:[k#90,SET-o#150,SET] + 000006:[k#93,SET-o#153,SET] 4: - 000007:[m#30,SET-u#60,SET] + 000007:[m#33,SET-u#63,SET] get-hints ---- -L0.000004 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) +L0.000004 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) maybe-compact ---- @@ -107,20 +107,20 @@ L4 m.SET.30:m u.SET.60:u ---- 0.0: - 000004:[a#300,RANGEDEL-k#72057594037927935,RANGEDEL] + 000004:[a#303,RANGEDEL-k#72057594037927935,RANGEDEL] 1: - 000005:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000005:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000006:[d#110,SET-i#140,SET] + 000006:[d#113,SET-i#143,SET] 3: - 000007:[k#90,SET-o#150,SET] + 000007:[k#93,SET-o#153,SET] 4: - 000008:[m#30,SET-u#60,SET] + 000008:[m#33,SET-u#63,SET] get-hints ---- -L0.000004 a-k seqnums(tombstone=300-300, file-smallest=110, type=point-key-only) -L1.000005 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) +L0.000004 a-k seqnums(tombstone=303-303, file-smallest=113, type=point-key-only) +L1.000005 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) maybe-compact ---- @@ -142,22 +142,22 @@ L4 m.SET.30:m u.SET.60:u ---- 0.0: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[d#110,SET-i#140,SET] + 000005:[d#113,SET-i#143,SET] 3: - 000006:[k#90,SET-o#150,SET] + 000006:[k#93,SET-o#153,SET] 4: - 000007:[m#30,SET-u#60,SET] + 000007:[m#33,SET-u#63,SET] get-hints ---- -L0.000004 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) +L0.000004 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) compact a-z ---- 5: - 000008:[b#230,RANGEDEL-u#0,SET] + 000008:[b#233,RANGEDEL-u#3,SET] maybe-compact ---- @@ -182,19 +182,19 @@ L0 e.SET.240:e m.SET.260:m ---- 0.0: - 000008:[e#240,SET-m#260,SET] + 000008:[e#243,SET-m#263,SET] 1: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[d#110,SET-i#140,SET] + 000005:[d#113,SET-i#143,SET] 3: - 000006:[k#90,SET-o#150,SET] + 000006:[k#93,SET-o#153,SET] 4: - 000007:[m#30,SET-u#60,SET] + 000007:[m#33,SET-u#63,SET] get-hints ---- -L1.000004 b-r seqnums(tombstone=200-230, file-smallest=90, type=point-key-only) +L1.000004 b-r seqnums(tombstone=203-233, file-smallest=93, type=point-key-only) # Tables 000005 and 000006 can be deleted as their largest sequence numbers fall # below the smallest sequence number of the range del. Table 000007 falls @@ -221,7 +221,7 @@ L6 a.SET.20:b a.RANGEDEL.15:z ---- 6: - 000004:[a#20,SETWITHDEL-z#72057594037927935,RANGEDEL] + 000004:[a#23,SETWITHDEL-z#72057594037927935,RANGEDEL] # Note that this test depends on stats being present on the sstables, so we # collect hints here. We expect none, as the table is in L6. @@ -391,23 +391,23 @@ OK describe-lsm ---- 0.0: - 000013:[a#10,RANGEDEL-z#72057594037927935,RANGEKEYDEL] + 000013:[a#13,RANGEDEL-z#72057594037927935,RANGEKEYDEL] 6: - 000004:[a#1,RANGEKEYSET-c#1,SET] - 000005:[d#2,RANGEKEYSET-f#72057594037927935,RANGEKEYSET] - 000006:[g#3,SET-h#3,SET] - 000007:[i#4,RANGEKEYSET-k#4,SET] - 000008:[l#5,RANGEKEYSET-n#72057594037927935,RANGEKEYSET] - 000009:[o#6,SET-q#6,SET] - 000010:[r#7,RANGEKEYSET-t#7,SET] - 000011:[u#8,RANGEKEYSET-w#72057594037927935,RANGEKEYSET] - 000012:[x#9,SET-z#9,SET] + 000004:[a#4,RANGEKEYSET-c#4,SET] + 000005:[d#5,RANGEKEYSET-f#72057594037927935,RANGEKEYSET] + 000006:[g#6,SET-h#6,SET] + 000007:[i#7,RANGEKEYSET-k#7,SET] + 000008:[l#8,RANGEKEYSET-n#72057594037927935,RANGEKEYSET] + 000009:[o#9,SET-q#9,SET] + 000010:[r#10,RANGEKEYSET-t#10,SET] + 000011:[u#11,RANGEKEYSET-w#72057594037927935,RANGEKEYSET] + 000012:[x#12,SET-z#12,SET] get-hints ---- -L0.000013 a-i seqnums(tombstone=10-10, file-smallest=3, type=point-key-only) -L0.000013 i-r seqnums(tombstone=10-10, file-smallest=4, type=point-and-range-key) -L0.000013 r-z seqnums(tombstone=10-10, file-smallest=8, type=range-key-only) +L0.000013 a-i seqnums(tombstone=13-13, file-smallest=6, type=point-key-only) +L0.000013 i-r seqnums(tombstone=13-13, file-smallest=7, type=point-and-range-key) +L0.000013 r-z seqnums(tombstone=13-13, file-smallest=11, type=range-key-only) maybe-compact ---- diff --git a/testdata/compaction_iter b/testdata/compaction_iter index e6ac8efbb63..b6f3a0858eb 100644 --- a/testdata/compaction_iter +++ b/testdata/compaction_iter @@ -1047,7 +1047,7 @@ tombstones ---- a#2,2:v2 a#1,15:b -a#0,2:v1 +a#3,2:v1 . a-b#1 . @@ -1071,7 +1071,7 @@ iter allow-zero-seqnum=true first next ---- -a#0,1:5[base] +a#3,1:5[base] . iter elide-tombstones=true @@ -1124,7 +1124,7 @@ next next ---- a#3,15:c -b#0,1:5[base] +b#3,1:5[base] . iter snapshots=2 diff --git a/testdata/compaction_iter_set_with_del b/testdata/compaction_iter_set_with_del index 796984e0c76..740a1e9066b 100644 --- a/testdata/compaction_iter_set_with_del +++ b/testdata/compaction_iter_set_with_del @@ -1049,7 +1049,7 @@ tombstones ---- a#2,2:v2 a#1,15:b -a#0,2:v1 +a#3,2:v1 . a-b#1 . @@ -1073,7 +1073,7 @@ iter allow-zero-seqnum=true first next ---- -a#0,1:5[base] +a#3,1:5[base] . iter elide-tombstones=true @@ -1126,7 +1126,7 @@ next next ---- a#3,15:c -b#0,1:5[base] +b#3,1:5[base] . iter snapshots=2 @@ -1284,7 +1284,7 @@ first next next ---- -a#0,18:c +a#3,18:c a#2,15:z . @@ -1296,7 +1296,7 @@ next ---- a#3,1:c a#2,15:z -a#0,18:b +a#3,18:b . iter allow-zero-seqnum=true snapshots=2 diff --git a/testdata/compaction_read_triggered b/testdata/compaction_read_triggered index cda5c67882e..64d69793f1d 100644 --- a/testdata/compaction_read_triggered +++ b/testdata/compaction_read_triggered @@ -6,9 +6,9 @@ L6 a.SET.54:a b.SET.4:b ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#54,SET-b#4,SET] + 000005:[a#58,SET-b#8,SET] add-read-compaction 5: a-b 000004 @@ -20,7 +20,7 @@ show-read-compactions maybe-compact ---- -[JOB 100] compacted(read) L5 [000004] (784 B) + L6 [000005] (784 B) -> L6 [000006] (778 B), in 1.0s (2.0s total), output rate 778 B/s +[JOB 100] compacted(read) L5 [000004] (784 B) + L6 [000005] (784 B) -> L6 [000006] (779 B), in 1.0s (2.0s total), output rate 779 B/s show-read-compactions ---- @@ -29,7 +29,7 @@ show-read-compactions version ---- 6: - 000006:[a#0,SET-b#0,SET] + 000006:[a#3,SET-b#3,SET] # Check to make sure another compaction will not take place @@ -45,9 +45,9 @@ L6 a.SET.54:a b.SET.4:b ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#54,SET-b#4,SET] + 000005:[a#58,SET-b#8,SET] add-read-compaction flushing=true 5: a-b 000004 @@ -68,9 +68,9 @@ show-read-compactions version ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#54,SET-b#4,SET] + 000005:[a#58,SET-b#8,SET] add-read-compaction flushing=false ---- @@ -81,7 +81,7 @@ show-read-compactions maybe-compact ---- -[JOB 100] compacted(read) L5 [000004] (784 B) + L6 [000005] (784 B) -> L6 [000006] (778 B), in 1.0s (2.0s total), output rate 778 B/s +[JOB 100] compacted(read) L5 [000004] (784 B) + L6 [000005] (784 B) -> L6 [000006] (779 B), in 1.0s (2.0s total), output rate 779 B/s show-read-compactions ---- @@ -90,7 +90,7 @@ show-read-compactions version ---- 6: - 000006:[a#0,SET-b#0,SET] + 000006:[a#3,SET-b#3,SET] # Test case where there is mismatch in the level of chosen read compaction and current version. # In this case, we skip the compaction. @@ -101,9 +101,9 @@ L6 a.SET.55:a b.SET.5:b ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#55,SET-b#5,SET] + 000005:[a#58,SET-b#8,SET] add-read-compaction 4: a-b 000004 @@ -124,9 +124,9 @@ show-read-compactions version ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#55,SET-b#5,SET] + 000005:[a#58,SET-b#8,SET] # The read compaction range overlaps with the appropriate level, but # the file number is different. @@ -138,9 +138,9 @@ L6 a.SET.55:a b.SET.5:b ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#55,SET-b#5,SET] + 000005:[a#58,SET-b#8,SET] add-read-compaction 5: a-b 000003 @@ -161,6 +161,6 @@ show-read-compactions version ---- 5: - 000004:[a#55,SET-b#5,SET] + 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#55,SET-b#5,SET] + 000005:[a#58,SET-b#8,SET] diff --git a/testdata/compaction_tombstones b/testdata/compaction_tombstones index 39fba3f04b3..fddeb661d28 100644 --- a/testdata/compaction_tombstones +++ b/testdata/compaction_tombstones @@ -6,7 +6,7 @@ L6 b.RANGEDEL.230:h h.RANGEDEL.200:r ---- 6: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] wait-pending-table-stats 000004 @@ -28,7 +28,7 @@ L6 b.RANGEDEL.230:h h.RANGEDEL.200:r ---- 6: - 000004:[b#230,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[b#233,RANGEDEL-r#72057594037927935,RANGEDEL] wait-pending-table-stats 000004 @@ -49,7 +49,7 @@ L6 a.SET.55:a b.RANGEDEL.5:h ---- 6: - 000004:[a#55,SET-h#72057594037927935,RANGEDEL] + 000004:[a#58,SET-h#72057594037927935,RANGEDEL] wait-pending-table-stats 000004 @@ -72,7 +72,7 @@ L6 a.SET.55:a b.DEL.5: ---- 6: - 000004:[a#55,SET-b#5,DEL] + 000004:[a#58,SET-b#8,DEL] wait-pending-table-stats 000004 @@ -90,7 +90,7 @@ maybe-compact version ---- 6: - 000005:[a#0,SET-a#0,SET] + 000005:[a#3,SET-a#3,SET] # Checking for a compaction again should not trigger a compaction, because # 000005 does not contain deletions. @@ -111,7 +111,7 @@ L6 a.DEL.60: a.SET.55:a b.SET.100:b c.SET.101:c d.SET.102:d b.RANGEDEL.103:z ---- 6: - 000004:[a#60,DEL-z#72057594037927935,RANGEDEL] + 000004:[a#63,DEL-z#72057594037927935,RANGEDEL] wait-pending-table-stats 000004 @@ -144,7 +144,7 @@ L6 a.DEL.20: a.SET.1:a b.SET.2:b c.SET.3:c d.SET.4:d e.SET.5:e f.SET.6:f g.SET.7:g h.SET.8:h i.SET.9:i j.SET.10:j ---- 6: - 000004:[a#20,DEL-j#10,SET] + 000004:[a#23,DEL-j#13,SET] wait-pending-table-stats 000004 @@ -178,12 +178,12 @@ L6 f.SET.30: z.SET.31: ---- 5: - 000004:[b#200,SET-cc#204,SET] - 000005:[d#302,SET-de#303,SET] - 000006:[m#320,SET-o#340,SET] + 000004:[b#203,SET-cc#207,SET] + 000005:[d#305,SET-de#306,SET] + 000006:[m#323,SET-o#343,SET] 6: - 000007:[a#103,RANGEDEL-e#72057594037927935,RANGEDEL] - 000008:[f#30,SET-z#31,SET] + 000007:[a#106,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[f#33,SET-z#34,SET] close-snapshot 103 @@ -221,11 +221,11 @@ L6 f.SET.007: x.SET.008: z.SET.009: ---- 5: - 000004:[a#101,DEL-c#103,DEL] - 000005:[m#107,SET-m#107,SET] + 000004:[a#104,DEL-c#106,DEL] + 000005:[m#110,SET-m#110,SET] 6: - 000006:[a#1,SET-c#3,SET] - 000007:[f#7,SET-z#9,SET] + 000006:[a#4,SET-c#6,SET] + 000007:[f#10,SET-z#12,SET] wait-pending-table-stats 000004 @@ -257,9 +257,9 @@ L6 rangekey:c-d:{(#3,RANGEKEYSET,@1)} ---- 6: - 000004:[a#1,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] - 000005:[b#2,RANGEKEYUNSET-c#72057594037927935,RANGEKEYUNSET] - 000006:[c#3,RANGEKEYSET-d#72057594037927935,RANGEKEYSET] + 000004:[a#4,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] + 000005:[b#5,RANGEKEYUNSET-c#72057594037927935,RANGEKEYUNSET] + 000006:[c#6,RANGEKEYSET-d#72057594037927935,RANGEKEYSET] wait-pending-table-stats 000004 diff --git a/testdata/event_listener b/testdata/event_listener index e728012cb8a..7e84d7f1ff8 100644 --- a/testdata/event_listener +++ b/testdata/event_listener @@ -199,7 +199,7 @@ compact 1 2.3 K 0 B 0 (size == estimated-debt, scor zmemtbl 0 0 B ztbl 0 0 B bcache 8 1.4 K 11.1% (score == hit-rate) - tcache 1 704 B 40.0% (score == hit-rate) + tcache 1 720 B 40.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 0 filter - - 0.0% (score == utility) diff --git a/testdata/ingest b/testdata/ingest index b14ad40fb62..25321c16766 100644 --- a/testdata/ingest +++ b/testdata/ingest @@ -27,7 +27,7 @@ ingest ext0 lsm ---- 6: - 000006:[a#1,SET-b#1,SET] + 000006:[a#4,SET-b#4,SET] metrics ---- @@ -48,7 +48,7 @@ compact 0 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 0 0 B ztbl 0 0 B bcache 8 1.5 K 42.9% (score == hit-rate) - tcache 1 704 B 50.0% (score == hit-rate) + tcache 1 720 B 50.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 0 filter - - 0.0% (score == utility) @@ -89,9 +89,9 @@ ingest ext1 lsm ---- 0.0: - 000007:[a#2,SET-b#2,DEL] + 000007:[a#5,SET-b#5,DEL] 6: - 000006:[a#1,SET-b#1,SET] + 000006:[a#4,SET-b#4,SET] iter seek-ge a @@ -119,11 +119,11 @@ ingest ext2 lsm ---- 0.1: - 000008:[a#3,SET-c#3,SET] + 000008:[a#6,SET-c#6,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] + 000007:[a#5,SET-b#5,DEL] 6: - 000006:[a#1,SET-b#1,SET] + 000006:[a#4,SET-b#4,SET] iter seek-ge a @@ -154,13 +154,13 @@ ingest ext3 lsm ---- 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] + 000008:[a#6,SET-c#6,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] + 000007:[a#5,SET-b#5,DEL] 6: - 000006:[a#1,SET-b#1,SET] + 000006:[a#4,SET-b#4,SET] iter seek-ge a @@ -191,14 +191,14 @@ ingest ext4 lsm ---- 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] + 000008:[a#6,SET-c#6,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] + 000007:[a#5,SET-b#5,DEL] 6: - 000006:[a#1,SET-b#1,SET] - 000010:[x#5,SET-y#5,SET] + 000006:[a#4,SET-b#4,SET] + 000010:[x#8,SET-y#8,SET] iter seek-lt y @@ -234,16 +234,16 @@ memtable flushed lsm ---- 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000010:[x#5,SET-y#5,SET] + 000006:[a#4,SET-b#4,SET] + 000010:[x#8,SET-y#8,SET] iter seek-ge j @@ -275,17 +275,17 @@ ingest ext6 lsm ---- 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] + 000006:[a#4,SET-b#4,SET] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] get m @@ -306,20 +306,20 @@ memtable flushed lsm ---- 0.3: - 000015:[a#11,RANGEDEL-z#72057594037927935,RANGEDEL] + 000015:[a#14,RANGEDEL-z#72057594037927935,RANGEDEL] 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] - 000017:[m#9,SET-m#9,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] + 000017:[m#12,SET-m#12,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] + 000006:[a#4,SET-b#4,SET] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] get a @@ -384,23 +384,23 @@ ingest ext9 lsm ---- 0.4: - 000019:[a#13,SET-g#13,SET] - 000018:[j#12,RANGEDEL-m#12,SET] + 000019:[a#16,SET-g#16,SET] + 000018:[j#15,RANGEDEL-m#15,SET] 0.3: - 000015:[a#11,RANGEDEL-z#72057594037927935,RANGEDEL] + 000015:[a#14,RANGEDEL-z#72057594037927935,RANGEDEL] 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] - 000017:[m#9,SET-m#9,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] + 000017:[m#12,SET-m#12,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] + 000006:[a#4,SET-b#4,SET] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] # Overlap with sst boundary containing range del sentinel (fileNum 000015) is not considered an overlap since # range del's end key is exclusive. Hence ext9 gets ingested into L6. @@ -437,26 +437,26 @@ d:40 lsm ---- 0.4: - 000019:[a#13,SET-g#13,SET] - 000018:[j#12,RANGEDEL-m#12,SET] + 000019:[a#16,SET-g#16,SET] + 000018:[j#15,RANGEDEL-m#15,SET] 0.3: - 000015:[a#11,RANGEDEL-z#72057594037927935,RANGEDEL] + 000015:[a#14,RANGEDEL-z#72057594037927935,RANGEDEL] 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] - 000017:[m#9,SET-m#9,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] + 000017:[m#12,SET-m#12,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000021:[d#14,SET-d#14,SET] - 000022:[i#15,RANGEDEL-j#72057594037927935,RANGEDEL] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] - 000020:[z#16,SET-z#16,SET] + 000006:[a#4,SET-b#4,SET] + 000021:[d#17,SET-d#17,SET] + 000022:[i#18,RANGEDEL-j#72057594037927935,RANGEDEL] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] + 000020:[z#19,SET-z#19,SET] # No overlap between fileNum 000019 that contains point key f, since f is ingested file's range del sentinel. @@ -470,27 +470,27 @@ ingest ext13 lsm ---- 0.4: - 000019:[a#13,SET-g#13,SET] - 000018:[j#12,RANGEDEL-m#12,SET] + 000019:[a#16,SET-g#16,SET] + 000018:[j#15,RANGEDEL-m#15,SET] 0.3: - 000015:[a#11,RANGEDEL-z#72057594037927935,RANGEDEL] + 000015:[a#14,RANGEDEL-z#72057594037927935,RANGEDEL] 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] - 000017:[m#9,SET-m#9,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] + 000017:[m#12,SET-m#12,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000021:[d#14,SET-d#14,SET] - 000023:[e#17,RANGEDEL-f#72057594037927935,RANGEDEL] - 000022:[i#15,RANGEDEL-j#72057594037927935,RANGEDEL] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] - 000020:[z#16,SET-z#16,SET] + 000006:[a#4,SET-b#4,SET] + 000021:[d#17,SET-d#17,SET] + 000023:[e#20,RANGEDEL-f#72057594037927935,RANGEDEL] + 000022:[i#18,RANGEDEL-j#72057594037927935,RANGEDEL] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] + 000020:[z#19,SET-z#19,SET] # Overlap with range delete keys in memtable, hence memtable will be flushed. @@ -509,31 +509,31 @@ memtable flushed lsm ---- 0.6: - 000024:[b#19,SET-b#19,SET] + 000024:[b#22,SET-b#22,SET] 0.5: - 000026:[a#18,RANGEDEL-d#72057594037927935,RANGEDEL] + 000026:[a#21,RANGEDEL-d#72057594037927935,RANGEDEL] 0.4: - 000019:[a#13,SET-g#13,SET] - 000018:[j#12,RANGEDEL-m#12,SET] + 000019:[a#16,SET-g#16,SET] + 000018:[j#15,RANGEDEL-m#15,SET] 0.3: - 000015:[a#11,RANGEDEL-z#72057594037927935,RANGEDEL] + 000015:[a#14,RANGEDEL-z#72057594037927935,RANGEDEL] 0.2: - 000009:[b#4,MERGE-c#4,DEL] + 000009:[b#7,MERGE-c#7,DEL] 0.1: - 000008:[a#3,SET-c#3,SET] - 000011:[k#8,SET-k#8,SET] + 000008:[a#6,SET-c#6,SET] + 000011:[k#11,SET-k#11,SET] 0.0: - 000007:[a#2,SET-b#2,DEL] - 000013:[j#6,SET-k#7,SET] - 000017:[m#9,SET-m#9,SET] + 000007:[a#5,SET-b#5,DEL] + 000013:[j#9,SET-k#10,SET] + 000017:[m#12,SET-m#12,SET] 6: - 000006:[a#1,SET-b#1,SET] - 000021:[d#14,SET-d#14,SET] - 000023:[e#17,RANGEDEL-f#72057594037927935,RANGEDEL] - 000022:[i#15,RANGEDEL-j#72057594037927935,RANGEDEL] - 000014:[n#10,SET-n#10,SET] - 000010:[x#5,SET-y#5,SET] - 000020:[z#16,SET-z#16,SET] + 000006:[a#4,SET-b#4,SET] + 000021:[d#17,SET-d#17,SET] + 000023:[e#20,RANGEDEL-f#72057594037927935,RANGEDEL] + 000022:[i#18,RANGEDEL-j#72057594037927935,RANGEDEL] + 000014:[n#13,SET-n#13,SET] + 000010:[x#8,SET-y#8,SET] + 000020:[z#19,SET-z#19,SET] reset ---- @@ -554,7 +554,7 @@ ingest ext15 lsm ---- 6: - 000004:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] reset ---- @@ -573,7 +573,7 @@ ingest ext16 lsm ---- 6: - 000004:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] reset ---- @@ -602,9 +602,9 @@ ingest ext18 lsm ---- 0.0: - 000005:[a#2,SET-c#2,SET] + 000005:[a#5,SET-c#5,SET] 6: - 000004:[a#1,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-b#72057594037927935,RANGEDEL] reset ---- @@ -636,10 +636,10 @@ ingest ext21 lsm ---- 0.0: - 000006:[c#3,SET-c#3,SET] + 000006:[c#6,SET-c#6,SET] 6: - 000005:[a#2,SET-b#2,SET] - 000004:[c#1,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[a#5,SET-b#5,SET] + 000004:[c#4,RANGEDEL-d#72057594037927935,RANGEDEL] reset ---- @@ -665,9 +665,9 @@ ingest ext23 lsm ---- 0.0: - 000005:[a#2,SET-b#2,SET] + 000005:[a#5,SET-b#5,SET] 6: - 000004:[a#1,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-b#72057594037927935,RANGEDEL] reset ---- @@ -692,6 +692,6 @@ ingest ext25 lsm ---- 0.0: - 000005:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] 6: - 000004:[a#1,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-b#72057594037927935,RANGEDEL] diff --git a/testdata/ingest_target_level b/testdata/ingest_target_level index e159220bb13..b198c2e02b2 100644 --- a/testdata/ingest_target_level +++ b/testdata/ingest_target_level @@ -13,7 +13,7 @@ L5 c.SET.2:2 ---- 5: - 000004:[b#1,SET-c#2,SET] + 000004:[b#4,SET-c#5,SET] # Overlapping cases. target @@ -54,12 +54,12 @@ L3 h.SET.2:2 ---- 0.1: - 000005:[d#5,SET-f#6,SET] + 000005:[d#8,SET-f#9,SET] 0.0: - 000004:[b#3,SET-e#4,SET] - 000006:[x#7,SET-y#8,SET] + 000004:[b#6,SET-e#7,SET] + 000006:[x#10,SET-y#11,SET] 3: - 000007:[g#1,SET-h#2,SET] + 000007:[g#4,SET-h#5,SET] # Files overlap with L0. Files ingested into L0. target @@ -94,11 +94,11 @@ L6 c.SET.1:1 ---- 5: - 000004:[a#4,SET-a#4,SET] - 000005:[c#3,SET-c#3,SET] + 000004:[a#7,SET-a#7,SET] + 000005:[c#6,SET-c#6,SET] 6: - 000006:[a#2,SET-a#2,SET] - 000007:[c#1,SET-c#1,SET] + 000006:[a#5,SET-a#5,SET] + 000007:[c#4,SET-c#4,SET] # The ingested file slips through the gaps in both L5 and L6. target @@ -118,11 +118,11 @@ L6 compact:a-c ---- 5: - 000004:[a#4,SET-a#4,SET] - 000005:[c#3,SET-c#3,SET] + 000004:[a#7,SET-a#7,SET] + 000005:[c#6,SET-c#6,SET] 6: - 000006:[a#2,SET-a#2,SET] - 000007:[c#1,SET-c#1,SET] + 000006:[a#5,SET-a#5,SET] + 000007:[c#4,SET-c#4,SET] # The ingested file cannot reach L6 as there is a compaction outputting a file # into the range [a,c]. @@ -140,9 +140,9 @@ L2 a.RANGEDEL.1:g ---- 0.0: - 000004:[c#4,SET-g#72057594037927935,RANGEDEL] + 000004:[c#7,SET-g#72057594037927935,RANGEDEL] 2: - 000005:[a#1,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[a#4,RANGEDEL-g#72057594037927935,RANGEDEL] # Overlapping cases: # - The ingested file overlaps with with [c,c]. @@ -188,7 +188,7 @@ L1 g.SET.0:g ---- 1: - 000004:[a#0,SET-g#0,SET] + 000004:[a#3,SET-g#3,SET] # Data overlap. target diff --git a/testdata/iterator_block_interval_filter b/testdata/iterator_block_interval_filter index d3d382c5e0b..845673ecfde 100644 --- a/testdata/iterator_block_interval_filter +++ b/testdata/iterator_block_interval_filter @@ -12,7 +12,7 @@ set e05 e set f06 f ---- 0.0: - 000005:[a01#1,SET-f06#6,SET] + 000005:[a01#4,SET-f06#9,SET] # Iterate without a filter. iter @@ -191,7 +191,7 @@ set d0704 d set e0605 e ---- 0.0: - 000005:[a1001#1,SET-e0605#5,SET] + 000005:[a1001#4,SET-e0605#8,SET] # Iterate without a filter. iter diff --git a/testdata/iterator_next_prev b/testdata/iterator_next_prev index f537b6494e8..02ce8ec5564 100644 --- a/testdata/iterator_next_prev +++ b/testdata/iterator_next_prev @@ -6,7 +6,7 @@ set c 2 ingest ext1 ---- 6: - 000004:[a#1,MERGE-c#1,SET] + 000004:[a#4,MERGE-c#4,SET] build ext2 del-range b c @@ -15,9 +15,9 @@ del-range b c ingest ext2 ---- 0.0: - 000005:[b#2,RANGEDEL-c#72057594037927935,RANGEDEL] + 000005:[b#5,RANGEDEL-c#72057594037927935,RANGEDEL] 6: - 000004:[a#1,MERGE-c#1,SET] + 000004:[a#4,MERGE-c#4,SET] # Regression test for a bug where range tombstones were not properly # ignored by Iterator.prevUserKey when switching from forward to @@ -57,7 +57,7 @@ merge z 2 ingest ext1 ---- 6: - 000004:[t#1,SET-z#1,MERGE] + 000004:[t#4,SET-z#4,MERGE] build ext2 del-range x y @@ -66,9 +66,9 @@ del-range x y ingest ext2 ---- 0.0: - 000005:[x#2,RANGEDEL-y#72057594037927935,RANGEDEL] + 000005:[x#5,RANGEDEL-y#72057594037927935,RANGEDEL] 6: - 000004:[t#1,SET-z#1,MERGE] + 000004:[t#4,SET-z#4,MERGE] # Regression test for a bug where range tombstones were not properly # ignored by Iterator.nextUserKey when switching from reverse to @@ -111,7 +111,7 @@ set e e ingest ext1 ---- 6: - 000004:[e#1,SET-e#1,SET] + 000004:[e#4,SET-e#4,SET] build ext2 set b b @@ -121,8 +121,8 @@ del-range c d ingest ext2 ---- 6: - 000005:[b#2,SET-d#72057594037927935,RANGEDEL] - 000004:[e#1,SET-e#1,SET] + 000005:[b#5,SET-d#72057594037927935,RANGEDEL] + 000004:[e#4,SET-e#4,SET] # The scenario requires iteration at a snapshot. The "last" operation # will exhaust the mergingIter looking backwards for visible @@ -222,7 +222,7 @@ merge a a ingest ext1 ---- 6: - 000004:[a#1,MERGE-a#1,MERGE] + 000004:[a#4,MERGE-a#4,MERGE] build ext2 set e e @@ -232,8 +232,8 @@ del-range c d ingest ext2 ---- 6: - 000004:[a#1,MERGE-a#1,MERGE] - 000005:[c#2,RANGEDEL-e#2,SET] + 000004:[a#4,MERGE-a#4,MERGE] + 000005:[c#5,RANGEDEL-e#5,SET] iter seq=2 set-bounds lower=a upper=e @@ -268,7 +268,7 @@ singledel b ingest ext1 ---- 6: - 000004:[a#1,SET-b#1,SET] + 000004:[a#4,SET-b#4,SET] iter first diff --git a/testdata/iterator_read_sampling b/testdata/iterator_read_sampling index 08409a7de7b..b8425a97309 100644 --- a/testdata/iterator_read_sampling +++ b/testdata/iterator_read_sampling @@ -11,13 +11,13 @@ L3 d.SET.1:1 ---- 0.0: - 000004:[a#4,SET-a#4,SET] + 000004:[a#7,SET-a#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[d#2,SET-d#2,SET] + 000006:[d#5,SET-d#5,SET] 3: - 000007:[d#1,SET-d#1,SET] + 000007:[d#4,SET-d#4,SET] set allowed-seeks=2 ---- @@ -97,13 +97,13 @@ L3 l.SET.8:8 ---- 0.0: - 000004:[a#4,SET-c#8,SET] + 000004:[a#7,SET-c#11,SET] 1: - 000005:[a#3,SET-c#9,SET] + 000005:[a#6,SET-c#12,SET] 2: - 000006:[d#2,SET-l#7,SET] + 000006:[d#5,SET-l#10,SET] 3: - 000007:[d#1,SET-l#8,SET] + 000007:[d#4,SET-l#11,SET] set allowed-seeks=2 ---- @@ -179,13 +179,13 @@ L3 d.SET.1:1 ---- 0.0: - 000004:[a#4,SET-a#4,SET] + 000004:[a#7,SET-a#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[d#2,SET-d#2,SET] + 000006:[d#5,SET-d#5,SET] 3: - 000007:[d#1,SET-d#1,SET] + 000007:[d#4,SET-d#4,SET] set allowed-seeks=2 ---- @@ -261,13 +261,13 @@ L3 d.SET.1:1 ---- 0.0: - 000004:[a#4,SET-a#4,SET] + 000004:[a#7,SET-a#7,SET] 1: - 000005:[b#3,SET-b#3,SET] + 000005:[b#6,SET-b#6,SET] 2: - 000006:[c#2,SET-c#2,SET] + 000006:[c#5,SET-c#5,SET] 3: - 000007:[d#1,SET-d#1,SET] + 000007:[d#4,SET-d#4,SET] set allowed-seeks=3 ---- @@ -315,13 +315,13 @@ L3 l.SET.8:8 ---- 0.0: - 000004:[a#4,SET-c#8,SET] + 000004:[a#7,SET-c#11,SET] 1: - 000005:[a#3,SET-c#9,SET] + 000005:[a#6,SET-c#12,SET] 2: - 000006:[d#2,SET-l#7,SET] + 000006:[d#5,SET-l#10,SET] 3: - 000007:[d#1,SET-l#8,SET] + 000007:[d#4,SET-l#11,SET] set allowed-seeks=1 ---- diff --git a/testdata/iterator_seek_opt b/testdata/iterator_seek_opt index edd70f8693a..7a834300bf9 100644 --- a/testdata/iterator_seek_opt +++ b/testdata/iterator_seek_opt @@ -13,13 +13,13 @@ L3 e.SET.1:1 ---- 0.0: - 000004:[a#4,SET-a#4,SET] + 000004:[a#7,SET-a#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[d#2,SET-d#2,SET] + 000006:[d#5,SET-d#5,SET] 3: - 000007:[b#1,SET-e#1,SET] + 000007:[b#4,SET-e#4,SET] # Simple case: three successive seeks, at increasing keys. Should use # trySeekUsingNext. diff --git a/testdata/iterator_table_filter b/testdata/iterator_table_filter index c78a5386cc0..67a2fd67617 100644 --- a/testdata/iterator_table_filter +++ b/testdata/iterator_table_filter @@ -9,13 +9,13 @@ L3 a.SET.1:1 ---- 0.0: - 000004:[a#4,SET-a#4,SET] + 000004:[a#7,SET-a#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[a#2,SET-a#2,SET] + 000006:[a#5,SET-a#5,SET] 3: - 000007:[a#1,SET-a#1,SET] + 000007:[a#4,SET-a#4,SET] iter first diff --git a/testdata/manual_compaction b/testdata/manual_compaction index f5834f3f49d..0da29924db1 100644 --- a/testdata/manual_compaction +++ b/testdata/manual_compaction @@ -6,7 +6,7 @@ set b 2 compact a-b ---- 6: - 000005:[a#1,SET-b#2,SET] + 000005:[a#4,SET-b#5,SET] batch set c 3 @@ -16,8 +16,8 @@ set d 4 compact c-d ---- 6: - 000005:[a#1,SET-b#2,SET] - 000007:[c#3,SET-d#4,SET] + 000005:[a#4,SET-b#5,SET] + 000007:[c#6,SET-d#7,SET] batch set b 5 @@ -27,7 +27,7 @@ set c 6 compact a-d ---- 6: - 000010:[a#0,SET-d#0,SET] + 000010:[a#3,SET-d#3,SET] # This also tests flushing a memtable that only contains range # deletions. @@ -48,14 +48,14 @@ L0 a.SET.2:v ---- 0.0: - 000005:[a#2,SET-a#2,SET] - 000004:[b#1,SET-b#1,SET] + 000005:[a#5,SET-a#5,SET] + 000004:[b#4,SET-b#4,SET] compact a-b ---- 1: - 000006:[a#0,SET-a#0,SET] - 000007:[b#0,SET-b#0,SET] + 000006:[a#3,SET-a#3,SET] + 000007:[b#3,SET-b#3,SET] # A range tombstone extends past the grandparent file boundary used to limit the # size of future compactions. Verify the range tombstone is split at that file @@ -74,12 +74,12 @@ L3 d.SET.0:v ---- 1: - 000004:[a#3,SET-a#3,SET] + 000004:[a#6,SET-a#6,SET] 2: - 000005:[a#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] wait-pending-table-stats 000005 @@ -88,16 +88,16 @@ num-entries: 1 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 1552 +range-deletions-bytes-estimate: 1554 compact a-e L1 ---- 2: - 000008:[a#3,SET-c#72057594037927935,RANGEDEL] - 000009:[c#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,SET-c#72057594037927935,RANGEDEL] + 000009:[c#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] wait-pending-table-stats 000008 @@ -106,7 +106,7 @@ num-entries: 2 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 776 +range-deletions-bytes-estimate: 777 # Same as above, except range tombstone covers multiple grandparent file boundaries. @@ -129,27 +129,27 @@ L3 g.SET.0:v ---- 1: - 000004:[a#3,SET-a#3,SET] + 000004:[a#6,SET-a#6,SET] 2: - 000005:[a#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] - 000009:[f#0,SET-g#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] + 000009:[f#3,SET-g#3,SET] compact a-e L1 ---- 2: - 000010:[a#3,SET-c#72057594037927935,RANGEDEL] - 000011:[c#2,RANGEDEL-e#72057594037927935,RANGEDEL] - 000012:[e#2,RANGEDEL-f#72057594037927935,RANGEDEL] - 000013:[f#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000010:[a#6,SET-c#72057594037927935,RANGEDEL] + 000011:[c#5,RANGEDEL-e#72057594037927935,RANGEDEL] + 000012:[e#5,RANGEDEL-f#72057594037927935,RANGEDEL] + 000013:[f#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] - 000009:[f#0,SET-g#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] + 000009:[f#3,SET-g#3,SET] # A range tombstone covers multiple grandparent file boundaries between point keys, # rather than after all point keys. @@ -171,23 +171,23 @@ L3 f.SET.1:v ---- 1: - 000004:[a#3,SET-h#3,SET] + 000004:[a#6,SET-h#6,SET] 2: - 000005:[a#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] compact a-e L1 ---- 2: - 000009:[a#3,SET-c#72057594037927935,RANGEDEL] - 000010:[c#2,RANGEDEL-h#3,SET] + 000009:[a#6,SET-c#72057594037927935,RANGEDEL] + 000010:[c#5,RANGEDEL-h#6,SET] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] # A range tombstone is the first and only item output by a compaction, and it # extends past the grandparent file boundary used to limit the size of future @@ -206,21 +206,21 @@ L3 d.SET.0:v ---- 1: - 000004:[a#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#6,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000005:[a#2,SET-a#2,SET] + 000005:[a#5,SET-a#5,SET] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] compact a-e L1 ---- 2: - 000008:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] - 000009:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] + 000009:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] # An elided range tombstone is the first item encountered by a compaction, # and the grandparent limit set by it extends to the next item, also a range @@ -241,22 +241,22 @@ L3 m.SET.0:v ---- 1: - 000004:[a#4,RANGEDEL-d#72057594037927935,RANGEDEL] - 000005:[grandparent#2,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#7,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[grandparent#5,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000006:[grandparent#1,SET-grandparent#1,SET] + 000006:[grandparent#4,SET-grandparent#4,SET] 3: - 000007:[grandparent#0,SET-grandparent#0,SET] - 000008:[m#0,SET-m#0,SET] + 000007:[grandparent#3,SET-grandparent#3,SET] + 000008:[m#3,SET-m#3,SET] compact a-h L1 ---- 2: - 000009:[grandparent#2,RANGEDEL-m#72057594037927935,RANGEDEL] - 000010:[m#2,RANGEDEL-z#72057594037927935,RANGEDEL] + 000009:[grandparent#5,RANGEDEL-m#72057594037927935,RANGEDEL] + 000010:[m#5,RANGEDEL-z#72057594037927935,RANGEDEL] 3: - 000007:[grandparent#0,SET-grandparent#0,SET] - 000008:[m#0,SET-m#0,SET] + 000007:[grandparent#3,SET-grandparent#3,SET] + 000008:[m#3,SET-m#3,SET] # Setup such that grandparent overlap limit is exceeded multiple times at the same user key ("b"). # Ensures the compaction output files are non-overlapping. @@ -275,23 +275,23 @@ L3 b.SET.0:v ---- 1: - 000004:[a#2,SET-c#2,SET] + 000004:[a#5,SET-c#5,SET] 2: - 000005:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000005:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] 3: - 000006:[b#2,SET-b#2,SET] - 000007:[b#1,SET-b#1,SET] - 000008:[b#0,SET-b#0,SET] + 000006:[b#5,SET-b#5,SET] + 000007:[b#4,SET-b#4,SET] + 000008:[b#3,SET-b#3,SET] compact a-c L1 ---- 2: - 000009:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] - 000010:[b#3,RANGEDEL-c#2,SET] + 000009:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] + 000010:[b#6,RANGEDEL-c#5,SET] 3: - 000006:[b#2,SET-b#2,SET] - 000007:[b#1,SET-b#1,SET] - 000008:[b#0,SET-b#0,SET] + 000006:[b#5,SET-b#5,SET] + 000007:[b#4,SET-b#4,SET] + 000008:[b#3,SET-b#3,SET] # Regression test for a bug where compaction would stop process range # tombstones for an input level upon finding an sstable in the input @@ -313,7 +313,7 @@ set z 1 compact a-z ---- 6: - 000005:[a#1,SET-z#5,SET] + 000005:[a#4,SET-z#8,SET] build ext1 set a 2 @@ -327,10 +327,10 @@ del-range c z ingest ext1 ext2 ---- 0.0: - 000006:[a#6,SET-a#6,SET] - 000007:[b#7,SET-z#72057594037927935,RANGEDEL] + 000006:[a#9,SET-a#9,SET] + 000007:[b#10,SET-z#72057594037927935,RANGEDEL] 6: - 000005:[a#1,SET-z#5,SET] + 000005:[a#4,SET-z#8,SET] iter first @@ -346,7 +346,7 @@ z:1 compact a-z ---- 6: - 000008:[a#0,SET-z#0,SET] + 000008:[a#3,SET-z#3,SET] iter first @@ -377,23 +377,23 @@ L3 b.SET.1:1 ---- 0.0: - 000004:[c#4,SET-c#4,SET] + 000004:[c#7,SET-c#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[a#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000006:[a#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000007:[b#1,SET-b#1,SET] + 000007:[b#4,SET-b#4,SET] compact a-e L1 ---- 0.0: - 000004:[c#4,SET-c#4,SET] + 000004:[c#7,SET-c#7,SET] 2: - 000008:[a#3,SET-b#72057594037927935,RANGEDEL] - 000009:[b#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,SET-b#72057594037927935,RANGEDEL] + 000009:[b#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000007:[b#1,SET-b#1,SET] + 000007:[b#4,SET-b#4,SET] # We should only see a:3 and c:4 at this point. @@ -435,18 +435,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -472,18 +472,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -513,18 +513,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#4,SET-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#7,SET-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#4,SET-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#7,SET-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -556,10 +556,10 @@ L3 d.SET.0:0 ---- 1: - 000004:[a#3,SET-e#72057594037927935,RANGEDEL] + 000004:[a#6,SET-e#72057594037927935,RANGEDEL] 3: - 000005:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] - 000006:[c#0,SET-d#0,SET] + 000005:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] + 000006:[c#3,SET-d#3,SET] iter last @@ -571,11 +571,11 @@ a:3 compact a-e L1 ---- 2: - 000007:[a#3,SET-c#72057594037927935,RANGEDEL] - 000008:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#6,SET-c#72057594037927935,RANGEDEL] + 000008:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000005:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] - 000006:[c#0,SET-d#0,SET] + 000005:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] + 000006:[c#3,SET-d#3,SET] iter last @@ -600,17 +600,17 @@ L3 c.RANGEDEL.2:d ---- 1: - 000004:[a#3,SET-e#72057594037927935,RANGEDEL] + 000004:[a#6,SET-e#72057594037927935,RANGEDEL] 3: - 000005:[c#2,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[c#5,RANGEDEL-d#72057594037927935,RANGEDEL] compact a-f L1 ---- 2: - 000006:[a#3,SET-c#72057594037927935,RANGEDEL] - 000007:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000006:[a#6,SET-c#72057594037927935,RANGEDEL] + 000007:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000005:[c#2,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[c#5,RANGEDEL-d#72057594037927935,RANGEDEL] # Test a scenario where we the last point key in an sstable has a # seqnum of 0, but there is another range tombstone later in the @@ -625,16 +625,16 @@ L3 b.SET.0:0 ---- 1: - 000004:[a#0,SET-d#72057594037927935,RANGEDEL] + 000004:[a#3,SET-d#72057594037927935,RANGEDEL] 3: - 000005:[b#0,SET-b#0,SET] + 000005:[b#3,SET-b#3,SET] compact a-e L1 ---- 2: - 000006:[a#0,SET-a#0,SET] + 000006:[a#3,SET-a#3,SET] 3: - 000005:[b#0,SET-b#0,SET] + 000005:[b#3,SET-b#3,SET] define target-file-sizes=(1, 1, 1, 1) L0 @@ -643,8 +643,8 @@ L0 a.SET.2:v ---- 0.0: - 000005:[a#2,SET-a#2,SET] - 000004:[b#1,SET-b#1,SET] + 000005:[a#5,SET-a#5,SET] + 000004:[b#4,SET-b#4,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=b ---- @@ -653,14 +653,14 @@ async-compact a-b L0 ---- manual compaction blocked until ongoing finished 1: - 000006:[a#0,SET-a#0,SET] - 000007:[b#0,SET-b#0,SET] + 000006:[a#3,SET-a#3,SET] + 000007:[b#3,SET-b#3,SET] compact a-b L1 ---- 2: - 000008:[a#0,SET-a#0,SET] - 000009:[b#0,SET-b#0,SET] + 000008:[a#3,SET-a#3,SET] + 000009:[b#3,SET-b#3,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=b ---- @@ -669,8 +669,8 @@ async-compact a-b L2 ---- manual compaction blocked until ongoing finished 3: - 000010:[a#0,SET-a#0,SET] - 000011:[b#0,SET-b#0,SET] + 000010:[a#3,SET-a#3,SET] + 000011:[b#3,SET-b#3,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=b ---- @@ -682,8 +682,8 @@ async-compact a-b L3 ---- manual compaction did not block for ongoing 4: - 000012:[a#0,SET-a#0,SET] - 000013:[b#0,SET-b#0,SET] + 000012:[a#3,SET-a#3,SET] + 000013:[b#3,SET-b#3,SET] remove-ongoing-compaction ---- @@ -695,8 +695,8 @@ async-compact a-b L4 ---- manual compaction blocked until ongoing finished 5: - 000014:[a#0,SET-a#0,SET] - 000015:[b#0,SET-b#0,SET] + 000014:[a#3,SET-a#3,SET] + 000015:[b#3,SET-b#3,SET] # Test of a scenario where consecutive elided range tombstones and grandparent # boundaries could result in an invariant violation in the rangedel fragmenter. @@ -723,25 +723,25 @@ L3 k.SET.1:foo ---- 1: - 000004:[a#4,RANGEDEL-f#72057594037927935,RANGEDEL] - 000005:[g#6,RANGEDEL-j#72057594037927935,RANGEDEL] - 000006:[k#5,RANGEDEL-q#72057594037927935,RANGEDEL] + 000004:[a#7,RANGEDEL-f#72057594037927935,RANGEDEL] + 000005:[g#9,RANGEDEL-j#72057594037927935,RANGEDEL] + 000006:[k#8,RANGEDEL-q#72057594037927935,RANGEDEL] 2: - 000007:[a#2,SET-a#2,SET] + 000007:[a#5,SET-a#5,SET] 3: - 000008:[a#1,SET-c#1,SET] - 000009:[ff#1,SET-ff#1,SET] - 000010:[k#1,SET-k#1,SET] + 000008:[a#4,SET-c#4,SET] + 000009:[ff#4,SET-ff#4,SET] + 000010:[k#4,SET-k#4,SET] compact a-q L1 ---- 2: - 000011:[a#4,RANGEDEL-d#72057594037927935,RANGEDEL] - 000012:[k#5,RANGEDEL-m#72057594037927935,RANGEDEL] + 000011:[a#7,RANGEDEL-d#72057594037927935,RANGEDEL] + 000012:[k#8,RANGEDEL-m#72057594037927935,RANGEDEL] 3: - 000008:[a#1,SET-c#1,SET] - 000009:[ff#1,SET-ff#1,SET] - 000010:[k#1,SET-k#1,SET] + 000008:[a#4,SET-c#4,SET] + 000009:[ff#4,SET-ff#4,SET] + 000010:[k#4,SET-k#4,SET] # Test a case where a new output file is started, there are no previous output # files, there are no additional keys (key = nil) and the rangedel fragmenter @@ -757,18 +757,18 @@ L3 q.SET.6:6 ---- 1: - 000004:[a#10,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[a#13,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[g#7,RANGEDEL-h#72057594037927935,RANGEDEL] + 000005:[g#10,RANGEDEL-h#72057594037927935,RANGEDEL] 3: - 000006:[q#6,SET-q#6,SET] + 000006:[q#9,SET-q#9,SET] compact a-r L1 ---- 2: - 000007:[q#8,RANGEDEL-r#72057594037927935,RANGEDEL] + 000007:[q#11,RANGEDEL-r#72057594037927935,RANGEDEL] 3: - 000006:[q#6,SET-q#6,SET] + 000006:[q#9,SET-q#9,SET] define target-file-sizes=(100, 100, 100) L1 @@ -789,27 +789,27 @@ L4 f.SET.0:0 ---- 1: - 000004:[a#10,RANGEDEL-j#10,SET] + 000004:[a#13,RANGEDEL-j#13,SET] 2: - 000005:[f#7,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[f#10,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[c#6,SET-c#6,SET] - 000007:[c#5,SET-c#5,SET] - 000008:[c#4,SET-c#4,SET] + 000006:[c#9,SET-c#9,SET] + 000007:[c#8,SET-c#8,SET] + 000008:[c#7,SET-c#7,SET] 4: - 000009:[a#0,SET-f#0,SET] + 000009:[a#3,SET-f#3,SET] compact a-r L1 ---- 2: - 000010:[a#10,RANGEDEL-b#0,SET] - 000011:[d#0,RANGEDEL-j#10,SET] + 000010:[a#13,RANGEDEL-b#3,SET] + 000011:[d#3,RANGEDEL-j#13,SET] 3: - 000006:[c#6,SET-c#6,SET] - 000007:[c#5,SET-c#5,SET] - 000008:[c#4,SET-c#4,SET] + 000006:[c#9,SET-c#9,SET] + 000007:[c#8,SET-c#8,SET] + 000008:[c#7,SET-c#7,SET] 4: - 000009:[a#0,SET-f#0,SET] + 000009:[a#3,SET-f#3,SET] # Test a snapshot that separates a range deletion from all the data that it # deletes. Ensure that we respect the target-file-size and split into multiple @@ -825,16 +825,16 @@ L2 d.SET.0:foo ---- 1: - 000004:[a#15,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#18,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000005:[c#0,SET-d#0,SET] + 000005:[c#3,SET-d#3,SET] compact a-z L1 ---- 2: - 000006:[a#15,RANGEDEL-c#72057594037927935,RANGEDEL] - 000007:[c#15,RANGEDEL-d#72057594037927935,RANGEDEL] - 000008:[d#15,RANGEDEL-z#72057594037927935,RANGEDEL] + 000006:[a#18,RANGEDEL-c#72057594037927935,RANGEDEL] + 000007:[c#18,RANGEDEL-d#72057594037927935,RANGEDEL] + 000008:[d#18,RANGEDEL-z#72057594037927935,RANGEDEL] # Test an interaction between a range deletion that will be elided with # output splitting. Ensure that the output is still split (previous versions @@ -851,15 +851,15 @@ L2 d.SET.0:foo ---- 1: - 000004:[a#10,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#13,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000005:[c#0,SET-d#0,SET] + 000005:[c#3,SET-d#3,SET] compact a-z L1 ---- 2: - 000006:[b#0,SET-b#0,SET] - 000007:[c#0,SET-c#0,SET] + 000006:[b#3,SET-b#3,SET] + 000007:[c#3,SET-c#3,SET] define target-file-sizes=(1, 1, 1, 1) L0 @@ -874,12 +874,12 @@ L3 c.SET.0:v ---- 0.0: - 000004:[a#3,SET-b#2,SET] + 000004:[a#6,SET-b#5,SET] 2: - 000005:[a#1,SET-a#1,SET] + 000005:[a#4,SET-a#4,SET] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-c#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-c#3,SET] set-concurrent-compactions num=3 ---- @@ -887,9 +887,9 @@ set-concurrent-compactions num=3 compact a-c parallel hide-file-num ---- 4: - [a#0,SET-a#0,SET] - [b#0,SET-b#0,SET] - [c#0,SET-c#0,SET] + [a#3,SET-a#3,SET] + [b#3,SET-b#3,SET] + [c#3,SET-c#3,SET] define target-file-sizes=(1, 1, 1, 1) L0 @@ -912,16 +912,16 @@ L3 c.SET.0:v ---- 0.1: - 000004:[a#3,SET-b#2,SET] + 000004:[a#6,SET-b#5,SET] 0.0: - 000005:[a#2,SET-c#2,SET] + 000005:[a#5,SET-c#5,SET] 2: - 000006:[a#1,SET-b#1,SET] - 000007:[c#1,SET-c#1,SET] - 000008:[d#0,SET-d#0,SET] + 000006:[a#4,SET-b#4,SET] + 000007:[c#4,SET-c#4,SET] + 000008:[d#3,SET-d#3,SET] 3: - 000009:[a#0,SET-b#0,SET] - 000010:[c#0,SET-c#0,SET] + 000009:[a#3,SET-b#3,SET] + 000010:[c#3,SET-c#3,SET] set-concurrent-compactions num=2 ---- @@ -929,16 +929,16 @@ set-concurrent-compactions num=2 compact a-c L0 parallel ---- 1: - 000011:[a#3,SET-a#3,SET] - 000012:[b#2,SET-b#2,SET] - 000013:[c#2,SET-c#2,SET] + 000011:[a#6,SET-a#6,SET] + 000012:[b#5,SET-b#5,SET] + 000013:[c#5,SET-c#5,SET] 2: - 000006:[a#1,SET-b#1,SET] - 000007:[c#1,SET-c#1,SET] - 000008:[d#0,SET-d#0,SET] + 000006:[a#4,SET-b#4,SET] + 000007:[c#4,SET-c#4,SET] + 000008:[d#3,SET-d#3,SET] 3: - 000009:[a#0,SET-b#0,SET] - 000010:[c#0,SET-c#0,SET] + 000009:[a#3,SET-b#3,SET] + 000010:[c#3,SET-c#3,SET] add-ongoing-compaction startLevel=3 outputLevel=4 start=a end=d ---- @@ -953,13 +953,13 @@ async-compact a-d L1 parallel ---- manual compaction did not block for ongoing 2: - 000014:[a#3,SET-a#3,SET] - 000015:[b#2,SET-b#2,SET] - 000016:[c#2,SET-c#2,SET] - 000008:[d#0,SET-d#0,SET] + 000014:[a#6,SET-a#6,SET] + 000015:[b#5,SET-b#5,SET] + 000016:[c#5,SET-c#5,SET] + 000008:[d#3,SET-d#3,SET] 3: - 000009:[a#0,SET-b#0,SET] - 000010:[c#0,SET-c#0,SET] + 000009:[a#3,SET-b#3,SET] + 000010:[c#3,SET-c#3,SET] remove-ongoing-compaction ---- @@ -970,10 +970,10 @@ set-concurrent-compactions num=3 compact a-d parallel hide-file-num ---- 4: - [a#0,SET-a#0,SET] - [b#0,SET-b#0,SET] - [c#0,SET-c#0,SET] - [d#0,SET-d#0,SET] + [a#3,SET-a#3,SET] + [b#3,SET-b#3,SET] + [c#3,SET-c#3,SET] + [d#3,SET-d#3,SET] # Create a contrived compaction that forces point key and rangedel iterators # to stay in sync to emit a correct view of visible and deleted keys. Note that @@ -995,14 +995,14 @@ L3 start=tmgc.RANGEDEL.383 end=tvsalezade.RANGEDEL.72057594037927935 tmgc.RANGEDEL.356:tvsalezade ---- 3: - 000004:[tmgc#391,MERGE-tmgc#391,MERGE] - 000005:[tmgc#384,MERGE-tmgc#384,MERGE] - 000006:[tmgc#383,RANGEDEL-tvsalezade#72057594037927935,RANGEDEL] + 000004:[tmgc#394,MERGE-tmgc#394,MERGE] + 000005:[tmgc#387,MERGE-tmgc#387,MERGE] + 000006:[tmgc#386,RANGEDEL-tvsalezade#72057594037927935,RANGEDEL] compact a-z L3 ---- 4: - 000007:[tmgc#391,MERGE-tmgc#384,MERGE] + 000007:[tmgc#394,MERGE-tmgc#387,MERGE] # baz should NOT be visible in the value. diff --git a/testdata/manual_compaction_range_keys b/testdata/manual_compaction_range_keys index b88be723f81..2ee9f51d486 100644 --- a/testdata/manual_compaction_range_keys +++ b/testdata/manual_compaction_range_keys @@ -15,34 +15,34 @@ L3 c.SET.0:v ---- 0.0: - 000004:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-a#3,SET] ranges:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000004:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#6,SET-a#6,SET] ranges:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] 2: - 000005:[a#2,SET-a#2,SET] points:[a#2,SET-a#2,SET] + 000005:[a#5,SET-a#5,SET] points:[a#5,SET-a#5,SET] 3: - 000006:[a#0,SET-c#72057594037927935,RANGEKEYSET] points:[a#0,SET-b#0,SET] ranges:[b#1,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] - 000007:[c#0,SET-c#0,SET] points:[c#0,SET-c#0,SET] + 000006:[a#3,SET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-b#3,SET] ranges:[b#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000007:[c#3,SET-c#3,SET] points:[c#3,SET-c#3,SET] compact a-d L0 ---- 1: - 000008:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-a#3,SET] ranges:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000008:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#6,SET-a#6,SET] ranges:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] 2: - 000005:[a#2,SET-a#2,SET] points:[a#2,SET-a#2,SET] + 000005:[a#5,SET-a#5,SET] points:[a#5,SET-a#5,SET] 3: - 000006:[a#0,SET-c#72057594037927935,RANGEKEYSET] points:[a#0,SET-b#0,SET] ranges:[b#1,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] - 000007:[c#0,SET-c#0,SET] points:[c#0,SET-c#0,SET] + 000006:[a#3,SET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-b#3,SET] ranges:[b#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000007:[c#3,SET-c#3,SET] points:[c#3,SET-c#3,SET] compact a-d L1 ---- 2: - 000009:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-a#3,SET] ranges:[a#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000009:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[a#6,SET-a#6,SET] ranges:[a#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] 3: - 000006:[a#0,SET-c#72057594037927935,RANGEKEYSET] points:[a#0,SET-b#0,SET] ranges:[b#1,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] - 000007:[c#0,SET-c#0,SET] points:[c#0,SET-c#0,SET] + 000006:[a#3,SET-c#72057594037927935,RANGEKEYSET] points:[a#3,SET-b#3,SET] ranges:[b#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000007:[c#3,SET-c#3,SET] points:[c#3,SET-c#3,SET] compact a-d L2 ---- 3: - 000010:[a#4,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] points:[a#0,SET-a#0,SET] ranges:[a#4,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] - 000011:[b#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[b#0,SET-b#0,SET] ranges:[b#4,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] - 000007:[c#0,SET-c#0,SET] points:[c#0,SET-c#0,SET] + 000010:[a#7,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] points:[a#3,SET-a#3,SET] ranges:[a#7,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] + 000011:[b#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] points:[b#3,SET-b#3,SET] ranges:[b#7,RANGEKEYSET-c#72057594037927935,RANGEKEYSET] + 000007:[c#3,SET-c#3,SET] points:[c#3,SET-c#3,SET] diff --git a/testdata/manual_compaction_set_with_del b/testdata/manual_compaction_set_with_del index 3e5d2084518..1841850ee33 100644 --- a/testdata/manual_compaction_set_with_del +++ b/testdata/manual_compaction_set_with_del @@ -6,7 +6,7 @@ set b 2 compact a-b ---- 6: - 000005:[a#1,SET-b#2,SET] + 000005:[a#4,SET-b#5,SET] batch set c 3 @@ -16,8 +16,8 @@ set d 4 compact c-d ---- 6: - 000005:[a#1,SET-b#2,SET] - 000007:[c#3,SET-d#4,SET] + 000005:[a#4,SET-b#5,SET] + 000007:[c#6,SET-d#7,SET] batch set b 5 @@ -27,7 +27,7 @@ set c 6 compact a-d ---- 6: - 000010:[a#0,SET-d#0,SET] + 000010:[a#3,SET-d#3,SET] # This also tests flushing a memtable that only contains range # deletions. @@ -48,14 +48,14 @@ L0 a.SET.2:v ---- 0.0: - 000005:[a#2,SET-a#2,SET] - 000004:[b#1,SET-b#1,SET] + 000005:[a#5,SET-a#5,SET] + 000004:[b#4,SET-b#4,SET] compact a-b ---- 1: - 000006:[a#0,SET-a#0,SET] - 000007:[b#0,SET-b#0,SET] + 000006:[a#3,SET-a#3,SET] + 000007:[b#3,SET-b#3,SET] # A range tombstone extends past the grandparent file boundary used to limit the # size of future compactions. Verify the range tombstone is split at that file @@ -74,12 +74,12 @@ L3 d.SET.0:v ---- 1: - 000004:[a#3,SET-a#3,SET] + 000004:[a#6,SET-a#6,SET] 2: - 000005:[a#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] wait-pending-table-stats 000005 @@ -88,16 +88,16 @@ num-entries: 1 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 1552 +range-deletions-bytes-estimate: 1554 compact a-e L1 ---- 2: - 000008:[a#3,SETWITHDEL-c#72057594037927935,RANGEDEL] - 000009:[c#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,SETWITHDEL-c#72057594037927935,RANGEDEL] + 000009:[c#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] wait-pending-table-stats 000008 @@ -106,7 +106,7 @@ num-entries: 2 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 776 +range-deletions-bytes-estimate: 777 # Same as above, except range tombstone covers multiple grandparent file boundaries. @@ -129,27 +129,27 @@ L3 g.SET.0:v ---- 1: - 000004:[a#3,SET-a#3,SET] + 000004:[a#6,SET-a#6,SET] 2: - 000005:[a#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] - 000009:[f#0,SET-g#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] + 000009:[f#3,SET-g#3,SET] compact a-e L1 ---- 2: - 000010:[a#3,SETWITHDEL-c#72057594037927935,RANGEDEL] - 000011:[c#2,RANGEDEL-e#72057594037927935,RANGEDEL] - 000012:[e#2,RANGEDEL-f#72057594037927935,RANGEDEL] - 000013:[f#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000010:[a#6,SETWITHDEL-c#72057594037927935,RANGEDEL] + 000011:[c#5,RANGEDEL-e#72057594037927935,RANGEDEL] + 000012:[e#5,RANGEDEL-f#72057594037927935,RANGEDEL] + 000013:[f#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] - 000009:[f#0,SET-g#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] + 000009:[f#3,SET-g#3,SET] # A range tombstone covers multiple grandparent file boundaries between point keys, # rather than after all point keys. @@ -171,23 +171,23 @@ L3 f.SET.1:v ---- 1: - 000004:[a#3,SET-h#3,SET] + 000004:[a#6,SET-h#6,SET] 2: - 000005:[a#2,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] compact a-e L1 ---- 2: - 000009:[a#3,SETWITHDEL-c#72057594037927935,RANGEDEL] - 000010:[c#2,RANGEDEL-h#3,SET] + 000009:[a#6,SETWITHDEL-c#72057594037927935,RANGEDEL] + 000010:[c#5,RANGEDEL-h#6,SET] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] - 000008:[e#0,SET-f#1,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] + 000008:[e#3,SET-f#4,SET] # A range tombstone is the first and only item output by a compaction, and it # extends past the grandparent file boundary used to limit the size of future @@ -206,21 +206,21 @@ L3 d.SET.0:v ---- 1: - 000004:[a#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#6,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000005:[a#2,SET-a#2,SET] + 000005:[a#5,SET-a#5,SET] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] compact a-e L1 ---- 2: - 000008:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] - 000009:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] + 000009:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[a#0,SET-b#0,SET] - 000007:[c#0,SET-d#0,SET] + 000006:[a#3,SET-b#3,SET] + 000007:[c#3,SET-d#3,SET] # An elided range tombstone is the first item encountered by a compaction, # and the grandparent limit set by it extends to the next item, also a range @@ -241,22 +241,22 @@ L3 m.SET.0:v ---- 1: - 000004:[a#4,RANGEDEL-d#72057594037927935,RANGEDEL] - 000005:[grandparent#2,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#7,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[grandparent#5,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000006:[grandparent#1,SET-grandparent#1,SET] + 000006:[grandparent#4,SET-grandparent#4,SET] 3: - 000007:[grandparent#0,SET-grandparent#0,SET] - 000008:[m#0,SET-m#0,SET] + 000007:[grandparent#3,SET-grandparent#3,SET] + 000008:[m#3,SET-m#3,SET] compact a-h L1 ---- 2: - 000009:[grandparent#2,RANGEDEL-m#72057594037927935,RANGEDEL] - 000010:[m#2,RANGEDEL-z#72057594037927935,RANGEDEL] + 000009:[grandparent#5,RANGEDEL-m#72057594037927935,RANGEDEL] + 000010:[m#5,RANGEDEL-z#72057594037927935,RANGEDEL] 3: - 000007:[grandparent#0,SET-grandparent#0,SET] - 000008:[m#0,SET-m#0,SET] + 000007:[grandparent#3,SET-grandparent#3,SET] + 000008:[m#3,SET-m#3,SET] # Setup such that grandparent overlap limit is exceeded multiple times at the same user key ("b"). # Ensures the compaction output files are non-overlapping. @@ -275,23 +275,23 @@ L3 b.SET.0:v ---- 1: - 000004:[a#2,SET-c#2,SET] + 000004:[a#5,SET-c#5,SET] 2: - 000005:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000005:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] 3: - 000006:[b#2,SET-b#2,SET] - 000007:[b#1,SET-b#1,SET] - 000008:[b#0,SET-b#0,SET] + 000006:[b#5,SET-b#5,SET] + 000007:[b#4,SET-b#4,SET] + 000008:[b#3,SET-b#3,SET] compact a-c L1 ---- 2: - 000009:[a#3,RANGEDEL-b#72057594037927935,RANGEDEL] - 000010:[b#3,RANGEDEL-c#2,SET] + 000009:[a#6,RANGEDEL-b#72057594037927935,RANGEDEL] + 000010:[b#6,RANGEDEL-c#5,SET] 3: - 000006:[b#2,SET-b#2,SET] - 000007:[b#1,SET-b#1,SET] - 000008:[b#0,SET-b#0,SET] + 000006:[b#5,SET-b#5,SET] + 000007:[b#4,SET-b#4,SET] + 000008:[b#3,SET-b#3,SET] # Regression test for a bug where compaction would stop process range # tombstones for an input level upon finding an sstable in the input @@ -313,7 +313,7 @@ set z 1 compact a-z ---- 6: - 000005:[a#1,SET-z#5,SET] + 000005:[a#4,SET-z#8,SET] build ext1 set a 2 @@ -327,10 +327,10 @@ del-range c z ingest ext1 ext2 ---- 0.0: - 000006:[a#6,SET-a#6,SET] - 000007:[b#7,SET-z#72057594037927935,RANGEDEL] + 000006:[a#9,SET-a#9,SET] + 000007:[b#10,SET-z#72057594037927935,RANGEDEL] 6: - 000005:[a#1,SET-z#5,SET] + 000005:[a#4,SET-z#8,SET] iter first @@ -346,7 +346,7 @@ z:1 compact a-z ---- 6: - 000008:[a#0,SET-z#0,SET] + 000008:[a#3,SET-z#3,SET] iter first @@ -377,23 +377,23 @@ L3 b.SET.1:1 ---- 0.0: - 000004:[c#4,SET-c#4,SET] + 000004:[c#7,SET-c#7,SET] 1: - 000005:[a#3,SET-a#3,SET] + 000005:[a#6,SET-a#6,SET] 2: - 000006:[a#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000006:[a#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000007:[b#1,SET-b#1,SET] + 000007:[b#4,SET-b#4,SET] compact a-e L1 ---- 0.0: - 000004:[c#4,SET-c#4,SET] + 000004:[c#7,SET-c#7,SET] 2: - 000008:[a#3,SETWITHDEL-b#72057594037927935,RANGEDEL] - 000009:[b#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#6,SETWITHDEL-b#72057594037927935,RANGEDEL] + 000009:[b#5,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000007:[b#1,SET-b#1,SET] + 000007:[b#4,SET-b#4,SET] # We should only see a:3 and c:4 at this point. @@ -435,18 +435,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -472,18 +472,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -513,18 +513,18 @@ L3 b.SET.1:1 ---- 1: - 000004:[a#4,SET-a#4,SET] - 000005:[b#4,SET-e#72057594037927935,RANGEDEL] + 000004:[a#7,SET-a#7,SET] + 000005:[b#7,SET-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] compact a-e L1 ---- 2: - 000007:[a#4,SET-a#4,SET] - 000008:[b#4,SET-e#72057594037927935,RANGEDEL] + 000007:[a#7,SET-a#7,SET] + 000008:[b#7,SET-e#72057594037927935,RANGEDEL] 3: - 000006:[b#1,SET-b#1,SET] + 000006:[b#4,SET-b#4,SET] iter first @@ -556,10 +556,10 @@ L3 d.SET.0:0 ---- 1: - 000004:[a#3,SET-e#72057594037927935,RANGEDEL] + 000004:[a#6,SET-e#72057594037927935,RANGEDEL] 3: - 000005:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] - 000006:[c#0,SET-d#0,SET] + 000005:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] + 000006:[c#3,SET-d#3,SET] iter last @@ -571,11 +571,11 @@ a:3 compact a-e L1 ---- 2: - 000007:[a#3,SET-c#72057594037927935,RANGEDEL] - 000008:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[a#6,SET-c#72057594037927935,RANGEDEL] + 000008:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000005:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] - 000006:[c#0,SET-d#0,SET] + 000005:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] + 000006:[c#3,SET-d#3,SET] iter last @@ -600,17 +600,17 @@ L3 c.RANGEDEL.2:d ---- 1: - 000004:[a#3,SET-e#72057594037927935,RANGEDEL] + 000004:[a#6,SET-e#72057594037927935,RANGEDEL] 3: - 000005:[c#2,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[c#5,RANGEDEL-d#72057594037927935,RANGEDEL] compact a-f L1 ---- 2: - 000006:[a#3,SET-c#72057594037927935,RANGEDEL] - 000007:[c#3,RANGEDEL-e#72057594037927935,RANGEDEL] + 000006:[a#6,SET-c#72057594037927935,RANGEDEL] + 000007:[c#6,RANGEDEL-e#72057594037927935,RANGEDEL] 3: - 000005:[c#2,RANGEDEL-d#72057594037927935,RANGEDEL] + 000005:[c#5,RANGEDEL-d#72057594037927935,RANGEDEL] # Test a scenario where we the last point key in an sstable has a @@ -626,16 +626,16 @@ L3 b.SET.0:0 ---- 1: - 000004:[a#0,SET-d#72057594037927935,RANGEDEL] + 000004:[a#3,SET-d#72057594037927935,RANGEDEL] 3: - 000005:[b#0,SET-b#0,SET] + 000005:[b#3,SET-b#3,SET] compact a-e L1 ---- 2: - 000006:[a#0,SET-a#0,SET] + 000006:[a#3,SET-a#3,SET] 3: - 000005:[b#0,SET-b#0,SET] + 000005:[b#3,SET-b#3,SET] define target-file-sizes=(1, 1, 1, 1) L0 @@ -644,8 +644,8 @@ L0 a.SET.2:v ---- 0.0: - 000005:[a#2,SET-a#2,SET] - 000004:[b#1,SET-b#1,SET] + 000005:[a#5,SET-a#5,SET] + 000004:[b#4,SET-b#4,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=z ---- @@ -654,14 +654,14 @@ async-compact a-b L0 ---- manual compaction blocked until ongoing finished 1: - 000006:[a#0,SET-a#0,SET] - 000007:[b#0,SET-b#0,SET] + 000006:[a#3,SET-a#3,SET] + 000007:[b#3,SET-b#3,SET] compact a-b L1 ---- 2: - 000008:[a#0,SET-a#0,SET] - 000009:[b#0,SET-b#0,SET] + 000008:[a#3,SET-a#3,SET] + 000009:[b#3,SET-b#3,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=z ---- @@ -670,8 +670,8 @@ async-compact a-b L2 ---- manual compaction blocked until ongoing finished 3: - 000010:[a#0,SET-a#0,SET] - 000011:[b#0,SET-b#0,SET] + 000010:[a#3,SET-a#3,SET] + 000011:[b#3,SET-b#3,SET] add-ongoing-compaction startLevel=0 outputLevel=1 start=a end=z ---- @@ -683,8 +683,8 @@ async-compact a-b L3 ---- manual compaction did not block for ongoing 4: - 000012:[a#0,SET-a#0,SET] - 000013:[b#0,SET-b#0,SET] + 000012:[a#3,SET-a#3,SET] + 000013:[b#3,SET-b#3,SET] remove-ongoing-compaction ---- @@ -696,8 +696,8 @@ async-compact a-b L4 ---- manual compaction blocked until ongoing finished 5: - 000014:[a#0,SET-a#0,SET] - 000015:[b#0,SET-b#0,SET] + 000014:[a#3,SET-a#3,SET] + 000015:[b#3,SET-b#3,SET] # Test of a scenario where consecutive elided range tombstones and grandparent # boundaries could result in an invariant violation in the rangedel fragmenter. @@ -724,25 +724,25 @@ L3 k.SET.1:foo ---- 1: - 000004:[a#4,RANGEDEL-f#72057594037927935,RANGEDEL] - 000005:[g#6,RANGEDEL-j#72057594037927935,RANGEDEL] - 000006:[k#5,RANGEDEL-q#72057594037927935,RANGEDEL] + 000004:[a#7,RANGEDEL-f#72057594037927935,RANGEDEL] + 000005:[g#9,RANGEDEL-j#72057594037927935,RANGEDEL] + 000006:[k#8,RANGEDEL-q#72057594037927935,RANGEDEL] 2: - 000007:[a#2,SET-a#2,SET] + 000007:[a#5,SET-a#5,SET] 3: - 000008:[a#1,SET-c#1,SET] - 000009:[ff#1,SET-ff#1,SET] - 000010:[k#1,SET-k#1,SET] + 000008:[a#4,SET-c#4,SET] + 000009:[ff#4,SET-ff#4,SET] + 000010:[k#4,SET-k#4,SET] compact a-q L1 ---- 2: - 000011:[a#4,RANGEDEL-d#72057594037927935,RANGEDEL] - 000012:[k#5,RANGEDEL-m#72057594037927935,RANGEDEL] + 000011:[a#7,RANGEDEL-d#72057594037927935,RANGEDEL] + 000012:[k#8,RANGEDEL-m#72057594037927935,RANGEDEL] 3: - 000008:[a#1,SET-c#1,SET] - 000009:[ff#1,SET-ff#1,SET] - 000010:[k#1,SET-k#1,SET] + 000008:[a#4,SET-c#4,SET] + 000009:[ff#4,SET-ff#4,SET] + 000010:[k#4,SET-k#4,SET] # Test a case where a new output file is started, there are no previous output # files, there are no additional keys (key = nil) and the rangedel fragmenter @@ -758,18 +758,18 @@ L3 q.SET.6:6 ---- 1: - 000004:[a#10,RANGEDEL-r#72057594037927935,RANGEDEL] + 000004:[a#13,RANGEDEL-r#72057594037927935,RANGEDEL] 2: - 000005:[g#7,RANGEDEL-h#72057594037927935,RANGEDEL] + 000005:[g#10,RANGEDEL-h#72057594037927935,RANGEDEL] 3: - 000006:[q#6,SET-q#6,SET] + 000006:[q#9,SET-q#9,SET] compact a-r L1 ---- 2: - 000007:[q#8,RANGEDEL-r#72057594037927935,RANGEDEL] + 000007:[q#11,RANGEDEL-r#72057594037927935,RANGEDEL] 3: - 000006:[q#6,SET-q#6,SET] + 000006:[q#9,SET-q#9,SET] define target-file-sizes=(100, 100, 100) L1 @@ -790,27 +790,27 @@ L4 f.SET.0:0 ---- 1: - 000004:[a#10,RANGEDEL-j#10,SET] + 000004:[a#13,RANGEDEL-j#13,SET] 2: - 000005:[f#7,RANGEDEL-g#72057594037927935,RANGEDEL] + 000005:[f#10,RANGEDEL-g#72057594037927935,RANGEDEL] 3: - 000006:[c#6,SET-c#6,SET] - 000007:[c#5,SET-c#5,SET] - 000008:[c#4,SET-c#4,SET] + 000006:[c#9,SET-c#9,SET] + 000007:[c#8,SET-c#8,SET] + 000008:[c#7,SET-c#7,SET] 4: - 000009:[a#0,SET-f#0,SET] + 000009:[a#3,SET-f#3,SET] compact a-r L1 ---- 2: - 000010:[a#10,RANGEDEL-b#0,SET] - 000011:[d#0,RANGEDEL-j#10,SET] + 000010:[a#13,RANGEDEL-b#3,SET] + 000011:[d#3,RANGEDEL-j#13,SET] 3: - 000006:[c#6,SET-c#6,SET] - 000007:[c#5,SET-c#5,SET] - 000008:[c#4,SET-c#4,SET] + 000006:[c#9,SET-c#9,SET] + 000007:[c#8,SET-c#8,SET] + 000008:[c#7,SET-c#7,SET] 4: - 000009:[a#0,SET-f#0,SET] + 000009:[a#3,SET-f#3,SET] # Test a snapshot that separates a range deletion from all the data that it # deletes. Ensure that we respect the target-file-size and split into multiple @@ -826,16 +826,16 @@ L2 d.SET.0:foo ---- 1: - 000004:[a#15,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#18,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000005:[c#0,SET-d#0,SET] + 000005:[c#3,SET-d#3,SET] compact a-z L1 ---- 2: - 000006:[a#15,RANGEDEL-c#72057594037927935,RANGEDEL] - 000007:[c#15,RANGEDEL-d#72057594037927935,RANGEDEL] - 000008:[d#15,RANGEDEL-z#72057594037927935,RANGEDEL] + 000006:[a#18,RANGEDEL-c#72057594037927935,RANGEDEL] + 000007:[c#18,RANGEDEL-d#72057594037927935,RANGEDEL] + 000008:[d#18,RANGEDEL-z#72057594037927935,RANGEDEL] # Test an interaction between a range deletion that will be elided with # output splitting. Ensure that the output is still split (previous versions @@ -852,12 +852,12 @@ L2 d.SET.0:foo ---- 1: - 000004:[a#10,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#13,RANGEDEL-z#72057594037927935,RANGEDEL] 2: - 000005:[c#0,SET-d#0,SET] + 000005:[c#3,SET-d#3,SET] compact a-z L1 ---- 2: - 000006:[b#0,SET-b#0,SET] - 000007:[c#0,SET-c#0,SET] + 000006:[b#3,SET-b#3,SET] + 000007:[c#3,SET-c#3,SET] diff --git a/testdata/manual_flush b/testdata/manual_flush index dab975166b8..8e8a21d9248 100644 --- a/testdata/manual_flush +++ b/testdata/manual_flush @@ -7,7 +7,7 @@ set b 2 flush ---- 0.0: - 000005:[a#1,SET-b#2,SET] + 000005:[a#4,SET-b#5,SET] reset ---- @@ -22,7 +22,7 @@ del b flush ---- 0.0: - 000005:[a#3,DEL-b#4,DEL] + 000005:[a#6,DEL-b#7,DEL] batch set a 3 @@ -32,9 +32,9 @@ set a 3 flush ---- 0.1: - 000007:[a#5,SET-a#5,SET] + 000007:[a#8,SET-a#8,SET] 0.0: - 000005:[a#3,DEL-b#4,DEL] + 000005:[a#6,DEL-b#7,DEL] batch set c 4 @@ -44,10 +44,10 @@ set c 4 flush ---- 0.1: - 000007:[a#5,SET-a#5,SET] + 000007:[a#8,SET-a#8,SET] 0.0: - 000005:[a#3,DEL-b#4,DEL] - 000009:[c#6,SET-c#6,SET] + 000005:[a#6,DEL-b#7,DEL] + 000009:[c#9,SET-c#9,SET] reset ---- @@ -61,7 +61,7 @@ del-range a c flush ---- 0.0: - 000005:[a#3,RANGEDEL-c#72057594037927935,RANGEDEL] + 000005:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] reset ---- @@ -74,7 +74,7 @@ set b 2 async-flush ---- 0.0: - 000005:[a#1,SET-b#2,SET] + 000005:[a#4,SET-b#5,SET] # Test that synchronous flushes can happen even when a cleaning turn is held. reset @@ -91,7 +91,7 @@ set b 2 flush ---- 0.0: - 000005:[a#1,SET-b#2,SET] + 000005:[a#4,SET-b#5,SET] release-cleaning-turn ---- diff --git a/testdata/marked_for_compaction b/testdata/marked_for_compaction index 5464a24aa83..4474676bcbb 100644 --- a/testdata/marked_for_compaction +++ b/testdata/marked_for_compaction @@ -6,9 +6,9 @@ L1 d.SET.0:foo ---- 0.0: - 000004:[c#11,SET-c#11,SET] points:[c#11,SET-c#11,SET] + 000004:[c#14,SET-c#14,SET] points:[c#14,SET-c#14,SET] 1: - 000005:[c#0,SET-d#0,SET] points:[c#0,SET-d#0,SET] + 000005:[c#3,SET-d#3,SET] points:[c#3,SET-d#3,SET] mark-for-compaction file=000005 ---- @@ -20,9 +20,9 @@ marked L0.000004 maybe-compact ---- -[JOB 100] compacted(rewrite) L1 [000005] (779 B) + L1 [] (0 B) -> L1 [000006] (779 B), in 1.0s (2.0s total), output rate 779 B/s +[JOB 100] compacted(rewrite) L1 [000005] (780 B) + L1 [] (0 B) -> L1 [000006] (780 B), in 1.0s (2.0s total), output rate 780 B/s [JOB 100] compacted(rewrite) L0 [000004] (773 B) + L0 [] (0 B) -> L0 [000007] (773 B), in 1.0s (2.0s total), output rate 773 B/s 0.0: - 000007:[c#11,SET-c#11,SET] points:[c#11,SET-c#11,SET] + 000007:[c#14,SET-c#14,SET] points:[c#14,SET-c#14,SET] 1: - 000006:[c#0,SET-d#0,SET] points:[c#0,SET-d#0,SET] + 000006:[c#3,SET-d#3,SET] points:[c#3,SET-d#3,SET] diff --git a/testdata/metrics b/testdata/metrics index 49e95e11e7d..dba6bbab3da 100644 --- a/testdata/metrics +++ b/testdata/metrics @@ -8,7 +8,7 @@ iter-new a flush ---- 0.0: - 000005:[a#1,SET-a#1,SET] + 000005:[a#4,SET-a#4,SET] # iter b references both a memtable and sstable 5. @@ -34,7 +34,7 @@ compact 0 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 1 256 K ztbl 0 0 B bcache 4 698 B 0.0% (score == hit-rate) - tcache 1 704 B 0.0% (score == hit-rate) + tcache 1 720 B 0.0% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 1 filter - - 0.0% (score == utility) @@ -50,8 +50,8 @@ set b 2 flush ---- 0.0: - 000005:[a#1,SET-a#1,SET] - 000007:[b#2,SET-b#2,SET] + 000005:[a#4,SET-a#4,SET] + 000007:[b#5,SET-b#5,SET] # iter c references both a memtable and sstables 5 and 7. @@ -61,7 +61,7 @@ iter-new c compact a-z ---- 6: - 000008:[a#0,SET-b#0,SET] + 000008:[a#3,SET-b#3,SET] metrics ---- @@ -73,8 +73,8 @@ __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___writ 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 - 6 1 778 B - 1.5 K 0 B 0 0 B 0 778 B 1 1.5 K 1 0.5 - total 1 778 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 + 6 1 779 B - 1.5 K 0 B 0 0 B 0 779 B 1 1.5 K 1 0.5 + total 1 779 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 flush 2 compact 1 0 B 0 B 0 (size == estimated-debt, score = in-progress-bytes, in = num-in-progress) ctype 1 0 0 0 0 0 0 (default, delete, elision, move, read, rewrite, multi-level) @@ -106,8 +106,8 @@ __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___writ 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 - 6 1 778 B - 1.5 K 0 B 0 0 B 0 778 B 1 1.5 K 1 0.5 - total 1 778 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 + 6 1 779 B - 1.5 K 0 B 0 0 B 0 779 B 1 1.5 K 1 0.5 + total 1 779 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 flush 2 compact 1 0 B 0 B 0 (size == estimated-debt, score = in-progress-bytes, in = num-in-progress) ctype 1 0 0 0 0 0 0 (default, delete, elision, move, read, rewrite, multi-level) @@ -136,8 +136,8 @@ __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___writ 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 - 6 1 778 B - 1.5 K 0 B 0 0 B 0 778 B 1 1.5 K 1 0.5 - total 1 778 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 + 6 1 779 B - 1.5 K 0 B 0 0 B 0 779 B 1 1.5 K 1 0.5 + total 1 779 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 flush 2 compact 1 0 B 0 B 0 (size == estimated-debt, score = in-progress-bytes, in = num-in-progress) ctype 1 0 0 0 0 0 0 (default, delete, elision, move, read, rewrite, multi-level) @@ -145,7 +145,7 @@ compact 1 0 B 0 B 0 (size == estimated-debt, scor zmemtbl 1 256 K ztbl 1 771 B bcache 4 698 B 42.9% (score == hit-rate) - tcache 1 704 B 66.7% (score == hit-rate) + tcache 1 720 B 66.7% (score == hit-rate) snaps 0 - 0 (score == earliest seq num) titers 1 filter - - 0.0% (score == utility) @@ -169,8 +169,8 @@ __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___writ 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0 - 6 1 778 B - 1.5 K 0 B 0 0 B 0 778 B 1 1.5 K 1 0.5 - total 1 778 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 + 6 1 779 B - 1.5 K 0 B 0 0 B 0 779 B 1 1.5 K 1 0.5 + total 1 779 B - 84 B 0 B 0 0 B 0 2.3 K 3 1.5 K 1 28.6 flush 2 compact 1 0 B 0 B 0 (size == estimated-debt, score = in-progress-bytes, in = num-in-progress) ctype 1 0 0 0 0 0 0 (default, delete, elision, move, read, rewrite, multi-level) diff --git a/testdata/range_del b/testdata/range_del index 14b950b8849..442a3ca30c2 100644 --- a/testdata/range_del +++ b/testdata/range_del @@ -641,9 +641,9 @@ L1 ---- mem: 1 1: - 000004:[a#3,SET-a#3,SET] - 000005:[a#2,SET-a#2,SET] - 000006:[a#1,SET-a#1,SET] + 000004:[a#6,SET-a#6,SET] + 000005:[a#5,SET-a#5,SET] + 000006:[a#4,SET-a#4,SET] get seq=1 a @@ -720,9 +720,9 @@ L1 ---- mem: 1 1: - 000004:[a#3,MERGE-a#3,MERGE] - 000005:[a#2,MERGE-a#2,MERGE] - 000006:[a#1,MERGE-a#1,MERGE] + 000004:[a#6,MERGE-a#6,MERGE] + 000005:[a#5,MERGE-a#5,MERGE] + 000006:[a#4,MERGE-a#4,MERGE] get seq=1 a @@ -803,11 +803,11 @@ L3 ---- mem: 1 1: - 000004:[a#3,MERGE-a#3,MERGE] + 000004:[a#6,MERGE-a#6,MERGE] 2: - 000005:[a#2,MERGE-a#2,MERGE] + 000005:[a#5,MERGE-a#5,MERGE] 3: - 000006:[a#1,MERGE-a#1,MERGE] + 000006:[a#4,MERGE-a#4,MERGE] get seq=1 a @@ -916,13 +916,13 @@ L3 ---- mem: 1 0.0: - 000004:[a#4,SET-d#4,SET] + 000004:[a#7,SET-d#7,SET] 1: - 000005:[a#3,SET-d#3,SET] + 000005:[a#6,SET-d#6,SET] 2: - 000006:[a#2,RANGEDEL-d#2,SET] + 000006:[a#5,RANGEDEL-d#5,SET] 3: - 000007:[a#1,SET-d#1,SET] + 000007:[a#4,SET-d#4,SET] get seq=2 a @@ -1042,11 +1042,11 @@ L2 ---- mem: 1 1: - 000004:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] - 000005:[b#2,RANGEDEL-c#72057594037927935,RANGEDEL] - 000006:[c#2,RANGEDEL-d#72057594037927935,RANGEDEL] + 000004:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] + 000005:[b#5,RANGEDEL-c#72057594037927935,RANGEDEL] + 000006:[c#5,RANGEDEL-d#72057594037927935,RANGEDEL] 2: - 000007:[a#1,SET-d#1,SET] + 000007:[a#4,SET-d#4,SET] get seq=2 a @@ -1138,9 +1138,9 @@ L2 ---- mem: 1 1: - 000004:[a#1,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-b#72057594037927935,RANGEDEL] 2: - 000005:[a#2,SET-a#2,SET] + 000005:[a#5,SET-a#5,SET] get seq=3 a @@ -1163,23 +1163,23 @@ L0 ---- mem: 1 0.1: - 000005:[a#2,SET-a#2,SET] - 000006:[c#3,SET-c#3,SET] + 000005:[a#5,SET-a#5,SET] + 000006:[c#6,SET-c#6,SET] 0.0: - 000004:[a#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-e#72057594037927935,RANGEDEL] compact a-e ---- 1: - 000007:[a#2,SET-c#72057594037927935,RANGEDEL] - 000008:[c#3,SET-e#72057594037927935,RANGEDEL] + 000007:[a#5,SET-c#72057594037927935,RANGEDEL] + 000008:[c#6,SET-e#72057594037927935,RANGEDEL] compact d-e ---- 1: - 000007:[a#2,SET-c#72057594037927935,RANGEDEL] + 000007:[a#5,SET-c#72057594037927935,RANGEDEL] 2: - 000008:[c#3,SET-e#72057594037927935,RANGEDEL] + 000008:[c#6,SET-e#72057594037927935,RANGEDEL] iter seq=4 seek-ge b @@ -1201,23 +1201,23 @@ L0 ---- mem: 1 0.1: - 000005:[a#2,SET-a#2,SET] - 000006:[c#3,SET-c#3,SET] + 000005:[a#5,SET-a#5,SET] + 000006:[c#6,SET-c#6,SET] 0.0: - 000004:[a#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-e#72057594037927935,RANGEDEL] compact a-e ---- 1: - 000007:[a#2,SET-c#72057594037927935,RANGEDEL] - 000008:[c#3,SET-e#72057594037927935,RANGEDEL] + 000007:[a#5,SET-c#72057594037927935,RANGEDEL] + 000008:[c#6,SET-e#72057594037927935,RANGEDEL] compact a-b ---- 1: - 000008:[c#3,SET-e#72057594037927935,RANGEDEL] + 000008:[c#6,SET-e#72057594037927935,RANGEDEL] 2: - 000007:[a#2,SET-c#72057594037927935,RANGEDEL] + 000007:[a#5,SET-c#72057594037927935,RANGEDEL] iter seq=4 seek-lt d @@ -1247,29 +1247,29 @@ L2 ---- mem: 1 0.1: - 000005:[a#2,SET-a#2,SET] - 000006:[c#3,SET-c#3,SET] + 000005:[a#5,SET-a#5,SET] + 000006:[c#6,SET-c#6,SET] 0.0: - 000004:[a#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000004:[a#4,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000007:[d#0,SET-d#0,SET] + 000007:[d#3,SET-d#3,SET] compact a-b ---- 1: - 000008:[a#2,SET-c#72057594037927935,RANGEDEL] - 000009:[c#3,SET-d#72057594037927935,RANGEDEL] - 000010:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000008:[a#5,SET-c#72057594037927935,RANGEDEL] + 000009:[c#6,SET-d#72057594037927935,RANGEDEL] + 000010:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000007:[d#0,SET-d#0,SET] + 000007:[d#3,SET-d#3,SET] compact d-e ---- 1: - 000008:[a#2,SET-c#72057594037927935,RANGEDEL] - 000009:[c#3,SET-d#72057594037927935,RANGEDEL] + 000008:[a#5,SET-c#72057594037927935,RANGEDEL] + 000009:[c#6,SET-d#72057594037927935,RANGEDEL] 3: - 000011:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000011:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] get seq=4 c @@ -1279,11 +1279,11 @@ c:v compact a-b L1 ---- 1: - 000009:[c#3,SET-d#72057594037927935,RANGEDEL] + 000009:[c#6,SET-d#72057594037927935,RANGEDEL] 2: - 000008:[a#2,SET-c#72057594037927935,RANGEDEL] + 000008:[a#5,SET-c#72057594037927935,RANGEDEL] 3: - 000011:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000011:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] get seq=4 c @@ -1307,34 +1307,34 @@ L2 ---- mem: 1 0.1: - 000005:[a#2,SET-a#2,SET] - 000006:[c#3,SET-c#3,SET] + 000005:[a#5,SET-a#5,SET] + 000006:[c#6,SET-c#6,SET] 0.0: - 000004:[a#1,RANGEDEL-e#72057594037927935,RANGEDEL] - 000007:[f#4,SET-f#4,SET] + 000004:[a#4,RANGEDEL-e#72057594037927935,RANGEDEL] + 000007:[f#7,SET-f#7,SET] 2: - 000008:[d#0,SET-d#0,SET] + 000008:[d#3,SET-d#3,SET] compact a-b ---- 0.0: - 000007:[f#4,SET-f#4,SET] + 000007:[f#7,SET-f#7,SET] 1: - 000009:[a#2,SET-c#72057594037927935,RANGEDEL] - 000010:[c#3,SET-d#72057594037927935,RANGEDEL] - 000011:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000009:[a#5,SET-c#72057594037927935,RANGEDEL] + 000010:[c#6,SET-d#72057594037927935,RANGEDEL] + 000011:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000008:[d#0,SET-d#0,SET] + 000008:[d#3,SET-d#3,SET] compact d-e ---- 0.0: - 000007:[f#4,SET-f#4,SET] + 000007:[f#7,SET-f#7,SET] 1: - 000009:[a#2,SET-c#72057594037927935,RANGEDEL] - 000010:[c#3,SET-d#72057594037927935,RANGEDEL] + 000009:[a#5,SET-c#72057594037927935,RANGEDEL] + 000010:[c#6,SET-d#72057594037927935,RANGEDEL] 3: - 000012:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000012:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] get seq=4 c @@ -1344,20 +1344,20 @@ c:v compact f-f L0 ---- 1: - 000009:[a#2,SET-c#72057594037927935,RANGEDEL] - 000010:[c#3,SET-d#72057594037927935,RANGEDEL] - 000007:[f#4,SET-f#4,SET] + 000009:[a#5,SET-c#72057594037927935,RANGEDEL] + 000010:[c#6,SET-d#72057594037927935,RANGEDEL] + 000007:[f#7,SET-f#7,SET] 3: - 000012:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000012:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] compact a-f L1 ---- 2: - 000013:[a#2,SET-c#72057594037927935,RANGEDEL] - 000014:[c#3,SET-d#72057594037927935,RANGEDEL] - 000015:[f#4,SET-f#4,SET] + 000013:[a#5,SET-c#72057594037927935,RANGEDEL] + 000014:[c#6,SET-d#72057594037927935,RANGEDEL] + 000015:[f#7,SET-f#7,SET] 3: - 000012:[d#1,RANGEDEL-e#72057594037927935,RANGEDEL] + 000012:[d#4,RANGEDEL-e#72057594037927935,RANGEDEL] get seq=4 c @@ -1377,13 +1377,13 @@ L2 ---- mem: 1 0.1: - 000005:[a#4,RANGEDEL-f#72057594037927935,RANGEDEL] + 000005:[a#7,RANGEDEL-f#72057594037927935,RANGEDEL] 0.0: - 000004:[a#3,RANGEDEL-f#72057594037927935,RANGEDEL] + 000004:[a#6,RANGEDEL-f#72057594037927935,RANGEDEL] 1: - 000006:[b#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000006:[b#5,RANGEDEL-e#72057594037927935,RANGEDEL] 2: - 000007:[c#1,RANGEDEL-d#72057594037927935,RANGEDEL] + 000007:[c#4,RANGEDEL-d#72057594037927935,RANGEDEL] wait-pending-table-stats 000007 @@ -1444,13 +1444,13 @@ L3 ---- mem: 1 0.0: - 000004:[a#4,SET-d#4,SET] + 000004:[a#7,SET-d#7,SET] 1: - 000005:[a#3,SET-d#3,SET] + 000005:[a#6,SET-d#6,SET] 2: - 000006:[a#2,RANGEDEL-d#2,SET] + 000006:[a#5,RANGEDEL-d#5,SET] 3: - 000007:[a#1,SET-d#1,SET] + 000007:[a#4,SET-d#4,SET] wait-pending-table-stats 000007 @@ -1506,14 +1506,14 @@ L2 ---- mem: 1 0.1: - 000004:[a#6,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#9,RANGEDEL-z#72057594037927935,RANGEDEL] 0.0: - 000005:[a#5,RANGEDEL-d#72057594037927935,RANGEDEL] - 000006:[e#4,RANGEDEL-z#72057594037927935,RANGEDEL] + 000005:[a#8,RANGEDEL-d#72057594037927935,RANGEDEL] + 000006:[e#7,RANGEDEL-z#72057594037927935,RANGEDEL] 1: - 000007:[a#2,SET-c#2,SET] + 000007:[a#5,SET-c#5,SET] 2: - 000008:[x#1,SET-x#1,SET] + 000008:[x#4,SET-x#4,SET] wait-pending-table-stats 000005 diff --git a/testdata/rangekeys b/testdata/rangekeys index fc49b35aac3..980bfb94f1e 100644 --- a/testdata/rangekeys +++ b/testdata/rangekeys @@ -773,10 +773,10 @@ flush lsm ---- 0.1: - 000009:[bar#3,DEL-zoo#72057594037927935,RANGEKEYSET] + 000009:[bar#6,DEL-zoo#72057594037927935,RANGEKEYSET] 0.0: - 000005:[bar#1,SET-bar#1,SET] - 000007:[bax#2,RANGEKEYSET-zoo#72057594037927935,RANGEKEYSET] + 000005:[bar#4,SET-bar#4,SET] + 000007:[bax#5,RANGEKEYSET-zoo#72057594037927935,RANGEKEYSET] # Assert that First correctly finds [bax,zoo), despite the discovery of # [foo,zoo) triggering the switch to combined iteration. @@ -858,12 +858,12 @@ flush lsm ---- 0.1: - 000013:[b#5,RANGEDEL-y#72057594037927935,RANGEDEL] + 000013:[b#8,RANGEDEL-y#72057594037927935,RANGEDEL] 0.0: - 000005:[a#1,SET-a#1,SET] - 000009:[d#3,RANGEKEYSET-e#72057594037927935,RANGEKEYSET] - 000011:[l#4,RANGEKEYSET-m#72057594037927935,RANGEKEYSET] - 000007:[z#2,SET-z#2,SET] + 000005:[a#4,SET-a#4,SET] + 000009:[d#6,RANGEKEYSET-e#72057594037927935,RANGEKEYSET] + 000011:[l#7,RANGEKEYSET-m#72057594037927935,RANGEKEYSET] + 000007:[z#5,SET-z#5,SET] combined-iter seek-ge k diff --git a/testdata/read_compaction_queue b/testdata/read_compaction_queue index 9f0cc3da306..54644d6691e 100644 --- a/testdata/read_compaction_queue +++ b/testdata/read_compaction_queue @@ -275,7 +275,7 @@ L5: n-w 4 add-compaction L5: x-z 5 ---- - + print-size ---- 5 diff --git a/testdata/singledel_manual_compaction b/testdata/singledel_manual_compaction index a6c7a714239..d150a5c3520 100644 --- a/testdata/singledel_manual_compaction +++ b/testdata/singledel_manual_compaction @@ -17,15 +17,15 @@ L5 a.SET.6:v1 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 2: - 000005:[a#9,SET-a#9,SET] + 000005:[a#12,SET-a#12,SET] 3: - 000006:[a#8,DEL-a#8,DEL] + 000006:[a#11,DEL-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # No data. iter @@ -37,13 +37,13 @@ first compact a-b L2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000009:[a#9,SET-a#9,SET] + 000009:[a#12,SET-a#12,SET] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # No data. iter @@ -55,20 +55,20 @@ first compact a-b L1 ---- 2: - 000010:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000010:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000009:[a#9,SET-a#9,SET] + 000009:[a#12,SET-a#12,SET] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] compact a-b L2 ---- 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # Deleted data reappears. iter diff --git a/testdata/singledel_manual_compaction_set_with_del b/testdata/singledel_manual_compaction_set_with_del index 29afc2e1ad6..eebbda6379e 100644 --- a/testdata/singledel_manual_compaction_set_with_del +++ b/testdata/singledel_manual_compaction_set_with_del @@ -16,15 +16,15 @@ L5 a.SET.6:v1 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 2: - 000005:[a#9,SET-a#9,SET] + 000005:[a#12,SET-a#12,SET] 3: - 000006:[a#8,DEL-a#8,DEL] + 000006:[a#11,DEL-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # No data. iter @@ -36,13 +36,13 @@ first compact a-b L2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000009:[a#9,SETWITHDEL-a#9,SETWITHDEL] + 000009:[a#12,SETWITHDEL-a#12,SETWITHDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # No data. iter @@ -54,22 +54,22 @@ first compact a-b L1 ---- 2: - 000010:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000010:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000009:[a#9,SETWITHDEL-a#9,SETWITHDEL] + 000009:[a#12,SETWITHDEL-a#12,SETWITHDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] compact a-b L2 ---- 3: - 000011:[a#10,DEL-a#10,DEL] + 000011:[a#13,DEL-a#13,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] 5: - 000008:[a#6,SET-a#6,SET] + 000008:[a#9,SET-a#9,SET] # Deleted data is not resurrected. iter @@ -89,13 +89,13 @@ L4 a.SET.7:v2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 2: - 000005:[a#9,SET-a#9,SET] + 000005:[a#12,SET-a#12,SET] 3: - 000006:[a#8,SINGLEDEL-a#8,SINGLEDEL] + 000006:[a#11,SINGLEDEL-a#11,SINGLEDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -107,11 +107,11 @@ first compact a-b L2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SETWITHDEL-a#9,SETWITHDEL] + 000008:[a#12,SETWITHDEL-a#12,SETWITHDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -123,18 +123,18 @@ first compact a-b L1 ---- 2: - 000009:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000009:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SETWITHDEL-a#9,SETWITHDEL] + 000008:[a#12,SETWITHDEL-a#12,SETWITHDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] compact a-b L2 ---- 3: - 000010:[a#10,DEL-a#10,DEL] + 000010:[a#13,DEL-a#13,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # Deleted data is not resurrected. iter @@ -155,13 +155,13 @@ L4 a.SET.7:v2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 2: - 000005:[a#9,SET-a#9,SET] + 000005:[a#12,SET-a#12,SET] 3: - 000006:[a#8,DEL-a#8,DEL] + 000006:[a#11,DEL-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -173,11 +173,11 @@ first compact a-b L2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SET-a#8,DEL] + 000008:[a#12,SET-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -191,19 +191,19 @@ close-snapshots compact a-b L1 ---- 2: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SET-a#8,DEL] + 000008:[a#12,SET-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # The DEL survives. compact a-b L2 ---- 3: - 000009:[a#8,DEL-a#8,DEL] + 000009:[a#11,DEL-a#11,DEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data iter @@ -224,13 +224,13 @@ L4 a.SET.7:v2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 2: - 000005:[a#9,SET-a#9,SET] + 000005:[a#12,SET-a#12,SET] 3: - 000006:[a#8,SINGLEDEL-a#8,SINGLEDEL] + 000006:[a#11,SINGLEDEL-a#11,SINGLEDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -242,11 +242,11 @@ first compact a-b L2 ---- 1: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SET-a#8,SINGLEDEL] + 000008:[a#12,SET-a#11,SINGLEDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data. iter @@ -260,19 +260,19 @@ close-snapshots compact a-b L1 ---- 2: - 000004:[a#10,SINGLEDEL-a#10,SINGLEDEL] + 000004:[a#13,SINGLEDEL-a#13,SINGLEDEL] 3: - 000008:[a#9,SET-a#8,SINGLEDEL] + 000008:[a#12,SET-a#11,SINGLEDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # The SINGLEDEL survives. compact a-b L2 ---- 3: - 000009:[a#8,SINGLEDEL-a#8,SINGLEDEL] + 000009:[a#11,SINGLEDEL-a#11,SINGLEDEL] 4: - 000007:[a#7,SET-a#7,SET] + 000007:[a#10,SET-a#10,SET] # No data iter diff --git a/testdata/split_user_key_migration b/testdata/split_user_key_migration index c8ffdedebe8..24b26bd8318 100644 --- a/testdata/split_user_key_migration +++ b/testdata/split_user_key_migration @@ -3,7 +3,7 @@ L1 d.SET.110:d e.SET.140:e ---- 1: - 000004:[d#110,SET-e#140,SET] points:[d#110,SET-e#140,SET] + 000004:[d#113,SET-e#143,SET] points:[d#113,SET-e#143,SET] reopen ---- @@ -21,8 +21,8 @@ set f f force-ingest paths=(ef) level=1 ---- 1: - 000004:[d#110,SET-e#140,SET] points:[d#110,SET-e#140,SET] - 000008:[e#1,SET-f#1,SET] points:[e#1,SET-f#1,SET] + 000004:[d#113,SET-e#143,SET] points:[d#113,SET-e#143,SET] + 000008:[e#4,SET-f#4,SET] points:[e#4,SET-f#4,SET] format-major-version ---- @@ -68,7 +68,7 @@ disable-automatic-compactions false ratchet-format-major-version 007 ---- -[JOB 100] compacted(rewrite) L1 [000004 000008] (1.6 K) + L1 [] (0 B) -> L1 [000013] (786 B), in 1.0s (2.0s total), output rate 786 B/s +[JOB 100] compacted(rewrite) L1 [000004 000008] (1.6 K) + L1 [] (0 B) -> L1 [000013] (787 B), in 1.0s (2.0s total), output rate 787 B/s format-major-version ---- @@ -77,7 +77,7 @@ format-major-version lsm ---- 1: - 000013:[d#0,SET-f#0,SET] + 000013:[d#3,SET-f#3,SET] # Reset to a new LSM. @@ -90,9 +90,9 @@ L1 x.SET.0:x y.SET.5:y ---- 1: - 000004:[b#0,SET-c#5,SET] points:[b#0,SET-c#5,SET] - 000005:[l#5,SET-m#0,SET] points:[l#5,SET-m#0,SET] - 000006:[x#0,SET-y#5,SET] points:[x#0,SET-y#5,SET] + 000004:[b#3,SET-c#8,SET] points:[b#3,SET-c#8,SET] + 000005:[l#8,SET-m#3,SET] points:[l#8,SET-m#3,SET] + 000006:[x#3,SET-y#8,SET] points:[x#3,SET-y#8,SET] build ab set a a @@ -112,12 +112,12 @@ set x x force-ingest paths=(ab, cd, wx) level=1 ---- 1: - 000007:[a#1,SET-b#1,SET] points:[a#1,SET-b#1,SET] - 000004:[b#0,SET-c#5,SET] points:[b#0,SET-c#5,SET] - 000008:[c#2,SET-d#2,SET] points:[c#2,SET-d#2,SET] - 000005:[l#5,SET-m#0,SET] points:[l#5,SET-m#0,SET] - 000009:[w#3,SET-x#3,SET] points:[w#3,SET-x#3,SET] - 000006:[x#0,SET-y#5,SET] points:[x#0,SET-y#5,SET] + 000007:[a#4,SET-b#4,SET] points:[a#4,SET-b#4,SET] + 000004:[b#3,SET-c#8,SET] points:[b#3,SET-c#8,SET] + 000008:[c#5,SET-d#5,SET] points:[c#5,SET-d#5,SET] + 000005:[l#8,SET-m#3,SET] points:[l#8,SET-m#3,SET] + 000009:[w#6,SET-x#6,SET] points:[w#6,SET-x#6,SET] + 000006:[x#3,SET-y#8,SET] points:[x#3,SET-y#8,SET] format-major-version ---- @@ -139,15 +139,15 @@ disable-automatic-compactions false ratchet-format-major-version 007 ---- -[JOB 100] compacted(rewrite) L1 [000007 000004 000008] (2.4 K) + L1 [] (0 B) -> L1 [000011] (794 B), in 1.0s (2.0s total), output rate 794 B/s -[JOB 100] compacted(rewrite) L1 [000009 000006] (1.6 K) + L1 [] (0 B) -> L1 [000012] (786 B), in 1.0s (2.0s total), output rate 786 B/s +[JOB 100] compacted(rewrite) L1 [000007 000004 000008] (2.4 K) + L1 [] (0 B) -> L1 [000011] (795 B), in 1.0s (2.0s total), output rate 795 B/s +[JOB 100] compacted(rewrite) L1 [000009 000006] (1.6 K) + L1 [] (0 B) -> L1 [000012] (787 B), in 1.0s (2.0s total), output rate 787 B/s lsm ---- 1: - 000011:[a#0,SET-d#0,SET] - 000005:[l#5,SET-m#0,SET] - 000012:[w#0,SET-y#0,SET] + 000011:[a#3,SET-d#3,SET] + 000005:[l#8,SET-m#3,SET] + 000012:[w#3,SET-y#3,SET] format-major-version ---- diff --git a/testdata/table_stats b/testdata/table_stats index 252a1a0809d..91a33ebd716 100644 --- a/testdata/table_stats +++ b/testdata/table_stats @@ -7,7 +7,7 @@ del c flush ---- 0.0: - 000005:[a#1,SET-c#3,DEL] + 000005:[a#4,SET-c#6,DEL] wait-pending-table-stats 000005 @@ -21,7 +21,7 @@ range-deletions-bytes-estimate: 0 compact a-c ---- 6: - 000005:[a#1,SET-c#3,DEL] + 000005:[a#4,SET-c#6,DEL] batch del-range a c @@ -30,9 +30,9 @@ del-range a c flush ---- 0.0: - 000007:[a#4,RANGEDEL-c#72057594037927935,RANGEDEL] + 000007:[a#7,RANGEDEL-c#72057594037927935,RANGEDEL] 6: - 000005:[a#1,SET-c#3,DEL] + 000005:[a#4,SET-c#6,DEL] wait-pending-table-stats 000007 @@ -81,12 +81,12 @@ set b 2 flush ---- 0.0: - 000012:[a#5,SET-b#6,SET] + 000012:[a#8,SET-b#9,SET] compact a-c ---- 6: - 000012:[a#5,SET-b#6,SET] + 000012:[a#8,SET-b#9,SET] enable ---- @@ -113,9 +113,9 @@ del-range a c flush ---- 0.0: - 000014:[a#7,RANGEDEL-c#72057594037927935,RANGEDEL] + 000014:[a#10,RANGEDEL-c#72057594037927935,RANGEDEL] 6: - 000012:[a#5,SET-b#6,SET] + 000012:[a#8,SET-b#9,SET] compact a-c ---- @@ -149,30 +149,30 @@ L6 e.SET.5:v ---- 4: - 000004:[a#8,RANGEDEL-f#72057594037927935,RANGEDEL] + 000004:[a#11,RANGEDEL-f#72057594037927935,RANGEDEL] 5: - 000005:[b#7,SET-b#7,SET] + 000005:[b#10,SET-b#10,SET] 6: - 000006:[a#1,SET-a#1,SET] - 000007:[b#2,SET-b#2,SET] - 000008:[c#3,SET-c#3,SET] - 000009:[d#4,SET-d#4,SET] - 000010:[e#5,SET-e#5,SET] + 000006:[a#4,SET-a#4,SET] + 000007:[b#5,SET-b#5,SET] + 000008:[c#6,SET-c#6,SET] + 000009:[d#7,SET-d#7,SET] + 000010:[e#8,SET-e#8,SET] compact a-b L4 ---- 5: - 000011:[a#8,RANGEDEL-b#72057594037927935,RANGEDEL] - 000012:[b#8,RANGEDEL-c#72057594037927935,RANGEDEL] - 000013:[c#8,RANGEDEL-d#72057594037927935,RANGEDEL] - 000014:[d#8,RANGEDEL-e#72057594037927935,RANGEDEL] - 000015:[e#8,RANGEDEL-f#72057594037927935,RANGEDEL] + 000011:[a#11,RANGEDEL-b#72057594037927935,RANGEDEL] + 000012:[b#11,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[c#11,RANGEDEL-d#72057594037927935,RANGEDEL] + 000014:[d#11,RANGEDEL-e#72057594037927935,RANGEDEL] + 000015:[e#11,RANGEDEL-f#72057594037927935,RANGEDEL] 6: - 000006:[a#1,SET-a#1,SET] - 000007:[b#2,SET-b#2,SET] - 000008:[c#3,SET-c#3,SET] - 000009:[d#4,SET-d#4,SET] - 000010:[e#5,SET-e#5,SET] + 000006:[a#4,SET-a#4,SET] + 000007:[b#5,SET-b#5,SET] + 000008:[c#6,SET-c#6,SET] + 000009:[d#7,SET-d#7,SET] + 000010:[e#8,SET-e#8,SET] wait-pending-table-stats 000011 @@ -200,7 +200,7 @@ L6 e.SET.5:e a.RANGEDEL.15:f m.SET.5:m g.RANGEDEL.15:z ---- 6: - 000004:[a#15,RANGEDEL-z#72057594037927935,RANGEDEL] + 000004:[a#18,RANGEDEL-z#72057594037927935,RANGEDEL] wait-pending-table-stats 000004 @@ -232,12 +232,12 @@ L2 e.SET.0:e h.SET.0:h ---- 0.0: - 000004:[a#2,RANGEDEL-b#72057594037927935,RANGEDEL] + 000004:[a#5,RANGEDEL-b#72057594037927935,RANGEDEL] 1: - 000005:[d#1,RANGEDEL-f#72057594037927935,RANGEDEL] + 000005:[d#4,RANGEDEL-f#72057594037927935,RANGEDEL] 2: - 000006:[a#0,SET-d#0,SET] - 000007:[e#0,SET-h#0,SET] + 000006:[a#3,SET-d#3,SET] + 000007:[e#3,SET-h#3,SET] # Table 000004 deletes the first block in table 000006. wait-pending-table-stats @@ -247,7 +247,7 @@ num-entries: 1 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 33 +range-deletions-bytes-estimate: 34 # Table 000005 deletes the second block in table 000006 (containing 'd') and the # first block in table 000007 (containing 'e'). @@ -258,7 +258,7 @@ num-entries: 1 num-deletions: 1 num-range-key-sets: 0 point-deletions-bytes-estimate: 0 -range-deletions-bytes-estimate: 66 +range-deletions-bytes-estimate: 68 # Test the interaction between point and range key deletions. @@ -276,7 +276,7 @@ range-key-unset a b @2 flush ---- 0.0: - 000005:[a#3,RANGEKEYUNSET-b#72057594037927935,RANGEKEYSET] + 000005:[a#6,RANGEKEYUNSET-b#72057594037927935,RANGEKEYSET] # Add a table that contains only point keys, to the right of the existing table. batch @@ -286,14 +286,14 @@ set c c flush ---- 0.0: - 000005:[a#3,RANGEKEYUNSET-b#72057594037927935,RANGEKEYSET] - 000007:[c#4,SET-c#4,SET] + 000005:[a#6,RANGEKEYUNSET-b#72057594037927935,RANGEKEYSET] + 000007:[c#7,SET-c#7,SET] compact a-c ---- 6: - 000008:[a#2,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] - 000009:[c#0,SET-c#0,SET] + 000008:[a#5,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] + 000009:[c#3,SET-c#3,SET] # Add a table that contains a RANGEKEYDEL covering the first table in L6. batch @@ -303,10 +303,10 @@ range-key-del a b flush ---- 0.0: - 000011:[a#5,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] + 000011:[a#8,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] 6: - 000008:[a#2,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] - 000009:[c#0,SET-c#0,SET] + 000008:[a#5,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] + 000009:[c#3,SET-c#3,SET] # Add one more table containing a RANGEDEL. batch @@ -316,12 +316,12 @@ del-range a c flush ---- 0.1: - 000013:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[a#9,RANGEDEL-c#72057594037927935,RANGEDEL] 0.0: - 000011:[a#5,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] + 000011:[a#8,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] 6: - 000008:[a#2,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] - 000009:[c#0,SET-c#0,SET] + 000008:[a#5,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] + 000009:[c#3,SET-c#3,SET] # Compute stats on the table containing range key del. It should not show an # estimate for deleted point keys as there are no tables below it that contain @@ -355,14 +355,14 @@ del-range a z range-key-del a z ---- 0.2: - 000014:[a#7,RANGEKEYDEL-z#72057594037927935,RANGEDEL] + 000014:[a#10,RANGEKEYDEL-z#72057594037927935,RANGEDEL] 0.1: - 000013:[a#6,RANGEDEL-c#72057594037927935,RANGEDEL] + 000013:[a#9,RANGEDEL-c#72057594037927935,RANGEDEL] 0.0: - 000011:[a#5,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] + 000011:[a#8,RANGEKEYDEL-b#72057594037927935,RANGEKEYDEL] 6: - 000008:[a#2,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] - 000009:[c#0,SET-c#0,SET] + 000008:[a#5,RANGEKEYSET-b#72057594037927935,RANGEKEYSET] + 000009:[c#3,SET-c#3,SET] compact a-z ---- @@ -381,7 +381,7 @@ set b b flush ---- 0.0: - 000005:[b#1,SET-b#1,SET] + 000005:[b#4,SET-b#4,SET] # A table with a mixture of point and range keys. batch @@ -392,14 +392,14 @@ range-key-set d d @1 foo flush ---- 0.0: - 000005:[b#1,SET-b#1,SET] - 000007:[c#2,SET-c#2,SET] + 000005:[b#4,SET-b#4,SET] + 000007:[c#5,SET-c#5,SET] compact a-z ---- 6: - 000008:[b#0,SET-b#0,SET] - 000009:[c#0,SET-c#0,SET] + 000008:[b#3,SET-b#3,SET] + 000009:[c#3,SET-c#3,SET] # The table with the range key del, that spans the previous two tables. batch @@ -409,10 +409,10 @@ range-key-del a z flush ---- 0.0: - 000011:[a#4,RANGEKEYDEL-z#72057594037927935,RANGEKEYDEL] + 000011:[a#7,RANGEKEYDEL-z#72057594037927935,RANGEKEYDEL] 6: - 000008:[b#0,SET-b#0,SET] - 000009:[c#0,SET-c#0,SET] + 000008:[b#3,SET-b#3,SET] + 000009:[c#3,SET-c#3,SET] # The hint on table 000011 does estimates zero size for range deleted point # keys. @@ -438,11 +438,11 @@ L6 rangekey:c-d:{(#1,RANGEKEYSET,@1,foo)} ---- 4: - 000004:[a#4,RANGEDEL-c#72057594037927935,RANGEDEL] + 000004:[a#7,RANGEDEL-c#72057594037927935,RANGEDEL] 5: - 000005:[a#2,RANGEDEL-e#72057594037927935,RANGEDEL] + 000005:[a#5,RANGEDEL-e#72057594037927935,RANGEDEL] 6: - 000006:[c#1,RANGEKEYSET-d#72057594037927935,RANGEKEYSET] + 000006:[c#4,RANGEKEYSET-d#72057594037927935,RANGEKEYSET] # The table in L5 should not contain an estimate for the table below it, which # contains only range keys. diff --git a/tool/lsm_data.go b/tool/lsm_data.go index 70aac728e9a..e524b85fc01 100644 --- a/tool/lsm_data.go +++ b/tool/lsm_data.go @@ -718,6 +718,7 @@ let version = { } } + // TODO(peter): display smallest/largest key. reason.text( "[" + this.levelsInfo[i].levelString + @@ -727,11 +728,6 @@ let version = { humanize(data.Files[fileNum].Size) + ")" + overlapInfo + - " <" + - data.Keys[data.Files[fileNum].Smallest].Pretty + - ", " + - data.Keys[data.Files[fileNum].Largest].Pretty + - ">" + "]" ); diff --git a/tool/make_test_find_db.go b/tool/make_test_find_db.go index 0bd55bd89f6..84bd2caa939 100644 --- a/tool/make_test_find_db.go +++ b/tool/make_test_find_db.go @@ -112,7 +112,7 @@ func (d *db) ingest(keyVals ...string) { log.Fatal(err) } - if err := d.db.Ingest([]string{path}); err != nil { + if err := d.db.Ingest([]string{path}, nil); err != nil { log.Fatal(err) } } diff --git a/tool/testdata/find b/tool/testdata/find index 9ac3490d16a..339b5915342 100644 --- a/tool/testdata/find +++ b/tool/testdata/find @@ -13,22 +13,22 @@ testdata/find-db aaa ---- 000002.log - aaa#1,SET [31] + aaa#4,SET [31] 000004.log - aaa#8,DEL [] -000005.sst [aaa#1,SET-ccc#5,MERGE] + aaa#11,DEL [] +000005.sst [aaa#4,SET-ccc#8,MERGE] (flushed to L0, moved to L6) - aaa#1,SET [31] -000008.sst [aaa#0,SET-ccc#0,MERGE] + aaa#4,SET [31] +000008.sst [aaa#3,SET-ccc#3,MERGE] (compacted L0 [...] + L6 [000005]) - aaa#0,SET [31] -000010.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + aaa#3,SET [31] +000010.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (flushed to L0) - aaa#8,DEL [] -000011.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + aaa#11,DEL [] +000011.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (compacted L0 [000010] + L6 [000008 ...]) - aaa#8,DEL [] - aaa#0,SET [31] + aaa#11,DEL [] + aaa#3,SET [31] find testdata/find-db @@ -37,27 +37,27 @@ bbb --value=pretty:test-comparer ---- 000002.log - 626262#2,SET test value formatter: 2 + 626262#5,SET test value formatter: 2 000004.log - 626262-656565#10,RANGEDEL -000005.sst [616161#1,SET-636363#5,MERGE] + 626262-656565#13,RANGEDEL +000005.sst [616161#4,SET-636363#8,MERGE] (flushed to L0, moved to L6) - 626262#2,SET test value formatter: 2 -000006.sst [626262#6,SET-636363#6,SET] + 626262#5,SET test value formatter: 2 +000006.sst [626262#9,SET-636363#9,SET] (ingested to L0) - 626262#6,SET test value formatter: 22 -000008.sst [616161#0,SET-636363#0,MERGE] + 626262#9,SET test value formatter: 22 +000008.sst [616161#3,SET-636363#3,MERGE] (compacted L0 [000006] + L6 [000005]) - 626262#6,SET test value formatter: 22 - 626262#0,SET test value formatter: 2 -000010.sst [616161#8,DEL-656565#72057594037927935,RANGEDEL] + 626262#9,SET test value formatter: 22 + 626262#3,SET test value formatter: 2 +000010.sst [616161#11,DEL-656565#72057594037927935,RANGEDEL] (flushed to L0) - 626262-656565#10,RANGEDEL -000011.sst [616161#8,DEL-656565#72057594037927935,RANGEDEL] + 626262-656565#13,RANGEDEL +000011.sst [616161#11,DEL-656565#72057594037927935,RANGEDEL] (compacted L0 [000010] + L6 [000008 ...]) - 626262-656565#10,RANGEDEL - 626262#6,SET test value formatter: 22 - 626262#0,SET test value formatter: 2 + 626262-656565#13,RANGEDEL + 626262#9,SET test value formatter: 22 + 626262#3,SET test value formatter: 2 find testdata/find-db @@ -65,30 +65,30 @@ hex:636363 --value=null ---- 000002.log - ccc#3,MERGE - ccc#4,MERGE - ccc#5,MERGE + ccc#6,MERGE + ccc#7,MERGE + ccc#8,MERGE 000004.log - ccc#9,SINGLEDEL - bbb-eee#10,RANGEDEL -000005.sst [aaa#1,SET-ccc#5,MERGE] + ccc#12,SINGLEDEL + bbb-eee#13,RANGEDEL +000005.sst [aaa#4,SET-ccc#8,MERGE] (flushed to L0, moved to L6) - ccc#5,MERGE -000006.sst [bbb#6,SET-ccc#6,SET] + ccc#8,MERGE +000006.sst [bbb#9,SET-ccc#9,SET] (ingested to L0) - ccc#6,SET -000008.sst [aaa#0,SET-ccc#0,MERGE] + ccc#9,SET +000008.sst [aaa#3,SET-ccc#3,MERGE] (compacted L0 [000006] + L6 [000005]) - ccc#6,SET - ccc#0,MERGE -000010.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + ccc#9,SET + ccc#3,MERGE +000010.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (flushed to L0) - bbb-eee#10,RANGEDEL -000011.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + bbb-eee#13,RANGEDEL +000011.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (compacted L0 [000010] + L6 [000008 ...]) - bbb-eee#10,RANGEDEL - ccc#6,SET - ccc#0,MERGE + bbb-eee#13,RANGEDEL + ccc#9,SET + ccc#3,MERGE find testdata/find-db @@ -105,27 +105,27 @@ find-db/archive/000002.log find-db/archive/000004.log find-db/000009.log find-db/archive/000005.sst -find-db/archive/000006.sst: global seqnum: 6 -find-db/archive/000007.sst: global seqnum: 7 +find-db/archive/000006.sst: global seqnum: 9 +find-db/archive/000007.sst: global seqnum: 10 find-db/archive/000008.sst find-db/archive/000010.sst find-db/archive/000011.sst 000004.log - bbb-eee#10,RANGEDEL -000007.sst [ddd#7,SET-ddd#7,SET] + bbb-eee#13,RANGEDEL +000007.sst [ddd#10,SET-ddd#10,SET] (ingested to L6) - ddd#7,SET [3333] -000010.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + ddd#10,SET [3333] +000010.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (flushed to L0) - bbb-eee#10,RANGEDEL -000011.sst [aaa#8,DEL-eee#72057594037927935,RANGEDEL] + bbb-eee#13,RANGEDEL +000011.sst [aaa#11,DEL-eee#72057594037927935,RANGEDEL] (compacted L0 [000010] + L6 [000007 ...]) - bbb-eee#10,RANGEDEL - ddd#7,SET [3333] + bbb-eee#13,RANGEDEL + ddd#10,SET [3333] find testdata/find-db eee ---- 000004.log - bbb-eee#10,RANGEDEL + bbb-eee#13,RANGEDEL diff --git a/tool/testdata/find-db/MANIFEST-000001 b/tool/testdata/find-db/MANIFEST-000001 index c3aa9f4a31ebf59f4ff51fcdf61380b96bd68ad5..4a888c4d23cd61cd765b8deacbf089ec0759416e 100644 GIT binary patch literal 424 zcma*jy-LJD6b0ZrnVXaO*&x^o_A0v|xP@pX>;qWYI+=t)P?SZi#L99DK@cCo)(4OU z#nyJgQo$Fn6*So-yA~GS>P+!*?%c`B!_p!N(Q${TU4MLZ;)cagMofON9Y7+Aq-1&a zvANtWA1uKtP<#dJoD)pbvEKVg)tMFI(evjG#Ov1eevd>YKykpo(B4I_zgD7zbs*fs zW?3fmyr#`sw>7Iz{oP6^HUY^UY@X-BG~!n_rWJ4JgM;-_Xf_}K{*atEMNO8o zrmxZaPIUw=EvJFZgI-}TtE?J{cevQc(*I0X`r0aSp!Osf0+Mdgf+!`LM) FwLcR+Rk;8F literal 422 zcmcbvxmQ|3j-q)6Ek!7?fL(8flL-=HWt=& z2G$8|+=+>ajEoFmz@40&%)|;|GqSR=KDs`mnT_$2n{{Haj~^ z$KFqU8x4UvI9b@)7(fQ4vvEv^cnG460qh|t8>j;6Aw9|VL{K1bGIO(Vr896H0_$Pm zfZCOsn#%tl0ywx}dU~yt+)RLW@vv~Qu>q|Ek{s!5+)eDD@WZARr~+!;K1=OFc9>Rf E0Fvlc?f?J) diff --git a/tool/testdata/find-db/OPTIONS-000003 b/tool/testdata/find-db/OPTIONS-000003 index 54d3bc2a176..3d8cd16fa8a 100644 --- a/tool/testdata/find-db/OPTIONS-000003 +++ b/tool/testdata/find-db/OPTIONS-000003 @@ -5,11 +5,14 @@ bytes_per_sync=524288 cache_size=8388608 cleaner=archive + compaction_debt_concurrency=1073741824 comparer=alt-comparer delete_range_flush_delay=0s disable_wal=false flush_split_bytes=4194304 + format_major_version=1 l0_compaction_concurrency=10 + l0_compaction_file_threshold=500 l0_compaction_threshold=4 l0_stop_writes_threshold=12 lbase_max_bytes=67108864 @@ -18,13 +21,19 @@ max_open_files=1000 mem_table_size=4194304 mem_table_stop_writes_threshold=2 - min_compaction_rate=4194304 - min_flush_rate=1048576 + min_deletion_rate=0 merger=test-merger + read_compaction_rate=16000 + read_sampling_multiplier=16 strict_wal_tail=true + table_cache_shards=32 table_property_collectors=[] + unique_id=0 + validate_on_ingest=false wal_dir= wal_bytes_per_sync=0 + max_writer_concurrency=0 + force_writer_parallelism=false [Level "0"] block_restart_interval=16 diff --git a/tool/testdata/find-db/archive/000002.log b/tool/testdata/find-db/archive/000002.log index 6a11fe78bb7845c9127b33fa1087abba3aacd17d..315ce6d81480ac4c716a1f1357576ade0987a0d8 100644 GIT binary patch literal 161 zcmZ2?dC6X32395p1_l-&1_4G8W=>2@WHby@|7QV}Wd+N@WRsGT7>yP!&^Ck0vLVSb kF()S{Ga55Q8mB>J*)e2IHe@FILS;EHWKAI=tjs_f01f33fB*mh literal 161 zcmZ3iKkb1q11l2)0|O%vg8+zTWKK*>WHi*v+Qko*1*rf5h-^|)5~I<27THLsEHgwB mEX%~4oSe*P?0m094JylmA#1WIz-J#+mK8(R6e7Y3G8F)k>kth7hN=jl*O-+S}v9bbb01SN!82|tP diff --git a/tool/testdata/find-db/archive/000005.sst b/tool/testdata/find-db/archive/000005.sst index 757f66395a9ab024c3a08893c8dc6d354925bd90..b8d895afd517f73d7ba8fd97a7e7a6d22ab0fa72 100644 GIT binary patch delta 69 zcmbQhHi6BCftxWgF_Dpl0SpX*+@z!=Mpg*dh=H3qIXRh$1Hv^nF$FOgK}7SxPS=e# GSC{}ZBMIvO delta 69 zcmbQhHi6BCftxWgF_Dpx0SpX*+@z!=MkWZ?h=H3qIXRh$6~Z+(F$FQeYTKKIk8QNM G!UO;>*a@=$ diff --git a/tool/testdata/find-db/archive/000008.sst b/tool/testdata/find-db/archive/000008.sst index 61a051be6016b7d29eeaec259478f02a6792709e..673756d5fd428edbe0327a89d24ba6b37ae550d6 100644 GIT binary patch delta 170 zcmbQvHiIqRUx9&}F)=Zbk(q&&QNfUbn<*(NiIJ0)Pk_P5h=pBHTEYmZAUQc%P+Gu@ znS+^$*G$9M#FPOD7(uuq*fWHIlPN{;KLjw?!=xD0WA65EjFV<$blF_RXwRtsROiVp z1|jC6{N(K7lq9`^qWpr?qLR$i;`m!Ea7}#6t}SHT$Ex5WHu)oy)Ffuk$=u950J|0_ Aj{pDw delta 169 zcmbQiHk~cbUxtC3F)=Zbk%5y@!H|KQDJdz5k&Tm|$H<6MLoGd6UV8+bB z%*3gt!(eP;$^Zn6AUu(!;0^;PQ;Oh!2w<>-NipocG-c1mcxgsP=grlO_KfTH}W&Bz$Dc|D^&qmg}`jSzznOHqDuc5zCQ zUO`cQL26M+W@_v)S006ZMJYWC- delta 282 zcmZo+Z(^^{Qefa_NK8y*;9y{7HJ{g-h$RoD|+TRhp48YV$@$d&X2gw$H03!5(m>IGP#182LS7|Ku!Pv diff --git a/tool/testdata/manifest_dump b/tool/testdata/manifest_dump index 55b9c7bf0f5..78bdc9a8d0e 100644 --- a/tool/testdata/manifest_dump +++ b/tool/testdata/manifest_dump @@ -198,45 +198,46 @@ MANIFEST-000001 25/1 log-num: 2 next-file-num: 3 -36/2 + last-seq-num: 3 +38/2 log-num: 4 next-file-num: 6 - last-seq-num: 5 - added: L0 000005:784<#1-#5>[aaa#1,SET-ccc#5,MERGE] (2021-04-01T20:24:02Z) -88/3 + last-seq-num: 8 + added: L0 000005:784<#4-#8>[aaa#4,SET-ccc#8,MERGE] (2022-07-24T06:11:24Z) +90/3 next-file-num: 6 - last-seq-num: 5 + last-seq-num: 8 deleted: L0 000005 - added: L6 000005:784<#1-#5>[aaa#1,SET-ccc#5,MERGE] (2021-04-01T20:24:02Z) -141/4 + added: L6 000005:784<#4-#8>[aaa#4,SET-ccc#8,MERGE] (2022-07-24T06:11:24Z) +143/4 next-file-num: 7 - last-seq-num: 6 - added: L0 000006:817<#6-#6>[bbb#6,SET-ccc#6,SET] (2021-04-01T20:24:02Z) -191/5 + last-seq-num: 9 + added: L0 000006:817<#9-#9>[bbb#9,SET-ccc#9,SET] (2022-07-24T06:11:24Z) +193/5 next-file-num: 8 - last-seq-num: 7 - added: L6 000007:808<#7-#7>[ddd#7,SET-ddd#7,SET] (2021-04-01T20:24:02Z) -241/6 + last-seq-num: 10 + added: L6 000007:808<#10-#10>[ddd#10,SET-ddd#10,SET] (2022-07-24T06:11:24Z) +243/6 next-file-num: 9 - last-seq-num: 7 + last-seq-num: 10 deleted: L0 000006 deleted: L6 000005 - added: L6 000008:791<#0-#6>[aaa#0,SET-ccc#0,MERGE] (2021-04-01T20:24:02Z) -297/7 + added: L6 000008:792<#3-#9>[aaa#3,SET-ccc#3,MERGE] (2022-07-24T06:11:24Z) +299/7 log-num: 9 next-file-num: 11 - last-seq-num: 10 - added: L0 000010:834<#8-#10>[aaa#8,DEL-eee#72057594037927935,RANGEDEL] (2021-04-01T20:24:02Z) -349/8 + last-seq-num: 13 + added: L0 000010:834<#11-#13>[aaa#11,DEL-eee#72057594037927935,RANGEDEL] (2022-07-24T06:11:24Z) +351/8 next-file-num: 12 - last-seq-num: 10 + last-seq-num: 13 deleted: L0 000010 deleted: L6 000007 deleted: L6 000008 - added: L6 000011:898<#0-#10>[aaa#8,DEL-eee#72057594037927935,RANGEDEL] (2021-04-01T20:24:02Z) -408/9 + added: L6 000011:900<#3-#13>[aaa#11,DEL-eee#72057594037927935,RANGEDEL] (2022-07-24T06:11:24Z) +410/9 next-file-num: 12 - last-seq-num: 10 + last-seq-num: 13 deleted: L6 000011 EOF --- L0 --- diff --git a/tool/testdata/manifest_summarize b/tool/testdata/manifest_summarize index ec6c6d9ad4b..a8f7389945c 100644 --- a/tool/testdata/manifest_summarize +++ b/tool/testdata/manifest_summarize @@ -17,13 +17,13 @@ manifest summarize ---- MANIFEST-000001 _______L0_______L1_______L2_______L3_______L4_______L5_______L6_____TOTAL -2021-04-01T20:24:02Z +2022-07-24T06:11:24Z Ingest+Flush 2.4 K . . . . . 808 B 3.2 K Ingest+Flush 2.4 K/s . . . . . 808 B/s 3.2 K/s - Compact (out) 2.4 K . . . . . 898 B 3.3 K - Compact (out) 2.4 K/s . . . . . 898 B/s 3.3 K/s -2021-04-01T20:24:02Z + Compact (out) 2.4 K . . . . . 900 B 3.3 K + Compact (out) 2.4 K/s . . . . . 900 B/s 3.3 K/s +2022-07-24T06:11:24Z --- -Estimated start time: 2021-04-01T20:24:02Z -Estimated end time: 2021-04-01T20:24:02Z +Estimated start time: 2022-07-24T06:11:24Z +Estimated end time: 2022-07-24T06:11:24Z Estimated duration: 0s diff --git a/tool/testdata/sstable_properties b/tool/testdata/sstable_properties index 52412eb1153..fb307c4ecd3 100644 --- a/tool/testdata/sstable_properties +++ b/tool/testdata/sstable_properties @@ -156,7 +156,7 @@ rocksdb.comparator: alt-comparer rocksdb.compression: Snappy rocksdb.compression_options: window_bits=-14; level=32767; strategy=0; max_dict_bytes=0; zstd_max_train_bytes=0; enabled=0; rocksdb.creation.time: 0 -rocksdb.data.size: 90 +rocksdb.data.size: 92 rocksdb.filter.size: 0 rocksdb.fixed.key.length: 0 rocksdb.format.version: 0 diff --git a/version_set_test.go b/version_set_test.go index 2bf0fc89297..2a22dfe29eb 100644 --- a/version_set_test.go +++ b/version_set_test.go @@ -22,7 +22,7 @@ func writeAndIngest(t *testing.T, mem vfs.FS, d *DB, k InternalKey, v []byte, fi w := sstable.NewWriter(f, sstable.WriterOptions{}) require.NoError(t, w.Add(k, v)) require.NoError(t, w.Close()) - require.NoError(t, d.Ingest([]string{path})) + require.NoError(t, d.Ingest([]string{path}, nil)) } func TestVersionSetCheckpoint(t *testing.T) { @@ -87,7 +87,7 @@ func TestVersionSetSeqNums(t *testing.T) { require.NotNil(t, manifest) defer manifest.Close() rr := record.NewReader(manifest, 0 /* logNum */) - lastSeqNum := uint64(0) + lastSeqNum := uint64(sstable.SeqNumZero) for { r, err := rr.Next() if err == io.EOF { @@ -102,7 +102,7 @@ func TestVersionSetSeqNums(t *testing.T) { } } // 2 ingestions happened, so LastSeqNum should equal 2. - require.Equal(t, uint64(2), lastSeqNum) + require.Equal(t, uint64(sstable.SeqNumZero+2), lastSeqNum) // logSeqNum is always one greater than the last assigned sequence number. require.Equal(t, d.mu.versions.atomic.logSeqNum, lastSeqNum+1) } From cfa7d806efee973185516c00940feb673cc1b7b9 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Tue, 26 Jul 2022 23:55:48 -0500 Subject: [PATCH 5/6] *: demonstrating shared sstable in tests --- compaction.go | 4 +- data_test.go | 1 + disagg_test.go | 209 +++++++++++++++++++++++++++++----------- ingest.go | 70 +++++++------- ingest_test.go | 2 +- sstable/shared.go | 4 +- testdata/iterator_stats | 2 +- 7 files changed, 194 insertions(+), 98 deletions(-) diff --git a/compaction.go b/compaction.go index abf95c9149c..ee51f64eb25 100644 --- a/compaction.go +++ b/compaction.go @@ -3264,10 +3264,10 @@ func init() { // it can access all the data in the table setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { // The output sst is shared so update its boundaries - meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest + meta.FileSmallest, meta.FileLargest = meta.Smallest.Clone(), meta.Largest.Clone() // assign virtual boundaries for all boundary properties - lb, ub := meta.Smallest, meta.Largest + lb, ub := meta.Smallest.Clone(), meta.Largest.Clone() meta.Smallest, meta.Largest = lb, ub meta.SmallestPointKey, meta.LargestPointKey = lb, ub diff --git a/data_test.go b/data_test.go index 33bf4a1921d..f2f61a1bd46 100644 --- a/data_test.go +++ b/data_test.go @@ -1010,6 +1010,7 @@ func runForceIngestCmd(td *datadriven.TestData, d *DB) error { } } _, err := d.ingest(paths, nil, func( + *DB, tableNewIters, IterOptions, Compare, diff --git a/disagg_test.go b/disagg_test.go index 80f1578db62..15922d6b667 100644 --- a/disagg_test.go +++ b/disagg_test.go @@ -4,31 +4,50 @@ import ( "bytes" "fmt" "math/rand" - "os" + "strconv" + "strings" "testing" "time" + "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/manifest" + "github.com/cockroachdb/pebble/sstable" "github.com/cockroachdb/pebble/vfs" "github.com/stretchr/testify/require" ) -func generateRandomKeys(klen uint64, num uint64) [][]byte { - const letters = "abcdefghijklmnopqrstuvwxyz" - rand.Seed(time.Now().UnixNano()) +func decimalToString(num uint64, digits uint64) []byte { + k := []byte(strconv.FormatUint(num, 10)) + zeros := digits - uint64(len(k)) + ret := append(make([]byte, zeros), k...) + for i := uint64(0); i < zeros; i++ { + ret[i] = byte('0') + } + return ret +} + +// generateRandomKeys generates STRING decimal keys from 0 to num-1 +func generateRandomKeys(t *testing.T, num uint64) ([][]byte, uint64) { + t.Helper() + var digits uint64 = 0 + tnum := num - 1 + for tnum != 0 { + digits++ + tnum /= 10 + } keys := make([][]byte, num) for i := uint64(0); i < num; i++ { - keys[i] = make([]byte, klen) - for j := range keys[i] { - keys[i][j] = letters[rand.Intn(len(letters))] - } + keys[i] = decimalToString(i, digits) } - return keys + rand.Shuffle(int(num), func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) + + return keys, digits } func printLevels(t *testing.T, d *DB) map[string]bool { + t.Helper() visible := make(map[string]bool) // check the number of table files in each level t.Logf("Level metadata") @@ -41,14 +60,18 @@ func printLevels(t *testing.T, d *DB) map[string]bool { fm := iter.First() for fm != nil { if fm.IsShared { - t.Logf(" -- sst %d is shared with virtual bound (%s %s) file bound (%s %s)\n", - fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey, fm.FileSmallest.UserKey, fm.FileLargest.UserKey) + t.Logf(" -- sst %d (physical filenum %d) is shared with virtual bound (%s %s) file bound (%s %s)\n", + fm.FileNum, fm.PhysicalFileNum, fm.Smallest.UserKey, fm.Largest.UserKey, fm.FileSmallest.UserKey, fm.FileLargest.UserKey) visible[string(fm.Smallest.UserKey)] = true - visible[string(fm.FileLargest.UserKey)] = false + visible[string(fm.Largest.UserKey)] = true + if d.cmp(fm.Smallest.UserKey, fm.FileSmallest.UserKey) > 0 { + visible[string(fm.FileSmallest.UserKey)] = false + } + if d.cmp(fm.FileLargest.UserKey, fm.Largest.UserKey) > 0 { + visible[string(fm.FileLargest.UserKey)] = false + } } else { t.Logf(" -- sst %d is local with bound (%s %s)\n", fm.FileNum, fm.Smallest.UserKey, fm.Largest.UserKey) - //visible[string(fm.Smallest.UserKey)] = true - //visible[string(fm.FileLargest.UserKey)] = true } fm = iter.Next() } @@ -58,6 +81,7 @@ func printLevels(t *testing.T, d *DB) map[string]bool { } func validateBoundaries(t *testing.T, d *DB, visible *map[string]bool) { + t.Helper() // no compaction should have happened, take a snapshot snapshot := d.NewSnapshot() t.Logf("Validating ...") @@ -79,14 +103,9 @@ func validateBoundaries(t *testing.T, d *DB, visible *map[string]bool) { require.NoError(t, snapshot.Close()) } -func TestMain(m *testing.M) { - code := m.Run() - // reset to default function - - os.Exit(code) -} - func TestDBWithSharedSST(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + fs := vfs.NewMem() sharedfs := vfs.NewMem() @@ -109,30 +128,26 @@ func TestDBWithSharedSST(t *testing.T) { } const N = 1000000 - keys := generateRandomKeys(8, N) - value := bytes.Repeat([]byte("x"), 4096) + keys, _ := generateRandomKeys(t, N) + value := bytes.Repeat([]byte("x"), 256) - // inject key boundaries function to filter out the upper setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { // The output sst is shared so update its boundaries - meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest + meta.FileSmallest, meta.FileLargest = meta.Smallest.Clone(), meta.Largest.Clone() - // Only one key for each table for testing - lb, ub := meta.Smallest, meta.Smallest + // assign virtual boundaries for all boundary properties + lb, ub := meta.Smallest.Clone(), meta.Smallest.Clone() meta.Smallest, meta.Largest = lb, ub meta.SmallestPointKey, meta.LargestPointKey = lb, ub - meta.CreatorUniqueID = creatorUniqueID + 1 // fake foreign sst + meta.CreatorUniqueID = creatorUniqueID + 1 meta.PhysicalFileNum = meta.FileNum - - t.Logf(" -- new shared sst with virtual bound (%s %s) with file bound (%s %s)", - lb.UserKey, ub.UserKey, meta.FileSmallest.UserKey, meta.FileLargest.UserKey) } // repeatly inserting/updating a random key t.Log("Inserting ...") for i := 0; i < N; i++ { - if i%100000 == 0 && i != 0 { + if i%(N/10) == 0 && i != 0 { t.Logf("set %d keys", i) } key := &keys[i] @@ -161,22 +176,31 @@ func TestDBWithSharedSST(t *testing.T) { // revert setSharedSSTMetadata = func(meta *manifest.FileMetadata, creatorUniqueID uint32) { - meta.FileSmallest, meta.FileLargest = meta.Smallest, meta.Largest - lb, ub := meta.Smallest, meta.Largest + // The output sst is shared so update its boundaries + meta.FileSmallest, meta.FileLargest = meta.Smallest.Clone(), meta.Largest.Clone() + + // assign virtual boundaries for all boundary properties + lb, ub := meta.Smallest.Clone(), meta.Largest.Clone() meta.Smallest, meta.Largest = lb, ub meta.SmallestPointKey, meta.LargestPointKey = lb, ub + meta.CreatorUniqueID = creatorUniqueID meta.PhysicalFileNum = meta.FileNum } } func TestIngestSharedSST(t *testing.T) { + rand.Seed(time.Now().Unix()) + + uid1 := uint32(rand.Uint32()) + uid2 := uid1 + 10 - fs1, fs2 := vfs.NewMem(), vfs.NewMem() - uid1, uid2 := uint32(rand.Uint32()), uint32(rand.Uint32()) + fs1 := vfs.NewMem() + fs2 := vfs.NewMem() sharedfs := vfs.NewMem() - t.Log("Opening ...") + t.Log("Creating two Pebble instances d1 and d2") + t.Log("\033[1;32mDone\033[0m") d1, err := Open("", &Options{ FS: fs1, SharedFS: sharedfs, @@ -198,66 +222,137 @@ func TestIngestSharedSST(t *testing.T) { require.NoError(t, sharedfs.MkdirAll(fmt.Sprintf("%d/%d", uid2, i), 0755)) } - const N = 100000 - keys := generateRandomKeys(8, N) - value := bytes.Repeat([]byte("x"), 4096) + const N = 1000000 + keys, digits := generateRandomKeys(t, N) + value := bytes.Repeat([]byte("x"), 256) // repeatly inserting/updating a random key - t.Log("Inserting ...") + t.Logf("Inserting %d keys to d1", N) for i := 0; i < N; i++ { - if i%10000 == 0 && i != 0 { - t.Logf("set %d keys", i) + if i%(N/10) == 0 && i != 0 { + t.Logf(" -- set %d keys", i) } key := &keys[i] if err := d1.Set(*key, value, nil); err != nil { t.Fatalf("set key %s error %v", key, err) } } + t.Log("\033[1;32mDone\033[0m") t.Logf("Printing d1 LSM") printLevels(t, d1) + t.Log("\033[1;32mDone\033[0m") - // test skipshared callback - t.Logf("Iterating and creating sharedmeta...") - var smeta []SharedSSTMeta + // exporting a range + start := rand.Uint64() % (N / 2) + end := start + (N / 2) - 1 + startkey := decimalToString(start, digits) + endkey := decimalToString(end, digits) + t.Logf("Exporting keys in range %s to %s (inclusive) from d1 and ingest to d2", startkey, endkey) - rand.Seed(time.Now().Unix()) + var smeta []SharedSSTMeta cb := func(meta *manifest.FileMetadata) { - // Randomly pick a table here.. - if rand.Uint32()%2 == 1 { + // If no overlap, just return + if d1.cmp(meta.Smallest.UserKey, endkey) > 0 || d1.cmp(meta.Largest.UserKey, startkey) < 0 { return } + + lk := meta.Smallest.UserKey + if d1.cmp(meta.Smallest.UserKey, startkey) < 0 { + lk = startkey + } + hk := meta.Largest.UserKey + if d1.cmp(meta.Largest.UserKey, endkey) > 0 { + hk = endkey + } + m := SharedSSTMeta{ CreatorUniqueID: meta.CreatorUniqueID, PhysicalFileNum: meta.PhysicalFileNum, - Smallest: meta.Smallest, - Largest: meta.Smallest, // Only copy one key + Smallest: base.MakeInternalKey(lk, 0, InternalKeyKindMax), + Largest: base.MakeInternalKey(hk, 0, InternalKeyKindMax), FileSmallest: meta.FileSmallest, FileLargest: meta.FileLargest, } smeta = append(smeta, m) } + + f, err := fs1.Create("export") + require.NoError(t, err) + w := sstable.NewWriter(f, sstable.WriterOptions{}) + iterOpts := &IterOptions{SkipSharedFile: true, SharedFileCallback: cb} iter := d1.NewIter(iterOpts) require.NotEqual(t, nil, iter) - require.Equal(t, true, iter.First()) - for iter.Next() { - // do nothing + for i := iter.First(); i; i = iter.Next() { + if d1.cmp(iter.Key(), startkey) >= 0 && d1.cmp(iter.Key(), endkey) <= 0 { + w.Set(iter.Key(), iter.Value()) + } } require.NoError(t, iter.Close()) + require.NoError(t, w.Close()) + // copy "export" from d1's fs to d2's fs + vfs.CopyAcrossFS(fs1, "export", fs2, "export") + fs1.Remove("export") + + require.NoError(t, d2.Ingest([]string{"export"}, nil)) require.NoError(t, d2.Ingest(nil, smeta)) + t.Log("\033[1;32mDone\033[0m") t.Logf("Printing d2 LSM") printLevels(t, d2) + t.Log("\033[1;32mDone\033[0m") + + t.Logf("Verifying key visibility by iterating over d2's key space") + v := make(map[string]bool) + for i := start; i <= end; i++ { + k := string(decimalToString(i, digits)) + v[k] = false + } - t.Logf("Iterating over d2's key space") - iter = d2.NewIter(&IterOptions{}) + iter = d2.NewIter(&IterOptions{SkipSharedFile: false}) require.NotEqual(t, nil, iter) for i := iter.First(); i; i = iter.Next() { - t.Logf(" - key: %s", iter.Key()) + // first check not ingested keys (should not be included) + k := iter.Key() + if _, ok := v[string(k)]; ok { + v[string(k)] = true + } else { + t.Fatalf("out of range key %s found in d2", k) + } + } + t.Logf(" -- No out-of-range-key found in d2") + for i := start; i <= end; i++ { + k := string(decimalToString(i, digits)) + if !v[k] { + t.Fatalf("in range key %s not found in d2", k) + } } + t.Logf(" -- All in-range key found in d2") require.NoError(t, iter.Close()) + t.Log("\033[1;32mDone\033[0m") + + // Print out the contents in the directories + t.Logf("Printing d1 local files (.sst only):") + list, err := fs1.List("") + require.NoError(t, err) + for i := range list { + if strings.HasSuffix(list[i], ".sst") { + t.Logf(" - %s", list[i]) + } + } + t.Log("\033[1;32mDone\033[0m") + + t.Logf("Printing d2 local files (.sst only):") + list, err = fs2.List("") + require.NoError(t, err) + for i := range list { + if strings.HasSuffix(list[i], ".sst") { + t.Logf(" - %s", list[i]) + } + } + t.Log("\033[1;32mDone\033[0m") require.NoError(t, d1.Close()) require.NoError(t, d2.Close()) diff --git a/ingest.go b/ingest.go index 326eac16fd7..9711bcb3cd5 100644 --- a/ingest.go +++ b/ingest.go @@ -37,17 +37,7 @@ func ingestValidateKey(opts *Options, key *InternalKey, isShared bool) error { return base.CorruptionErrorf("pebble: external sstable has corrupted key: %s", key.Pretty(opts.Comparer.FormatKey)) } - // Default case: current db has no shared fs - expectedSeqNum := uint64(0) - // If the current DB has shared fs - if opts.SharedFS != nil { - // Imported sst is local - if !isShared { - expectedSeqNum = sstable.SeqNumZero - } - // Note: if the imported sst is shared, we expect 0 which is sstable.seqNumL6All - } - if key.SeqNum() != expectedSeqNum { + if key.SeqNum() != 0 { return base.CorruptionErrorf("pebble: external sstable has non-zero seqnum: %s", key.Pretty(opts.Comparer.FormatKey)) } @@ -529,6 +519,7 @@ func overlapWithIterator( } func ingestTargetLevel( + d *DB, newIters tableNewIters, iterOps IterOptions, cmp Compare, @@ -598,36 +589,44 @@ func ingestTargetLevel( targetLevel := 0 // Do we overlap with keys in L0? - iter := v.Levels[0].Iter() - for meta0 := iter.First(); meta0 != nil; meta0 = iter.Next() { - c1 := sstableKeyCompare(cmp, meta.Smallest, meta0.Largest) - c2 := sstableKeyCompare(cmp, meta.Largest, meta0.Smallest) - if c1 > 0 || c2 < 0 { - continue - } + // Note(chen): only non-shared ssts need to go through this path + if !meta.IsShared { + iter := v.Levels[0].Iter() + for meta0 := iter.First(); meta0 != nil; meta0 = iter.Next() { + c1 := sstableKeyCompare(cmp, meta.Smallest, meta0.Largest) + c2 := sstableKeyCompare(cmp, meta.Largest, meta0.Smallest) + if c1 > 0 || c2 < 0 { + continue + } - iter, rangeDelIter, err := newIters(iter.Current(), nil, internalIterOpts{}) - if err != nil { - return 0, err - } - overlap := overlapWithIterator(iter, &rangeDelIter, meta, cmp) - iter.Close() - if rangeDelIter != nil { - rangeDelIter.Close() - } - if overlap { - if meta.IsShared { - panic("ingestTargetLevel: shared sst has overlaps with L0 which is not allowed") + iter, rangeDelIter, err := newIters(iter.Current(), nil, internalIterOpts{}) + if err != nil { + return 0, err + } + overlap := overlapWithIterator(iter, &rangeDelIter, meta, cmp) + iter.Close() + if rangeDelIter != nil { + rangeDelIter.Close() + } + if overlap { + return targetLevel, nil } - return targetLevel, nil } } level := baseLevel - if meta.IsShared { - level = 5 + lowest := numLevels + + if d.opts.SharedFS != nil { + // Shared SST only ingest into L5/L6 + if meta.IsShared { + level = 5 + } else { + lowest = sharedLevel + } } - for ; level < numLevels; level++ { + + for ; level < lowest; level++ { levelIter := newLevelIter(iterOps, cmp, nil /* split */, newIters, v.Levels[level].Iter(), manifest.Level(level), nil) var rangeDelIter keyspan.FragmentIterator @@ -919,6 +918,7 @@ func (d *DB) ingest( } type ingestTargetLevelFunc func( + d *DB, newIters tableNewIters, iterOps IterOptions, cmp Compare, @@ -956,7 +956,7 @@ func (d *DB) ingestApply( m := meta[i] f := &ve.NewFiles[i] var err error - f.Level, err = findTargetLevel(d.newIters, iterOps, d.cmp, current, baseLevel, d.mu.compact.inProgress, m) + f.Level, err = findTargetLevel(d, d.newIters, iterOps, d.cmp, current, baseLevel, d.mu.compact.inProgress, m) if err != nil { d.mu.versions.logUnlock() return nil, err diff --git a/ingest_test.go b/ingest_test.go index fde30730f48..fc84021bbe6 100644 --- a/ingest_test.go +++ b/ingest_test.go @@ -559,7 +559,7 @@ func TestIngestTargetLevel(t *testing.T) { var buf bytes.Buffer for _, target := range strings.Split(td.Input, "\n") { meta := parseMeta(target) - level, err := ingestTargetLevel(d.newIters, IterOptions{logger: d.opts.Logger}, + level, err := ingestTargetLevel(d, d.newIters, IterOptions{logger: d.opts.Logger}, d.cmp, d.mu.versions.currentVersion(), 1, d.mu.compact.inProgress, meta) if err != nil { return err.Error() diff --git a/sstable/shared.go b/sstable/shared.go index 0636058fa39..308b5801cc7 100644 --- a/sstable/shared.go +++ b/sstable/shared.go @@ -113,9 +113,9 @@ func (i *tableIterator) getCurrUserKey() InternalKey { var k InternalKey switch i.Iterator.(type) { case *twoLevelIterator: - k = i.Iterator.(*twoLevelIterator).data.ikey + k = i.Iterator.(*twoLevelIterator).data.ikey.Clone() case *singleLevelIterator: - k = i.Iterator.(*singleLevelIterator).data.ikey + k = i.Iterator.(*singleLevelIterator).data.ikey.Clone() default: panic("tableIterator: i.Iterator is not singleLevelIterator or twoLevelIterator") } diff --git a/testdata/iterator_stats b/testdata/iterator_stats index 4c18e1fadf6..d5dae4feda4 100644 --- a/testdata/iterator_stats +++ b/testdata/iterator_stats @@ -6,7 +6,7 @@ set c 2 ingest ext1 ---- 6: - 000004:[a#1,MERGE-c#1,SET] + 000004:[a#4,MERGE-c#4,SET] iter first From 83a1783cac54eb904015464c8f98e116744bb75d Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Sun, 14 Aug 2022 17:05:05 -0500 Subject: [PATCH 6/6] *: doc for shared sst --- sharedsst.md | 261 +++++++++++++++++++++++++++++ testdata/compaction_read_triggered | 6 +- 2 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 sharedsst.md diff --git a/sharedsst.md b/sharedsst.md new file mode 100644 index 00000000000..07509e0a9af --- /dev/null +++ b/sharedsst.md @@ -0,0 +1,261 @@ +# Implementation Details of Shared SSTables + +This document describes the implementation details of shared SSTable functionality in Pebble. The current +implementation is more like a PoC and it may not work with all corner cases. However, the major body of this set of +functionalities has already been implemented and can be used for future reference, if needed. + +Shared SSTable is part of the disaggregated storage rfc. For more details of the disaggregated storage concept and +details, you can refer to the following records: + +- [RFC: disaggregated storage](https://github.com/cockroachdb/cockroach/pull/70419) +- [Virtual sstable proposal](https://github.com/cockroachdb/pebble/issues/1683) +- Disaggregated storage prototype (by Bilal Akhtar) + - [CockroachDB](https://github.com/itsbilal/cockroach/tree/disagg-storage-prototype) + - [Pebble](https://github.com/itsbilal/pebble/tree/disagg-storage-wip) + +## Problem Setup + +The disaggregated storage prototype has already added essential functions for storing L5/L6 SSTables in a Pebble +instance. When the `SharedFS` and `SharedDir` options are specified in a Pebble instance, the output SSTable of a +compaction will be moved to the shared file system, which is a `vfs`-like interface that represents a remote blob +storage (e.g., AWS S3). The shared storage is essentially shared amongst multiple Pebble instances, and the path a +SSTable is stored in an individual directory in the shared storage system with the creator Pebble's `UniqueID`, which +is a new property of a Pebble instance. Meanwhile, necessary functions in the CockroachDB codebase, like the cloud file +system wrapper, are implemented in the prototype. + +With the prototype, Pebble supports a secondary file system that it can read and write. Also, with the updated logic in +the compaction routines, the majority of all the data (L5/L6 data, which can stand for up to 99% of all the data) are +stored in a remote file system. + +Starting from here, there is still a significant part of the story that is not completed, the sharing of SSTables +between different Pebble instances that share the same secondary (shared) file system. By supporting SSTable sharing, +when one replica sends KV data to another replica, it does not need to scan all the KV pairs and send them over the +wire. Instead, it just need to send data for L0 to L4, which is only 1% of the total data set. For the data in L5 and +L6, it only sends some sort of metadata that represents a remote SSTable reference in the shared file system. The +receiver replica can just insert the reference into its MANIFEST and correctly retrieve the data in the future, without +writing real data into the disk. The benefits here are obvious - significantly reduing the mass of data tansfer when +sending data from one replica to another. This is especially useful for range rebalancing, or adding new nodes into a +range replication. + +To this end, we need to implement the following functionalities: + +- Part 1: A new virtual representation of a SSTable, that only appears in the MANIFEST but does not correspond to a + local file. It corresponds to an SSTable created by another Pebble instance and it is stored in the foreign Pebble's + directory in the shared file system. In the meantime, the local Pebble instance may only read a portion of the data + in the physical SSTable, so the virtual SSTable may have a different key boundary. Note that the virtual key boundary + must be inside the physical key boundary. + +- Part 2: A new internal iterator that handles local SSTables and shared SSTables differently. It follows the old + routines for local SSTables. For imported shared SSTables, it needs to maintain compatibility of the key visibility + and sequence numbers, because multiple LSM-Trees that share the same SSTable usually have different data and shapes. + +- Part 3: A new export and ingestion function that can let Pebble actually share data in action without sending real KV + data for shared SSTables if those SSTables are shared by the two instances. A new struct of shared SSTable metadata is + needed to represent a slice of virtual SSTables that correspond to multiple shared SSTables. In addition, the user + needs a special type of iterator that can expose the underlying SSTable information to a limited extent so that the + exported KV data/metadata can be constructed by the user correctly. + +## Major Updates + +The major modifications are in these files/directories: + +``` +─── pebble/ + ├── internal/ + │ └── manifest/ + │ ├── version.go + │ └── version_edit.go + ├── sstable/ + │ ├── reader.go + │ ├── shared.go + │ └── shared_test.go + ├── compaction.go + ├── disagg_test.go + └── ingest.go +``` + +The remaining contents in the commit history are mainly test case updates or minor, non-structural changes. Next, I +will document the major changes by different parts of the functionalities, as said in the previous section. + +### Part 1: Virtual SSTable Representation + +A virtual SSTable has a smaller boundary than the file's key boundary. To simplify the implementation, the `Smallest` +and `Largest` property of `FileMetadata` struct is reused to encode the virtual boundary. This guarantees the +compatibility with existing codebase, like the tree structure of a level. + +In `version.go`, the `FileMetadata` struct has more updated fields to record necessary metadata that defines: + +```golang +type FileMetadata struct { + ... + + // IsShared indicates whether the file is local-only or shared + // among Pebble instances + IsShared bool + + // CreatorUniqueID is the sst creator's UniqueID. + // This is used in MakeSharedSSTPath + CreatorUniqueID uint32 + + // PhysicalFileNum is the file's file num when it is created + // This is used in MakeSharedSSTPath + PhysicalFileNum base.FileNum + + // FileSmallest and FileLargest record the key boundaries of the file + // instead of the virtual boundaries that the current Pebble instance + // can read + FileSmallest InternalKey + FileLargest InternalKey +} +``` + +When `IsShared` is set to `true`, this file may be a externally created, virtual SSTable, or a locally created SSTable +but can be future shared with other Pebble instances. The `CreatorUniqueID` and `PhysicalFileNum` are the creator's +`UniqueID` and `FileNum` (of that SSTable). Specifically, the `FileNum` of a shared SSTable may be different from +the `PhysicalFileNum` on a virtual SSTable, as it is imported from another Pebble instance. Finally, the `FileSmallest` +and `FileLargest` cache the file key boundary of the SSTable file in the file system, and a virtual SSTable must +satisfy the invariant where the virtual boundaries are within the physical boundaries. + +The `version_edit.go` modifications implement the storing/loading of the corresponding `FileMetadata` updates in a +`VersionEdit`. + +### Part 2: Interal Iterators for Shared SSTable + +When iterating over a SSTable internally, in `sstable/reader.go` there are already two kinds of iterators to serve this +purpose, the `singleLevelIterator` and the `twoLevelIterator`. On top of them, the `compactionIter` and +`twoLevelCompactionIter` wrapped an internal single/two-level iterator and also record the bytes that have been +iterated over for compaction metrics. + +```golang +type tableIterator struct { + Iterator + rangeDelIter keyspan.FragmentIterator +} +``` + +Inside `sstable/shared.go`, a new wrapper iterator, namely `tableIterator`, is implemented. The `tableIterator` wraps +around a single/two-level iterator, and implements the `InternalIterator`/`Iterator` interface. The main job of this +struct is twofold. First, it applies virtual SSTable boundaries on the fly. Take the `SeekGE` function as an example: + +```golang +func (i *tableIterator) seekGEShared( + prefix, key []byte, flags base.SeekGEFlags, +) (*InternalKey, []byte) { + r := i.getReader() + ib := i.cmpSharedBound(key) + if ib > 0 { + // The search key overflows + i.setExhaustedBounds(+1) + return nil, nil + } else if ib < 0 { + // The search key underflows, substitute it with the lower shared bound + key = r.meta.Smallest.UserKey + } + + ... +} +``` + +At the beginning, the function checks whether the search key is inside or outside the virtual SSTable boundary. If it +overflows, `nil` is returned. If it underflows, the search key will be substituted by the lower bound and passed into +the underlying wrapped iterator. + +The second important job is to maintain sequence number compatibility. The details can be found in the disaggregated +storage rfc (`Reads -> Manufacturing sequence numbers for reads -> Solution`). In general, L5 point keys use sequence +number of 2, L5 range deletes use 1 and all L6 keys use 0. To this end, a `tableIterator` maintains an internal +`FragmentIterator` to iterate over L5 range deletes and pre-apply them. For each key, according to the rfc, only the +latest version of a key is exposed. As a result, a reposition of a `tableIterator` will automatically point to the +latest version of a key. + +The `Iterator` struct in the `sstable` package also requires an extra pair of `SetLevel` and `GetLevel` function. +Therefore, from the level iterator layer, the level of the SSTable that an iterator is reading can be passed in. See +the `newIters` function in `table_cache.go`. + +In `sstable/shared_test.go`, a unit test is implemented. + +### Part 3: Export and Ingestion + +The implementation also includes a set of updates to the `Ingest` function of a Pebble instance. When two Pebble +instances share the same secondary storage and one instance (the sender) wants to send data to another instance (the +receiver), it only needs to send L0-L4 data. For L5-L6, only metadata (a serialized representation of a set of virtual +SSTables) needs to be sent. + +To this end, the `Ingest` function now supports a secondary parameter: + +```golang +func (d *DB) Ingest(paths []string, smeta []SharedSSTMeta) error +``` + +where the `SharedSSTMeta` is a new struct that contains essential information to define a virtual (shared) SSTable. +The struct is defined as: + +```golang +type SharedSSTMeta struct { + CreatorUniqueID uint32 + PhysicalFileNum base.FileNum + Smallest InternalKey + Largest InternalKey + FileSmallest InternalKey + FileLargest InternalKey +} +``` + +It is also worth noting that the `ingestTargetLevel` function is updated accordingly. In general, L0-L4 SSTables can +only be ingested into L0-L4, and L5-L6 shared SSTables can only be ingested into L5 or L6. In addition, since now the +exported SSTables are not completely sequential (i.e., constructed by an iterator), when ingesting local SSTables and +shared SSTables at the same time, there might be overlaps in the ingested keys. As a result, currently, it is needed to +ingest local SSTables and shared SSTables separately (i.e., in two `Ingest` calls). + +To let the user construct exported SSTable data easily, a new set of options are added to user iterators: + +```golang +type IterOptions struct { + ... + + // SkipSharedFile determines whether the iterator will read shared sstables during + // its iteration. If the callback function is not nil, it will pass in the + // FileMetadata of the shared table for internal usage (e.g., constructing msg) + SkipSharedFile bool + SharedFileCallback func(*manifest.FileMetadata) + + ... +} +``` + +When the `SkipSharedFile` flag is set, the user iterator will skip shared SSTables when iterating over a range of user +keys. In the meantime, if `SharedFileCallback` is not `nil`, it will be called and the corresponding `FileMetadata` of +that shared SSTable will be passed into the function so that the user can construct the slice of `SharedSSTMeta`. If a +shared SSTable is not accessed by the level iterator, its (meta)data will not be exposed to the callback function. A +typical usage of these extra options is constructing an iterator and let a writer write in-range data into some +to-be-exported SSTables, then construct the slice of `SharedSSTMeta` when shared SSTables are accessed. + +In `disagg_test.go`, two unit tests are implemented. The `TestDBWithSharedSST` tests the overall shared SSTable +correctness, and the `TestIngestSharedSST` uses two Pebble instances and a contiguous set of integer keys to test the +ingestion of shared SSTables. + +## Future Work + +1. **CockroachDB Integration**. This includes two parts. The first part is to let CRDB export states/snapshots using shared + SSTable functionalities inside Pebble if two replicas share the same shared storage. Another import part is + shared reference count and obsolete data removal (GC). Currently, the removal of shared SSTables are disabled on + both its creator instance or any instance that shares it. The reason is that one node may exit the replication group + later but another one which is sharing some SSTables with it may not. Currently, there is no ownership concept in + shared SSTables, so no instance will initiate a deletion of shared SSTables (it will only be removed from a new + `Version`, if needed, but the files will always be there). A shared SSTable can be deleted only when no Pebble + instance is referencing it any more. This can be done by using a standalone thread (service) that maintains + reference information and performs background GC. Another potential solution is introducing a new Raft log type that + synchronizes the reference information amongst replicas, and the last dereferencer can safely remove the file on the + shared storage. This is feasible as virtual SSTables, in the context of CRDB, are usually organized in the unit of + key ranges. As a result, the reference information is part of a range's distributed state. + +2. **Range Key Support**. Currently, the implementation of shared SSTables assume that only point keys and range + deletes can appear in the system. As range keys are introduced, the implementation needs to be extended to further + adopt them. + +3. **Optimal Caching**. To let disaggregated storage enabled CRDB has throughput on-par with vanilla CRDB, we need a + viable and performant caching solution. It can be a per-LSM cache for shared SSTables, or a new distributed cache + service. Currently, if we enable a large enough persistent cache on each node that can store all the shared states, + the shared SSTable functionality can greatly accelerate range state replication. The shared storage overhead is + amortized by later read requests (aka. lazy loading). This can be further optimized. + + diff --git a/testdata/compaction_read_triggered b/testdata/compaction_read_triggered index 64d69793f1d..cf96e1349df 100644 --- a/testdata/compaction_read_triggered +++ b/testdata/compaction_read_triggered @@ -8,7 +8,7 @@ a.SET.54:a b.SET.4:b 5: 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#58,SET-b#8,SET] + 000005:[a#57,SET-b#7,SET] add-read-compaction 5: a-b 000004 @@ -47,7 +47,7 @@ a.SET.54:a b.SET.4:b 5: 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#58,SET-b#8,SET] + 000005:[a#57,SET-b#7,SET] add-read-compaction flushing=true 5: a-b 000004 @@ -70,7 +70,7 @@ version 5: 000004:[a#58,SET-b#8,SET] 6: - 000005:[a#58,SET-b#8,SET] + 000005:[a#57,SET-b#7,SET] add-read-compaction flushing=false ----