github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/objstorage/objstorageprovider/remote_readable.go (about)

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package objstorageprovider
     6  
     7  import (
     8  	"context"
     9  	"io"
    10  
    11  	"github.com/cockroachdb/pebble/internal/base"
    12  	"github.com/cockroachdb/pebble/objstorage"
    13  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
    14  	"github.com/cockroachdb/pebble/objstorage/remote"
    15  )
    16  
    17  const remoteMaxReadaheadSize = 1024 * 1024 /* 1MB */
    18  
    19  // remoteReadable is a very simple implementation of Readable on top of the
    20  // ReadCloser returned by remote.Storage.CreateObject.
    21  type remoteReadable struct {
    22  	objReader remote.ObjectReader
    23  	size      int64
    24  	fileNum   base.DiskFileNum
    25  	provider  *provider
    26  }
    27  
    28  var _ objstorage.Readable = (*remoteReadable)(nil)
    29  
    30  func (p *provider) newRemoteReadable(
    31  	objReader remote.ObjectReader, size int64, fileNum base.DiskFileNum,
    32  ) *remoteReadable {
    33  	return &remoteReadable{
    34  		objReader: objReader,
    35  		size:      size,
    36  		fileNum:   fileNum,
    37  		provider:  p,
    38  	}
    39  }
    40  
    41  // ReadAt is part of the objstorage.Readable interface.
    42  func (r *remoteReadable) ReadAt(ctx context.Context, p []byte, offset int64) error {
    43  	return r.readInternal(ctx, p, offset, false /* forCompaction */)
    44  }
    45  
    46  // readInternal performs a read for the object, using the cache when
    47  // appropriate.
    48  func (r *remoteReadable) readInternal(
    49  	ctx context.Context, p []byte, offset int64, forCompaction bool,
    50  ) error {
    51  	if cache := r.provider.remote.cache; cache != nil {
    52  		flags := sharedcache.ReadFlags{
    53  			// Don't add data to the cache if this read is for a compaction.
    54  			ReadOnly: forCompaction,
    55  		}
    56  		return r.provider.remote.cache.ReadAt(ctx, r.fileNum, p, offset, r.objReader, r.size, flags)
    57  	}
    58  	return r.objReader.ReadAt(ctx, p, offset)
    59  }
    60  
    61  func (r *remoteReadable) Close() error {
    62  	defer func() { r.objReader = nil }()
    63  	return r.objReader.Close()
    64  }
    65  
    66  func (r *remoteReadable) Size() int64 {
    67  	return r.size
    68  }
    69  
    70  func (r *remoteReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
    71  	// TODO(radu): use a pool.
    72  	rh := &remoteReadHandle{readable: r}
    73  	rh.readahead.state = makeReadaheadState(remoteMaxReadaheadSize)
    74  	return rh
    75  }
    76  
    77  type remoteReadHandle struct {
    78  	readable  *remoteReadable
    79  	readahead struct {
    80  		state  readaheadState
    81  		data   []byte
    82  		offset int64
    83  	}
    84  	forCompaction bool
    85  }
    86  
    87  var _ objstorage.ReadHandle = (*remoteReadHandle)(nil)
    88  
    89  // ReadAt is part of the objstorage.ReadHandle interface.
    90  func (r *remoteReadHandle) ReadAt(ctx context.Context, p []byte, offset int64) error {
    91  	readaheadSize := r.maybeReadahead(offset, len(p))
    92  
    93  	// Check if we already have the data from a previous read-ahead.
    94  	if rhSize := int64(len(r.readahead.data)); rhSize > 0 {
    95  		if r.readahead.offset <= offset && r.readahead.offset+rhSize > offset {
    96  			n := copy(p, r.readahead.data[offset-r.readahead.offset:])
    97  			if n == len(p) {
    98  				// All data was available.
    99  				return nil
   100  			}
   101  			// Use the data that we had and do a shorter read.
   102  			offset += int64(n)
   103  			p = p[n:]
   104  			readaheadSize -= n
   105  		}
   106  	}
   107  
   108  	if readaheadSize > len(p) {
   109  		// Don't try to read past EOF.
   110  		if offset+int64(readaheadSize) > r.readable.size {
   111  			readaheadSize = int(r.readable.size - offset)
   112  			if readaheadSize <= 0 {
   113  				// This shouldn't happen in practice (Pebble should never try to read
   114  				// past EOF).
   115  				return io.EOF
   116  			}
   117  		}
   118  		r.readahead.offset = offset
   119  		// TODO(radu): we need to somehow account for this memory.
   120  		if cap(r.readahead.data) >= readaheadSize {
   121  			r.readahead.data = r.readahead.data[:readaheadSize]
   122  		} else {
   123  			r.readahead.data = make([]byte, readaheadSize)
   124  		}
   125  
   126  		if err := r.readable.readInternal(ctx, r.readahead.data, offset, r.forCompaction); err != nil {
   127  			// Make sure we don't treat the data as valid next time.
   128  			r.readahead.data = r.readahead.data[:0]
   129  			return err
   130  		}
   131  		copy(p, r.readahead.data)
   132  		return nil
   133  	}
   134  
   135  	return r.readable.readInternal(ctx, p, offset, r.forCompaction)
   136  }
   137  
   138  func (r *remoteReadHandle) maybeReadahead(offset int64, len int) int {
   139  	if r.forCompaction {
   140  		return remoteMaxReadaheadSize
   141  	}
   142  	return int(r.readahead.state.maybeReadahead(offset, int64(len)))
   143  }
   144  
   145  // Close is part of the objstorage.ReadHandle interface.
   146  func (r *remoteReadHandle) Close() error {
   147  	r.readable = nil
   148  	r.readahead.data = nil
   149  	return nil
   150  }
   151  
   152  // SetupForCompaction is part of the objstorage.ReadHandle interface.
   153  func (r *remoteReadHandle) SetupForCompaction() {
   154  	r.forCompaction = true
   155  }
   156  
   157  // RecordCacheHit is part of the objstorage.ReadHandle interface.
   158  func (r *remoteReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
   159  	if !r.forCompaction {
   160  		r.readahead.state.recordCacheHit(offset, size)
   161  	}
   162  }