Repository: cockroachdb/pebble
Pull request #3749 (2024-07-12 14:35:23)

sstable: add context to NewReader
#### objstorage: don't take context for NewReadHandle

The context in `NewReadHandle` is not useful (the actual read
operations pass their own context).

#### sstable: add context to NewReader

This change plumbs a context to `NewReader` covering the most
important `newIters` code path. This context is/will be used to
trace slow footer and metaindex reads.

Informs #3728

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/3749)
<!-- Reviewable:end -->


Patch:
diff --git a/external_iterator.go b/external_iterator.go
index 56a80b1a..0e037e4d 100644
--- a/external_iterator.go
+++ b/external_iterator.go
@@ -55,7 +55,7 @@ func NewExternalIterWithContext(
 	}
 	for _, levelFiles := range files {
 		seqNumOffset -= len(levelFiles)
-		subReaders, err := openExternalTables(o, levelFiles, seqNumOffset, o.MakeReaderOptions())
+		subReaders, err := openExternalTables(ctx, o, levelFiles, seqNumOffset, o.MakeReaderOptions())
 		readers = append(readers, subReaders)
 		if err != nil {
 			// Close all the opened readers.
@@ -253,7 +253,11 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) error {
 }
 
 func openExternalTables(
-	o *Options, files []sstable.ReadableFile, seqNumOffset int, readerOpts sstable.ReaderOptions,
+	ctx context.Context,
+	o *Options,
+	files []sstable.ReadableFile,
+	seqNumOffset int,
+	readerOpts sstable.ReaderOptions,
 ) (readers []*sstable.Reader, err error) {
 	readers = make([]*sstable.Reader, 0, len(files))
 	for i := range files {
@@ -261,7 +265,7 @@ func openExternalTables(
 		if err != nil {
 			return readers, err
 		}
-		r, err := sstable.NewReader(readable, readerOpts)
+		r, err := sstable.NewReader(ctx, readable, readerOpts)
 		if err != nil {
 			return readers, err
 		}
diff --git a/ingest.go b/ingest.go
index ce36063d..d533e481 100644
--- a/ingest.go
+++ b/ingest.go
@@ -242,6 +242,7 @@ func ingestLoad1External(
 // ingestLoad1 creates the FileMetadata for one file. This file will be owned
 // by this store.
 func ingestLoad1(
+	ctx context.Context,
 	opts *Options,
 	fmv FormatMajorVersion,
 	readable objstorage.Readable,
@@ -254,7 +255,7 @@ func ingestLoad1(
 		CacheID: cacheID,
 		FileNum: base.PhysicalTableDiskFileNum(fileNum),
 	})
-	r, err := sstable.NewReader(readable, o)
+	r, err := sstable.NewReader(ctx, readable, o)
 	if err != nil {
 		return nil, err
 	}
@@ -430,6 +431,8 @@ func ingestLoad(
 	cacheID uint64,
 	pending []base.FileNum,
 ) (ingestLoadResult, error) {
+	ctx := context.TODO()
+
 	localFileNums := pending[:len(paths)]
 	sharedFileNums := pending[len(paths) : len(paths)+len(shared)]
 	externalFileNums := pending[len(paths)+len(shared) : len(paths)+len(shared)+len(external)]
@@ -446,7 +449,7 @@ func ingestLoad(
 		if err != nil {
 			return ingestLoadResult{}, err
 		}
-		m, err := ingestLoad1(opts, fmv, readable, cacheID, localFileNums[i])
+		m, err := ingestLoad1(ctx, opts, fmv, readable, cacheID, localFileNums[i])
 		if err != nil {
 			return ingestLoadResult{}, err
 		}
diff --git a/internal/base/lazy_value.go b/internal/base/lazy_value.go
index cc6d56d0..ebeb62ba 100644
--- a/internal/base/lazy_value.go
+++ b/internal/base/lazy_value.go
@@ -4,7 +4,11 @@
 
 package base
 
-import "github.com/cockroachdb/pebble/internal/invariants"
+import (
+	"context"
+
+	"github.com/cockroachdb/pebble/internal/invariants"
+)
 
 // A value can have user-defined attributes that are a function of the value
 // byte slice. For now, we only support "short attributes", which can be
@@ -184,7 +188,8 @@ type ValueFetcher interface {
 	// will allocate a new slice for the value. In either case it will set
 	// callerOwned to true.
 	Fetch(
-		handle []byte, valLen int32, buf []byte) (val []byte, callerOwned bool, err error)
+		ctx context.Context, handle []byte, valLen int32, buf []byte,
+	) (val []byte, callerOwned bool, err error)
 }
 
 // Value returns the underlying value.
@@ -201,15 +206,17 @@ func (lv *LazyValue) Value(buf []byte) (val []byte, callerOwned bool, err error)
 	// more performance. I suspect that inlining this only matters in
 	// micro-benchmarks, and in actual use cases in CockroachDB it will not
 	// matter because there is substantial work done with a fetched value.
-	return lv.fetchValue(buf)
+	return lv.fetchValue(context.TODO(), buf)
 }
 
 // INVARIANT: lv.Fetcher != nil
-func (lv *LazyValue) fetchValue(buf []byte) (val []byte, callerOwned bool, err error) {
+func (lv *LazyValue) fetchValue(
+	ctx context.Context, buf []byte,
+) (val []byte, callerOwned bool, err error) {
 	f := lv.Fetcher
 	if !f.fetched {
 		f.fetched = true
-		f.value, f.callerOwned, f.err = f.Fetcher.Fetch(
+		f.value, f.callerOwned, f.err = f.Fetcher.Fetch(ctx,
 			lv.ValueOrHandle, lv.Fetcher.Attribute.ValueLen, buf)
 	}
 	return f.value, f.callerOwned, f.err
diff --git a/metamorphic/build.go b/metamorphic/build.go
index 2bf4f4a0..85ed851f 100644
--- a/metamorphic/build.go
+++ b/metamorphic/build.go
@@ -242,7 +242,11 @@ func openExternalObj(
 	opts := sstable.ReaderOptions{
 		Comparer: t.opts.Comparer,
 	}
-	reader, err = sstable.NewReader(objstorageprovider.NewRemoteReadable(objReader, objSize), opts)
+	reader, err = sstable.NewReader(
+		context.Background(),
+		objstorageprovider.NewRemoteReadable(objReader, objSize),
+		opts,
+	)
 	panicIfErr(err)
 
 	start := bounds.Start
diff --git a/objstorage/objstorage.go b/objstorage/objstorage.go
index 556e2ea4..0a79cd65 100644
--- a/objstorage/objstorage.go
+++ b/objstorage/objstorage.go
@@ -38,7 +38,7 @@ type Readable interface {
 	// The ReadHandle must be closed before the Readable is closed.
 	//
 	// Multiple separate ReadHandles can be used.
-	NewReadHandle(ctx context.Context, readBeforeSize ReadBeforeSize) ReadHandle
+	NewReadHandle(readBeforeSize ReadBeforeSize) ReadHandle
 }
 
 // ReadBeforeSize specifies whether the first read should read additional
@@ -376,7 +376,7 @@ type RemoteObjectToAttach struct {
 
 // Copy copies the specified range from the input to the output.
 func Copy(ctx context.Context, in Readable, out Writable, offset, length uint64) error {
-	r := in.NewReadHandle(ctx, NoReadBefore)
+	r := in.NewReadHandle(NoReadBefore)
 	r.SetupForCompaction()
 	buf := make([]byte, 256<<10)
 	end := offset + length
diff --git a/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go b/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go
index 76cb9583..a0f4b1ff 100644
--- a/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go
+++ b/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go
@@ -81,7 +81,7 @@ func (t *Tracer) WrapWritable(
 	return &writable{
 		w:       w,
 		fileNum: fileNum,
-		g:       makeEventGenerator(ctx, t),
+		g:       makeEventGenerator(infoFromCtx(ctx), t),
 	}
 }
 
@@ -128,17 +128,19 @@ func (t *Tracer) WrapReadable(
 	ctx context.Context, r objstorage.Readable, fileNum base.DiskFileNum,
 ) objstorage.Readable {
 	res := &readable{
-		r:       r,
-		fileNum: fileNum,
+		r:           r,
+		fileNum:     fileNum,
+		baseCtxInfo: infoFromCtx(ctx),
 	}
-	res.mu.g = makeEventGenerator(ctx, t)
+	res.mu.g = makeEventGenerator(res.baseCtxInfo, t)
 	return res
 }
 
 type readable struct {
-	r       objstorage.Readable
-	fileNum base.DiskFileNum
-	mu      struct {
+	r           objstorage.Readable
+	fileNum     base.DiskFileNum
+	baseCtxInfo ctxInfo
+	mu          struct {
 		sync.Mutex
 		g eventGenerator
 	}
@@ -171,16 +173,14 @@ func (r *readable) Size() int64 {
 }
 
 // NewReadHandle is part of the objstorage.Readable interface.
-func (r *readable) NewReadHandle(
-	ctx context.Context, readBeforeSize objstorage.ReadBeforeSize,
-) objstorage.ReadHandle {
+func (r *readable) NewReadHandle(readBeforeSize objstorage.ReadBeforeSize) objstorage.ReadHandle {
 	// It's safe to get the tracer from the generator without the mutex since it never changes.
 	t := r.mu.g.t
 	return &readHandle{
-		rh:       r.r.NewReadHandle(ctx, readBeforeSize),
+		rh:       r.r.NewReadHandle(readBeforeSize),
 		fileNum:  r.fileNum,
 		handleID: t.handleID.Add(1),
-		g:        makeEventGenerator(ctx, t),
+		g:        makeEventGenerator(r.baseCtxInfo, t),
 	}
 }
 
@@ -310,10 +310,10 @@ type eventGenerator struct {
 	buf         eventBuf
 }
 
-func makeEventGenerator(ctx context.Context, t *Tracer) eventGenerator {
+func makeEventGenerator(baseCtxInfo ctxInfo, t *Tracer) eventGenerator {
 	return eventGenerator{
 		t:           t,
-		baseCtxInfo: infoFromCtx(ctx),
+		baseCtxInfo: baseCtxInfo,
 	}
 }
 
diff --git a/objstorage/objstorageprovider/remote_readable.go b/objstorage/objstorageprovider/remote_readable.go
index cfa79c4d..737d1213 100644
--- a/objstorage/objstorageprovider/remote_readable.go
+++ b/objstorage/objstorageprovider/remote_readable.go
@@ -90,7 +90,7 @@ func (r *remoteReadable) Size() int64 {
 // iterators after they are constructed. Consider fixing this oddity.
 
 func (r *remoteReadable) NewReadHandle(
-	ctx context.Context, readBeforeSize objstorage.ReadBeforeSize,
+	readBeforeSize objstorage.ReadBeforeSize,
 ) objstorage.ReadHandle {
 	rh := remoteReadHandlePool.Get().(*remoteReadHandle)
 	*rh = remoteReadHandle{readable: r, readBeforeSize: readBeforeSize, buffered: rh.buffered}
diff --git a/objstorage/objstorageprovider/vfs_readable.go b/objstorage/objstorageprovider/vfs_readable.go
index f9c902b3..f61eea03 100644
--- a/objstorage/objstorageprovider/vfs_readable.go
+++ b/objstorage/objstorageprovider/vfs_readable.go
@@ -80,7 +80,7 @@ func (r *fileReadable) Size() int64 {
 
 // NewReadHandle is part of the objstorage.Readable interface.
 func (r *fileReadable) NewReadHandle(
-	ctx context.Context, readBeforeSize objstorage.ReadBeforeSize,
+	readBeforeSize objstorage.ReadBeforeSize,
 ) objstorage.ReadHandle {
 	rh := readHandlePool.Get().(*vfsReadHandle)
 	rh.init(r)
@@ -229,7 +229,6 @@ func (rh *PreallocatedReadHandle) Close() error {
 // (currently this happens if we are reading from a local file).
 // The returned handle still needs to be closed.
 func UsePreallocatedReadHandle(
-	ctx context.Context,
 	readable objstorage.Readable,
 	readBeforeSize objstorage.ReadBeforeSize,
 	rh *PreallocatedReadHandle,
@@ -238,5 +237,5 @@ func UsePreallocatedReadHandle(
 		rh.init(r)
 		return rh
 	}
-	return readable.NewReadHandle(ctx, readBeforeSize)
+	return readable.NewReadHandle(readBeforeSize)
 }
diff --git a/open.go b/open.go
index 20aa384c..7f65bc4b 100644
--- a/open.go
+++ b/open.go
@@ -937,7 +937,7 @@ func (d *DB) replayWAL(
 						}
 					}
 					// NB: ingestLoad1 will close readable.
-					meta[i], err = ingestLoad1(d.opts, d.FormatMajorVersion(), readable, d.cacheID, base.PhysicalTableFileNum(n))
+					meta[i], err = ingestLoad1(context.TODO(), d.opts, d.FormatMajorVersion(), readable, d.cacheID, base.PhysicalTableFileNum(n))
 					if err != nil {
 						return nil, 0, errors.Wrap(err, "pebble: error when loading flushable ingest files")
 					}
diff --git a/replay/replay.go b/replay/replay.go
index 5d5f6e4c..f997b703 100644
--- a/replay/replay.go
+++ b/replay/replay.go
@@ -993,7 +993,7 @@ func loadFlushedSSTableKeys(
 				f.Close()
 				return err
 			}
-			r, err := sstable.NewReader(readable, readOpts)
+			r, err := sstable.NewReader(context.Background(), readable, readOpts)
 			if err != nil {
 				return err
 			}
diff --git a/sstable/copier.go b/sstable/copier.go
index 00bfbf80..9a58ae18 100644
--- a/sstable/copier.go
+++ b/sstable/copier.go
@@ -49,7 +49,7 @@ func CopySpan(
 	o WriterOptions,
 	start, end InternalKey,
 ) (size uint64, _ error) {
-	r, err := NewReader(input, rOpts)
+	r, err := NewReader(ctx, input, rOpts)
 	if err != nil {
 		input.Close()
 		output.Abort()
@@ -95,7 +95,7 @@ func CopySpan(
 	// ReadBeforeForIndexAndFilter attempts to read the top-level index, filter
 	// and lower-level index blocks with one read.
 	rh := objstorageprovider.UsePreallocatedReadHandle(
-		ctx, r.readable, objstorage.ReadBeforeForIndexAndFilter, &preallocRH)
+		r.readable, objstorage.ReadBeforeForIndexAndFilter, &preallocRH)
 	defer rh.Close()
 	indexH, err := r.readIndex(ctx, rh, nil, nil)
 	if err != nil {
diff --git a/sstable/reader.go b/sstable/reader.go
index 9efe281d..57de24ee 100644
--- a/sstable/reader.go
+++ b/sstable/reader.go
@@ -776,7 +776,7 @@ func (r *Reader) ValidateBlockChecksums() error {
 
 	// Check all blocks sequentially. Make use of read-ahead, given we are
 	// scanning the entire file from start to end.
-	rh := r.readable.NewReadHandle(context.TODO(), objstorage.NoReadBefore)
+	rh := r.readable.NewReadHandle(objstorage.NoReadBefore)
 	defer rh.Close()
 
 	for _, bh := range blocks {
@@ -943,7 +943,10 @@ func (r *Reader) TableFormat() (TableFormat, error) {
 
 // NewReader returns a new table reader for the file. Closing the reader will
 // close the file.
-func NewReader(f objstorage.Readable, o ReaderOptions) (*Reader, error) {
+//
+// The context is used for tracing any operations performed by NewReader; it is
+// NOT stored for future use.
+func NewReader(ctx context.Context, f objstorage.Readable, o ReaderOptions) (*Reader, error) {
 	if f == nil {
 		return nil, errors.New("pebble/table: nil file")
 	}
@@ -966,9 +969,8 @@ func NewReader(f objstorage.Readable, o ReaderOptions) (*Reader, error) {
 	}
 
 	var preallocRH objstorageprovider.PreallocatedReadHandle
-	ctx := context.TODO()
 	rh := objstorageprovider.UsePreallocatedReadHandle(
-		ctx, r.readable, objstorage.ReadBeforeForNewReader, &preallocRH)
+		r.readable, objstorage.ReadBeforeForNewReader, &preallocRH)
 	defer rh.Close()
 
 	footer, err := readFooter(ctx, f, rh, r.logger)
@@ -1073,7 +1075,7 @@ func (s *simpleReadable) Size() int64 {
 
 // NewReaddHandle is part of the objstorage.Readable interface.
 func (s *simpleReadable) NewReadHandle(
-	ctx context.Context, readBeforeSize objstorage.ReadBeforeSize,
+	readBeforeSize objstorage.ReadBeforeSize,
 ) objstorage.ReadHandle {
 	return &s.rh
 }
diff --git a/sstable/reader_iter_single_lvl.go b/sstable/reader_iter_single_lvl.go
index c0efbb61..b32293c6 100644
--- a/sstable/reader_iter_single_lvl.go
+++ b/sstable/reader_iter_single_lvl.go
@@ -247,9 +247,9 @@ func (i *singleLevelIterator) init(
 	i.iterStats.init(categoryAndQoS, statsCollector)
 
 	i.indexFilterRH = objstorageprovider.UsePreallocatedReadHandle(
-		ctx, r.readable, objstorage.ReadBeforeForIndexAndFilter, &i.indexFilterRHPrealloc)
+		r.readable, objstorage.ReadBeforeForIndexAndFilter, &i.indexFilterRHPrealloc)
 	i.dataRH = objstorageprovider.UsePreallocatedReadHandle(
-		ctx, r.readable, objstorage.NoReadBefore, &i.dataRHPrealloc)
+		r.readable, objstorage.NoReadBefore, &i.dataRHPrealloc)
 
 	if r.tableFormat >= TableFormatPebblev3 {
 		if r.Properties.NumValueBlocks > 0 {
@@ -269,7 +269,7 @@ func (i *singleLevelIterator) init(
 				stats:  stats,
 			}
 			i.data.SetGetLazyValue(i.vbReader.getLazyValueForPrefixAndValueHandle)
-			i.vbRH = objstorageprovider.UsePreallocatedReadHandle(ctx, r.readable, objstorage.NoReadBefore, &i.vbRHPrealloc)
+			i.vbRH = objstorageprovider.UsePreallocatedReadHandle(r.readable, objstorage.NoReadBefore, &i.vbRHPrealloc)
 		}
 		i.data.SetHasValuePrefix(true)
 	}
diff --git a/sstable/suffix_rewriter.go b/sstable/suffix_rewriter.go
index f1c3385e..419cbdeb 100644
--- a/sstable/suffix_rewriter.go
+++ b/sstable/suffix_rewriter.go
@@ -505,7 +505,8 @@ func RewriteKeySuffixesViaWriter(
 
 // NewMemReader opens a reader over the SST stored in the passed []byte.
 func NewMemReader(sst []byte, o ReaderOptions) (*Reader, error) {
-	return NewReader(newMemReader(sst), o)
+	// Since all operations are from memory, plumbing a context here is not useful.
+	return NewReader(context.Background(), newMemReader(sst), o)
 }
 
 func readBlockBuf(r *Reader, bh block.Handle, buf []byte) ([]byte, []byte, error) {
@@ -570,8 +571,6 @@ func (m *memReader) Size() int64 {
 }
 
 // NewReadHandle is part of objstorage.Readable.
-func (m *memReader) NewReadHandle(
-	ctx context.Context, readBeforeSize objstorage.ReadBeforeSize,
-) objstorage.ReadHandle {
+func (m *memReader) NewReadHandle(readBeforeSize objstorage.ReadBeforeSize) objstorage.ReadHandle {
 	return &m.rh
 }
diff --git a/sstable/value_block.go b/sstable/value_block.go
index 0df0d1f5..f748bc22 100644
--- a/sstable/value_block.go
+++ b/sstable/value_block.go
@@ -683,9 +683,9 @@ type blockProviderWhenClosed struct {
 	r  *Reader
 }
 
-func (bpwc *blockProviderWhenClosed) open() error {
+func (bpwc *blockProviderWhenClosed) open(ctx context.Context) error {
 	var err error
-	bpwc.r, err = bpwc.rp.GetReader()
+	bpwc.r, err = bpwc.rp.GetReader(ctx)
 	return err
 }
 
@@ -715,7 +715,7 @@ func (bpwc blockProviderWhenClosed) readBlockForVBR(
 // ReaderProvider supports the implementation of blockProviderWhenClosed.
 // GetReader and Close can be called multiple times in pairs.
 type ReaderProvider interface {
-	GetReader() (r *Reader, err error)
+	GetReader(ctx context.Context) (r *Reader, err error)
 	Close()
 }
 
@@ -728,7 +728,7 @@ type TrivialReaderProvider struct {
 var _ ReaderProvider = TrivialReaderProvider{}
 
 // GetReader implements ReaderProvider.
-func (trp TrivialReaderProvider) GetReader() (*Reader, error) {
+func (trp TrivialReaderProvider) GetReader(ctx context.Context) (*Reader, error) {
 	return trp.Reader, nil
 }
 
@@ -803,7 +803,7 @@ func (r *valueBlockReader) close() {
 
 // Fetch implements base.ValueFetcher.
 func (r *valueBlockReader) Fetch(
-	handle []byte, valLen int32, buf []byte,
+	ctx context.Context, handle []byte, valLen int32, buf []byte,
 ) (val []byte, callerOwned bool, err error) {
 	if !r.closed {
 		val, err := r.getValueInternal(handle, valLen)
@@ -814,7 +814,7 @@ func (r *valueBlockReader) Fetch(
 	}
 
 	bp := blockProviderWhenClosed{rp: r.rp}
-	err = bp.open()
+	err = bp.open(ctx)
 	if err != nil {
 		return nil, false, err
 	}
diff --git a/table_cache.go b/table_cache.go
index 3ed78896..d428ccdd 100644
--- a/table_cache.go
+++ b/table_cache.go
@@ -254,7 +254,7 @@ func (c *tableCacheContainer) withCommonReader(
 	meta *fileMetadata, fn func(sstable.CommonReader) error,
 ) error {
 	s := c.tableCache.getShard(meta.FileBacking.DiskFileNum)
-	v := s.findNode(meta.FileBacking, &c.dbOpts)
+	v := s.findNode(context.TODO(), meta.FileBacking, &c.dbOpts)
 	defer s.unrefValue(v)
 	if v.err != nil {
 		return v.err
@@ -264,7 +264,7 @@ func (c *tableCacheContainer) withCommonReader(
 
 func (c *tableCacheContainer) withReader(meta physicalMeta, fn func(*sstable.Reader) error) error {
 	s := c.tableCache.getShard(meta.FileBacking.DiskFileNum)
-	v := s.findNode(meta.FileBacking, &c.dbOpts)
+	v := s.findNode(context.TODO(), meta.FileBacking, &c.dbOpts)
 	defer s.unrefValue(v)
 	if v.err != nil {
 		return v.err
@@ -277,7 +277,7 @@ func (c *tableCacheContainer) withVirtualReader(
 	meta virtualMeta, fn func(sstable.VirtualReader) error,
 ) error {
 	s := c.tableCache.getShard(meta.FileBacking.DiskFileNum)
-	v := s.findNode(meta.FileBacking, &c.dbOpts)
+	v := s.findNode(context.TODO(), meta.FileBacking, &c.dbOpts)
 	defer s.unrefValue(v)
 	if v.err != nil {
 		return v.err
@@ -469,7 +469,7 @@ func (c *tableCacheShard) newIters(
 	// refCount. If opening the underlying table resulted in error, then we
 	// decrement this straight away. Otherwise, we pass that responsibility to
 	// the sstable iterator, which decrements when it is closed.
-	v := c.findNode(file.FileBacking, dbOpts)
+	v := c.findNode(ctx, file.FileBacking, dbOpts)
 	if v.err != nil {
 		defer c.unrefValue(v)
 		return iterSet{}, v.err
@@ -684,10 +684,10 @@ var _ sstable.ReaderProvider = &tableCacheShardReaderProvider{}
 //
 // TODO(bananabrick): We could return a wrapper over the Reader to ensure
 // that the reader isn't used for other purposes.
-func (rp *tableCacheShardReaderProvider) GetReader() (*sstable.Reader, error) {
+func (rp *tableCacheShardReaderProvider) GetReader(ctx context.Context) (*sstable.Reader, error) {
 	// Calling findNode gives us the responsibility of decrementing v's
 	// refCount.
-	v := rp.c.findNode(rp.file.FileBacking, rp.dbOpts)
+	v := rp.c.findNode(ctx, rp.file.FileBacking, rp.dbOpts)
 	if v.err != nil {
 		defer rp.c.unrefValue(v)
 		return nil, v.err
@@ -707,7 +707,7 @@ func (c *tableCacheShard) getTableProperties(
 	file *fileMetadata, dbOpts *tableCacheOpts,
 ) (*sstable.Properties, error) {
 	// Calling findNode gives us the responsibility of decrementing v's refCount here
-	v := c.findNode(file.FileBacking, dbOpts)
+	v := c.findNode(context.TODO(), file.FileBacking, dbOpts)
 	defer c.unrefValue(v)
 
 	if v.err != nil {
@@ -783,7 +783,9 @@ func (c *tableCacheShard) unrefValue(v *tableCacheValue) {
 // findNode returns the node for the table with the given file number, creating
 // that node if it didn't already exist. The caller is responsible for
 // decrementing the returned node's refCount.
-func (c *tableCacheShard) findNode(b *fileBacking, dbOpts *tableCacheOpts) *tableCacheValue {
+func (c *tableCacheShard) findNode(
+	ctx context.Context, b *fileBacking, dbOpts *tableCacheOpts,
+) *tableCacheValue {
 	// The backing must have a positive refcount (otherwise it could be deleted at any time).
 	b.MustHaveRefs()
 	// Caution! Here fileMetadata can be a physical or virtual table. Table cache
@@ -794,11 +796,11 @@ func (c *tableCacheShard) findNode(b *fileBacking, dbOpts *tableCacheOpts) *tabl
 		backingFileNum: b.DiskFileNum,
 	}
 
-	return c.findNodeInternal(info, dbOpts)
+	return c.findNodeInternal(ctx, info, dbOpts)
 }
 
 func (c *tableCacheShard) findNodeInternal(
-	loadInfo loadInfo, dbOpts *tableCacheOpts,
+	ctx context.Context, loadInfo loadInfo, dbOpts *tableCacheOpts,
 ) *tableCacheValue {
 	// Fast-path for a hit in the cache.
 	c.mu.RLock()
@@ -882,7 +884,7 @@ func (c *tableCacheShard) findNodeInternal(
 	// Note adding to the cache lists must complete before we begin loading the
 	// table as a failure during load will result in the node being unlinked.
 	pprof.Do(context.Background(), tableCacheLabels, func(context.Context) {
-		v.load(loadInfo, c, dbOpts)
+		v.load(ctx, loadInfo, c, dbOpts)
 	})
 	return v
 }
@@ -1113,12 +1115,14 @@ type loadInfo struct {
 	backingFileNum base.DiskFileNum
 }
 
-func (v *tableCacheValue) load(loadInfo loadInfo, c *tableCacheShard, dbOpts *tableCacheOpts) {
+func (v *tableCacheValue) load(
+	ctx context.Context, loadInfo loadInfo, c *tableCacheShard, dbOpts *tableCacheOpts,
+) {
 	// Try opening the file first.
 	var f objstorage.Readable
 	var err error
 	f, err = dbOpts.objProvider.OpenForReading(
-		context.TODO(), fileTypeTable, loadInfo.backingFileNum, objstorage.OpenOptions{MustExist: true},
+		ctx, fileTypeTable, loadInfo.backingFileNum, objstorage.OpenOptions{MustExist: true},
 	)
 	if err == nil {
 		o := dbOpts.readerOpts
@@ -1127,7 +1131,7 @@ func (v *tableCacheValue) load(loadInfo loadInfo, c *tableCacheShard, dbOpts *ta
 			CacheID: dbOpts.cacheID,
 			FileNum: loadInfo.backingFileNum,
 		})
-		v.reader, err = sstable.NewReader(f, o)
+		v.reader, err = sstable.NewReader(ctx, f, o)
 	}
 	if err == nil {
 		var objMeta objstorage.ObjectMetadata
diff --git a/tool/db.go b/tool/db.go
index 787e4e40..04e29d69 100644
--- a/tool/db.go
+++ b/tool/db.go
@@ -883,7 +883,7 @@ func (d *dbT) addProps(
 	if err != nil {
 		return err
 	}
-	r, err := sstable.NewReader(f, sstable.ReaderOptions{
+	r, err := sstable.NewReader(ctx, f, sstable.ReaderOptions{
 		Mergers:   d.mergers,
 		Comparers: d.comparers,
 	})
diff --git a/tool/db_io_bench.go b/tool/db_io_bench.go
index 20cd08f2..ba2fd3f7 100644
--- a/tool/db_io_bench.go
+++ b/tool/db_io_bench.go
@@ -248,7 +248,7 @@ func performIOs(readables []objstorage.Readable, ios []benchIO) error {
 	ctx := context.Background()
 	rh := make([]objstorage.ReadHandle, len(readables))
 	for i := range rh {
-		rh[i] = readables[i].NewReadHandle(ctx, objstorage.NoReadBefore)
+		rh[i] = readables[i].NewReadHandle(objstorage.NoReadBefore)
 	}
 	defer func() {
 		for i := range rh {
diff --git a/tool/find.go b/tool/find.go
index 3ddc7c53..690a1831 100644
--- a/tool/find.go
+++ b/tool/find.go
@@ -7,6 +7,7 @@ package tool
 import (
 	"bytes"
 	"cmp"
+	"context"
 	"fmt"
 	"io"
 	"path/filepath"
@@ -452,7 +453,7 @@ func (f *findT) searchTables(stdout io.Writer, searchKey []byte, refs []findRef)
 			if err != nil {
 				return err
 			}
-			r, err := sstable.NewReader(readable, opts)
+			r, err := sstable.NewReader(context.Background(), readable, opts)
 			if err != nil {
 				f.errors = append(f.errors, fmt.Sprintf("Unable to decode sstable %s, %s", fl.path, err.Error()))
 				// Ensure the error only gets printed once.
diff --git a/tool/sstable.go b/tool/sstable.go
index a8fe9fa6..90391e1c 100644
--- a/tool/sstable.go
+++ b/tool/sstable.go
@@ -6,6 +6,7 @@ package tool
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"os"
@@ -158,7 +159,7 @@ func (s *sstableT) newReader(f vfs.File) (*sstable.Reader, error) {
 			Cache: c,
 		},
 	})
-	return sstable.NewReader(readable, o)
+	return sstable.NewReader(context.Background(), readable, o)
 }
 
 func (s *sstableT) runCheck(cmd *cobra.Command, args []string) {


Test patch:
diff --git a/compaction_test.go b/compaction_test.go
index 4a7e95dd..2a88a8dd 100644
--- a/compaction_test.go
+++ b/compaction_test.go
@@ -657,7 +657,7 @@ func TestCompaction(t *testing.T) {
 				if err != nil {
 					return "", "", errors.WithStack(err)
 				}
-				r, err := sstable.NewReader(f, sstable.ReaderOptions{})
+				r, err := sstable.NewReader(context.Background(), f, sstable.ReaderOptions{})
 				if err != nil {
 					return "", "", errors.WithStack(err)
 				}
diff --git a/data_test.go b/data_test.go
index 8cb4de7d..c5d569f5 100644
--- a/data_test.go
+++ b/data_test.go
@@ -6,6 +6,7 @@ package pebble
 
 import (
 	"bytes"
+	"context"
 	crand "crypto/rand"
 	"fmt"
 	"io"
@@ -1232,7 +1233,7 @@ func runSSTablePropertiesCmd(t *testing.T, td *datadriven.TestData, d *DB) strin
 		CacheID: 0,
 		FileNum: backingFileNum,
 	})
-	r, err := sstable.NewReader(readable, readerOpts)
+	r, err := sstable.NewReader(context.Background(), readable, readerOpts)
 	if err != nil {
 		return err.Error()
 	}
diff --git a/ingest_test.go b/ingest_test.go
index 015e3954..8d8bedc0 100644
--- a/ingest_test.go
+++ b/ingest_test.go
@@ -3556,7 +3556,7 @@ func TestIngestValidation(t *testing.T) {
 				require.NoError(t, err)
 				// Compute the layout of the sstable in order to find the
 				// appropriate block locations to corrupt.
-				r, err := sstable.NewReader(readable, sstable.ReaderOptions{})
+				r, err := sstable.NewReader(context.Background(), readable, sstable.ReaderOptions{})
 				require.NoError(t, err)
 				l, err := r.Layout()
 				require.NoError(t, err)
diff --git a/internal/base/lazy_value_test.go b/internal/base/lazy_value_test.go
index 82ad51c7..43735985 100644
--- a/internal/base/lazy_value_test.go
+++ b/internal/base/lazy_value_test.go
@@ -6,6 +6,7 @@ package base
 
 import (
 	"bytes"
+	"context"
 	"testing"
 	"unsafe"
 
@@ -16,7 +17,7 @@ type valueFetcherFunc func(
 	handle []byte, valLen int32, buf []byte) (val []byte, callerOwned bool, err error)
 
 func (v valueFetcherFunc) Fetch(
-	handle []byte, valLen int32, buf []byte,
+	ctx context.Context, handle []byte, valLen int32, buf []byte,
 ) (val []byte, callerOwned bool, err error) {
 	return v(handle, valLen, buf)
 }
diff --git a/iterator_histories_test.go b/iterator_histories_test.go
index f5de4658..89b70760 100644
--- a/iterator_histories_test.go
+++ b/iterator_histories_test.go
@@ -5,6 +5,7 @@ package pebble
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"path/filepath"
 	"strconv"
@@ -231,7 +232,7 @@ func TestIterHistories(t *testing.T) {
 				if err != nil {
 					return err.Error()
 				}
-				r, err := sstable.NewReader(readable, opts.MakeReaderOptions())
+				r, err := sstable.NewReader(context.Background(), readable, opts.MakeReaderOptions())
 				if err != nil {
 					return err.Error()
 				}
diff --git a/level_checker_test.go b/level_checker_test.go
index c0b31ed2..9aedb548 100644
--- a/level_checker_test.go
+++ b/level_checker_test.go
@@ -232,7 +232,7 @@ func TestCheckLevelsCornerCases(t *testing.T) {
 				// Set FileNum for logging purposes.
 				readerOpts := sstable.ReaderOptions{Comparer: testkeys.Comparer}
 				readerOpts.SetInternalCacheOpts(sstableinternal.CacheOptions{FileNum: base.DiskFileNum(fileNum - 1)})
-				r, err := sstable.NewReader(readable, readerOpts)
+				r, err := sstable.NewReader(context.Background(), readable, readerOpts)
 				if err != nil {
 					return err.Error()
 				}
diff --git a/level_iter_test.go b/level_iter_test.go
index 80e13532..506dd75a 100644
--- a/level_iter_test.go
+++ b/level_iter_test.go
@@ -272,7 +272,7 @@ func (lt *levelIterTest) runBuild(d *datadriven.TestData) string {
 	if err != nil {
 		return err.Error()
 	}
-	r, err := sstable.NewReader(readable, sstable.ReaderOptions{
+	r, err := sstable.NewReader(context.Background(), readable, sstable.ReaderOptions{
 		Filters: map[string]FilterPolicy{
 			fp.Name(): fp,
 		},
@@ -542,7 +542,7 @@ func buildLevelIterTables(
 		if err != nil {
 			b.Fatal(err)
 		}
-		readers[i], err = sstable.NewReader(readable, opts)
+		readers[i], err = sstable.NewReader(context.Background(), readable, opts)
 		if err != nil {
 			b.Fatal(err)
 		}
diff --git a/merging_iter_test.go b/merging_iter_test.go
index 083fc0aa..a7044702 100644
--- a/merging_iter_test.go
+++ b/merging_iter_test.go
@@ -261,7 +261,7 @@ func TestMergingIterCornerCases(t *testing.T) {
 				if err != nil {
 					return err.Error()
 				}
-				r, err := sstable.NewReader(readable, sstable.ReaderOptions{})
+				r, err := sstable.NewReader(context.Background(), readable, sstable.ReaderOptions{})
 				if err != nil {
 					return err.Error()
 				}
@@ -391,7 +391,7 @@ func buildMergingIterTables(
 		if err != nil {
 			b.Fatal(err)
 		}
-		readers[i], err = sstable.NewReader(readable, opts)
+		readers[i], err = sstable.NewReader(context.Background(), readable, opts)
 		if err != nil {
 			b.Fatal(err)
 		}
@@ -637,7 +637,7 @@ func buildLevelsForMergingIterSeqSeek(
 			if err != nil {
 				b.Fatal(err)
 			}
-			r, err := sstable.NewReader(readable, opts)
+			r, err := sstable.NewReader(context.Background(), readable, opts)
 			if err != nil {
 				b.Fatal(err)
 			}
diff --git a/metamorphic/options_test.go b/metamorphic/options_test.go
index d6eeba46..1f405718 100644
--- a/metamorphic/options_test.go
+++ b/metamorphic/options_test.go
@@ -5,6 +5,7 @@
 package metamorphic
 
 import (
+	"context"
 	"fmt"
 	"io/fs"
 	"os"
@@ -202,7 +203,7 @@ func TestBlockPropertiesParse(t *testing.T) {
 			if err != nil {
 				return err
 			}
-			r, err := sstable.NewReader(readable, opts.Opts.MakeReaderOptions())
+			r, err := sstable.NewReader(context.Background(), readable, opts.Opts.MakeReaderOptions())
 			if err != nil {
 				return err
 			}
diff --git a/objstorage/objstorageprovider/provider_test.go b/objstorage/objstorageprovider/provider_test.go
index 73aeb23d..67e56870 100644
--- a/objstorage/objstorageprovider/provider_test.go
+++ b/objstorage/objstorageprovider/provider_test.go
@@ -205,10 +205,10 @@ func TestProvider(t *testing.T) {
 				var rh objstorage.ReadHandle
 				// Test both ways of getting a handle.
 				if rand.Intn(2) == 0 {
-					rh = r.NewReadHandle(ctx, objstorage.NoReadBefore)
+					rh = r.NewReadHandle(objstorage.NoReadBefore)
 				} else {
 					var prealloc PreallocatedReadHandle
-					rh = UsePreallocatedReadHandle(ctx, r, objstorage.NoReadBefore, &prealloc)
+					rh = UsePreallocatedReadHandle(r, objstorage.NoReadBefore, &prealloc)
 				}
 				if forCompaction {
 					rh.SetupForCompaction()
diff --git a/objstorage/objstorageprovider/remote_readable_test.go b/objstorage/objstorageprovider/remote_readable_test.go
index 721c2ff7..e26cd366 100644
--- a/objstorage/objstorageprovider/remote_readable_test.go
+++ b/objstorage/objstorageprovider/remote_readable_test.go
@@ -79,7 +79,7 @@ func TestRemoteReadHandle(t *testing.T) {
 			}
 			var readBeforeSize int
 			d.ScanArgs(t, "read-before-size", &readBeforeSize)
-			rh = rr.NewReadHandle(context.Background(), objstorage.ReadBeforeSize(readBeforeSize))
+			rh = rr.NewReadHandle(objstorage.ReadBeforeSize(readBeforeSize))
 			if d.HasArg("setup-for-compaction") {
 				rh.SetupForCompaction()
 			}
diff --git a/objstorage/test_utils.go b/objstorage/test_utils.go
index 928eeeb6..ca225f53 100644
--- a/objstorage/test_utils.go
+++ b/objstorage/test_utils.go
@@ -58,6 +58,6 @@ func (f *MemObj) Size() int64 {
 }
 
 // NewReadHandle is part of the Readable interface.
-func (f *MemObj) NewReadHandle(ctx context.Context, readBeforeSize ReadBeforeSize) ReadHandle {
+func (f *MemObj) NewReadHandle(readBeforeSize ReadBeforeSize) ReadHandle {
 	return &NoopReadHandle{readable: f}
 }
diff --git a/sstable/data_test.go b/sstable/data_test.go
index 7cb4a5aa..88ccf969 100644
--- a/sstable/data_test.go
+++ b/sstable/data_test.go
@@ -240,7 +240,7 @@ func runBuildRawCmd(
 	if err != nil {
 		return nil, nil, err
 	}
-	r, err := NewReader(f1, ReaderOptions{})
+	r, err := NewReader(context.Background(), f1, ReaderOptions{})
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/sstable/random_test.go b/sstable/random_test.go
index 2e39abe9..3801fdfe 100644
--- a/sstable/random_test.go
+++ b/sstable/random_test.go
@@ -5,6 +5,7 @@
 package sstable
 
 import (
+	"context"
 	"fmt"
 	"math/rand"
 	"runtime/debug"
@@ -76,7 +77,7 @@ func runErrorInjectionTest(t *testing.T, seed int64) {
 	}))
 	readable, err := NewSimpleReadable(f)
 	require.NoError(t, err)
-	r, err := NewReader(readable, cfg.readerOpts())
+	r, err := NewReader(context.Background(), readable, cfg.readerOpts())
 	require.NoError(t, err)
 	defer r.Close()
 
diff --git a/sstable/reader_test.go b/sstable/reader_test.go
index 8f9d1969..e529ada7 100644
--- a/sstable/reader_test.go
+++ b/sstable/reader_test.go
@@ -696,7 +696,7 @@ func TestInvalidReader(t *testing.T) {
 		{invalid, "invalid table"},
 	}
 	for _, tc := range testCases {
-		r, err := NewReader(tc.readable, ReaderOptions{})
+		r, err := NewReader(context.Background(), tc.readable, ReaderOptions{})
 		if !strings.Contains(err.Error(), tc.expected) {
 			t.Fatalf("expected %q, but found %q", tc.expected, err.Error())
 		}
@@ -1008,7 +1008,7 @@ func TestReadaheadSetupForV3TablesWithMultipleVersions(t *testing.T) {
 	require.NoError(t, w.Close())
 	f1, err := provider.OpenForReading(context.Background(), base.FileTypeTable, base.DiskFileNum(0), objstorage.OpenOptions{})
 	require.NoError(t, err)
-	r, err := NewReader(f1, ReaderOptions{Comparer: testkeys.Comparer})
+	r, err := NewReader(context.Background(), f1, ReaderOptions{Comparer: testkeys.Comparer})
 	require.NoError(t, err)
 	defer r.Close()
 	{
@@ -1796,7 +1796,7 @@ func buildTestTableWithProvider(
 
 	c := cache.New(128 << 20)
 	defer c.Unref()
-	r, err := NewReader(f1, ReaderOptions{
+	r, err := NewReader(context.Background(), f1, ReaderOptions{
 		internal: sstableinternal.ReaderOptions{
 			CacheOpts: sstableinternal.CacheOptions{
 				Cache: c,
@@ -2466,5 +2466,5 @@ func newReader(r ReadableFile, o ReaderOptions) (*Reader, error) {
 	if err != nil {
 		return nil, err
 	}
-	return NewReader(readable, o)
+	return NewReader(context.Background(), readable, o)
 }
diff --git a/sstable/test_utils.go b/sstable/test_utils.go
index 94b7bf47..c3d17a7b 100644
--- a/sstable/test_utils.go
+++ b/sstable/test_utils.go
@@ -5,6 +5,8 @@
 package sstable
 
 import (
+	"context"
+
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/testutils"
@@ -16,7 +18,7 @@ import (
 func ReadAll(
 	r objstorage.Readable,
 ) (points []base.InternalKV, rangeDels, rangeKeys []keyspan.Span) {
-	reader := testutils.CheckErr(NewReader(r, ReaderOptions{}))
+	reader := testutils.CheckErr(NewReader(context.Background(), r, ReaderOptions{}))
 	defer reader.Close()
 	pointIter := testutils.CheckErr(reader.NewIter(NoTransforms, nil /* lower */, nil /* upper */))
 	defer pointIter.Close()
diff --git a/table_cache_test.go b/table_cache_test.go
index 5427bc09..c88903b6 100644
--- a/table_cache_test.go
+++ b/table_cache_test.go
@@ -1112,7 +1112,7 @@ func TestTableCacheClockPro(t *testing.T) {
 		m := &fileMetadata{FileNum: FileNum(key)}
 		m.InitPhysicalBacking()
 		m.FileBacking.Ref()
-		v := cache.findNode(m.FileBacking, dbOpts)
+		v := cache.findNode(context.Background(), m.FileBacking, dbOpts)
 		cache.unrefValue(v)
 
 		hit := cache.hits.Load() != oldHits
@@ -1236,7 +1236,7 @@ func BenchmarkTableCacheHotPath(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		v := cache.findNode(m.FileBacking, dbOpts)
+		v := cache.findNode(context.Background(), m.FileBacking, dbOpts)
 		cache.unrefValue(v)
 	}
 }
diff --git a/table_stats_test.go b/table_stats_test.go
index 76d74d9b..8d9ceeb3 100644
--- a/table_stats_test.go
+++ b/table_stats_test.go
@@ -6,6 +6,7 @@ package pebble
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"strings"
 	"testing"
@@ -253,7 +254,7 @@ func TestTableRangeDeletionIter(t *testing.T) {
 			if err != nil {
 				return err.Error()
 			}
-			r, err = sstable.NewReader(readable, sstable.ReaderOptions{})
+			r, err = sstable.NewReader(context.Background(), readable, sstable.ReaderOptions{})
 			if err != nil {
 				return err.Error()
 			}