Repository: cockroachdb/pebble
Pull request #3883 (2024-08-22 21:22:56)

sstable: convert RawWriter into an interface
Convert RawWriter into an interface that's satisfied by the existing RawRowWriter type. Future work will introduce a RawColWriter that writes sstables with columnar blocks.

cockroach-teamcity: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cockroachdb/pebble/3883)
<!-- Reviewable:end -->

jbowens: TFTR!

Patch:
diff --git a/compaction.go b/compaction.go
index 7947748a..fe6024bc 100644
--- a/compaction.go
+++ b/compaction.go
@@ -2666,7 +2666,7 @@ func (c *compaction) makeVersionEdit(result compact.Result) (*versionEdit, error
 // compaction or flush.
 func (d *DB) newCompactionOutput(
 	jobID JobID, c *compaction, writerOpts sstable.WriterOptions,
-) (objstorage.ObjectMetadata, *sstable.RawWriter, CPUWorkHandle, error) {
+) (objstorage.ObjectMetadata, *sstable.RawRowWriter, CPUWorkHandle, error) {
 	d.mu.Lock()
 	diskFileNum := d.mu.versions.getNextDiskFileNum()
 	d.mu.Unlock()
diff --git a/internal/compact/run.go b/internal/compact/run.go
index 02a638da..75439a1e 100644
--- a/internal/compact/run.go
+++ b/internal/compact/run.go
@@ -132,7 +132,7 @@ func (r *Runner) MoreDataToWrite() bool {
 // Result.Tables. Should only be called if MoreDataToWrite() returned true.
 //
 // WriteTable always closes the Writer.
-func (r *Runner) WriteTable(objMeta objstorage.ObjectMetadata, tw *sstable.RawWriter) {
+func (r *Runner) WriteTable(objMeta objstorage.ObjectMetadata, tw *sstable.RawRowWriter) {
 	if r.err != nil {
 		panic("error already encountered")
 	}
@@ -159,7 +159,7 @@ func (r *Runner) WriteTable(objMeta objstorage.ObjectMetadata, tw *sstable.RawWr
 	r.tables[len(r.tables)-1].WriterMeta = *writerMeta
 }
 
-func (r *Runner) writeKeysToTable(tw *sstable.RawWriter) (splitKey []byte, _ error) {
+func (r *Runner) writeKeysToTable(tw *sstable.RawRowWriter) (splitKey []byte, _ error) {
 	firstKey := base.MinUserKey(r.cmp, spanStartOrNil(&r.lastRangeDelSpan), spanStartOrNil(&r.lastRangeKeySpan))
 	if r.key != nil && firstKey == nil {
 		firstKey = r.key.UserKey
diff --git a/internal/compact/spans.go b/internal/compact/spans.go
index d8d5bd47..41206e19 100644
--- a/internal/compact/spans.go
+++ b/internal/compact/spans.go
@@ -182,7 +182,7 @@ func (c *RangeKeySpanCompactor) elideInLastStripe(
 //
 // The span can contain either only RANGEDEL keys or only range keys.
 func SplitAndEncodeSpan(
-	cmp base.Compare, span *keyspan.Span, upToKey []byte, tw *sstable.RawWriter,
+	cmp base.Compare, span *keyspan.Span, upToKey []byte, tw *sstable.RawRowWriter,
 ) error {
 	if span.Empty() {
 		return nil
diff --git a/metamorphic/build.go b/metamorphic/build.go
index f6909e48..7040a3c2 100644
--- a/metamorphic/build.go
+++ b/metamorphic/build.go
@@ -96,7 +96,7 @@ func writeSSTForIngestion(
 		if err != nil {
 			return nil, err
 		}
-		if err := w.Raw().Add(k.K, valBytes); err != nil {
+		if err := w.Raw().AddWithForceObsolete(k.K, valBytes, false); err != nil {
 			return nil, err
 		}
 	}
diff --git a/metamorphic/ops.go b/metamorphic/ops.go
index 84572330..10337389 100644
--- a/metamorphic/ops.go
+++ b/metamorphic/ops.go
@@ -1928,7 +1928,7 @@ func (r *replicateOp) runSharedReplicate(
 			if err != nil {
 				panic(err)
 			}
-			return w.Raw().Add(base.MakeInternalKey(key.UserKey, 0, key.Kind()), val)
+			return w.Raw().AddWithForceObsolete(base.MakeInternalKey(key.UserKey, 0, key.Kind()), val, false)
 		},
 		func(start, end []byte, seqNum base.SeqNum) error {
 			return w.DeleteRange(start, end)
@@ -1991,7 +1991,7 @@ func (r *replicateOp) runExternalReplicate(
 			if err != nil {
 				panic(err)
 			}
-			return w.Raw().Add(base.MakeInternalKey(key.UserKey, 0, key.Kind()), val)
+			return w.Raw().AddWithForceObsolete(base.MakeInternalKey(key.UserKey, 0, key.Kind()), val, false)
 		},
 		func(start, end []byte, seqNum base.SeqNum) error {
 			return w.DeleteRange(start, end)
diff --git a/sstable/raw_writer.go b/sstable/rowblk_writer.go
similarity index 94%
rename from sstable/raw_writer.go
rename to sstable/rowblk_writer.go
index 56aec74b..e06e81e3 100644
--- a/sstable/raw_writer.go
+++ b/sstable/rowblk_writer.go
@@ -33,83 +33,6 @@ const encodedBHPEstimatedSize = binary.MaxVarintLen64 * 2
 
 var errWriterClosed = errors.New("pebble: writer is closed")
 
-// WriterMetadata holds info about a finished sstable.
-type WriterMetadata struct {
-	Size          uint64
-	SmallestPoint InternalKey
-	// LargestPoint, LargestRangeKey, LargestRangeDel should not be accessed
-	// before Writer.Close is called, because they may only be set on
-	// Writer.Close.
-	LargestPoint     InternalKey
-	SmallestRangeDel InternalKey
-	LargestRangeDel  InternalKey
-	SmallestRangeKey InternalKey
-	LargestRangeKey  InternalKey
-	HasPointKeys     bool
-	HasRangeDelKeys  bool
-	HasRangeKeys     bool
-	SmallestSeqNum   base.SeqNum
-	LargestSeqNum    base.SeqNum
-	Properties       Properties
-}
-
-// SetSmallestPointKey sets the smallest point key to the given key.
-// NB: this method set the "absolute" smallest point key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetSmallestPointKey(k InternalKey) {
-	m.SmallestPoint = k
-	m.HasPointKeys = true
-}
-
-// SetSmallestRangeDelKey sets the smallest rangedel key to the given key.
-// NB: this method set the "absolute" smallest rangedel key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetSmallestRangeDelKey(k InternalKey) {
-	m.SmallestRangeDel = k
-	m.HasRangeDelKeys = true
-}
-
-// SetSmallestRangeKey sets the smallest range key to the given key.
-// NB: this method set the "absolute" smallest range key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetSmallestRangeKey(k InternalKey) {
-	m.SmallestRangeKey = k
-	m.HasRangeKeys = true
-}
-
-// SetLargestPointKey sets the largest point key to the given key.
-// NB: this method set the "absolute" largest point key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetLargestPointKey(k InternalKey) {
-	m.LargestPoint = k
-	m.HasPointKeys = true
-}
-
-// SetLargestRangeDelKey sets the largest rangedel key to the given key.
-// NB: this method set the "absolute" largest rangedel key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetLargestRangeDelKey(k InternalKey) {
-	m.LargestRangeDel = k
-	m.HasRangeDelKeys = true
-}
-
-// SetLargestRangeKey sets the largest range key to the given key.
-// NB: this method set the "absolute" largest range key. Any existing key is
-// overridden.
-func (m *WriterMetadata) SetLargestRangeKey(k InternalKey) {
-	m.LargestRangeKey = k
-	m.HasRangeKeys = true
-}
-
-func (m *WriterMetadata) updateSeqNum(seqNum base.SeqNum) {
-	if m.SmallestSeqNum > seqNum {
-		m.SmallestSeqNum = seqNum
-	}
-	if m.LargestSeqNum < seqNum {
-		m.LargestSeqNum = seqNum
-	}
-}
-
 // flushDecisionOptions holds parameters to inform the sstable block flushing
 // heuristics.
 type flushDecisionOptions struct {
@@ -120,8 +43,10 @@ type flushDecisionOptions struct {
 	sizeClassAwareThreshold int
 }
 
-// RawWriter is a table writer.
-type RawWriter struct {
+// RawRowWriter is a sstable RawWriter that writes sstables with row-oriented
+// blocks. All table formats TableFormatPebblev4 and earlier write row-oriented
+// blocks and use RawRowWriter.
+type RawRowWriter struct {
 	layout layoutWriter
 	meta   WriterMetadata
 	err    error
@@ -233,7 +158,7 @@ type coordinationState struct {
 	sizeEstimate dataBlockEstimates
 }
 
-func (c *coordinationState) init(parallelismEnabled bool, writer *RawWriter) {
+func (c *coordinationState) init(parallelismEnabled bool, writer *RawRowWriter) {
 	c.parallelismEnabled = parallelismEnabled
 	// useMutex is false regardless of parallelismEnabled, because we do not do
 	// parallel compression yet.
@@ -659,7 +584,7 @@ type indexBlockAndBlockProperties struct {
 // added ordered by their start key, but they can be added out of order from
 // point entries. Additionally, range deletion tombstones must be fragmented
 // (i.e. by keyspan.Fragmenter).
-func (w *RawWriter) Add(key InternalKey, value []byte) error {
+func (w *RawRowWriter) Add(key InternalKey, value []byte) error {
 	if w.isStrictObsolete {
 		return errors.Errorf("use AddWithForceObsolete")
 	}
@@ -677,7 +602,9 @@ func (w *RawWriter) Add(key InternalKey, value []byte) error {
 // that strict-obsolete ssts must satisfy. S2, due to RANGEDELs, is solely the
 // responsibility of the caller. S1 is solely the responsibility of the
 // callee.
-func (w *RawWriter) AddWithForceObsolete(key InternalKey, value []byte, forceObsolete bool) error {
+func (w *RawRowWriter) AddWithForceObsolete(
+	key InternalKey, value []byte, forceObsolete bool,
+) error {
 	if w.err != nil {
 		return w.err
 	}
@@ -695,7 +622,7 @@ func (w *RawWriter) AddWithForceObsolete(key InternalKey, value []byte, forceObs
 	return w.addPoint(key, value, forceObsolete)
 }
 
-func (w *RawWriter) makeAddPointDecisionV2(key InternalKey) error {
+func (w *RawRowWriter) makeAddPointDecisionV2(key InternalKey) error {
 	prevTrailer := w.lastPointKeyInfo.trailer
 	w.lastPointKeyInfo.trailer = key.Trailer
 	if w.dataBlockBuf.dataBlock.EntryCount() == 0 {
@@ -715,7 +642,7 @@ func (w *RawWriter) makeAddPointDecisionV2(key InternalKey) error {
 }
 
 // REQUIRES: at least one point has been written to the Writer.
-func (w *RawWriter) getLastPointUserKey() []byte {
+func (w *RawRowWriter) getLastPointUserKey() []byte {
 	if w.dataBlockBuf.dataBlock.EntryCount() == 0 {
 		panic(errors.AssertionFailedf("no point keys added to writer"))
 	}
@@ -723,7 +650,7 @@ func (w *RawWriter) getLastPointUserKey() []byte {
 }
 
 // REQUIRES: w.tableFormat >= TableFormatPebblev3
-func (w *RawWriter) makeAddPointDecisionV3(
+func (w *RawRowWriter) makeAddPointDecisionV3(
 	key InternalKey, valueLen int,
 ) (setHasSamePrefix bool, writeToValueBlock bool, isObsolete bool, err error) {
 	prevPointKeyInfo := w.lastPointKeyInfo
@@ -839,7 +766,7 @@ func (w *RawWriter) makeAddPointDecisionV3(
 	return setHasSamePrefix, considerWriteToValueBlock, isObsolete, nil
 }
 
-func (w *RawWriter) addPoint(key InternalKey, value []byte, forceObsolete bool) error {
+func (w *RawRowWriter) addPoint(key InternalKey, value []byte, forceObsolete bool) error {
 	if w.isStrictObsolete && key.Kind() == InternalKeyKindMerge {
 		return errors.Errorf("MERGE not supported in a strict-obsolete sstable")
 	}
@@ -968,7 +895,7 @@ func (w *RawWriter) addPoint(key InternalKey, value []byte, forceObsolete bool)
 	return nil
 }
 
-func (w *RawWriter) prettyTombstone(k InternalKey, value []byte) fmt.Formatter {
+func (w *RawRowWriter) prettyTombstone(k InternalKey, value []byte) fmt.Formatter {
 	return keyspan.Span{
 		Start: k.UserKey,
 		End:   value,
@@ -976,7 +903,7 @@ func (w *RawWriter) prettyTombstone(k InternalKey, value []byte) fmt.Formatter {
 	}.Pretty(w.formatKey)
 }
 
-func (w *RawWriter) addTombstone(key InternalKey, value []byte) error {
+func (w *RawRowWriter) addTombstone(key InternalKey, value []byte) error {
 	if !w.disableKeyOrderChecks && w.rangeDelBlock.EntryCount() > 0 {
 		// Check that tombstones are being added in fragmented order. If the two
 		// tombstones overlap, their start and end keys must be identical.
@@ -1034,7 +961,7 @@ func (w *RawWriter) addTombstone(key InternalKey, value []byte) error {
 	return nil
 }
 
-func (w *RawWriter) encodeFragmentedRangeKeySpan(span keyspan.Span) {
+func (w *RawRowWriter) encodeFragmentedRangeKeySpan(span keyspan.Span) {
 	// This method is the emit function of the Fragmenter.
 	//
 	// NB: The span should only contain range keys and be internally consistent
@@ -1061,7 +988,7 @@ func (w *RawWriter) encodeFragmentedRangeKeySpan(span keyspan.Span) {
 // spans that are perfectly aligned (same start and end keys), spans may not
 // overlap. Range keys may be added out of order relative to point keys and
 // range deletions.
-func (w *RawWriter) addRangeKey(key InternalKey, value []byte) error {
+func (w *RawRowWriter) addRangeKey(key InternalKey, value []byte) error {
 	if !w.disableKeyOrderChecks && w.rangeKeyBlock.EntryCount() > 0 {
 		prevStartKey := w.rangeKeyBlock.CurKey()
 		prevEndKey, _, err := rangekey.DecodeEndKey(prevStartKey.Kind(), w.rangeKeyBlock.CurValue())
@@ -1143,7 +1070,7 @@ func (w *RawWriter) addRangeKey(key InternalKey, value []byte) error {
 	return nil
 }
 
-func (w *RawWriter) maybeAddToFilter(key []byte) {
+func (w *RawRowWriter) maybeAddToFilter(key []byte) {
 	if w.filter != nil {
 		prefix := key[:w.split(key)]
 		w.filter.addKey(prefix)
@@ -1155,7 +1082,7 @@ func (w *RawWriter) maybeAddToFilter(key []byte) {
 // the deletion size exceeds a threshold. It should be called after the
 // data block has been finished.
 // Invariant: w.dataBlockBuf.uncompressed must already be populated.
-func (w *RawWriter) maybeIncrementTombstoneDenseBlocks() {
+func (w *RawRowWriter) maybeIncrementTombstoneDenseBlocks() {
 	minSize := w.deletionSizeRatioThreshold * float32(len(w.dataBlockBuf.uncompressed))
 	if w.dataBlockBuf.numDeletions > w.numDeletionsThreshold || float32(w.dataBlockBuf.deletionSize) > minSize {
 		w.props.NumTombstoneDenseBlocks++
@@ -1164,7 +1091,7 @@ func (w *RawWriter) maybeIncrementTombstoneDenseBlocks() {
 	w.dataBlockBuf.deletionSize = 0
 }
 
-func (w *RawWriter) flush(key InternalKey) error {
+func (w *RawRowWriter) flush(key InternalKey) error {
 	// We're finishing a data block.
 	err := w.finishDataBlockProps(w.dataBlockBuf)
 	if err != nil {
@@ -1241,7 +1168,7 @@ func (w *RawWriter) flush(key InternalKey) error {
 	return err
 }
 
-func (w *RawWriter) maybeFlush(key InternalKey, valueLen int) error {
+func (w *RawRowWriter) maybeFlush(key InternalKey, valueLen int) error {
 	if !w.dataBlockBuf.shouldFlush(key, valueLen, w.dataBlockOptions, w.allocatorSizeClasses) {
 		return nil
 	}
@@ -1259,7 +1186,7 @@ func (w *RawWriter) maybeFlush(key InternalKey, valueLen int) error {
 // dataBlockBuf.dataBlockProps set by this method must be encoded before any future use of the
 // dataBlockBuf.blockPropsEncoder, since the properties slice will get reused by the
 // blockPropsEncoder.
-func (w *RawWriter) finishDataBlockProps(buf *dataBlockBuf) error {
+func (w *RawRowWriter) finishDataBlockProps(buf *dataBlockBuf) error {
 	if len(w.blockPropCollectors) == 0 {
 		return nil
 	}
@@ -1281,7 +1208,7 @@ func (w *RawWriter) finishDataBlockProps(buf *dataBlockBuf) error {
 // the Writer.blockPropsEncoder, since the properties slice will get reused by the blockPropsEncoder.
 // maybeAddBlockPropertiesToBlockHandle should only be called if block is being written synchronously
 // with the Writer client.
-func (w *RawWriter) maybeAddBlockPropertiesToBlockHandle(
+func (w *RawRowWriter) maybeAddBlockPropertiesToBlockHandle(
 	bh block.Handle,
 ) (block.HandleWithProperties, error) {
 	err := w.finishDataBlockProps(w.dataBlockBuf)
@@ -1291,7 +1218,7 @@ func (w *RawWriter) maybeAddBlockPropertiesToBlockHandle(
 	return block.HandleWithProperties{Handle: bh, Props: w.dataBlockBuf.dataBlockProps}, nil
 }
 
-func (w *RawWriter) indexEntrySep(
+func (w *RawRowWriter) indexEntrySep(
 	prevKey, key InternalKey, dataBlockBuf *dataBlockBuf,
 ) InternalKey {
 	// Make a rough guess that we want key-sized scratch to compute the separator.
@@ -1319,7 +1246,7 @@ func (w *RawWriter) indexEntrySep(
 //     encoded.
 //  2. addIndexEntry must not hold references to the flushIndexBuf, and the writeTo
 //     indexBlockBufs.
-func (w *RawWriter) addIndexEntry(
+func (w *RawRowWriter) addIndexEntry(
 	sep InternalKey,
 	bhp block.HandleWithProperties,
 	tmp []byte,
@@ -1350,7 +1277,7 @@ func (w *RawWriter) addIndexEntry(
 	return nil
 }
 
-func (w *RawWriter) addPrevDataBlockToIndexBlockProps() {
+func (w *RawRowWriter) addPrevDataBlockToIndexBlockProps() {
 	for i := range w.blockPropCollectors {
 		w.blockPropCollectors[i].AddPrevDataBlockToIndexBlock()
 	}
@@ -1367,13 +1294,13 @@ func (w *RawWriter) addPrevDataBlockToIndexBlockProps() {
 //
 // TODO: Improve coverage of this method. e.g. tests passed without the line
 // `w.twoLevelIndex = true` previously.
-func (w *RawWriter) addIndexEntrySync(
+func (w *RawRowWriter) addIndexEntrySync(
 	prevKey, key InternalKey, bhp block.HandleWithProperties, tmp []byte,
 ) error {
 	return w.addIndexEntrySep(w.indexEntrySep(prevKey, key, w.dataBlockBuf), bhp, tmp)
 }
 
-func (w *RawWriter) addIndexEntrySep(
+func (w *RawRowWriter) addIndexEntrySep(
 	sep InternalKey, bhp block.HandleWithProperties, tmp []byte,
 ) error {
 	shouldFlush := supportsTwoLevelIndex(
@@ -1530,7 +1457,7 @@ func cloneKeyWithBuf(k InternalKey, a bytealloc.A) (bytealloc.A, InternalKey) {
 //  1. Reuse w.blockPropsEncoder without first encoding the byte slice returned.
 //  2. Store the byte slice in the Writer since it is a copy and not supported by
 //     an underlying buffer.
-func (w *RawWriter) finishIndexBlockProps() ([]byte, error) {
+func (w *RawRowWriter) finishIndexBlockProps() ([]byte, error) {
 	w.blockPropsEncoder.resetProps()
 	for i := range w.blockPropCollectors {
 		scratch := w.blockPropsEncoder.getScratchForProp()
@@ -1552,7 +1479,7 @@ func (w *RawWriter) finishIndexBlockProps() ([]byte, error) {
 //     slice.
 //  2. None of the buffers owned by indexBuf will be shallow copied and stored elsewhere.
 //     That is, it must be safe to reuse indexBuf after finishIndexBlock has been called.
-func (w *RawWriter) finishIndexBlock(indexBuf *indexBlockBuf, props []byte) error {
+func (w *RawRowWriter) finishIndexBlock(indexBuf *indexBlockBuf, props []byte) error {
 	part := indexBlockAndBlockProperties{
 		nEntries: indexBuf.block.EntryCount(), properties: props,
 	}
@@ -1571,7 +1498,7 @@ func (w *RawWriter) finishIndexBlock(indexBuf *indexBlockBuf, props []byte) erro
 	return nil
 }
 
-func (w *RawWriter) writeTwoLevelIndex() (block.Handle, error) {
+func (w *RawRowWriter) writeTwoLevelIndex() (block.Handle, error) {
 	props, err := w.finishIndexBlockProps()
 	if err != nil {
 		return block.Handle{}, err
@@ -1608,7 +1535,7 @@ func (w *RawWriter) writeTwoLevelIndex() (block.Handle, error) {
 
 // assertFormatCompatibility ensures that the features present on the table are
 // compatible with the table format version.
-func (w *RawWriter) assertFormatCompatibility() error {
+func (w *RawRowWriter) assertFormatCompatibility() error {
 	// PebbleDBv1: block properties.
 	if len(w.blockPropCollectors) > 0 && w.tableFormat < TableFormatPebblev1 {
 		return errors.Newf(
@@ -1642,13 +1569,19 @@ func (w *RawWriter) assertFormatCompatibility() error {
 	return nil
 }
 
+// IsStrictObsolete returns true if the writer is configured to write and
+// enforce a 'strict obsolete' sstable.
+func (w *RawRowWriter) IsStrictObsolete() bool {
+	return w.isStrictObsolete
+}
+
 // UnsafeLastPointUserKey returns the last point key written to the writer to
 // which this option was passed during creation. The returned key points
 // directly into a buffer belonging to the Writer. The value's lifetime ends the
 // next time a point key is added to the Writer.
 //
 // Must not be called after Writer is closed.
-func (w *RawWriter) UnsafeLastPointUserKey() []byte {
+func (w *RawRowWriter) UnsafeLastPointUserKey() []byte {
 	if w != nil && w.dataBlockBuf.dataBlock.EntryCount() >= 1 {
 		// w.dataBlockBuf.dataBlock.curKey is guaranteed to point to the last point key
 		// which was added to the Writer.
@@ -1662,7 +1595,7 @@ func (w *RawWriter) UnsafeLastPointUserKey() []byte {
 //
 // This is a low-level API that bypasses the fragmenter. The spans passed to
 // this function must be fragmented and ordered.
-func (w *RawWriter) EncodeSpan(span keyspan.Span) error {
+func (w *RawRowWriter) EncodeSpan(span keyspan.Span) error {
 	if span.Empty() {
 		return nil
 	}
@@ -1677,9 +1610,14 @@ func (w *RawWriter) EncodeSpan(span keyspan.Span) error {
 	return w.rangeKeyEncoder.Encode(span)
 }
 
+// Error returns the current accumulated error, if any.
+func (w *RawRowWriter) Error() error {
+	return w.err
+}
+
 // Close finishes writing the table and closes the underlying file that the
 // table was written to.
-func (w *RawWriter) Close() (err error) {
+func (w *RawRowWriter) Close() (err error) {
 	defer func() {
 		if w.valueBlockWriter != nil {
 			releaseValueBlockWriter(w.valueBlockWriter)
@@ -1878,7 +1816,7 @@ func (w *RawWriter) Close() (err error) {
 
 // EstimatedSize returns the estimated size of the sstable being written if a
 // call to Finish() was made without adding additional keys.
-func (w *RawWriter) EstimatedSize() uint64 {
+func (w *RawRowWriter) EstimatedSize() uint64 {
 	if w == nil {
 		return 0
 	}
@@ -1889,7 +1827,7 @@ func (w *RawWriter) EstimatedSize() uint64 {
 
 // Metadata returns the metadata for the finished sstable. Only valid to call
 // after the sstable has been finished.
-func (w *RawWriter) Metadata() (*WriterMetadata, error) {
+func (w *RawRowWriter) Metadata() (*WriterMetadata, error) {
 	if !w.layout.IsFinished() {
 		return nil, errors.New("pebble: writer is not closed")
 	}
@@ -1898,9 +1836,9 @@ func (w *RawWriter) Metadata() (*WriterMetadata, error) {
 
 // NewRawWriter returns a new table writer for the file. Closing the writer will
 // close the file.
-func NewRawWriter(writable objstorage.Writable, o WriterOptions) *RawWriter {
+func NewRawWriter(writable objstorage.Writable, o WriterOptions) *RawRowWriter {
 	o = o.ensureDefaults()
-	w := &RawWriter{
+	w := &RawRowWriter{
 		layout: makeLayoutWriter(writable, o),
 		meta: WriterMetadata{
 			SmallestSeqNum: math.MaxUint64,
@@ -2018,7 +1956,7 @@ func NewRawWriter(writable objstorage.Writable, o WriterOptions) *RawWriter {
 
 // SetSnapshotPinnedProperties sets the properties for pinned keys. Should only
 // be used internally by Pebble.
-func (w *RawWriter) SetSnapshotPinnedProperties(
+func (w *RawRowWriter) SetSnapshotPinnedProperties(
 	pinnedKeyCount, pinnedKeySize, pinnedValueSize uint64,
 ) {
 	w.props.SnapshotPinnedKeys = pinnedKeyCount
diff --git a/sstable/suffix_rewriter.go b/sstable/suffix_rewriter.go
index c8a5d642..8a5a18cf 100644
--- a/sstable/suffix_rewriter.go
+++ b/sstable/suffix_rewriter.go
@@ -195,7 +195,7 @@ func rewriteBlocks[R blockRewriter](
 	return nil
 }
 
-func checkWriterFilterMatchesReader(r *Reader, w *RawWriter) error {
+func checkWriterFilterMatchesReader(r *Reader, w *RawRowWriter) error {
 	if r.Properties.FilterPolicyName != w.filter.policyName() {
 		return errors.New("mismatched filters")
 	}
@@ -206,7 +206,7 @@ func checkWriterFilterMatchesReader(r *Reader, w *RawWriter) error {
 }
 
 func rewriteDataBlocksToWriter(
-	r *Reader, w *RawWriter, data []block.HandleWithProperties, from, to []byte, concurrency int,
+	r *Reader, w *RawRowWriter, data []block.HandleWithProperties, from, to []byte, concurrency int,
 ) error {
 	if r.Properties.NumEntries == 0 {
 		// No point keys.
@@ -326,7 +326,7 @@ func rewriteDataBlocksToWriter(
 	return nil
 }
 
-func rewriteRangeKeyBlockToWriter(r *Reader, w *RawWriter, from, to []byte) error {
+func rewriteRangeKeyBlockToWriter(r *Reader, w *RawRowWriter, from, to []byte) error {
 	iter, err := r.NewRawRangeKeyIter(context.TODO(), NoFragmentTransforms)
 	if err != nil {
 		return err
diff --git a/sstable/write_queue.go b/sstable/write_queue.go
index ffe5fa52..e00d0b8a 100644
--- a/sstable/write_queue.go
+++ b/sstable/write_queue.go
@@ -40,7 +40,7 @@ func (task *writeTask) clear() {
 type writeQueue struct {
 	tasks  chan *writeTask
 	wg     sync.WaitGroup
-	writer *RawWriter
+	writer *RawRowWriter
 
 	// err represents an error which is encountered when the write queue attempts
 	// to write a block to disk. The error is stored here to skip unnecessary block
@@ -49,7 +49,7 @@ type writeQueue struct {
 	closed bool
 }
 
-func newWriteQueue(size int, writer *RawWriter) *writeQueue {
+func newWriteQueue(size int, writer *RawRowWriter) *writeQueue {
 	w := &writeQueue{}
 	w.tasks = make(chan *writeTask, size)
 	w.writer = writer
diff --git a/sstable/writer.go b/sstable/writer.go
index 2c0877eb..2ff142d2 100644
--- a/sstable/writer.go
+++ b/sstable/writer.go
@@ -13,12 +13,13 @@ import (
 
 // Writer is a table writer.
 type Writer struct {
-	rw *RawWriter
+	rw RawWriter
 	// To allow potentially overlapping (i.e. un-fragmented) range keys spans to
 	// be added to the Writer, a keyspan.Fragmenter is used to retain the keys
 	// and values, emitting fragmented, coalesced spans as appropriate. Range
 	// keys must be added in order of their start user-key.
 	fragmenter  keyspan.Fragmenter
+	comparer    *base.Comparer
 	rkBuf       []byte
 	keyspanKeys []keyspan.Key
 }
@@ -38,11 +39,12 @@ func NewWriter(writable objstorage.Writable, o WriterOptions) *Writer {
 			Format: o.Comparer.FormatKey,
 			Emit:   rw.encodeFragmentedRangeKeySpan,
 		},
+		comparer: o.Comparer,
 	}
 }
 
 // Raw returns the underlying RawWriter.
-func (w *Writer) Raw() *RawWriter { return w.rw }
+func (w *Writer) Raw() RawWriter { return w.rw }
 
 // Set sets the value for the given key. The sequence number is set to 0.
 // Intended for use to externally construct an sstable before ingestion into a
@@ -51,15 +53,15 @@ func (w *Writer) Raw() *RawWriter { return w.rw }
 //
 // TODO(peter): untested
 func (w *Writer) Set(key, value []byte) error {
-	if w.rw.err != nil {
-		return w.rw.err
+	if err := w.rw.Error(); err != nil {
+		return err
 	}
-	if w.rw.isStrictObsolete {
+	if w.rw.IsStrictObsolete() {
 		return errors.Errorf("use AddWithForceObsolete")
 	}
 	// forceObsolete is false based on the assumption that no RANGEDELs in the
 	// sstable delete the added points.
-	return w.rw.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindSet), value, false)
+	return w.rw.AddWithForceObsolete(base.MakeInternalKey(key, 0, InternalKeyKindSet), value, false)
 }
 
 // Delete deletes the value for the given key. The sequence number is set to
@@ -68,15 +70,15 @@ func (w *Writer) Set(key, value []byte) error {
 //
 // TODO(peter): untested
 func (w *Writer) Delete(key []byte) error {
-	if w.rw.err != nil {
-		return w.rw.err
+	if err := w.rw.Error(); err != nil {
+		return err
 	}
-	if w.rw.isStrictObsolete {
+	if w.rw.IsStrictObsolete() {
 		return errors.Errorf("use AddWithForceObsolete")
 	}
 	// forceObsolete is false based on the assumption that no RANGEDELs in the
 	// sstable delete the added points.
-	return w.rw.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil, false)
+	return w.rw.AddWithForceObsolete(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil, false)
 }
 
 // DeleteRange deletes all of the keys (and values) in the range [start,end)
@@ -89,8 +91,8 @@ func (w *Writer) Delete(key []byte) error {
 //
 // TODO(peter): untested
 func (w *Writer) DeleteRange(start, end []byte) error {
-	if w.rw.err != nil {
-		return w.rw.err
+	if err := w.rw.Error(); err != nil {
+		return err
 	}
 	return w.rw.EncodeSpan(keyspan.Span{
 		Start: start,
@@ -108,16 +110,16 @@ func (w *Writer) DeleteRange(start, end []byte) error {
 //
 // TODO(peter): untested
 func (w *Writer) Merge(key, value []byte) error {
-	if w.rw.err != nil {
-		return w.rw.err
+	if err := w.rw.Error(); err != nil {
+		return err
 	}
-	if w.rw.isStrictObsolete {
+	if w.rw.IsStrictObsolete() {
 		return errors.Errorf("use AddWithForceObsolete")
 	}
 	// forceObsolete is false based on the assumption that no RANGEDELs in the
 	// sstable that delete the added points. If the user configured this writer
 	// to be strict-obsolete, addPoint will reject the addition of this MERGE.
-	return w.rw.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value, false)
+	return w.rw.AddWithForceObsolete(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value, false)
 }
 
 // RangeKeySet sets a range between start (inclusive) and end (exclusive) with
@@ -182,18 +184,18 @@ func (w *Writer) RangeKeyDelete(start, end []byte) error {
 }
 
 func (w *Writer) addRangeKeySpanToFragmenter(span keyspan.Span) error {
-	if w.rw.compare(span.Start, span.End) >= 0 {
+	if w.comparer.Compare(span.Start, span.End) >= 0 {
 		return errors.Errorf(
 			"pebble: start key must be strictly less than end key",
 		)
 	}
-	if w.fragmenter.Start() != nil && w.rw.compare(w.fragmenter.Start(), span.Start) > 0 {
+	if w.fragmenter.Start() != nil && w.comparer.Compare(w.fragmenter.Start(), span.Start) > 0 {
 		return errors.Errorf("pebble: spans must be added in order: %s > %s",
-			w.rw.formatKey(w.fragmenter.Start()), w.rw.formatKey(span.Start))
+			w.comparer.FormatKey(w.fragmenter.Start()), w.comparer.FormatKey(span.Start))
 	}
 	// Add this span to the fragmenter.
 	w.fragmenter.Add(span)
-	return w.rw.err
+	return w.rw.Error()
 }
 
 // tempRangeKeyBuf returns a slice of length n from the Writer's rkBuf byte
@@ -228,10 +230,125 @@ func (w *Writer) tempRangeKeyCopy(k []byte) []byte {
 // Close finishes writing the table and closes the underlying file that the
 // table was written to.
 func (w *Writer) Close() (err error) {
-	if w.rw.err == nil {
+	if w.rw.Error() == nil {
 		// Write the range-key block, flushing any remaining spans from the
 		// fragmenter first.
 		w.fragmenter.Finish()
 	}
 	return w.rw.Close()
 }
+
+// RawWriter defines an interface for sstable writers. Implementations may vary
+// depending on the TableFormat being written.
+type RawWriter interface {
+	// Error returns the current accumulated error if any.
+	Error() error
+	// IsStrictObsolete returns true if the writer is configured to write and
+	// enforce a 'strict obsolete' sstable. This includes prohibiting the
+	// addition of MERGE keys. See the documentation in format.go for more
+	// details.
+	IsStrictObsolete() bool
+	// AddWithForceObsolete must be used when writing a strict-obsolete sstable.
+	//
+	// forceObsolete indicates whether the caller has determined that this key is
+	// obsolete even though it may be the latest point key for this userkey. This
+	// should be set to true for keys obsoleted by RANGEDELs, and is required for
+	// strict-obsolete sstables. It's optional for non-strict-obsolete sstables.
+	//
+	// Note that there are two properties, S1 and S2 (see comment in format.go)
+	// that strict-obsolete ssts must satisfy. S2, due to RANGEDELs, is solely the
+	// responsibility of the caller. S1 is solely the responsibility of the
+	// callee.
+	AddWithForceObsolete(
+		key InternalKey, value []byte, forceObsolete bool,
+	) error
+	// EncodeSpan encodes the keys in the given span. The span can contain
+	// either only RANGEDEL keys or only range keys.
+	//
+	// This is a low-level API that bypasses the fragmenter. The spans passed to
+	// this function must be fragmented and ordered.
+	EncodeSpan(span keyspan.Span) error
+	// Close finishes writing the table and closes the underlying file that the
+	// table was written to.
+	Close() error
+	// Metadata returns the metadata for the finished sstable. Only valid to
+	// call after the sstable has been finished.
+	Metadata() (*WriterMetadata, error)
+}
+
+// WriterMetadata holds info about a finished sstable.
+type WriterMetadata struct {
+	Size          uint64
+	SmallestPoint InternalKey
+	// LargestPoint, LargestRangeKey, LargestRangeDel should not be accessed
+	// before Writer.Close is called, because they may only be set on
+	// Writer.Close.
+	LargestPoint     InternalKey
+	SmallestRangeDel InternalKey
+	LargestRangeDel  InternalKey
+	SmallestRangeKey InternalKey
+	LargestRangeKey  InternalKey
+	HasPointKeys     bool
+	HasRangeDelKeys  bool
+	HasRangeKeys     bool
+	SmallestSeqNum   base.SeqNum
+	LargestSeqNum    base.SeqNum
+	Properties       Properties
+}
+
+// SetSmallestPointKey sets the smallest point key to the given key.
+// NB: this method set the "absolute" smallest point key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetSmallestPointKey(k InternalKey) {
+	m.SmallestPoint = k
+	m.HasPointKeys = true
+}
+
+// SetSmallestRangeDelKey sets the smallest rangedel key to the given key.
+// NB: this method set the "absolute" smallest rangedel key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetSmallestRangeDelKey(k InternalKey) {
+	m.SmallestRangeDel = k
+	m.HasRangeDelKeys = true
+}
+
+// SetSmallestRangeKey sets the smallest range key to the given key.
+// NB: this method set the "absolute" smallest range key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetSmallestRangeKey(k InternalKey) {
+	m.SmallestRangeKey = k
+	m.HasRangeKeys = true
+}
+
+// SetLargestPointKey sets the largest point key to the given key.
+// NB: this method set the "absolute" largest point key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetLargestPointKey(k InternalKey) {
+	m.LargestPoint = k
+	m.HasPointKeys = true
+}
+
+// SetLargestRangeDelKey sets the largest rangedel key to the given key.
+// NB: this method set the "absolute" largest rangedel key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetLargestRangeDelKey(k InternalKey) {
+	m.LargestRangeDel = k
+	m.HasRangeDelKeys = true
+}
+
+// SetLargestRangeKey sets the largest range key to the given key.
+// NB: this method set the "absolute" largest range key. Any existing key is
+// overridden.
+func (m *WriterMetadata) SetLargestRangeKey(k InternalKey) {
+	m.LargestRangeKey = k
+	m.HasRangeKeys = true
+}
+
+func (m *WriterMetadata) updateSeqNum(seqNum base.SeqNum) {
+	if m.SmallestSeqNum > seqNum {
+		m.SmallestSeqNum = seqNum
+	}
+	if m.LargestSeqNum < seqNum {
+		m.LargestSeqNum = seqNum
+	}
+}


Test patch:
diff --git a/data_test.go b/data_test.go
index 3e356b2d..3679a39d 100644
--- a/data_test.go
+++ b/data_test.go
@@ -586,7 +586,7 @@ func runBuildRemoteCmd(td *datadriven.TestData, d *DB, storage remote.Storage) e
 	for kv := iter.First(); kv != nil; kv = iter.Next() {
 		tmp := kv.K
 		tmp.SetSeqNum(0)
-		if err := w.Raw().Add(tmp, kv.InPlaceValue()); err != nil {
+		if err := w.Raw().AddWithForceObsolete(tmp, kv.InPlaceValue(), false); err != nil {
 			return err
 		}
 	}
@@ -599,7 +599,7 @@ func runBuildRemoteCmd(td *datadriven.TestData, d *DB, storage remote.Storage) e
 		for ; s != nil && err == nil; s, err = rdi.Next() {
 			err = rangedel.Encode(*s, func(k base.InternalKey, v []byte) error {
 				k.SetSeqNum(0)
-				return w.Raw().Add(k, v)
+				return w.Raw().AddWithForceObsolete(k, v, false)
 			})
 			if err != nil {
 				return err
@@ -680,7 +680,7 @@ func runBuildCmd(td *datadriven.TestData, d *DB, fs vfs.FS) error {
 	for kv := iter.First(); kv != nil; kv = iter.Next() {
 		tmp := kv.K
 		tmp.SetSeqNum(0)
-		if err := w.Raw().Add(tmp, kv.InPlaceValue()); err != nil {
+		if err := w.Raw().AddWithForceObsolete(tmp, kv.InPlaceValue(), false); err != nil {
 			return err
 		}
 	}
@@ -693,7 +693,7 @@ func runBuildCmd(td *datadriven.TestData, d *DB, fs vfs.FS) error {
 		for ; s != nil && err == nil; s, err = rdi.Next() {
 			err = rangedel.Encode(*s, func(k base.InternalKey, v []byte) error {
 				k.SetSeqNum(0)
-				return w.Raw().Add(k, v)
+				return w.Raw().AddWithForceObsolete(k, v, false)
 			})
 			if err != nil {
 				return err
diff --git a/ingest_test.go b/ingest_test.go
index fd703e60..200365ba 100644
--- a/ingest_test.go
+++ b/ingest_test.go
@@ -3224,7 +3224,7 @@ func TestIngestFileNumReuseCrash(t *testing.T) {
 func TestIngest_UpdateSequenceNumber(t *testing.T) {
 	mem := vfs.NewMem()
 	cmp := base.DefaultComparer.Compare
-	parse := func(input string) (*sstable.RawWriter, error) {
+	parse := func(input string) (*sstable.RawRowWriter, error) {
 		f, err := mem.Create("ext", vfs.WriteCategoryUnspecified)
 		if err != nil {
 			return nil, err
diff --git a/level_iter_test.go b/level_iter_test.go
index 9b105e1e..471e8c42 100644
--- a/level_iter_test.go
+++ b/level_iter_test.go
@@ -508,7 +508,7 @@ func buildLevelIterTables(
 		files[i] = f
 	}
 
-	writers := make([]*sstable.RawWriter, len(files))
+	writers := make([]*sstable.RawRowWriter, len(files))
 	for i := range files {
 		writers[i] = sstable.NewRawWriter(objstorageprovider.NewFileWritable(files[i]), sstable.WriterOptions{
 			BlockRestartInterval: restartInterval,
diff --git a/merging_iter_test.go b/merging_iter_test.go
index 7a0b912f..2e2ed5de 100644
--- a/merging_iter_test.go
+++ b/merging_iter_test.go
@@ -340,7 +340,7 @@ func buildMergingIterTables(
 		files[i] = f
 	}
 
-	writers := make([]*sstable.RawWriter, len(files))
+	writers := make([]*sstable.RawRowWriter, len(files))
 	for i := range files {
 		writers[i] = sstable.NewRawWriter(objstorageprovider.NewFileWritable(files[i]), sstable.WriterOptions{
 			BlockRestartInterval: restartInterval,
@@ -543,7 +543,7 @@ func buildLevelsForMergingIterSeqSeek(
 	}
 
 	const targetL6FirstFileSize = 2 << 20
-	writers := make([][]*sstable.RawWriter, levelCount)
+	writers := make([][]*sstable.RawRowWriter, levelCount)
 	// A policy unlikely to have false positives.
 	filterPolicy := bloom.FilterPolicy(100)
 	for i := range files {
diff --git a/scan_internal_test.go b/scan_internal_test.go
index 9a7fff1d..e8e6255a 100644
--- a/scan_internal_test.go
+++ b/scan_internal_test.go
@@ -441,7 +441,7 @@ func TestScanInternal(t *testing.T) {
 					var err error
 					value, _, err = kv.Value(value)
 					require.NoError(t, err)
-					require.NoError(t, w.Raw().Add(kv.K, value))
+					require.NoError(t, w.Raw().AddWithForceObsolete(kv.K, value, false))
 				}
 				points.Close()
 				require.NoError(t, w.Close())
diff --git a/sstable/data_test.go b/sstable/data_test.go
index bb00706c..20f57353 100644
--- a/sstable/data_test.go
+++ b/sstable/data_test.go
@@ -152,7 +152,7 @@ func runBuildCmd(
 	for _, v := range rangeDels {
 		for _, k := range v.Keys {
 			ik := base.InternalKey{UserKey: v.Start, Trailer: k.Trailer}
-			if err := w.Raw().Add(ik, v.End); err != nil {
+			if err := w.Raw().AddWithForceObsolete(ik, v.End, false); err != nil {
 				return nil, nil, err
 			}
 		}
@@ -227,20 +227,8 @@ func runBuildRawCmd(
 		j := strings.Index(data, ":")
 		key := base.ParseInternalKey(data[:j])
 		value := []byte(data[j+1:])
-		switch key.Kind() {
-		case base.InternalKeyKindRangeKeyDelete,
-			base.InternalKeyKindRangeKeyUnset,
-			base.InternalKeyKindRangeKeySet:
-			// Note: specifying range keys directly (instead of spans) should only be
-			// done to check for error handling; they will not contribute to block
-			// properties.
-			if err := w.Raw().addRangeKey(key, value); err != nil {
-				return nil, nil, err
-			}
-		default:
-			if err := w.Raw().Add(key, value); err != nil {
-				return nil, nil, err
-			}
+		if err := w.Raw().AddWithForceObsolete(key, value, false); err != nil {
+			return nil, nil, err
 		}
 	}
 	if err := w.Close(); err != nil {
diff --git a/sstable/testdata/writer b/sstable/testdata/writer
index 6053e7de..24417dd1 100644
--- a/sstable/testdata/writer
+++ b/sstable/testdata/writer
@@ -203,18 +203,6 @@ rangekey: a-b:{(#1,RANGEKEYSET,@10,foo)}
 ----
 pebble: spans must be added in order: b > a
 
-build-raw
-a.RANGEKEYDEL.1:c
-b.RANGEKEYDEL.2:d
-----
-pebble: overlapping range keys must be fragmented: a#1,RANGEKEYDEL, b#2,RANGEKEYDEL
-
-build-raw
-a.RANGEKEYDEL.2:c
-a.RANGEKEYDEL.1:d
-----
-pebble: overlapping range keys must be fragmented: a#2,RANGEKEYDEL, a#1,RANGEKEYDEL
-
 build-raw
 rangekey: a-c:{(#1,RANGEKEYSET,@10,foo)}
 rangekey: c-d:{(#2,RANGEKEYSET,@10,foo)}
@@ -225,12 +213,6 @@ seqnums:  [1-2]
 # Range keys may have perfectly aligned spans (including sequence numbers),
 # though the key kinds must be ordered (descending).
 
-build-raw
-a.RANGEKEYDEL.1:b
-a.RANGEKEYDEL.1:b
-----
-pebble: range keys starts must be added in increasing order: a#1,RANGEKEYDEL, a#1,RANGEKEYDEL
-
 build-raw
 rangekey: a-b:{(#1,RANGEKEYSET,@10,foo) (#1,RANGEKEYUNSET,@t10) (#1,RANGEKEYDEL)}
 ----
diff --git a/sstable/testdata/writer_v3 b/sstable/testdata/writer_v3
index 883a629d..d7216b39 100644
--- a/sstable/testdata/writer_v3
+++ b/sstable/testdata/writer_v3
@@ -177,18 +177,6 @@ rangekey: a-b:{(#1,RANGEKEYSET,@10,foo)}
 ----
 pebble: spans must be added in order: b > a
 
-build-raw
-a.RANGEKEYDEL.1:c
-b.RANGEKEYDEL.2:d
-----
-pebble: overlapping range keys must be fragmented: a#1,RANGEKEYDEL, b#2,RANGEKEYDEL
-
-build-raw
-a.RANGEKEYDEL.2:c
-a.RANGEKEYDEL.1:d
-----
-pebble: overlapping range keys must be fragmented: a#2,RANGEKEYDEL, a#1,RANGEKEYDEL
-
 build-raw
 rangekey: a-c:{(#1,RANGEKEYSET,@10,foo)}
 rangekey: c-d:{(#2,RANGEKEYSET,@10,foo)}
@@ -199,12 +187,6 @@ seqnums:  [1-2]
 # Range keys may have perfectly aligned spans (including sequence numbers),
 # though the key kinds must be ordered (descending).
 
-build-raw
-a.RANGEKEYDEL.1:b
-a.RANGEKEYDEL.1:b
-----
-pebble: range keys starts must be added in increasing order: a#1,RANGEKEYDEL, a#1,RANGEKEYDEL
-
 build-raw
 rangekey: a-b:{(#1,RANGEKEYSET,@10,foo) (#1,RANGEKEYUNSET,@10) (#1,RANGEKEYDEL)}
 ----
diff --git a/sstable/writer_test.go b/sstable/writer_test.go
index 737be2cf..0662cf61 100644
--- a/sstable/writer_test.go
+++ b/sstable/writer_test.go
@@ -528,7 +528,7 @@ func TestParallelWriterErrorProp(t *testing.T) {
 	w := NewWriter(objstorageprovider.NewFileWritable(f), opts)
 	// Directly testing this, because it's difficult to get the Writer to
 	// encounter an error, precisely when the writeQueue is doing block writes.
-	w.rw.coordination.writeQueue.err = errors.New("write queue write error")
+	w.rw.(*RawRowWriter).coordination.writeQueue.err = errors.New("write queue write error")
 	w.Set(ikey("a").UserKey, nil)
 	w.Set(ikey("b").UserKey, nil)
 	err = w.Close()
diff --git a/table_cache_test.go b/table_cache_test.go
index fb5ccc9e..a7f1c03c 100644
--- a/table_cache_test.go
+++ b/table_cache_test.go
@@ -176,7 +176,7 @@ func newTableCacheContainerTest(
 		}
 		tw := sstable.NewWriter(w, sstable.WriterOptions{TableFormat: sstable.TableFormatPebblev2})
 		ik := base.ParseInternalKey(fmt.Sprintf("k.SET.%d", i))
-		if err := tw.Raw().Add(ik, xxx[:i]); err != nil {
+		if err := tw.Raw().AddWithForceObsolete(ik, xxx[:i], false); err != nil {
 			return nil, nil, errors.Wrap(err, "tw.Set")
 		}
 		if err := tw.RangeKeySet([]byte("k"), []byte("l"), nil, xxx[:i]); err != nil {