Skip to content

Commit 781add9

Browse files
committed
db: extract VersionEdit logic from exciseTable
Add a separate function for applying an excise to a `VersionEdit`; `exciseTable` is simplified to return the left and right tables.
1 parent a54616a commit 781add9

File tree

4 files changed

+105
-93
lines changed

4 files changed

+105
-93
lines changed

compaction.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,10 +1350,11 @@ func (d *DB) runIngestFlush(c *compaction) (*manifest.VersionEdit, error) {
13501350
// Iterate through all levels and find files that intersect with exciseSpan.
13511351
for layer, ls := range c.version.AllLevelsAndSublevels() {
13521352
for m := range ls.Overlaps(d.cmp, ingestFlushable.exciseSpan.UserKeyBounds()).All() {
1353-
newFiles, err := d.exciseTable(context.TODO(), ingestFlushable.exciseSpan.UserKeyBounds(), m, ve, layer.Level())
1353+
leftTable, rightTable, err := d.exciseTable(context.TODO(), ingestFlushable.exciseSpan.UserKeyBounds(), m, layer.Level())
13541354
if err != nil {
13551355
return nil, err
13561356
}
1357+
newFiles := applyExciseToVersionEdit(ve, m, leftTable, rightTable, layer.Level())
13571358
replacedFiles[m.FileNum] = newFiles
13581359
updateLevelMetricsOnExcise(m, layer.Level(), newFiles)
13591360
}
@@ -2732,16 +2733,17 @@ func (d *DB) applyHintOnFile(
27322733
return nil, nil
27332734
}
27342735
// The hint overlaps with only a part of the file, not the entirety of it. We need
2735-
// to use d.excise. (hintOverlap == hintExcisesFile)
2736+
// to use d.exciseTable. (hintOverlap == hintExcisesFile)
27362737
if d.FormatMajorVersion() < FormatVirtualSSTables {
27372738
panic("pebble: delete-only compaction hint excising a file is not supported in this version")
27382739
}
27392740

27402741
levelMetrics.TablesExcised++
2741-
newFiles, err = d.exciseTable(context.TODO(), base.UserKeyBoundsEndExclusive(h.start, h.end), f, ve, level)
2742+
leftTable, rightTable, err := d.exciseTable(context.TODO(), base.UserKeyBoundsEndExclusive(h.start, h.end), f, level)
27422743
if err != nil {
27432744
return nil, errors.Wrap(err, "error when running excise for delete-only compaction")
27442745
}
2746+
newFiles = applyExciseToVersionEdit(ve, f, leftTable, rightTable, level)
27452747
return newFiles, nil
27462748
}
27472749

excise.go

Lines changed: 90 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,27 @@ func (d *DB) Excise(ctx context.Context, span KeyRange) error {
4444
return err
4545
}
4646

47-
// exciseTable updates ve to include a replacement of the table m with new
48-
// virtual sstables that exclude exciseSpan, returning a slice of newly-created
49-
// files if any. If the entirety of m is deleted by exciseSpan, no new sstables
50-
// are added and m is deleted. Note that ve is updated in-place.
47+
// exciseTable initializes up to two virtual tables for what is left over after
48+
// excising the given span from the table.
49+
//
50+
// Returns the left and/or right tables, if they exist.
5151
//
5252
// The file bounds must overlap with the excise span.
5353
//
5454
// This method is agnostic to whether d.mu is held or not. Some cases call it with
5555
// the db mutex held (eg. ingest-time excises), while in the case of compactions
5656
// the mutex is not held.
5757
func (d *DB) exciseTable(
58-
ctx context.Context, exciseSpan base.UserKeyBounds, m *tableMetadata, ve *versionEdit, level int,
59-
) ([]manifest.NewTableEntry, error) {
60-
numCreatedFiles := 0
58+
ctx context.Context, exciseSpan base.UserKeyBounds, m *tableMetadata, level int,
59+
) (leftTable, rightTable *tableMetadata, _ error) {
6160
// Check if there's actually an overlap between m and exciseSpan.
6261
mBounds := m.UserKeyBounds()
6362
if !exciseSpan.Overlaps(d.cmp, &mBounds) {
64-
return nil, base.AssertionFailedf("excise span does not overlap table")
63+
return nil, nil, base.AssertionFailedf("excise span does not overlap table")
6564
}
66-
ve.DeletedTables[deletedFileEntry{
67-
Level: level,
68-
FileNum: m.FileNum,
69-
}] = m
7065
// Fast path: m sits entirely within the exciseSpan, so just delete it.
7166
if exciseSpan.ContainsInternalKey(d.cmp, m.Smallest) && exciseSpan.ContainsInternalKey(d.cmp, m.Largest) {
72-
return nil, nil
67+
return nil, nil, nil
7368
}
7469

7570
// The file partially overlaps the excise span; we will need to open it to
@@ -79,11 +74,10 @@ func (d *DB) exciseTable(
7974
layer: manifest.Level(level),
8075
}, internalIterOpts{}, iterPointKeys|iterRangeDeletions|iterRangeKeys)
8176
if err != nil {
82-
return nil, err
77+
return nil, nil, err
8378
}
8479
defer iters.CloseAll()
8580

86-
needsBacking := false
8781
// Create a file to the left of the excise span, if necessary.
8882
// The bounds of this file will be [m.Smallest, lastKeyBefore(exciseSpan.Start)].
8983
//
@@ -107,7 +101,7 @@ func (d *DB) exciseTable(
107101
// have changed since our previous calculation. Do this optimiaztino as part of
108102
// https://github.com/cockroachdb/pebble/issues/2112 .
109103
if d.cmp(m.Smallest.UserKey, exciseSpan.Start) < 0 {
110-
leftFile := &tableMetadata{
104+
leftTable = &tableMetadata{
111105
Virtual: true,
112106
FileBacking: m.FileBacking,
113107
FileNum: d.mu.versions.getNextFileNum(),
@@ -118,93 +112,73 @@ func (d *DB) exciseTable(
118112
LargestSeqNumAbsolute: m.LargestSeqNumAbsolute,
119113
SyntheticPrefixAndSuffix: m.SyntheticPrefixAndSuffix,
120114
}
121-
if err := determineLeftTableBounds(d.cmp, m, leftFile, exciseSpan.Start, iters); err != nil {
122-
return nil, err
115+
if err := determineLeftTableBounds(d.cmp, m, leftTable, exciseSpan.Start, iters); err != nil {
116+
return nil, nil, err
123117
}
124118

125-
if leftFile.HasRangeKeys || leftFile.HasPointKeys {
119+
if leftTable.HasRangeKeys || leftTable.HasPointKeys {
126120
var err error
127-
leftFile.Size, err = d.fileCache.estimateSize(m, leftFile.Smallest.UserKey, leftFile.Largest.UserKey)
121+
leftTable.Size, err = d.fileCache.estimateSize(m, leftTable.Smallest.UserKey, leftTable.Largest.UserKey)
128122
if err != nil {
129-
return nil, err
123+
return nil, nil, err
130124
}
131-
if leftFile.Size == 0 {
125+
if leftTable.Size == 0 {
132126
// On occasion, estimateSize gives us a low estimate, i.e. a 0 file size,
133127
// such as if the excised file only has range keys/dels and no point
134128
// keys. This can cause panics in places where we divide by file sizes.
135129
// Correct for it here.
136-
leftFile.Size = 1
130+
leftTable.Size = 1
137131
}
138-
if err := leftFile.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
139-
return nil, err
132+
if err := leftTable.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
133+
return nil, nil, err
140134
}
141-
leftFile.ValidateVirtual(m)
142-
ve.NewTables = append(ve.NewTables, newTableEntry{Level: level, Meta: leftFile})
143-
needsBacking = true
144-
numCreatedFiles++
135+
leftTable.ValidateVirtual(m)
136+
} else {
137+
leftTable = nil
145138
}
146139
}
147140
// Create a file to the right, if necessary.
148-
if exciseSpan.ContainsInternalKey(d.cmp, m.Largest) {
149-
// No key exists to the right of the excise span in this file.
150-
if needsBacking && !m.Virtual {
151-
// If m is virtual, then its file backing is already known to the manifest.
152-
// We don't need to create another file backing. Note that there must be
153-
// only one CreatedBackingTables entry per backing sstable. This is
154-
// indicated by the VersionEdit.CreatedBackingTables invariant.
155-
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
156-
}
157-
return ve.NewTables[len(ve.NewTables)-numCreatedFiles:], nil
158-
}
159-
// Create a new file, rightFile, between [firstKeyAfter(exciseSpan.End), m.Largest].
160-
//
161-
// See comment before the definition of leftFile for the motivation behind
162-
// calculating tight user-key bounds.
163-
rightFile := &tableMetadata{
164-
Virtual: true,
165-
FileBacking: m.FileBacking,
166-
FileNum: d.mu.versions.getNextFileNum(),
167-
// Note that these are loose bounds for smallest/largest seqnums, but they're
168-
// sufficient for maintaining correctness.
169-
SmallestSeqNum: m.SmallestSeqNum,
170-
LargestSeqNum: m.LargestSeqNum,
171-
LargestSeqNumAbsolute: m.LargestSeqNumAbsolute,
172-
SyntheticPrefixAndSuffix: m.SyntheticPrefixAndSuffix,
173-
}
174-
if err := determineRightTableBounds(d.cmp, m, rightFile, exciseSpan.End, iters); err != nil {
175-
return nil, err
176-
}
177-
if rightFile.HasRangeKeys || rightFile.HasPointKeys {
178-
var err error
179-
rightFile.Size, err = d.fileCache.estimateSize(m, rightFile.Smallest.UserKey, rightFile.Largest.UserKey)
180-
if err != nil {
181-
return nil, err
141+
if !exciseSpan.End.IsUpperBoundForInternalKey(d.cmp, m.Largest) {
142+
// Create a new file, rightFile, between [firstKeyAfter(exciseSpan.End), m.Largest].
143+
//
144+
// See comment before the definition of leftFile for the motivation behind
145+
// calculating tight user-key bounds.
146+
rightTable = &tableMetadata{
147+
Virtual: true,
148+
FileBacking: m.FileBacking,
149+
FileNum: d.mu.versions.getNextFileNum(),
150+
// Note that these are loose bounds for smallest/largest seqnums, but they're
151+
// sufficient for maintaining correctness.
152+
SmallestSeqNum: m.SmallestSeqNum,
153+
LargestSeqNum: m.LargestSeqNum,
154+
LargestSeqNumAbsolute: m.LargestSeqNumAbsolute,
155+
SyntheticPrefixAndSuffix: m.SyntheticPrefixAndSuffix,
182156
}
183-
if rightFile.Size == 0 {
184-
// On occasion, estimateSize gives us a low estimate, i.e. a 0 file size,
185-
// such as if the excised file only has range keys/dels and no point keys.
186-
// This can cause panics in places where we divide by file sizes. Correct
187-
// for it here.
188-
rightFile.Size = 1
157+
if err := determineRightTableBounds(d.cmp, m, rightTable, exciseSpan.End, iters); err != nil {
158+
return nil, nil, err
189159
}
190-
if err := rightFile.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
191-
return nil, err
160+
if rightTable.HasRangeKeys || rightTable.HasPointKeys {
161+
var err error
162+
rightTable.Size, err = d.fileCache.estimateSize(m, rightTable.Smallest.UserKey, rightTable.Largest.UserKey)
163+
if err != nil {
164+
return nil, nil, err
165+
}
166+
if rightTable.Size == 0 {
167+
// On occasion, estimateSize gives us a low estimate, i.e. a 0 file size,
168+
// such as if the excised file only has range keys/dels and no point keys.
169+
// This can cause panics in places where we divide by file sizes. Correct
170+
// for it here.
171+
rightTable.Size = 1
172+
}
173+
if err := rightTable.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
174+
return nil, nil, err
175+
}
176+
rightTable.ValidateVirtual(m)
177+
} else {
178+
rightTable = nil
192179
}
193-
rightFile.ValidateVirtual(m)
194-
ve.NewTables = append(ve.NewTables, newTableEntry{Level: level, Meta: rightFile})
195-
needsBacking = true
196-
numCreatedFiles++
197-
}
198-
199-
if needsBacking && !m.Virtual {
200-
// If m is virtual, then its file backing is already known to the manifest.
201-
// We don't need to create another file backing. Note that there must be
202-
// only one CreatedBackingTables entry per backing sstable. This is
203-
// indicated by the VersionEdit.CreatedBackingTables invariant.
204-
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
205180
}
206-
207-
return ve.NewTables[len(ve.NewTables)-numCreatedFiles:], nil
181+
return leftTable, rightTable, nil
208182
}
209183

210184
// exciseOverlapBounds examines the provided list of snapshots, examining each
@@ -363,3 +337,34 @@ func determineRightTableBounds(
363337
}
364338
return nil
365339
}
340+
341+
// applyExciseToVersionEdit updates ve with a table deletion for the original
342+
// table and table additions for the left and/or right table.
343+
//
344+
// Either or both of leftTable/rightTable can be nil.
345+
func applyExciseToVersionEdit(
346+
ve *versionEdit, originalTable, leftTable, rightTable *tableMetadata, level int,
347+
) (newFiles []manifest.NewTableEntry) {
348+
ve.DeletedTables[deletedFileEntry{
349+
Level: level,
350+
FileNum: originalTable.FileNum,
351+
}] = originalTable
352+
if leftTable == nil && rightTable == nil {
353+
return
354+
}
355+
if !originalTable.Virtual {
356+
// If the original table was virtual, then its file backing is already known
357+
// to the manifest; we don't need to create another file backing. Note that
358+
// there must be only one CreatedBackingTables entry per backing sstable.
359+
// This is indicated by the VersionEdit.CreatedBackingTables invariant.
360+
ve.CreatedBackingTables = append(ve.CreatedBackingTables, originalTable.FileBacking)
361+
}
362+
originalLen := len(ve.NewTables)
363+
if leftTable != nil {
364+
ve.NewTables = append(ve.NewTables, newTableEntry{Level: level, Meta: leftTable})
365+
}
366+
if rightTable != nil {
367+
ve.NewTables = append(ve.NewTables, newTableEntry{Level: level, Meta: rightTable})
368+
}
369+
return ve.NewTables[originalLen:]
370+
}

ingest.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,10 +1867,11 @@ func (d *DB) ingestSplit(
18671867
// as we're guaranteed to not have any data overlap between splitFile and
18681868
// s.ingestFile. d.excise will return an error if we pass an inclusive user
18691869
// key bound _and_ we end up seeing data overlap at the end key.
1870-
added, err := d.exciseTable(ctx, base.UserKeyBoundsFromInternal(s.ingestFile.Smallest, s.ingestFile.Largest), splitFile, ve, s.level)
1870+
leftTable, rightTable, err := d.exciseTable(ctx, base.UserKeyBoundsFromInternal(s.ingestFile.Smallest, s.ingestFile.Largest), splitFile, s.level)
18711871
if err != nil {
18721872
return err
18731873
}
1874+
added := applyExciseToVersionEdit(ve, splitFile, leftTable, rightTable, s.level)
18741875
replacedFiles[splitFile.FileNum] = added
18751876
for i := range added {
18761877
addedBounds := added[i].Meta.UserKeyBounds()
@@ -2098,10 +2099,11 @@ func (d *DB) ingestApply(
20982099
// out.
20992100
for layer, ls := range current.AllLevelsAndSublevels() {
21002101
for m := range ls.Overlaps(d.cmp, exciseSpan.UserKeyBounds()).All() {
2101-
newFiles, err := d.exciseTable(ctx, exciseSpan.UserKeyBounds(), m, ve, layer.Level())
2102+
leftTable, rightTable, err := d.exciseTable(ctx, exciseSpan.UserKeyBounds(), m, layer.Level())
21022103
if err != nil {
21032104
return nil, err
21042105
}
2106+
newFiles := applyExciseToVersionEdit(ve, m, leftTable, rightTable, layer.Level())
21052107
replacedFiles[m.FileNum] = newFiles
21062108
updateLevelMetricsOnExcise(m, layer.Level(), newFiles)
21072109
}

ingest_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -895,10 +895,11 @@ func TestExcise(t *testing.T) {
895895
for l, ls := range current.AllLevelsAndSublevels() {
896896
iter := ls.Iter()
897897
for m := iter.SeekGE(d.cmp, exciseSpan.Start); m != nil && d.cmp(m.Smallest.UserKey, exciseSpan.End) < 0; m = iter.Next() {
898-
_, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, ve, l.Level())
898+
leftTable, rightTable, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, l.Level())
899899
if err != nil {
900900
td.Fatalf(t, "error when excising %s: %s", m.FileNum, err.Error())
901901
}
902+
applyExciseToVersionEdit(ve, m, leftTable, rightTable, l.Level())
902903
}
903904
}
904905

@@ -1245,13 +1246,14 @@ func testIngestSharedImpl(
12451246
for level := range current.Levels {
12461247
iter := current.Levels[level].Iter()
12471248
for m := iter.SeekGE(d.cmp, exciseSpan.Start); m != nil && d.cmp(m.Smallest.UserKey, exciseSpan.End) < 0; m = iter.Next() {
1248-
_, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, ve, level)
1249+
leftTable, rightTable, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, level)
12491250
if err != nil {
12501251
d.mu.Lock()
12511252
d.mu.versions.logUnlock()
12521253
d.mu.Unlock()
12531254
return fmt.Sprintf("error when excising %s: %s", m.FileNum, err.Error())
12541255
}
1256+
applyExciseToVersionEdit(ve, m, leftTable, rightTable, level)
12551257
}
12561258
}
12571259
d.mu.Lock()
@@ -1749,13 +1751,14 @@ func TestConcurrentExcise(t *testing.T) {
17491751
for level := range current.Levels {
17501752
iter := current.Levels[level].Iter()
17511753
for m := iter.SeekGE(d.cmp, exciseSpan.Start); m != nil && d.cmp(m.Smallest.UserKey, exciseSpan.End) < 0; m = iter.Next() {
1752-
_, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, ve, level)
1754+
leftTable, rightTable, err := d.exciseTable(context.Background(), exciseSpan.UserKeyBounds(), m, level)
17531755
if err != nil {
17541756
d.mu.Lock()
17551757
d.mu.versions.logUnlock()
17561758
d.mu.Unlock()
17571759
return fmt.Sprintf("error when excising %s: %s", m.FileNum, err.Error())
17581760
}
1761+
applyExciseToVersionEdit(ve, m, leftTable, rightTable, level)
17591762
}
17601763
}
17611764
d.mu.Lock()

0 commit comments

Comments
 (0)