Upgrade versions of vendored libs
This commit is contained in:
4
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
4
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
@@ -27,8 +27,8 @@ func (h *NoopReadHandle) ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
// Close is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) Close() error { return nil }
|
||||
|
||||
// MaxReadahead is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) MaxReadahead() {}
|
||||
// SetupForCompaction is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) SetupForCompaction() {}
|
||||
|
||||
// RecordCacheHit is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {}
|
||||
|
||||
122
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
122
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
@@ -8,7 +8,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
@@ -16,6 +19,9 @@ import (
|
||||
type Readable interface {
|
||||
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||
//
|
||||
// Does not return partial results; if off + len(p) is past the end of the
|
||||
// object, an error is returned.
|
||||
//
|
||||
// Clients of ReadAt can execute parallel ReadAt calls on the
|
||||
// same Readable.
|
||||
ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
@@ -39,14 +45,18 @@ type Readable interface {
|
||||
type ReadHandle interface {
|
||||
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||
//
|
||||
// Does not return partial results; if off + len(p) is past the end of the
|
||||
// object, an error is returned.
|
||||
//
|
||||
// Parallel ReadAt calls on the same ReadHandle are not allowed.
|
||||
ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
|
||||
Close() error
|
||||
|
||||
// MaxReadahead configures the implementation to expect large sequential
|
||||
// reads. Used to skip any initial read-ahead ramp-up.
|
||||
MaxReadahead()
|
||||
// SetupForCompaction informs the implementation that the read handle will
|
||||
// be used to read data blocks for a compaction. The implementation can expect
|
||||
// sequential reads, and can decide to not retain data in any caches.
|
||||
SetupForCompaction()
|
||||
|
||||
// RecordCacheHit informs the implementation that we were able to retrieve a
|
||||
// block from cache. This is useful for example when the implementation is
|
||||
@@ -80,21 +90,71 @@ type ObjectMetadata struct {
|
||||
DiskFileNum base.DiskFileNum
|
||||
FileType base.FileType
|
||||
|
||||
// The fields below are only set if the object is on shared storage.
|
||||
Shared struct {
|
||||
// The fields below are only set if the object is on remote storage.
|
||||
Remote struct {
|
||||
// CreatorID identifies the DB instance that originally created the object.
|
||||
//
|
||||
// Only used when CustomObjectName is not set.
|
||||
CreatorID CreatorID
|
||||
// CreatorFileNum is the identifier for the object within the context of the
|
||||
// DB instance that originally created the object.
|
||||
//
|
||||
// Only used when CustomObjectName is not set.
|
||||
CreatorFileNum base.DiskFileNum
|
||||
|
||||
// CustomObjectName (if it is set) overrides the object name that is normally
|
||||
// derived from the CreatorID and CreatorFileNum.
|
||||
CustomObjectName string
|
||||
// CleanupMethod indicates the method for cleaning up unused shared objects.
|
||||
CleanupMethod SharedCleanupMethod
|
||||
// Locator identifies the remote.Storage implementation for this object.
|
||||
Locator remote.Locator
|
||||
// Storage is the remote.Storage object corresponding to the Locator. Used
|
||||
// to avoid lookups in hot paths.
|
||||
Storage remote.Storage
|
||||
}
|
||||
}
|
||||
|
||||
// IsShared returns true if the object is on shared storage.
|
||||
// IsRemote returns true if the object is on remote storage.
|
||||
func (meta *ObjectMetadata) IsRemote() bool {
|
||||
return meta.IsShared() || meta.IsExternal()
|
||||
}
|
||||
|
||||
// IsExternal returns true if the object is on remote storage but is not owned
|
||||
// by any Pebble instances in the cluster.
|
||||
func (meta *ObjectMetadata) IsExternal() bool {
|
||||
return meta.Remote.CustomObjectName != ""
|
||||
}
|
||||
|
||||
// IsShared returns true if the object is on remote storage and is owned by a
|
||||
// Pebble instance in the cluster (potentially shared between multiple
|
||||
// instances).
|
||||
func (meta *ObjectMetadata) IsShared() bool {
|
||||
return meta.Shared.CreatorID.IsSet()
|
||||
return meta.Remote.CreatorID.IsSet()
|
||||
}
|
||||
|
||||
// AssertValid checks that the metadata is sane.
|
||||
func (meta *ObjectMetadata) AssertValid() {
|
||||
if !meta.IsRemote() {
|
||||
// Verify all Remote fields are empty.
|
||||
if meta.Remote != (ObjectMetadata{}).Remote {
|
||||
panic(errors.AssertionFailedf("meta.Remote not empty: %#v", meta.Remote))
|
||||
}
|
||||
} else {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
if meta.Remote.CreatorID == 0 {
|
||||
panic(errors.AssertionFailedf("CreatorID not set"))
|
||||
}
|
||||
if meta.Remote.CreatorFileNum == base.FileNum(0).DiskFileNum() {
|
||||
panic(errors.AssertionFailedf("CreatorFileNum not set"))
|
||||
}
|
||||
}
|
||||
if meta.Remote.CleanupMethod != SharedNoCleanup && meta.Remote.CleanupMethod != SharedRefTracking {
|
||||
panic(errors.AssertionFailedf("invalid CleanupMethod %d", meta.Remote.CleanupMethod))
|
||||
}
|
||||
if meta.Remote.Storage == nil {
|
||||
panic(errors.AssertionFailedf("Storage not set"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreatorID identifies the DB instance that originally created a shared object.
|
||||
@@ -115,7 +175,7 @@ const (
|
||||
// keep track of references via reference marker objects.
|
||||
SharedRefTracking SharedCleanupMethod = iota
|
||||
|
||||
// SharedNoCleanup is used for shared objects that are managed externally; the
|
||||
// SharedNoCleanup is used for remote objects that are managed externally; the
|
||||
// objstorage provider never deletes such objects.
|
||||
SharedNoCleanup
|
||||
)
|
||||
@@ -148,7 +208,7 @@ type CreateOptions struct {
|
||||
// created by the provider, or existing objects the Provider was informed about
|
||||
// via AddObjects.
|
||||
//
|
||||
// Objects are currently backed by a vfs.File or a shared.Storage object.
|
||||
// Objects are currently backed by a vfs.File or a remote.Storage object.
|
||||
type Provider interface {
|
||||
// OpenForReading opens an existing object.
|
||||
OpenForReading(
|
||||
@@ -203,50 +263,62 @@ type Provider interface {
|
||||
List() []ObjectMetadata
|
||||
|
||||
// SetCreatorID sets the CreatorID which is needed in order to use shared
|
||||
// objects. Shared object usage is disabled until this method is called the
|
||||
// objects. Remote object usage is disabled until this method is called the
|
||||
// first time. Once set, the Creator ID is persisted and cannot change.
|
||||
//
|
||||
// Cannot be called if shared storage is not configured for the provider.
|
||||
SetCreatorID(creatorID CreatorID) error
|
||||
|
||||
// SharedObjectBacking encodes the shared object metadata.
|
||||
SharedObjectBacking(meta *ObjectMetadata) (SharedObjectBackingHandle, error)
|
||||
// IsSharedForeign returns whether this object is owned by a different node.
|
||||
IsSharedForeign(meta ObjectMetadata) bool
|
||||
|
||||
// AttachSharedObjects registers existing shared objects with this provider.
|
||||
AttachSharedObjects(objs []SharedObjectToAttach) ([]ObjectMetadata, error)
|
||||
// RemoteObjectBacking encodes the remote object metadata for the given object.
|
||||
RemoteObjectBacking(meta *ObjectMetadata) (RemoteObjectBackingHandle, error)
|
||||
|
||||
// CreateExternalObjectBacking creates a backing for an existing object with a
|
||||
// custom object name. The object is considered to be managed outside of
|
||||
// Pebble and will never be removed by Pebble.
|
||||
CreateExternalObjectBacking(locator remote.Locator, objName string) (RemoteObjectBacking, error)
|
||||
|
||||
// AttachRemoteObjects registers existing remote objects with this provider.
|
||||
AttachRemoteObjects(objs []RemoteObjectToAttach) ([]ObjectMetadata, error)
|
||||
|
||||
Close() error
|
||||
|
||||
// IsNotExistError indicates whether the error is known to report that a file or
|
||||
// directory does not exist.
|
||||
IsNotExistError(err error) bool
|
||||
|
||||
// Metrics returns metrics about objstorage. Currently, it only returns metrics
|
||||
// about the shared cache.
|
||||
Metrics() sharedcache.Metrics
|
||||
}
|
||||
|
||||
// SharedObjectBacking encodes the metadata necessary to incorporate a shared
|
||||
// RemoteObjectBacking encodes the metadata necessary to incorporate a shared
|
||||
// object into a different Pebble instance. The encoding is specific to a given
|
||||
// Provider implementation.
|
||||
type SharedObjectBacking []byte
|
||||
type RemoteObjectBacking []byte
|
||||
|
||||
// SharedObjectBackingHandle is a container for a SharedObjectBacking which
|
||||
// RemoteObjectBackingHandle is a container for a RemoteObjectBacking which
|
||||
// ensures that the backing stays valid. A backing can otherwise become invalid
|
||||
// if this provider unrefs the shared object. The SharedObjectBackingHandle
|
||||
// if this provider unrefs the shared object. The RemoteObjectBackingHandle
|
||||
// delays any unref until Close.
|
||||
type SharedObjectBackingHandle interface {
|
||||
type RemoteObjectBackingHandle interface {
|
||||
// Get returns the backing. The backing is only guaranteed to be valid until
|
||||
// Close is called (or until the Provider is closed). If Close was already
|
||||
// called, returns an error.
|
||||
Get() (SharedObjectBacking, error)
|
||||
Get() (RemoteObjectBacking, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
// SharedObjectToAttach contains the arguments needed to attach an existing shared object.
|
||||
type SharedObjectToAttach struct {
|
||||
// RemoteObjectToAttach contains the arguments needed to attach an existing remote object.
|
||||
type RemoteObjectToAttach struct {
|
||||
// FileNum is the file number that will be used to refer to this object (in
|
||||
// the context of this instance).
|
||||
FileNum base.DiskFileNum
|
||||
FileType base.FileType
|
||||
// Backing contains the metadata for the shared object backing (normally
|
||||
// Backing contains the metadata for the remote object backing (normally
|
||||
// generated from a different instance, but using the same Provider
|
||||
// implementation).
|
||||
Backing SharedObjectBacking
|
||||
Backing RemoteObjectBacking
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ const (
|
||||
// RecordCacheHitOp happens when a read is satisfied from the block cache. See
|
||||
// objstorage.ReadHandle.RecordCacheHit().
|
||||
RecordCacheHitOp
|
||||
// MaxReadaheadOp is a "meta operation" that configures a read handle for
|
||||
// large sequential reads. See objstorage.ReadHandle.MaxReadahead().
|
||||
MaxReadaheadOp
|
||||
// SetupForCompactionOp is a "meta operation" that configures a read handle
|
||||
// for large sequential reads. See objstorage.ReadHandle.SetupForCompaction().
|
||||
SetupForCompactionOp
|
||||
)
|
||||
|
||||
// Reason indicates the higher-level context of the operation.
|
||||
|
||||
@@ -209,14 +209,14 @@ func (rh *readHandle) Close() error {
|
||||
return rh.rh.Close()
|
||||
}
|
||||
|
||||
// MaxReadahead is part of the objstorage.ReadHandle interface.
|
||||
func (rh *readHandle) MaxReadahead() {
|
||||
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||
func (rh *readHandle) SetupForCompaction() {
|
||||
rh.g.add(context.Background(), Event{
|
||||
Op: MaxReadaheadOp,
|
||||
Op: SetupForCompactionOp,
|
||||
FileNum: rh.fileNum,
|
||||
HandleID: rh.handleID,
|
||||
})
|
||||
rh.rh.MaxReadahead()
|
||||
rh.rh.SetupForCompaction()
|
||||
}
|
||||
|
||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||
|
||||
198
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
198
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
@@ -17,8 +17,9 @@ import (
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
@@ -30,18 +31,20 @@ type provider struct {
|
||||
|
||||
tracer *objiotracing.Tracer
|
||||
|
||||
shared sharedSubsystem
|
||||
remote remoteSubsystem
|
||||
|
||||
mu struct {
|
||||
sync.RWMutex
|
||||
|
||||
shared struct {
|
||||
// catalogBatch accumulates shared object creations and deletions until
|
||||
remote struct {
|
||||
// catalogBatch accumulates remote object creations and deletions until
|
||||
// Sync is called.
|
||||
catalogBatch sharedobjcat.Batch
|
||||
catalogBatch remoteobjcat.Batch
|
||||
|
||||
storageObjects map[remote.Locator]remote.Storage
|
||||
}
|
||||
|
||||
// localObjectsChanged is set if non-shared objects were created or deleted
|
||||
// localObjectsChanged is set if non-remote objects were created or deleted
|
||||
// but Sync was not yet called.
|
||||
localObjectsChanged bool
|
||||
|
||||
@@ -89,14 +92,92 @@ type Settings struct {
|
||||
// out a large chunk of dirty filesystem buffers.
|
||||
BytesPerSync int
|
||||
|
||||
// Fields here are set only if the provider is to support shared objects
|
||||
// Local contains fields that are only relevant for files stored on the local
|
||||
// filesystem.
|
||||
Local struct {
|
||||
// TODO(radu): move FSCleaner, NoSyncOnClose, BytesPerSync here.
|
||||
|
||||
// ReadaheadConfigFn is a function used to retrieve the current readahead
|
||||
// mode. This function is run whenever a local object is open for reading.
|
||||
// If it is nil, DefaultReadaheadConfig is used.
|
||||
ReadaheadConfigFn func() ReadaheadConfig
|
||||
}
|
||||
|
||||
// Fields here are set only if the provider is to support remote objects
|
||||
// (experimental).
|
||||
Shared struct {
|
||||
Storage shared.Storage
|
||||
Remote struct {
|
||||
StorageFactory remote.StorageFactory
|
||||
|
||||
// If CreateOnShared is non-zero, sstables are created on remote storage using
|
||||
// the CreateOnSharedLocator (when the PreferSharedStorage create option is
|
||||
// true).
|
||||
CreateOnShared remote.CreateOnSharedStrategy
|
||||
CreateOnSharedLocator remote.Locator
|
||||
|
||||
// CacheSizeBytes is the size of the on-disk block cache for objects
|
||||
// on remote storage. If it is 0, no cache is used.
|
||||
CacheSizeBytes int64
|
||||
|
||||
// CacheBlockSize is the block size of the cache; if 0, the default of 32KB is used.
|
||||
CacheBlockSize int
|
||||
|
||||
// ShardingBlockSize is the size of a shard block. The cache is split into contiguous
|
||||
// ShardingBlockSize units. The units are distributed across multiple independent shards
|
||||
// of the cache, via a hash(offset) modulo num shards operation. The cache replacement
|
||||
// policies operate at the level of shard, not whole cache. This is done to reduce lock
|
||||
// contention.
|
||||
//
|
||||
// If ShardingBlockSize is 0, the default of 1 MB is used.
|
||||
ShardingBlockSize int64
|
||||
|
||||
// The number of independent shards the cache leverages. Each shard is the same size,
|
||||
// and a hash of filenum & offset map a read to a certain shard. If set to 0,
|
||||
// 2*runtime.GOMAXPROCS is used as the shard count.
|
||||
CacheShardCount int
|
||||
|
||||
// TODO(radu): allow the cache to live on another FS/location (e.g. to use
|
||||
// instance-local SSD).
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSettings initializes default settings (with no shared storage),
|
||||
// ReadaheadConfig controls the use of read-ahead.
|
||||
type ReadaheadConfig struct {
|
||||
// Informed is the type of read-ahead for operations that are known to read a
|
||||
// large consecutive chunk of a file.
|
||||
Informed ReadaheadMode
|
||||
|
||||
// Speculative is the type of read-ahead used automatically, when consecutive
|
||||
// reads are detected.
|
||||
Speculative ReadaheadMode
|
||||
}
|
||||
|
||||
// DefaultReadaheadConfig is the readahead config used when ReadaheadConfigFn is
|
||||
// not specified.
|
||||
var DefaultReadaheadConfig = ReadaheadConfig{
|
||||
Informed: FadviseSequential,
|
||||
Speculative: FadviseSequential,
|
||||
}
|
||||
|
||||
// ReadaheadMode indicates the type of read-ahead to use, either for informed
|
||||
// read-ahead (e.g. compactions) or speculative read-ahead.
|
||||
type ReadaheadMode uint8
|
||||
|
||||
const (
|
||||
// NoReadahead disables readahead altogether.
|
||||
NoReadahead ReadaheadMode = iota
|
||||
|
||||
// SysReadahead enables the use of SYS_READAHEAD call to prefetch data.
|
||||
// The prefetch window grows dynamically as consecutive writes are detected.
|
||||
SysReadahead
|
||||
|
||||
// FadviseSequential enables to use of FADV_SEQUENTIAL. For informed
|
||||
// read-ahead, FADV_SEQUENTIAL is used from the beginning. For speculative
|
||||
// read-ahead SYS_READAHEAD is first used until the window reaches the maximum
|
||||
// size, then we siwtch to FADV_SEQUENTIAL.
|
||||
FadviseSequential
|
||||
)
|
||||
|
||||
// DefaultSettings initializes default settings (with no remote storage),
|
||||
// suitable for tests and tools.
|
||||
func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||
return Settings{
|
||||
@@ -111,7 +192,13 @@ func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||
|
||||
// Open creates the provider.
|
||||
func Open(settings Settings) (objstorage.Provider, error) {
|
||||
return open(settings)
|
||||
// Note: we can't just `return open(settings)` because in an error case we
|
||||
// would return (*provider)(nil) which is not objstorage.Provider(nil).
|
||||
p, err := open(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func open(settings Settings) (p *provider, _ error) {
|
||||
@@ -142,8 +229,8 @@ func open(settings Settings) (p *provider, _ error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize shared subsystem (if configured) and add shared objects.
|
||||
if err := p.sharedInit(); err != nil {
|
||||
// Initialize remote subsystem (if configured) and add remote objects.
|
||||
if err := p.remoteInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -152,9 +239,9 @@ func open(settings Settings) (p *provider, _ error) {
|
||||
|
||||
// Close is part of the objstorage.Provider interface.
|
||||
func (p *provider) Close() error {
|
||||
var err error
|
||||
err := p.sharedClose()
|
||||
if p.fsDir != nil {
|
||||
err = p.fsDir.Close()
|
||||
err = firstError(err, p.fsDir.Close())
|
||||
p.fsDir = nil
|
||||
}
|
||||
if objiotracing.Enabled {
|
||||
@@ -182,10 +269,14 @@ func (p *provider) OpenForReading(
|
||||
}
|
||||
|
||||
var r objstorage.Readable
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
r, err = p.vfsOpenForReading(ctx, fileType, fileNum, opts)
|
||||
} else {
|
||||
r, err = p.sharedOpenForReading(ctx, meta, opts)
|
||||
r, err = p.remoteOpenForReading(ctx, meta, opts)
|
||||
if err != nil && p.isNotExistError(meta, err) {
|
||||
// Wrap the error so that IsNotExistError functions properly.
|
||||
err = errors.Mark(err, os.ErrNotExist)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -206,8 +297,8 @@ func (p *provider) Create(
|
||||
fileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (w objstorage.Writable, meta objstorage.ObjectMetadata, err error) {
|
||||
if opts.PreferSharedStorage && p.st.Shared.Storage != nil {
|
||||
w, meta, err = p.sharedCreate(ctx, fileType, fileNum, opts)
|
||||
if opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone {
|
||||
w, meta, err = p.sharedCreate(ctx, fileType, fileNum, p.st.Remote.CreateOnSharedLocator, opts)
|
||||
} else {
|
||||
w, meta, err = p.vfsCreate(ctx, fileType, fileNum)
|
||||
}
|
||||
@@ -224,8 +315,8 @@ func (p *provider) Create(
|
||||
|
||||
// Remove removes an object.
|
||||
//
|
||||
// Note that if the object is shared, the object is only (conceptually) removed
|
||||
// from this provider. If other providers have references on the shared object,
|
||||
// Note that if the object is remote, the object is only (conceptually) removed
|
||||
// from this provider. If other providers have references on the remote object,
|
||||
// it will not be removed.
|
||||
//
|
||||
// The object is not guaranteed to be durably removed until Sync is called.
|
||||
@@ -235,11 +326,15 @@ func (p *provider) Remove(fileType base.FileType, fileNum base.DiskFileNum) erro
|
||||
return err
|
||||
}
|
||||
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
err = p.vfsRemove(fileType, fileNum)
|
||||
} else {
|
||||
// TODO(radu): implement shared object removal (i.e. deref).
|
||||
// TODO(radu): implement remote object removal (i.e. deref).
|
||||
err = p.sharedUnref(meta)
|
||||
if err != nil && p.isNotExistError(meta, err) {
|
||||
// Wrap the error so that IsNotExistError functions properly.
|
||||
err = errors.Mark(err, os.ErrNotExist)
|
||||
}
|
||||
}
|
||||
if err != nil && !p.IsNotExistError(err) {
|
||||
// We want to be able to retry a Remove, so we keep the object in our list.
|
||||
@@ -252,15 +347,18 @@ func (p *provider) Remove(fileType base.FileType, fileNum base.DiskFileNum) erro
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *provider) isNotExistError(meta objstorage.ObjectMetadata, err error) bool {
|
||||
if meta.Remote.Storage != nil {
|
||||
return meta.Remote.Storage.IsNotExistError(err)
|
||||
}
|
||||
return oserror.IsNotExist(err)
|
||||
}
|
||||
|
||||
// IsNotExistError is part of the objstorage.Provider interface.
|
||||
func (p *provider) IsNotExistError(err error) bool {
|
||||
if oserror.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
if p.sharedStorage() != nil && p.sharedStorage().IsNotExistError(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// We use errors.Mark(err, os.ErrNotExist) for not-exist errors coming from
|
||||
// remote.Storage.
|
||||
return oserror.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Sync flushes the metadata from creation or removal of objects since the last Sync.
|
||||
@@ -288,7 +386,7 @@ func (p *provider) LinkOrCopyFromLocal(
|
||||
dstFileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.ObjectMetadata, error) {
|
||||
shared := opts.PreferSharedStorage && p.st.Shared.Storage != nil
|
||||
shared := opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone
|
||||
if !shared && srcFS == p.st.FS {
|
||||
// Wrap the normal filesystem with one which wraps newly created files with
|
||||
// vfs.NewSyncingFile.
|
||||
@@ -368,18 +466,18 @@ func (p *provider) Lookup(
|
||||
|
||||
// Path is part of the objstorage.Provider interface.
|
||||
func (p *provider) Path(meta objstorage.ObjectMetadata) string {
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
return p.vfsPath(meta.FileType, meta.DiskFileNum)
|
||||
}
|
||||
return p.sharedPath(meta)
|
||||
return p.remotePath(meta)
|
||||
}
|
||||
|
||||
// Size returns the size of the object.
|
||||
func (p *provider) Size(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
return p.vfsSize(meta.FileType, meta.DiskFileNum)
|
||||
}
|
||||
return p.sharedSize(meta)
|
||||
return p.remoteSize(meta)
|
||||
}
|
||||
|
||||
// List is part of the objstorage.Provider interface.
|
||||
@@ -396,17 +494,29 @@ func (p *provider) List() []objstorage.ObjectMetadata {
|
||||
return res
|
||||
}
|
||||
|
||||
// Metrics is part of the objstorage.Provider interface.
|
||||
func (p *provider) Metrics() sharedcache.Metrics {
|
||||
if p.remote.cache != nil {
|
||||
return p.remote.cache.Metrics()
|
||||
}
|
||||
return sharedcache.Metrics{}
|
||||
}
|
||||
|
||||
func (p *provider) addMetadata(meta objstorage.ObjectMetadata) {
|
||||
if invariants.Enabled {
|
||||
meta.AssertValid()
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.mu.knownObjects[meta.DiskFileNum] = meta
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
||||
if meta.IsRemote() {
|
||||
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||
FileNum: meta.DiskFileNum,
|
||||
FileType: meta.FileType,
|
||||
CreatorID: meta.Shared.CreatorID,
|
||||
CreatorFileNum: meta.Shared.CreatorFileNum,
|
||||
CleanupMethod: meta.Shared.CleanupMethod,
|
||||
CreatorID: meta.Remote.CreatorID,
|
||||
CreatorFileNum: meta.Remote.CreatorFileNum,
|
||||
Locator: meta.Remote.Locator,
|
||||
CleanupMethod: meta.Remote.CleanupMethod,
|
||||
})
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
@@ -422,14 +532,14 @@ func (p *provider) removeMetadata(fileNum base.DiskFileNum) {
|
||||
return
|
||||
}
|
||||
delete(p.mu.knownObjects, fileNum)
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.DeleteObject(fileNum)
|
||||
if meta.IsRemote() {
|
||||
p.mu.remote.catalogBatch.DeleteObject(fileNum)
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
// protectObject prevents the unreferencing of a shared object until
|
||||
// protectObject prevents the unreferencing of a remote object until
|
||||
// unprotectObject is called.
|
||||
func (p *provider) protectObject(fileNum base.DiskFileNum) {
|
||||
p.mu.Lock()
|
||||
|
||||
52
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/readahead.go
generated
vendored
52
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/readahead.go
generated
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/invariants"
|
||||
|
||||
const (
|
||||
// Constants for dynamic readahead of data blocks. Note that the size values
|
||||
// make sense as some multiple of the default block size; and they should
|
||||
@@ -11,15 +13,15 @@ const (
|
||||
minFileReadsForReadahead = 2
|
||||
// TODO(bilal): Have the initial size value be a factor of the block size,
|
||||
// as opposed to a hardcoded value.
|
||||
initialReadaheadSize = 64 << 10 /* 64KB */
|
||||
maxReadaheadSize = 256 << 10 /* 256KB */
|
||||
initialReadaheadSize = 64 << 10 /* 64KB */
|
||||
)
|
||||
|
||||
// readaheadState contains state variables related to readahead. Updated on
|
||||
// file reads.
|
||||
type readaheadState struct {
|
||||
// Number of sequential reads.
|
||||
numReads int64
|
||||
numReads int64
|
||||
maxReadaheadSize int64
|
||||
// Size issued to the next call to Prefetch. Starts at or above
|
||||
// initialReadaheadSize and grows exponentially until maxReadaheadSize.
|
||||
size int64
|
||||
@@ -32,22 +34,23 @@ type readaheadState struct {
|
||||
limit int64
|
||||
}
|
||||
|
||||
func makeReadaheadState() readaheadState {
|
||||
func makeReadaheadState(maxReadaheadSize int64) readaheadState {
|
||||
return readaheadState{
|
||||
size: initialReadaheadSize,
|
||||
size: initialReadaheadSize,
|
||||
maxReadaheadSize: maxReadaheadSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
currentReadEnd := offset + blockLength
|
||||
if rs.numReads >= minFileReadsForReadahead {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// This is a read that would have resulted in a readahead, had it
|
||||
// not been a cache hit.
|
||||
rs.limit = currentReadEnd
|
||||
return
|
||||
}
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+rs.maxReadaheadSize {
|
||||
// We read too far away from rs.limit to benefit from readahead in
|
||||
// any scenario. Reset all variables.
|
||||
rs.numReads = 1
|
||||
@@ -61,7 +64,7 @@ func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
// readahead.
|
||||
return
|
||||
}
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// Blocks are being read sequentially and would benefit from readahead
|
||||
// down the line.
|
||||
rs.numReads++
|
||||
@@ -80,6 +83,9 @@ func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
// Returns a size value (greater than 0) that should be prefetched if readahead
|
||||
// would be beneficial.
|
||||
func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
if invariants.Enabled && rs.maxReadaheadSize == 0 {
|
||||
panic("readaheadState not initialized")
|
||||
}
|
||||
currentReadEnd := offset + blockLength
|
||||
if rs.numReads >= minFileReadsForReadahead {
|
||||
// The minimum threshold of sequential reads to justify reading ahead
|
||||
@@ -89,28 +95,28 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// as well as the interval where a read would benefit from read ahead:
|
||||
// [rs.limit, rs.limit + rs.size]
|
||||
// We increase the latter interval to
|
||||
// [rs.limit, rs.limit + maxReadaheadSize] to account for cases where
|
||||
// [rs.limit, rs.limit + rs.maxReadaheadSize] to account for cases where
|
||||
// readahead may not be beneficial with a small readahead size, but over
|
||||
// time the readahead size would increase exponentially to make it
|
||||
// beneficial.
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// We are doing a read in the interval ahead of
|
||||
// the last readahead range. In the diagrams below, ++++ is the last
|
||||
// readahead range, ==== is the range represented by
|
||||
// [rs.limit, rs.limit + maxReadaheadSize], and ---- is the range
|
||||
// [rs.limit, rs.limit + rs.maxReadaheadSize], and ---- is the range
|
||||
// being read.
|
||||
//
|
||||
// rs.limit rs.limit + maxReadaheadSize
|
||||
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||
// ++++++++++|===========================|
|
||||
//
|
||||
// |-------------|
|
||||
// offset currentReadEnd
|
||||
//
|
||||
// This case is also possible, as are all cases with an overlap
|
||||
// between [rs.limit, rs.limit + maxReadaheadSize] and [offset,
|
||||
// between [rs.limit, rs.limit + rs.maxReadaheadSize] and [offset,
|
||||
// currentReadEnd]:
|
||||
//
|
||||
// rs.limit rs.limit + maxReadaheadSize
|
||||
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||
// ++++++++++|===========================|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -122,19 +128,19 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
rs.prevSize = rs.size
|
||||
// Increase rs.size for the next read.
|
||||
rs.size *= 2
|
||||
if rs.size > maxReadaheadSize {
|
||||
rs.size = maxReadaheadSize
|
||||
if rs.size > rs.maxReadaheadSize {
|
||||
rs.size = rs.maxReadaheadSize
|
||||
}
|
||||
return rs.prevSize
|
||||
}
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+rs.maxReadaheadSize {
|
||||
// The above conditional has rs.limit > rs.prevSize to confirm that
|
||||
// rs.limit - rs.prevSize would not underflow.
|
||||
// We read too far away from rs.limit to benefit from readahead in
|
||||
// any scenario. Reset all variables.
|
||||
// The case where we read too far ahead:
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -142,7 +148,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
//
|
||||
// Or too far behind:
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -160,7 +166,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// readahead, but there's no reason to issue a readahead call at the
|
||||
// moment.
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|===============|
|
||||
// (rs.limit)
|
||||
//
|
||||
@@ -170,11 +176,11 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
rs.numReads++
|
||||
return 0
|
||||
}
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// Blocks are being read sequentially and would benefit from readahead
|
||||
// down the line.
|
||||
//
|
||||
// (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |=============|
|
||||
//
|
||||
// |-------|
|
||||
@@ -186,7 +192,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// We read too far ahead of the last read, or before it. This indicates
|
||||
// a random read, where readahead is not desirable. Reset all variables.
|
||||
//
|
||||
// (rs.limit - maxReadaheadSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.maxReadaheadSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------|
|
||||
|
||||
376
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote.go
generated
vendored
Normal file
376
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote.go
generated
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
// remoteSubsystem contains the provider fields related to remote storage.
|
||||
// All fields remain unset if remote storage is not configured.
|
||||
type remoteSubsystem struct {
|
||||
catalog *remoteobjcat.Catalog
|
||||
// catalogSyncMutex is used to correctly serialize two sharedSync operations.
|
||||
// It must be acquired before the provider mutex.
|
||||
catalogSyncMutex sync.Mutex
|
||||
|
||||
cache *sharedcache.Cache
|
||||
|
||||
// shared contains the fields relevant to shared objects, i.e. objects that
|
||||
// are created by Pebble and potentially shared between Pebble instances.
|
||||
shared struct {
|
||||
// initialized guards access to the creatorID field.
|
||||
initialized atomic.Bool
|
||||
creatorID objstorage.CreatorID
|
||||
initOnce sync.Once
|
||||
|
||||
// checkRefsOnOpen controls whether we check the ref marker file when opening
|
||||
// an object. Normally this is true when invariants are enabled (but the provider
|
||||
// test tweaks this field).
|
||||
checkRefsOnOpen bool
|
||||
}
|
||||
}
|
||||
|
||||
// remoteInit initializes the remote object subsystem (if configured) and finds
|
||||
// any remote objects.
|
||||
func (p *provider) remoteInit() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return nil
|
||||
}
|
||||
catalog, contents, err := remoteobjcat.Open(p.st.FS, p.st.FSDirName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open remote object catalog")
|
||||
}
|
||||
p.remote.catalog = catalog
|
||||
p.remote.shared.checkRefsOnOpen = invariants.Enabled
|
||||
|
||||
// The creator ID may or may not be initialized yet.
|
||||
if contents.CreatorID.IsSet() {
|
||||
p.remote.initShared(contents.CreatorID)
|
||||
p.st.Logger.Infof("remote storage configured; creatorID = %s", contents.CreatorID)
|
||||
} else {
|
||||
p.st.Logger.Infof("remote storage configured; no creatorID yet")
|
||||
}
|
||||
|
||||
if p.st.Remote.CacheSizeBytes > 0 {
|
||||
const defaultBlockSize = 32 * 1024
|
||||
blockSize := p.st.Remote.CacheBlockSize
|
||||
if blockSize == 0 {
|
||||
blockSize = defaultBlockSize
|
||||
}
|
||||
|
||||
const defaultShardingBlockSize = 1024 * 1024
|
||||
shardingBlockSize := p.st.Remote.ShardingBlockSize
|
||||
if shardingBlockSize == 0 {
|
||||
shardingBlockSize = defaultShardingBlockSize
|
||||
}
|
||||
|
||||
numShards := p.st.Remote.CacheShardCount
|
||||
if numShards == 0 {
|
||||
numShards = 2 * runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
p.remote.cache, err = sharedcache.Open(
|
||||
p.st.FS, p.st.Logger, p.st.FSDirName, blockSize, shardingBlockSize, p.st.Remote.CacheSizeBytes, numShards)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open remote object cache")
|
||||
}
|
||||
}
|
||||
|
||||
for _, meta := range contents.Objects {
|
||||
o := objstorage.ObjectMetadata{
|
||||
DiskFileNum: meta.FileNum,
|
||||
FileType: meta.FileType,
|
||||
}
|
||||
o.Remote.CreatorID = meta.CreatorID
|
||||
o.Remote.CreatorFileNum = meta.CreatorFileNum
|
||||
o.Remote.CleanupMethod = meta.CleanupMethod
|
||||
o.Remote.Locator = meta.Locator
|
||||
o.Remote.CustomObjectName = meta.CustomObjectName
|
||||
o.Remote.Storage, err = p.ensureStorageLocked(o.Remote.Locator)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating remote.Storage object for locator '%s'", o.Remote.Locator)
|
||||
}
|
||||
if invariants.Enabled {
|
||||
o.AssertValid()
|
||||
}
|
||||
p.mu.knownObjects[o.DiskFileNum] = o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initShared initializes the creator ID, allowing use of shared objects.
|
||||
func (ss *remoteSubsystem) initShared(creatorID objstorage.CreatorID) {
|
||||
ss.shared.initOnce.Do(func() {
|
||||
ss.shared.creatorID = creatorID
|
||||
ss.shared.initialized.Store(true)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *provider) sharedClose() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if p.remote.cache != nil {
|
||||
err = p.remote.cache.Close()
|
||||
p.remote.cache = nil
|
||||
}
|
||||
if p.remote.catalog != nil {
|
||||
err = firstError(err, p.remote.catalog.Close())
|
||||
p.remote.catalog = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetCreatorID is part of the objstorage.Provider interface.
|
||||
func (p *provider) SetCreatorID(creatorID objstorage.CreatorID) error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return errors.AssertionFailedf("attempt to set CreatorID but remote storage not enabled")
|
||||
}
|
||||
// Note: this call is a cheap no-op if the creator ID was already set. This
|
||||
// call also checks if we are trying to change the ID.
|
||||
if err := p.remote.catalog.SetCreatorID(creatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
p.st.Logger.Infof("remote storage creatorID set to %s", creatorID)
|
||||
p.remote.initShared(creatorID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSharedForeign is part of the objstorage.Provider interface.
|
||||
func (p *provider) IsSharedForeign(meta objstorage.ObjectMetadata) bool {
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
return false
|
||||
}
|
||||
return meta.IsShared() && (meta.Remote.CreatorID != p.remote.shared.creatorID)
|
||||
}
|
||||
|
||||
func (p *provider) remoteCheckInitialized() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return errors.Errorf("remote object support not configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCheckInitialized() error {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
return errors.Errorf("remote object support not available: remote creator ID not yet set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSync() error {
|
||||
// Serialize parallel sync operations. Note that ApplyBatch is already
|
||||
// serialized internally, but we want to make sure they get called with
|
||||
// batches in the right order.
|
||||
p.remote.catalogSyncMutex.Lock()
|
||||
defer p.remote.catalogSyncMutex.Unlock()
|
||||
|
||||
batch := func() remoteobjcat.Batch {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
res := p.mu.remote.catalogBatch.Copy()
|
||||
p.mu.remote.catalogBatch.Reset()
|
||||
return res
|
||||
}()
|
||||
|
||||
if batch.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := p.remote.catalog.ApplyBatch(batch); err != nil {
|
||||
// Put back the batch (for the next Sync), appending any operations that
|
||||
// happened in the meantime.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
batch.Append(p.mu.remote.catalogBatch)
|
||||
p.mu.remote.catalogBatch = batch
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) remotePath(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.Locator != "" {
|
||||
return fmt.Sprintf("remote-%s://%s", meta.Remote.Locator, remoteObjectName(meta))
|
||||
}
|
||||
return "remote://" + remoteObjectName(meta)
|
||||
}
|
||||
|
||||
// sharedCreateRef creates a reference marker object.
|
||||
func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
return nil
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
writer, err := meta.Remote.Storage.CreateObject(refName)
|
||||
if err == nil {
|
||||
// The object is empty, just close the writer.
|
||||
err = writer.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating marker object %q", refName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCreate(
|
||||
_ context.Context,
|
||||
fileType base.FileType,
|
||||
fileNum base.DiskFileNum,
|
||||
locator remote.Locator,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
storage, err := p.ensureStorage(locator)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
meta := objstorage.ObjectMetadata{
|
||||
DiskFileNum: fileNum,
|
||||
FileType: fileType,
|
||||
}
|
||||
meta.Remote.CreatorID = p.remote.shared.creatorID
|
||||
meta.Remote.CreatorFileNum = fileNum
|
||||
meta.Remote.CleanupMethod = opts.SharedCleanupMethod
|
||||
meta.Remote.Locator = locator
|
||||
meta.Remote.Storage = storage
|
||||
|
||||
objName := remoteObjectName(meta)
|
||||
writer, err := storage.CreateObject(objName)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, errors.Wrapf(err, "creating object %q", objName)
|
||||
}
|
||||
return &sharedWritable{
|
||||
p: p,
|
||||
meta: meta,
|
||||
storageWriter: writer,
|
||||
}, meta, nil
|
||||
}
|
||||
|
||||
func (p *provider) remoteOpenForReading(
|
||||
ctx context.Context, meta objstorage.ObjectMetadata, opts objstorage.OpenOptions,
|
||||
) (objstorage.Readable, error) {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify we have a reference on this object; for performance reasons, we only
|
||||
// do this in testing scenarios.
|
||||
if p.remote.shared.checkRefsOnOpen && meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
if _, err := meta.Remote.Storage.Size(refName); err != nil {
|
||||
if meta.Remote.Storage.IsNotExistError(err) {
|
||||
if opts.MustExist {
|
||||
p.st.Logger.Fatalf("marker object %q does not exist", refName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, errors.Errorf("marker object %q does not exist", refName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "checking marker object %q", refName)
|
||||
}
|
||||
}
|
||||
objName := remoteObjectName(meta)
|
||||
reader, size, err := meta.Remote.Storage.ReadObject(ctx, objName)
|
||||
if err != nil {
|
||||
if opts.MustExist && meta.Remote.Storage.IsNotExistError(err) {
|
||||
p.st.Logger.Fatalf("object %q does not exist", objName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return p.newRemoteReadable(reader, size, meta.DiskFileNum), nil
|
||||
}
|
||||
|
||||
func (p *provider) remoteSize(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
objName := remoteObjectName(meta)
|
||||
return meta.Remote.Storage.Size(objName)
|
||||
}
|
||||
|
||||
// sharedUnref implements object "removal" with the remote backend. The ref
|
||||
// marker object is removed and the backing object is removed only if there are
|
||||
// no other ref markers.
|
||||
func (p *provider) sharedUnref(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Remote.CleanupMethod == objstorage.SharedNoCleanup {
|
||||
// Never delete objects in this mode.
|
||||
return nil
|
||||
}
|
||||
if p.isProtected(meta.DiskFileNum) {
|
||||
// TODO(radu): we need a mechanism to unref the object when it becomes
|
||||
// unprotected.
|
||||
return nil
|
||||
}
|
||||
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
// Tolerate a not-exists error.
|
||||
if err := meta.Remote.Storage.Delete(refName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
otherRefs, err := meta.Remote.Storage.List(sharedObjectRefPrefix(meta), "" /* delimiter */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(otherRefs) == 0 {
|
||||
objName := remoteObjectName(meta)
|
||||
if err := meta.Remote.Storage.Delete(objName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureStorageLocked populates the remote.Storage object for the given
|
||||
// locator, if necessary. p.mu must be held.
|
||||
func (p *provider) ensureStorageLocked(locator remote.Locator) (remote.Storage, error) {
|
||||
if p.mu.remote.storageObjects == nil {
|
||||
p.mu.remote.storageObjects = make(map[remote.Locator]remote.Storage)
|
||||
}
|
||||
if res, ok := p.mu.remote.storageObjects[locator]; ok {
|
||||
return res, nil
|
||||
}
|
||||
res, err := p.st.Remote.StorageFactory.CreateStorage(locator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.mu.remote.storageObjects[locator] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ensureStorage populates the remote.Storage object for the given locator, if necessary.
|
||||
func (p *provider) ensureStorage(locator remote.Locator) (remote.Storage, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.ensureStorageLocked(locator)
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +26,12 @@ const (
|
||||
// allows the "target" provider to check that the "source" provider kept its
|
||||
// reference on the object alive.
|
||||
tagRefCheckID = 4
|
||||
// tagLocator encodes the remote.Locator; if absent the locator is "". It is
|
||||
// followed by the locator string length and the locator string.
|
||||
tagLocator = 5
|
||||
// tagLocator encodes a custom object name (if present). It is followed by the
|
||||
// custom name string length and the string.
|
||||
tagCustomObjectName = 6
|
||||
|
||||
// Any new tags that don't have the tagNotSafeToIgnoreMask bit set must be
|
||||
// followed by the length of the data (so they can be skipped).
|
||||
@@ -34,83 +41,103 @@ const (
|
||||
tagNotSafeToIgnoreMask = 64
|
||||
)
|
||||
|
||||
func (p *provider) encodeSharedObjectBacking(
|
||||
func (p *provider) encodeRemoteObjectBacking(
|
||||
meta *objstorage.ObjectMetadata,
|
||||
) (objstorage.SharedObjectBacking, error) {
|
||||
if !meta.IsShared() {
|
||||
return nil, errors.AssertionFailedf("object %s not on shared storage", meta.DiskFileNum)
|
||||
) (objstorage.RemoteObjectBacking, error) {
|
||||
if !meta.IsRemote() {
|
||||
return nil, errors.AssertionFailedf("object %s not on remote storage", meta.DiskFileNum)
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*4)
|
||||
buf = binary.AppendUvarint(buf, tagCreatorID)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CreatorID))
|
||||
// TODO(radu): encode file type as well?
|
||||
buf = binary.AppendUvarint(buf, tagCreatorFileNum)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, tagCleanupMethod)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CleanupMethod))
|
||||
if meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CleanupMethod))
|
||||
if meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
buf = binary.AppendUvarint(buf, tagRefCheckID)
|
||||
buf = binary.AppendUvarint(buf, uint64(p.shared.creatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(p.remote.shared.creatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.DiskFileNum.FileNum()))
|
||||
}
|
||||
if meta.Remote.Locator != "" {
|
||||
buf = binary.AppendUvarint(buf, tagLocator)
|
||||
buf = encodeString(buf, string(meta.Remote.Locator))
|
||||
}
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
buf = binary.AppendUvarint(buf, tagCustomObjectName)
|
||||
buf = encodeString(buf, meta.Remote.CustomObjectName)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type sharedObjectBackingHandle struct {
|
||||
backing objstorage.SharedObjectBacking
|
||||
type remoteObjectBackingHandle struct {
|
||||
backing objstorage.RemoteObjectBacking
|
||||
fileNum base.DiskFileNum
|
||||
p *provider
|
||||
}
|
||||
|
||||
func (s *sharedObjectBackingHandle) Get() (objstorage.SharedObjectBacking, error) {
|
||||
func (s *remoteObjectBackingHandle) Get() (objstorage.RemoteObjectBacking, error) {
|
||||
if s.backing == nil {
|
||||
return nil, errors.Errorf("SharedObjectBackingHandle.Get() called after Close()")
|
||||
return nil, errors.Errorf("RemoteObjectBackingHandle.Get() called after Close()")
|
||||
}
|
||||
return s.backing, nil
|
||||
}
|
||||
|
||||
func (s *sharedObjectBackingHandle) Close() {
|
||||
func (s *remoteObjectBackingHandle) Close() {
|
||||
if s.backing != nil {
|
||||
s.backing = nil
|
||||
s.p.unprotectObject(s.fileNum)
|
||||
}
|
||||
}
|
||||
|
||||
var _ objstorage.SharedObjectBackingHandle = (*sharedObjectBackingHandle)(nil)
|
||||
var _ objstorage.RemoteObjectBackingHandle = (*remoteObjectBackingHandle)(nil)
|
||||
|
||||
// SharedObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) SharedObjectBacking(
|
||||
// RemoteObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) RemoteObjectBacking(
|
||||
meta *objstorage.ObjectMetadata,
|
||||
) (objstorage.SharedObjectBackingHandle, error) {
|
||||
backing, err := p.encodeSharedObjectBacking(meta)
|
||||
) (objstorage.RemoteObjectBackingHandle, error) {
|
||||
backing, err := p.encodeRemoteObjectBacking(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.protectObject(meta.DiskFileNum)
|
||||
return &sharedObjectBackingHandle{
|
||||
return &remoteObjectBackingHandle{
|
||||
backing: backing,
|
||||
fileNum: meta.DiskFileNum,
|
||||
p: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateExternalObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) CreateExternalObjectBacking(
|
||||
locator remote.Locator, objName string,
|
||||
) (objstorage.RemoteObjectBacking, error) {
|
||||
var meta objstorage.ObjectMetadata
|
||||
meta.Remote.Locator = locator
|
||||
meta.Remote.CustomObjectName = objName
|
||||
meta.Remote.CleanupMethod = objstorage.SharedNoCleanup
|
||||
return p.encodeRemoteObjectBacking(&meta)
|
||||
}
|
||||
|
||||
type decodedBacking struct {
|
||||
meta objstorage.ObjectMetadata
|
||||
// refToCheck is set only when meta.Shared.CleanupMethod is RefTracking
|
||||
// refToCheck is set only when meta.Remote.CleanupMethod is RefTracking
|
||||
refToCheck struct {
|
||||
creatorID objstorage.CreatorID
|
||||
fileNum base.DiskFileNum
|
||||
}
|
||||
}
|
||||
|
||||
// decodeSharedObjectBacking decodes the shared object metadata.
|
||||
// Returns the object metadata and (optionally) the creator ID of the provider
|
||||
// that encoded the backing whose ref marker needs to be checked.
|
||||
func decodeSharedObjectBacking(
|
||||
fileType base.FileType, fileNum base.DiskFileNum, buf objstorage.SharedObjectBacking,
|
||||
// decodeRemoteObjectBacking decodes the remote object metadata.
|
||||
//
|
||||
// Note that the meta.Remote.Storage field is not set.
|
||||
func decodeRemoteObjectBacking(
|
||||
fileType base.FileType, fileNum base.DiskFileNum, buf objstorage.RemoteObjectBacking,
|
||||
) (decodedBacking, error) {
|
||||
var creatorID, creatorFileNum, cleanupMethod, refCheckCreatorID, refCheckFileNum uint64
|
||||
var locator, customObjName string
|
||||
br := bytes.NewReader(buf)
|
||||
for {
|
||||
tag, err := binary.ReadUvarint(br)
|
||||
@@ -136,6 +163,12 @@ func decodeSharedObjectBacking(
|
||||
refCheckFileNum, err = binary.ReadUvarint(br)
|
||||
}
|
||||
|
||||
case tagLocator:
|
||||
locator, err = decodeString(br)
|
||||
|
||||
case tagCustomObjectName:
|
||||
customObjName, err = decodeString(br)
|
||||
|
||||
default:
|
||||
// Ignore unknown tags, unless they're not safe to ignore.
|
||||
if tag&tagNotSafeToIgnoreMask != 0 {
|
||||
@@ -151,37 +184,65 @@ func decodeSharedObjectBacking(
|
||||
return decodedBacking{}, err
|
||||
}
|
||||
}
|
||||
if creatorID == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing creator ID")
|
||||
}
|
||||
if creatorFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing creator file num")
|
||||
if customObjName == "" {
|
||||
if creatorID == 0 {
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing creator ID")
|
||||
}
|
||||
if creatorFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing creator file num")
|
||||
}
|
||||
}
|
||||
var res decodedBacking
|
||||
res.meta.DiskFileNum = fileNum
|
||||
res.meta.FileType = fileType
|
||||
res.meta.Shared.CreatorID = objstorage.CreatorID(creatorID)
|
||||
res.meta.Shared.CreatorFileNum = base.FileNum(creatorFileNum).DiskFileNum()
|
||||
res.meta.Shared.CleanupMethod = objstorage.SharedCleanupMethod(cleanupMethod)
|
||||
|
||||
if res.meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
res.meta.Remote.CreatorID = objstorage.CreatorID(creatorID)
|
||||
res.meta.Remote.CreatorFileNum = base.FileNum(creatorFileNum).DiskFileNum()
|
||||
res.meta.Remote.CleanupMethod = objstorage.SharedCleanupMethod(cleanupMethod)
|
||||
if res.meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
if refCheckCreatorID == 0 || refCheckFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing ref to check")
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing ref to check")
|
||||
}
|
||||
res.refToCheck.creatorID = objstorage.CreatorID(refCheckCreatorID)
|
||||
res.refToCheck.fileNum = base.FileNum(refCheckFileNum).DiskFileNum()
|
||||
}
|
||||
res.meta.Remote.Locator = remote.Locator(locator)
|
||||
res.meta.Remote.CustomObjectName = customObjName
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// AttachSharedObjects is part of the objstorage.Provider interface.
|
||||
func (p *provider) AttachSharedObjects(
|
||||
objs []objstorage.SharedObjectToAttach,
|
||||
func encodeString(buf []byte, s string) []byte {
|
||||
buf = binary.AppendUvarint(buf, uint64(len(s)))
|
||||
buf = append(buf, []byte(s)...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeString(br io.ByteReader) (string, error) {
|
||||
length, err := binary.ReadUvarint(br)
|
||||
if err != nil || length == 0 {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
for i := range buf {
|
||||
buf[i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// AttachRemoteObjects is part of the objstorage.Provider interface.
|
||||
func (p *provider) AttachRemoteObjects(
|
||||
objs []objstorage.RemoteObjectToAttach,
|
||||
) ([]objstorage.ObjectMetadata, error) {
|
||||
decoded := make([]decodedBacking, len(objs))
|
||||
for i, o := range objs {
|
||||
var err error
|
||||
decoded[i], err = decodeSharedObjectBacking(o.FileType, o.FileNum, o.Backing)
|
||||
decoded[i], err = decodeRemoteObjectBacking(o.FileType, o.FileNum, o.Backing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoded[i].meta.Remote.Storage, err = p.ensureStorage(decoded[i].meta.Remote.Locator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,7 +251,7 @@ func (p *provider) AttachSharedObjects(
|
||||
// Create the reference marker objects.
|
||||
// TODO(radu): parallelize this.
|
||||
for _, d := range decoded {
|
||||
if d.meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if d.meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
continue
|
||||
}
|
||||
if err := p.sharedCreateRef(d.meta); err != nil {
|
||||
@@ -199,10 +260,10 @@ func (p *provider) AttachSharedObjects(
|
||||
}
|
||||
// Check the "origin"'s reference.
|
||||
refName := sharedObjectRefName(d.meta, d.refToCheck.creatorID, d.refToCheck.fileNum)
|
||||
if _, err := p.sharedStorage().Size(refName); err != nil {
|
||||
if _, err := d.meta.Remote.Storage.Size(refName); err != nil {
|
||||
_ = p.sharedUnref(d.meta)
|
||||
// TODO(radu): clean up references previously created in this loop.
|
||||
if p.sharedStorage().IsNotExistError(err) {
|
||||
if d.meta.Remote.Storage.IsNotExistError(err) {
|
||||
return nil, errors.Errorf("origin marker object %q does not exist;"+
|
||||
" object probably removed from the provider which created the backing", refName)
|
||||
}
|
||||
@@ -214,11 +275,13 @@ func (p *provider) AttachSharedObjects(
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for _, d := range decoded {
|
||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
||||
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||
FileNum: d.meta.DiskFileNum,
|
||||
FileType: d.meta.FileType,
|
||||
CreatorID: d.meta.Shared.CreatorID,
|
||||
CreatorFileNum: d.meta.Shared.CreatorFileNum,
|
||||
CreatorID: d.meta.Remote.CreatorID,
|
||||
CreatorFileNum: d.meta.Remote.CreatorFileNum,
|
||||
CleanupMethod: d.meta.Remote.CleanupMethod,
|
||||
Locator: d.meta.Remote.Locator,
|
||||
})
|
||||
}
|
||||
}()
|
||||
@@ -11,16 +11,19 @@ import (
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
)
|
||||
|
||||
// sharedObjectName returns the name of an object on shared storage.
|
||||
// remoteObjectName returns the name of an object on remote storage.
|
||||
//
|
||||
// For sstables, the format is: <hash>-<creator-id>-<file-num>.sst
|
||||
// For example: 1a3f-2-000001.sst
|
||||
func sharedObjectName(meta objstorage.ObjectMetadata) string {
|
||||
func remoteObjectName(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return meta.Remote.CustomObjectName
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
@@ -34,25 +37,33 @@ func sharedObjectName(meta objstorage.ObjectMetadata) string {
|
||||
func sharedObjectRefName(
|
||||
meta objstorage.ObjectMetadata, refCreatorID objstorage.CreatorID, refFileNum base.DiskFileNum,
|
||||
) string {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
panic("ref object used when ref tracking disabled")
|
||||
}
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return fmt.Sprintf(
|
||||
"%s.ref.%d.%06d", meta.Remote.CustomObjectName, refCreatorID, refFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst.ref.%d.%06d",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(), refCreatorID, refFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(), refCreatorID, refFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
}
|
||||
|
||||
func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return meta.Remote.CustomObjectName + ".ref."
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst.ref.",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
@@ -64,10 +75,10 @@ func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
|
||||
//
|
||||
// For example: 1a3f-2-000001.sst.ref.5.000008
|
||||
func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
panic("ref object used when ref tracking disabled")
|
||||
}
|
||||
return sharedObjectRefName(meta, p.shared.creatorID, meta.DiskFileNum)
|
||||
return sharedObjectRefName(meta, p.remote.shared.creatorID, meta.DiskFileNum)
|
||||
}
|
||||
|
||||
// objHash returns a 16-bit hash value derived from the creator ID and creator
|
||||
@@ -76,5 +87,5 @@ func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
|
||||
func objHash(meta objstorage.ObjectMetadata) uint16 {
|
||||
const prime1 = 7459
|
||||
const prime2 = 17539
|
||||
return uint16(uint64(meta.Shared.CreatorID)*prime1 + uint64(meta.Shared.CreatorFileNum.FileNum())*prime2)
|
||||
return uint16(uint64(meta.Remote.CreatorID)*prime1 + uint64(meta.Remote.CreatorFileNum.FileNum())*prime2)
|
||||
}
|
||||
162
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_readable.go
generated
vendored
Normal file
162
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_readable.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
const remoteMaxReadaheadSize = 1024 * 1024 /* 1MB */
|
||||
|
||||
// remoteReadable is a very simple implementation of Readable on top of the
|
||||
// ReadCloser returned by remote.Storage.CreateObject.
|
||||
type remoteReadable struct {
|
||||
objReader remote.ObjectReader
|
||||
size int64
|
||||
fileNum base.DiskFileNum
|
||||
provider *provider
|
||||
}
|
||||
|
||||
var _ objstorage.Readable = (*remoteReadable)(nil)
|
||||
|
||||
func (p *provider) newRemoteReadable(
|
||||
objReader remote.ObjectReader, size int64, fileNum base.DiskFileNum,
|
||||
) *remoteReadable {
|
||||
return &remoteReadable{
|
||||
objReader: objReader,
|
||||
size: size,
|
||||
fileNum: fileNum,
|
||||
provider: p,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAt is part of the objstorage.Readable interface.
|
||||
func (r *remoteReadable) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
return r.readInternal(ctx, p, offset, false /* forCompaction */)
|
||||
}
|
||||
|
||||
// readInternal performs a read for the object, using the cache when
|
||||
// appropriate.
|
||||
func (r *remoteReadable) readInternal(
|
||||
ctx context.Context, p []byte, offset int64, forCompaction bool,
|
||||
) error {
|
||||
if cache := r.provider.remote.cache; cache != nil {
|
||||
flags := sharedcache.ReadFlags{
|
||||
// Don't add data to the cache if this read is for a compaction.
|
||||
ReadOnly: forCompaction,
|
||||
}
|
||||
return r.provider.remote.cache.ReadAt(ctx, r.fileNum, p, offset, r.objReader, r.size, flags)
|
||||
}
|
||||
return r.objReader.ReadAt(ctx, p, offset)
|
||||
}
|
||||
|
||||
func (r *remoteReadable) Close() error {
|
||||
defer func() { r.objReader = nil }()
|
||||
return r.objReader.Close()
|
||||
}
|
||||
|
||||
func (r *remoteReadable) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
func (r *remoteReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
|
||||
// TODO(radu): use a pool.
|
||||
rh := &remoteReadHandle{readable: r}
|
||||
rh.readahead.state = makeReadaheadState(remoteMaxReadaheadSize)
|
||||
return rh
|
||||
}
|
||||
|
||||
type remoteReadHandle struct {
|
||||
readable *remoteReadable
|
||||
readahead struct {
|
||||
state readaheadState
|
||||
data []byte
|
||||
offset int64
|
||||
}
|
||||
forCompaction bool
|
||||
}
|
||||
|
||||
var _ objstorage.ReadHandle = (*remoteReadHandle)(nil)
|
||||
|
||||
// ReadAt is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
readaheadSize := r.maybeReadahead(offset, len(p))
|
||||
|
||||
// Check if we already have the data from a previous read-ahead.
|
||||
if rhSize := int64(len(r.readahead.data)); rhSize > 0 {
|
||||
if r.readahead.offset <= offset && r.readahead.offset+rhSize > offset {
|
||||
n := copy(p, r.readahead.data[offset-r.readahead.offset:])
|
||||
if n == len(p) {
|
||||
// All data was available.
|
||||
return nil
|
||||
}
|
||||
// Use the data that we had and do a shorter read.
|
||||
offset += int64(n)
|
||||
p = p[n:]
|
||||
readaheadSize -= n
|
||||
}
|
||||
}
|
||||
|
||||
if readaheadSize > len(p) {
|
||||
// Don't try to read past EOF.
|
||||
if offset+int64(readaheadSize) > r.readable.size {
|
||||
readaheadSize = int(r.readable.size - offset)
|
||||
if readaheadSize <= 0 {
|
||||
// This shouldn't happen in practice (Pebble should never try to read
|
||||
// past EOF).
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
r.readahead.offset = offset
|
||||
// TODO(radu): we need to somehow account for this memory.
|
||||
if cap(r.readahead.data) >= readaheadSize {
|
||||
r.readahead.data = r.readahead.data[:readaheadSize]
|
||||
} else {
|
||||
r.readahead.data = make([]byte, readaheadSize)
|
||||
}
|
||||
|
||||
if err := r.readable.readInternal(ctx, r.readahead.data, offset, r.forCompaction); err != nil {
|
||||
// Make sure we don't treat the data as valid next time.
|
||||
r.readahead.data = r.readahead.data[:0]
|
||||
return err
|
||||
}
|
||||
copy(p, r.readahead.data)
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.readable.readInternal(ctx, p, offset, r.forCompaction)
|
||||
}
|
||||
|
||||
func (r *remoteReadHandle) maybeReadahead(offset int64, len int) int {
|
||||
if r.forCompaction {
|
||||
return remoteMaxReadaheadSize
|
||||
}
|
||||
return int(r.readahead.state.maybeReadahead(offset, int64(len)))
|
||||
}
|
||||
|
||||
// Close is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) Close() error {
|
||||
r.readable = nil
|
||||
r.readahead.data = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) SetupForCompaction() {
|
||||
r.forCompaction = true
|
||||
}
|
||||
|
||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
|
||||
if !r.forCompaction {
|
||||
r.readahead.state.recordCacheHit(offset, size)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sharedobjcat
|
||||
package remoteobjcat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -13,15 +13,16 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/record"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
"github.com/cockroachdb/pebble/vfs/atomicfs"
|
||||
)
|
||||
|
||||
// Catalog is used to manage the on-disk shared object catalog.
|
||||
// Catalog is used to manage the on-disk remote object catalog.
|
||||
//
|
||||
// The catalog file is a log of records, where each record is an encoded
|
||||
// versionEdit.
|
||||
// VersionEdit.
|
||||
type Catalog struct {
|
||||
fs vfs.FS
|
||||
dirname string
|
||||
@@ -29,7 +30,7 @@ type Catalog struct {
|
||||
sync.Mutex
|
||||
|
||||
creatorID objstorage.CreatorID
|
||||
objects map[base.DiskFileNum]SharedObjectMetadata
|
||||
objects map[base.DiskFileNum]RemoteObjectMetadata
|
||||
|
||||
marker *atomicfs.Marker
|
||||
|
||||
@@ -44,8 +45,8 @@ type Catalog struct {
|
||||
}
|
||||
}
|
||||
|
||||
// SharedObjectMetadata encapsulates the data stored in the catalog file for each object.
|
||||
type SharedObjectMetadata struct {
|
||||
// RemoteObjectMetadata encapsulates the data stored in the catalog file for each object.
|
||||
type RemoteObjectMetadata struct {
|
||||
// FileNum is the identifier for the object within the context of a single DB
|
||||
// instance.
|
||||
FileNum base.DiskFileNum
|
||||
@@ -56,24 +57,29 @@ type SharedObjectMetadata struct {
|
||||
// CreatorFileNum is the identifier for the object within the context of the
|
||||
// DB instance that originally created the object.
|
||||
CreatorFileNum base.DiskFileNum
|
||||
|
||||
// CleanupMethod indicates the method for cleaning up unused shared objects.
|
||||
CleanupMethod objstorage.SharedCleanupMethod
|
||||
// Locator identifies a remote.Storage implementation.
|
||||
Locator remote.Locator
|
||||
// CustomObjectName (if it is set) overrides the object name that is normally
|
||||
// derived from the CreatorID and CreatorFileNum.
|
||||
CustomObjectName string
|
||||
}
|
||||
|
||||
const (
|
||||
catalogFilenameBase = "SHARED-CATALOG"
|
||||
catalogMarkerName = "shared-catalog"
|
||||
catalogFilenameBase = "REMOTE-OBJ-CATALOG"
|
||||
catalogMarkerName = "remote-obj-catalog"
|
||||
|
||||
// We create a new file when the size exceeds 1MB (and some other conditions
|
||||
// hold; see record.RotationHelper).
|
||||
rotateFileSize = 1024 * 1024 // 1MB
|
||||
)
|
||||
|
||||
// CatalogContents contains the shared objects in the catalog.
|
||||
// CatalogContents contains the remote objects in the catalog.
|
||||
type CatalogContents struct {
|
||||
// CreatorID, if it is set.
|
||||
CreatorID objstorage.CreatorID
|
||||
Objects []SharedObjectMetadata
|
||||
Objects []RemoteObjectMetadata
|
||||
}
|
||||
|
||||
// Open creates a Catalog and loads any existing catalog file, returning the
|
||||
@@ -83,7 +89,7 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
||||
fs: fs,
|
||||
dirname: dirname,
|
||||
}
|
||||
c.mu.objects = make(map[base.DiskFileNum]SharedObjectMetadata)
|
||||
c.mu.objects = make(map[base.DiskFileNum]RemoteObjectMetadata)
|
||||
|
||||
var err error
|
||||
c.mu.marker, c.mu.catalogFilename, err = atomicfs.LocateMarker(fs, dirname, catalogMarkerName)
|
||||
@@ -102,7 +108,7 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
||||
}
|
||||
res := CatalogContents{
|
||||
CreatorID: c.mu.creatorID,
|
||||
Objects: make([]SharedObjectMetadata, 0, len(c.mu.objects)),
|
||||
Objects: make([]RemoteObjectMetadata, 0, len(c.mu.objects)),
|
||||
}
|
||||
for _, meta := range c.mu.objects {
|
||||
res.Objects = append(res.Objects, meta)
|
||||
@@ -130,9 +136,9 @@ func (c *Catalog) SetCreatorID(id objstorage.CreatorID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
ve := versionEdit{CreatorID: id}
|
||||
ve := VersionEdit{CreatorID: id}
|
||||
if err := c.writeToCatalogFileLocked(&ve); err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not write to shared object catalog: %v", err)
|
||||
return errors.Wrapf(err, "pebble: could not write to remote object catalog: %v", err)
|
||||
}
|
||||
c.mu.creatorID = id
|
||||
return nil
|
||||
@@ -159,14 +165,14 @@ func (c *Catalog) closeCatalogFile() error {
|
||||
|
||||
// Batch is used to perform multiple object additions/deletions at once.
|
||||
type Batch struct {
|
||||
ve versionEdit
|
||||
ve VersionEdit
|
||||
}
|
||||
|
||||
// AddObject adds a new object to the batch.
|
||||
//
|
||||
// The given FileNum must be new - it must not match that of any object that was
|
||||
// ever in the catalog.
|
||||
func (b *Batch) AddObject(meta SharedObjectMetadata) {
|
||||
func (b *Batch) AddObject(meta RemoteObjectMetadata) {
|
||||
b.ve.NewObjects = append(b.ve.NewObjects, meta)
|
||||
}
|
||||
|
||||
@@ -190,7 +196,7 @@ func (b *Batch) IsEmpty() bool {
|
||||
func (b *Batch) Copy() Batch {
|
||||
var res Batch
|
||||
if len(b.ve.NewObjects) > 0 {
|
||||
res.ve.NewObjects = make([]SharedObjectMetadata, len(b.ve.NewObjects))
|
||||
res.ve.NewObjects = make([]RemoteObjectMetadata, len(b.ve.NewObjects))
|
||||
copy(res.ve.NewObjects, b.ve.NewObjects)
|
||||
}
|
||||
if len(b.ve.DeletedObjects) > 0 {
|
||||
@@ -212,29 +218,40 @@ func (c *Catalog) ApplyBatch(b Batch) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
if _, exists := c.mu.objects[n]; !exists {
|
||||
return errors.AssertionFailedf("deleting non-existent object %s", n)
|
||||
// Sanity checks.
|
||||
toAdd := make(map[base.DiskFileNum]struct{}, len(b.ve.NewObjects))
|
||||
exists := func(n base.DiskFileNum) bool {
|
||||
_, ok := c.mu.objects[n]
|
||||
if !ok {
|
||||
_, ok = toAdd[n]
|
||||
}
|
||||
return ok
|
||||
}
|
||||
for _, meta := range b.ve.NewObjects {
|
||||
if _, exists := c.mu.objects[meta.FileNum]; exists {
|
||||
if exists(meta.FileNum) {
|
||||
return errors.AssertionFailedf("adding existing object %s", meta.FileNum)
|
||||
}
|
||||
toAdd[meta.FileNum] = struct{}{}
|
||||
}
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
if !exists(n) {
|
||||
return errors.AssertionFailedf("deleting non-existent object %s", n)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.writeToCatalogFileLocked(&b.ve); err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not write to shared object catalog: %v", err)
|
||||
return errors.Wrapf(err, "pebble: could not write to remote object catalog: %v", err)
|
||||
}
|
||||
|
||||
// Apply the batch to our current state.
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
delete(c.mu.objects, n)
|
||||
}
|
||||
// Add new objects before deleting any objects. This allows for cases where
|
||||
// the same batch adds and deletes an object.
|
||||
for _, meta := range b.ve.NewObjects {
|
||||
c.mu.objects[meta.FileNum] = meta
|
||||
}
|
||||
b.Reset()
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
delete(c.mu.objects, n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -243,7 +260,7 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
||||
f, err := c.fs.Open(catalogPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "pebble: could not open shared object catalog file %q for DB %q",
|
||||
err, "pebble: could not open remote object catalog file %q for DB %q",
|
||||
errors.Safe(filename), c.dirname,
|
||||
)
|
||||
}
|
||||
@@ -255,32 +272,26 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading shared object catalog file %q",
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
var ve versionEdit
|
||||
err = ve.Decode(r)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading shared object catalog file %q",
|
||||
var ve VersionEdit
|
||||
if err := ve.Decode(r); err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
// Apply the version edit to the current state.
|
||||
if ve.CreatorID.IsSet() {
|
||||
c.mu.creatorID = ve.CreatorID
|
||||
}
|
||||
for _, fileNum := range ve.DeletedObjects {
|
||||
delete(c.mu.objects, fileNum)
|
||||
}
|
||||
for _, meta := range ve.NewObjects {
|
||||
c.mu.objects[meta.FileNum] = meta
|
||||
if err := ve.Apply(&c.mu.creatorID, c.mu.objects); err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeToCatalogFileLocked writes a versionEdit to the catalog file.
|
||||
// writeToCatalogFileLocked writes a VersionEdit to the catalog file.
|
||||
// Creates a new file if this is the first write.
|
||||
func (c *Catalog) writeToCatalogFileLocked(ve *versionEdit) error {
|
||||
func (c *Catalog) writeToCatalogFileLocked(ve *VersionEdit) error {
|
||||
c.mu.rotationHelper.AddRecord(int64(len(ve.NewObjects) + len(ve.DeletedObjects)))
|
||||
snapshotSize := int64(len(c.mu.objects))
|
||||
|
||||
@@ -323,10 +334,10 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
||||
}
|
||||
recWriter := record.NewWriter(file)
|
||||
err = func() error {
|
||||
// Create a versionEdit that gets us from an empty catalog to the current state.
|
||||
var ve versionEdit
|
||||
// Create a VersionEdit that gets us from an empty catalog to the current state.
|
||||
var ve VersionEdit
|
||||
ve.CreatorID = c.mu.creatorID
|
||||
ve.NewObjects = make([]SharedObjectMetadata, 0, len(c.mu.objects))
|
||||
ve.NewObjects = make([]RemoteObjectMetadata, 0, len(c.mu.objects))
|
||||
for _, meta := range c.mu.objects {
|
||||
ve.NewObjects = append(ve.NewObjects, meta)
|
||||
}
|
||||
@@ -361,7 +372,7 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeRecord(ve *versionEdit, file vfs.File, recWriter *record.Writer) error {
|
||||
func writeRecord(ve *VersionEdit, file vfs.File, recWriter *record.Writer) error {
|
||||
w, err := recWriter.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sharedobjcat
|
||||
package remoteobjcat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -11,28 +11,37 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
// versionEdit is a modification to the shared object state which can be encoded
|
||||
// VersionEdit is a modification to the remote object state which can be encoded
|
||||
// into a record.
|
||||
//
|
||||
// TODO(radu): consider adding creation and deletion time for debugging purposes.
|
||||
type versionEdit struct {
|
||||
NewObjects []SharedObjectMetadata
|
||||
type VersionEdit struct {
|
||||
NewObjects []RemoteObjectMetadata
|
||||
DeletedObjects []base.DiskFileNum
|
||||
CreatorID objstorage.CreatorID
|
||||
}
|
||||
|
||||
const (
|
||||
// tagNewObject is followed by the FileNum, creator ID, creator FileNum, and
|
||||
// cleanup method.
|
||||
// tagNewObject is followed by the FileNum, creator ID, creator FileNum,
|
||||
// cleanup method, optional new object tags, and ending with a 0 byte.
|
||||
tagNewObject = 1
|
||||
// tagDeletedObject is followed by the FileNum.
|
||||
tagDeletedObject = 2
|
||||
// tagCreatorID is followed by the Creator ID for this store. This ID can
|
||||
// never change.
|
||||
tagCreatorID = 3
|
||||
// tagNewObjectLocator is an optional tag inside the tagNewObject payload. It
|
||||
// is followed by the encoded length of the locator string and the string.
|
||||
tagNewObjectLocator = 4
|
||||
// tagNewObjectCustomName is an optional tag inside the tagNewObject payload.
|
||||
// It is followed by the encoded length of the custom object name string
|
||||
// followed by the string.
|
||||
tagNewObjectCustomName = 5
|
||||
)
|
||||
|
||||
// Object type values. We don't want to encode FileType directly because it is
|
||||
@@ -61,8 +70,8 @@ func fileTypeToObjType(fileType base.FileType) (uint64, error) {
|
||||
}
|
||||
|
||||
// Encode encodes an edit to the specified writer.
|
||||
func (v *versionEdit) Encode(w io.Writer) error {
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*(len(v.NewObjects)*4+len(v.DeletedObjects)*2+2))
|
||||
func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*(len(v.NewObjects)*10+len(v.DeletedObjects)*2+2))
|
||||
for _, meta := range v.NewObjects {
|
||||
objType, err := fileTypeToObjType(meta.FileType)
|
||||
if err != nil {
|
||||
@@ -74,6 +83,16 @@ func (v *versionEdit) Encode(w io.Writer) error {
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CleanupMethod))
|
||||
if meta.Locator != "" {
|
||||
buf = binary.AppendUvarint(buf, uint64(tagNewObjectLocator))
|
||||
buf = encodeString(buf, string(meta.Locator))
|
||||
}
|
||||
if meta.CustomObjectName != "" {
|
||||
buf = binary.AppendUvarint(buf, uint64(tagNewObjectCustomName))
|
||||
buf = encodeString(buf, meta.CustomObjectName)
|
||||
}
|
||||
// Append 0 as the terminator for optional new object tags.
|
||||
buf = binary.AppendUvarint(buf, 0)
|
||||
}
|
||||
|
||||
for _, dfn := range v.DeletedObjects {
|
||||
@@ -89,7 +108,7 @@ func (v *versionEdit) Encode(w io.Writer) error {
|
||||
}
|
||||
|
||||
// Decode decodes an edit from the specified reader.
|
||||
func (v *versionEdit) Decode(r io.Reader) error {
|
||||
func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
br, ok := r.(io.ByteReader)
|
||||
if !ok {
|
||||
br = bufio.NewReader(r)
|
||||
@@ -107,6 +126,7 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
switch tag {
|
||||
case tagNewObject:
|
||||
var fileNum, creatorID, creatorFileNum, cleanupMethod uint64
|
||||
var locator, customName string
|
||||
var fileType base.FileType
|
||||
fileNum, err = binary.ReadUvarint(br)
|
||||
if err == nil {
|
||||
@@ -125,13 +145,34 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
if err == nil {
|
||||
cleanupMethod, err = binary.ReadUvarint(br)
|
||||
}
|
||||
for err == nil {
|
||||
var optionalTag uint64
|
||||
optionalTag, err = binary.ReadUvarint(br)
|
||||
if err != nil || optionalTag == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
switch optionalTag {
|
||||
case tagNewObjectLocator:
|
||||
locator, err = decodeString(br)
|
||||
|
||||
case tagNewObjectCustomName:
|
||||
customName, err = decodeString(br)
|
||||
|
||||
default:
|
||||
err = errors.Newf("unknown newObject tag %d", optionalTag)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
v.NewObjects = append(v.NewObjects, SharedObjectMetadata{
|
||||
FileNum: base.FileNum(fileNum).DiskFileNum(),
|
||||
FileType: fileType,
|
||||
CreatorID: objstorage.CreatorID(creatorID),
|
||||
CreatorFileNum: base.FileNum(creatorFileNum).DiskFileNum(),
|
||||
CleanupMethod: objstorage.SharedCleanupMethod(cleanupMethod),
|
||||
v.NewObjects = append(v.NewObjects, RemoteObjectMetadata{
|
||||
FileNum: base.FileNum(fileNum).DiskFileNum(),
|
||||
FileType: fileType,
|
||||
CreatorID: objstorage.CreatorID(creatorID),
|
||||
CreatorFileNum: base.FileNum(creatorFileNum).DiskFileNum(),
|
||||
CleanupMethod: objstorage.SharedCleanupMethod(cleanupMethod),
|
||||
Locator: remote.Locator(locator),
|
||||
CustomObjectName: customName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,8 +191,9 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
|
||||
default:
|
||||
// Ignore unknown tags.
|
||||
err = errors.Newf("unknown tag %d", tag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return errCorruptCatalog
|
||||
@@ -162,4 +204,51 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt shared object catalog")
|
||||
func encodeString(buf []byte, s string) []byte {
|
||||
buf = binary.AppendUvarint(buf, uint64(len(s)))
|
||||
buf = append(buf, []byte(s)...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeString(br io.ByteReader) (string, error) {
|
||||
length, err := binary.ReadUvarint(br)
|
||||
if err != nil || length == 0 {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
for i := range buf {
|
||||
buf[i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt remote object catalog")
|
||||
|
||||
// Apply the version edit to a creator ID and a map of objects.
|
||||
func (v *VersionEdit) Apply(
|
||||
creatorID *objstorage.CreatorID, objects map[base.DiskFileNum]RemoteObjectMetadata,
|
||||
) error {
|
||||
if v.CreatorID.IsSet() {
|
||||
*creatorID = v.CreatorID
|
||||
}
|
||||
for _, meta := range v.NewObjects {
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[meta.FileNum]; exists {
|
||||
return errors.AssertionFailedf("version edit adds existing object %s", meta.FileNum)
|
||||
}
|
||||
}
|
||||
objects[meta.FileNum] = meta
|
||||
}
|
||||
for _, fileNum := range v.DeletedObjects {
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[fileNum]; !exists {
|
||||
return errors.AssertionFailedf("version edit deletes non-existent object %s", fileNum)
|
||||
}
|
||||
}
|
||||
delete(objects, fileNum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
255
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
generated
vendored
255
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
)
|
||||
|
||||
// sharedSubsystem contains the provider fields related to shared storage.
|
||||
// All fields remain unset if shared storage is not configured.
|
||||
type sharedSubsystem struct {
|
||||
catalog *sharedobjcat.Catalog
|
||||
|
||||
// checkRefsOnOpen controls whether we check the ref marker file when opening
|
||||
// an object. Normally this is true when invariants are enabled (but the provider
|
||||
// test tweaks this field).
|
||||
checkRefsOnOpen bool
|
||||
|
||||
// initialized guards access to the creatorID field.
|
||||
initialized atomic.Bool
|
||||
creatorID objstorage.CreatorID
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func (ss *sharedSubsystem) init(creatorID objstorage.CreatorID) {
|
||||
ss.initOnce.Do(func() {
|
||||
ss.creatorID = creatorID
|
||||
ss.initialized.Store(true)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *provider) sharedStorage() shared.Storage {
|
||||
return p.st.Shared.Storage
|
||||
}
|
||||
|
||||
// sharedInit initializes the shared object subsystem (if configured) and finds
|
||||
// any shared objects.
|
||||
func (p *provider) sharedInit() error {
|
||||
if p.sharedStorage() == nil {
|
||||
return nil
|
||||
}
|
||||
catalog, contents, err := sharedobjcat.Open(p.st.FS, p.st.FSDirName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open shared object catalog")
|
||||
}
|
||||
p.shared.catalog = catalog
|
||||
p.shared.checkRefsOnOpen = invariants.Enabled
|
||||
|
||||
// The creator ID may or may not be initialized yet.
|
||||
if contents.CreatorID.IsSet() {
|
||||
p.shared.init(contents.CreatorID)
|
||||
p.st.Logger.Infof("shared storage configured; creatorID = %s", contents.CreatorID)
|
||||
} else {
|
||||
p.st.Logger.Infof("shared storage configured; no creatorID yet")
|
||||
}
|
||||
|
||||
for _, meta := range contents.Objects {
|
||||
o := objstorage.ObjectMetadata{
|
||||
DiskFileNum: meta.FileNum,
|
||||
FileType: meta.FileType,
|
||||
}
|
||||
o.Shared.CreatorID = meta.CreatorID
|
||||
o.Shared.CreatorFileNum = meta.CreatorFileNum
|
||||
o.Shared.CleanupMethod = meta.CleanupMethod
|
||||
p.mu.knownObjects[o.DiskFileNum] = o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCreatorID is part of the objstorage.Provider interface.
|
||||
func (p *provider) SetCreatorID(creatorID objstorage.CreatorID) error {
|
||||
if p.sharedStorage() == nil {
|
||||
return errors.AssertionFailedf("attempt to set CreatorID but shared storage not enabled")
|
||||
}
|
||||
// Note: this call is a cheap no-op if the creator ID was already set. This
|
||||
// call also checks if we are trying to change the ID.
|
||||
if err := p.shared.catalog.SetCreatorID(creatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.shared.initialized.Load() {
|
||||
p.st.Logger.Infof("shared storage creatorID set to %s", creatorID)
|
||||
p.shared.init(creatorID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCheckInitialized() error {
|
||||
if p.sharedStorage() == nil {
|
||||
return errors.Errorf("shared object support not configured")
|
||||
}
|
||||
if !p.shared.initialized.Load() {
|
||||
return errors.Errorf("shared object support not available: shared creator ID not yet set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSync() error {
|
||||
batch := func() sharedobjcat.Batch {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
res := p.mu.shared.catalogBatch.Copy()
|
||||
p.mu.shared.catalogBatch.Reset()
|
||||
return res
|
||||
}()
|
||||
|
||||
if batch.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.shared.catalog.ApplyBatch(batch)
|
||||
if err != nil {
|
||||
// We have to put back the batch (for the next Sync).
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
batch.Append(p.mu.shared.catalogBatch)
|
||||
p.mu.shared.catalogBatch = batch
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedPath(meta objstorage.ObjectMetadata) string {
|
||||
return "shared://" + sharedObjectName(meta)
|
||||
}
|
||||
|
||||
// sharedCreateRef creates a reference marker object.
|
||||
func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
return nil
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
writer, err := p.sharedStorage().CreateObject(refName)
|
||||
if err == nil {
|
||||
// The object is empty, just close the writer.
|
||||
err = writer.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating marker object %q", refName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCreate(
|
||||
_ context.Context,
|
||||
fileType base.FileType,
|
||||
fileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
meta := objstorage.ObjectMetadata{
|
||||
DiskFileNum: fileNum,
|
||||
FileType: fileType,
|
||||
}
|
||||
meta.Shared.CreatorID = p.shared.creatorID
|
||||
meta.Shared.CreatorFileNum = fileNum
|
||||
meta.Shared.CleanupMethod = opts.SharedCleanupMethod
|
||||
|
||||
objName := sharedObjectName(meta)
|
||||
writer, err := p.sharedStorage().CreateObject(objName)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, errors.Wrapf(err, "creating object %q", objName)
|
||||
}
|
||||
return &sharedWritable{
|
||||
p: p,
|
||||
meta: meta,
|
||||
storageWriter: writer,
|
||||
}, meta, nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedOpenForReading(
|
||||
ctx context.Context, meta objstorage.ObjectMetadata, opts objstorage.OpenOptions,
|
||||
) (objstorage.Readable, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify we have a reference on this object; for performance reasons, we only
|
||||
// do this in testing scenarios.
|
||||
if p.shared.checkRefsOnOpen && meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
if _, err := p.sharedStorage().Size(refName); err != nil {
|
||||
if p.sharedStorage().IsNotExistError(err) {
|
||||
if opts.MustExist {
|
||||
p.st.Logger.Fatalf("marker object %q does not exist", refName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, errors.Errorf("marker object %q does not exist", refName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "checking marker object %q", refName)
|
||||
}
|
||||
}
|
||||
objName := sharedObjectName(meta)
|
||||
reader, size, err := p.sharedStorage().ReadObject(ctx, objName)
|
||||
if err != nil {
|
||||
if opts.MustExist && p.sharedStorage().IsNotExistError(err) {
|
||||
p.st.Logger.Fatalf("object %q does not exist", objName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return newSharedReadable(reader, size), nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSize(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
objName := sharedObjectName(meta)
|
||||
return p.sharedStorage().Size(objName)
|
||||
}
|
||||
|
||||
// sharedUnref implements object "removal" with the shared backend. The ref
|
||||
// marker object is removed and the backing object is removed only if there are
|
||||
// no other ref markers.
|
||||
func (p *provider) sharedUnref(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Shared.CleanupMethod == objstorage.SharedNoCleanup {
|
||||
// Never delete objects in this mode.
|
||||
return nil
|
||||
}
|
||||
if p.isProtected(meta.DiskFileNum) {
|
||||
// TODO(radu): we need a mechanism to unref the object when it becomes
|
||||
// unprotected.
|
||||
return nil
|
||||
}
|
||||
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
// Tolerate a not-exists error.
|
||||
if err := p.sharedStorage().Delete(refName); err != nil && !p.sharedStorage().IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
otherRefs, err := p.sharedStorage().List(sharedObjectRefPrefix(meta), "" /* delimiter */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(otherRefs) == 0 {
|
||||
objName := sharedObjectName(meta)
|
||||
if err := p.sharedStorage().Delete(objName); err != nil && !p.sharedStorage().IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
65
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_readable.go
generated
vendored
65
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_readable.go
generated
vendored
@@ -1,65 +0,0 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
)
|
||||
|
||||
// sharedReadable is a very simple implementation of Readable on top of the
|
||||
// ReadCloser returned by shared.Storage.CreateObject.
|
||||
type sharedReadable struct {
|
||||
objReader shared.ObjectReader
|
||||
size int64
|
||||
}
|
||||
|
||||
var _ objstorage.Readable = (*sharedReadable)(nil)
|
||||
|
||||
func newSharedReadable(objReader shared.ObjectReader, size int64) *sharedReadable {
|
||||
return &sharedReadable{
|
||||
objReader: objReader,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *sharedReadable) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
return r.objReader.ReadAt(ctx, p, offset)
|
||||
}
|
||||
|
||||
func (r *sharedReadable) Close() error {
|
||||
defer func() { r.objReader = nil }()
|
||||
return r.objReader.Close()
|
||||
}
|
||||
|
||||
func (r *sharedReadable) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
func (r *sharedReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
|
||||
// TODO(radu): use a pool.
|
||||
return &sharedReadHandle{readable: r}
|
||||
}
|
||||
|
||||
type sharedReadHandle struct {
|
||||
readable *sharedReadable
|
||||
}
|
||||
|
||||
var _ objstorage.ReadHandle = (*sharedReadHandle)(nil)
|
||||
|
||||
func (r *sharedReadHandle) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
return r.readable.ReadAt(ctx, p, offset)
|
||||
}
|
||||
|
||||
func (r *sharedReadHandle) Close() error {
|
||||
r.readable = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *sharedReadHandle) MaxReadahead() {}
|
||||
|
||||
func (r *sharedReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {}
|
||||
21
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_writable.go
generated
vendored
21
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_writable.go
generated
vendored
@@ -10,9 +10,16 @@ import (
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
)
|
||||
|
||||
// NewRemoteWritable creates an objstorage.Writable out of an io.WriteCloser.
|
||||
func NewRemoteWritable(obj io.WriteCloser) objstorage.Writable {
|
||||
return &sharedWritable{storageWriter: obj}
|
||||
}
|
||||
|
||||
// sharedWritable is a very simple implementation of Writable on top of the
|
||||
// WriteCloser returned by shared.Storage.CreateObject.
|
||||
// WriteCloser returned by remote.Storage.CreateObject.
|
||||
type sharedWritable struct {
|
||||
// Either both p and meta must be unset / zero values, or both must be set.
|
||||
// The case where both are unset is true only in tests.
|
||||
p *provider
|
||||
meta objstorage.ObjectMetadata
|
||||
storageWriter io.WriteCloser
|
||||
@@ -36,9 +43,11 @@ func (w *sharedWritable) Finish() error {
|
||||
}
|
||||
|
||||
// Create the marker object.
|
||||
if err := w.p.sharedCreateRef(w.meta); err != nil {
|
||||
w.Abort()
|
||||
return err
|
||||
if w.p != nil {
|
||||
if err := w.p.sharedCreateRef(w.meta); err != nil {
|
||||
w.Abort()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -49,6 +58,8 @@ func (w *sharedWritable) Abort() {
|
||||
_ = w.storageWriter.Close()
|
||||
w.storageWriter = nil
|
||||
}
|
||||
w.p.removeMetadata(w.meta.DiskFileNum)
|
||||
if w.p != nil {
|
||||
w.p.removeMetadata(w.meta.DiskFileNum)
|
||||
}
|
||||
// TODO(radu): delete the object if it was created.
|
||||
}
|
||||
|
||||
900
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache/shared_cache.go
generated
vendored
Normal file
900
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache/shared_cache.go
generated
vendored
Normal file
@@ -0,0 +1,900 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sharedcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Exported to enable exporting from package pebble to enable
|
||||
// exporting metrics with below buckets in CRDB.
|
||||
var (
|
||||
IOBuckets = prometheus.ExponentialBucketsRange(float64(time.Millisecond*1), float64(10*time.Second), 50)
|
||||
ChannelWriteBuckets = prometheus.ExponentialBucketsRange(float64(time.Microsecond*1), float64(10*time.Second), 50)
|
||||
)
|
||||
|
||||
// Cache is a persistent cache backed by a local filesystem. It is intended
|
||||
// to cache data that is in slower shared storage (e.g. S3), hence the
|
||||
// package name 'sharedcache'.
|
||||
type Cache struct {
|
||||
shards []shard
|
||||
writeWorkers writeWorkers
|
||||
|
||||
bm blockMath
|
||||
shardingBlockSize int64
|
||||
|
||||
logger base.Logger
|
||||
metrics internalMetrics
|
||||
}
|
||||
|
||||
// Metrics is a struct containing metrics exported by the secondary cache.
|
||||
// TODO(josh): Reconsider the set of metrics exported by the secondary cache
|
||||
// before we release the secondary cache to users. We choose to export many metrics
|
||||
// right now, so we learn a lot from the benchmarking we are doing over the 23.2
|
||||
// cycle.
|
||||
type Metrics struct {
|
||||
// The number of sstable bytes stored in the cache.
|
||||
Size int64
|
||||
// The count of cache blocks in the cache (not sstable blocks).
|
||||
Count int64
|
||||
|
||||
// The number of calls to ReadAt.
|
||||
TotalReads int64
|
||||
// The number of calls to ReadAt that require reading data from 2+ shards.
|
||||
MultiShardReads int64
|
||||
// The number of calls to ReadAt that require reading data from 2+ cache blocks.
|
||||
MultiBlockReads int64
|
||||
// The number of calls to ReadAt where all data returned was read from the cache.
|
||||
ReadsWithFullHit int64
|
||||
// The number of calls to ReadAt where some data returned was read from the cache.
|
||||
ReadsWithPartialHit int64
|
||||
// The number of calls to ReadAt where no data returned was read from the cache.
|
||||
ReadsWithNoHit int64
|
||||
|
||||
// The number of times a cache block was evicted from the cache.
|
||||
Evictions int64
|
||||
// The number of times writing a cache block to the cache failed.
|
||||
WriteBackFailures int64
|
||||
|
||||
// The latency of calls to get some data from the cache.
|
||||
GetLatency prometheus.Histogram
|
||||
// The latency of reads of a single cache block from disk.
|
||||
DiskReadLatency prometheus.Histogram
|
||||
// The latency of writing data to write back to the cache to a channel.
|
||||
// Generally should be low, but if the channel is full, could be high.
|
||||
QueuePutLatency prometheus.Histogram
|
||||
// The latency of calls to put some data read from block storage into the cache.
|
||||
PutLatency prometheus.Histogram
|
||||
// The latency of writes of a single cache block to disk.
|
||||
DiskWriteLatency prometheus.Histogram
|
||||
}
|
||||
|
||||
// See docs at Metrics.
|
||||
type internalMetrics struct {
|
||||
count atomic.Int64
|
||||
|
||||
totalReads atomic.Int64
|
||||
multiShardReads atomic.Int64
|
||||
multiBlockReads atomic.Int64
|
||||
readsWithFullHit atomic.Int64
|
||||
readsWithPartialHit atomic.Int64
|
||||
readsWithNoHit atomic.Int64
|
||||
|
||||
evictions atomic.Int64
|
||||
writeBackFailures atomic.Int64
|
||||
|
||||
getLatency prometheus.Histogram
|
||||
diskReadLatency prometheus.Histogram
|
||||
queuePutLatency prometheus.Histogram
|
||||
putLatency prometheus.Histogram
|
||||
diskWriteLatency prometheus.Histogram
|
||||
}
|
||||
|
||||
const (
|
||||
// writeWorkersPerShard is used to establish the number of worker goroutines
|
||||
// that perform writes to the cache.
|
||||
writeWorkersPerShard = 4
|
||||
// writeTaskPerWorker is used to establish how many tasks can be queued up
|
||||
// until we have to block.
|
||||
writeTasksPerWorker = 4
|
||||
)
|
||||
|
||||
// Open opens a cache. If there is no existing cache at fsDir, a new one
|
||||
// is created.
|
||||
func Open(
|
||||
fs vfs.FS,
|
||||
logger base.Logger,
|
||||
fsDir string,
|
||||
blockSize int,
|
||||
// shardingBlockSize is the size of a shard block. The cache is split into contiguous
|
||||
// shardingBlockSize units. The units are distributed across multiple independent shards
|
||||
// of the cache, via a hash(offset) modulo num shards operation. The cache replacement
|
||||
// policies operate at the level of shard, not whole cache. This is done to reduce lock
|
||||
// contention.
|
||||
shardingBlockSize int64,
|
||||
sizeBytes int64,
|
||||
numShards int,
|
||||
) (*Cache, error) {
|
||||
if minSize := shardingBlockSize * int64(numShards); sizeBytes < minSize {
|
||||
// Up the size so that we have one block per shard. In practice, this should
|
||||
// only happen in tests.
|
||||
sizeBytes = minSize
|
||||
}
|
||||
|
||||
c := &Cache{
|
||||
logger: logger,
|
||||
bm: makeBlockMath(blockSize),
|
||||
shardingBlockSize: shardingBlockSize,
|
||||
}
|
||||
c.shards = make([]shard, numShards)
|
||||
blocksPerShard := sizeBytes / int64(numShards) / int64(blockSize)
|
||||
for i := range c.shards {
|
||||
if err := c.shards[i].init(c, fs, fsDir, i, blocksPerShard, blockSize, shardingBlockSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.writeWorkers.Start(c, numShards*writeWorkersPerShard)
|
||||
|
||||
c.metrics.getLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.diskReadLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.putLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.diskWriteLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
|
||||
// Measures a channel write, so lower min.
|
||||
c.metrics.queuePutLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: ChannelWriteBuckets})
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes the cache. Methods such as ReadAt should not be called after Close is
|
||||
// called.
|
||||
func (c *Cache) Close() error {
|
||||
c.writeWorkers.Stop()
|
||||
|
||||
var retErr error
|
||||
for i := range c.shards {
|
||||
if err := c.shards[i].close(); err != nil && retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
c.shards = nil
|
||||
return retErr
|
||||
}
|
||||
|
||||
// Metrics return metrics for the cache. Callers should not mutate
|
||||
// the returned histograms, which are pointer types.
|
||||
func (c *Cache) Metrics() Metrics {
|
||||
return Metrics{
|
||||
Count: c.metrics.count.Load(),
|
||||
Size: c.metrics.count.Load() * int64(c.bm.BlockSize()),
|
||||
TotalReads: c.metrics.totalReads.Load(),
|
||||
MultiShardReads: c.metrics.multiShardReads.Load(),
|
||||
MultiBlockReads: c.metrics.multiBlockReads.Load(),
|
||||
ReadsWithFullHit: c.metrics.readsWithFullHit.Load(),
|
||||
ReadsWithPartialHit: c.metrics.readsWithPartialHit.Load(),
|
||||
ReadsWithNoHit: c.metrics.readsWithNoHit.Load(),
|
||||
Evictions: c.metrics.evictions.Load(),
|
||||
WriteBackFailures: c.metrics.writeBackFailures.Load(),
|
||||
GetLatency: c.metrics.getLatency,
|
||||
DiskReadLatency: c.metrics.diskReadLatency,
|
||||
QueuePutLatency: c.metrics.queuePutLatency,
|
||||
PutLatency: c.metrics.putLatency,
|
||||
DiskWriteLatency: c.metrics.diskWriteLatency,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFlags contains options for Cache.ReadAt.
|
||||
type ReadFlags struct {
|
||||
// ReadOnly instructs ReadAt to not write any new data into the cache; it is
|
||||
// used when the data is unlikely to be used again.
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// ReadAt performs a read form an object, attempting to use cached data when
|
||||
// possible.
|
||||
func (c *Cache) ReadAt(
|
||||
ctx context.Context,
|
||||
fileNum base.DiskFileNum,
|
||||
p []byte,
|
||||
ofs int64,
|
||||
objReader remote.ObjectReader,
|
||||
objSize int64,
|
||||
flags ReadFlags,
|
||||
) error {
|
||||
c.metrics.totalReads.Add(1)
|
||||
if ofs >= objSize {
|
||||
if invariants.Enabled {
|
||||
panic(fmt.Sprintf("invalid ReadAt offset %v %v", ofs, objSize))
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
// TODO(radu): for compaction reads, we may not want to read from the cache at
|
||||
// all.
|
||||
{
|
||||
start := time.Now()
|
||||
n, err := c.get(fileNum, p, ofs)
|
||||
c.metrics.getLatency.Observe(float64(time.Since(start)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == len(p) {
|
||||
// Everything was in cache!
|
||||
c.metrics.readsWithFullHit.Add(1)
|
||||
return nil
|
||||
}
|
||||
if n == 0 {
|
||||
c.metrics.readsWithNoHit.Add(1)
|
||||
} else {
|
||||
c.metrics.readsWithPartialHit.Add(1)
|
||||
}
|
||||
|
||||
// Note this. The below code does not need the original ofs, as with the earlier
|
||||
// reading from the cache done, the relevant offset is ofs + int64(n). Same with p.
|
||||
ofs += int64(n)
|
||||
p = p[n:]
|
||||
|
||||
if invariants.Enabled {
|
||||
if n != 0 && c.bm.Remainder(ofs) != 0 {
|
||||
panic(fmt.Sprintf("after non-zero read from cache, ofs is not block-aligned: %v %v", ofs, n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flags.ReadOnly {
|
||||
return objReader.ReadAt(ctx, p, ofs)
|
||||
}
|
||||
|
||||
// We must do reads with offset & size that are multiples of the block size. Else
|
||||
// later cache hits may return incorrect zeroed results from the cache.
|
||||
firstBlockInd := c.bm.Block(ofs)
|
||||
adjustedOfs := c.bm.BlockOffset(firstBlockInd)
|
||||
|
||||
// Take the length of what is left to read plus the length of the adjustment of
|
||||
// the offset plus the size of a block minus one and divide by the size of a block
|
||||
// to get the number of blocks to read from the object.
|
||||
sizeOfOffAdjustment := int(ofs - adjustedOfs)
|
||||
adjustedLen := int(c.bm.RoundUp(int64(len(p) + sizeOfOffAdjustment)))
|
||||
adjustedP := make([]byte, adjustedLen)
|
||||
|
||||
// Read the rest from the object. We may need to cap the length to avoid past EOF reads.
|
||||
eofCap := int64(adjustedLen)
|
||||
if adjustedOfs+eofCap > objSize {
|
||||
eofCap = objSize - adjustedOfs
|
||||
}
|
||||
if err := objReader.ReadAt(ctx, adjustedP[:eofCap], adjustedOfs); err != nil {
|
||||
return err
|
||||
}
|
||||
copy(p, adjustedP[sizeOfOffAdjustment:])
|
||||
|
||||
start := time.Now()
|
||||
c.writeWorkers.QueueWrite(fileNum, adjustedP, adjustedOfs)
|
||||
c.metrics.queuePutLatency.Observe(float64(time.Since(start)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get attempts to read the requested data from the cache, if it is already
|
||||
// there.
|
||||
//
|
||||
// If all data is available, returns n = len(p).
|
||||
//
|
||||
// If data is partially available, a prefix of the data is read; returns n < len(p)
|
||||
// and no error. If no prefix is available, returns n = 0 and no error.
|
||||
func (c *Cache) get(fileNum base.DiskFileNum, p []byte, ofs int64) (n int, _ error) {
|
||||
// The data extent might cross shard boundaries, hence the loop. In the hot
|
||||
// path, max two iterations of this loop will be executed, since reads are sized
|
||||
// in units of sstable block size.
|
||||
var multiShard bool
|
||||
for {
|
||||
shard := c.getShard(fileNum, ofs+int64(n))
|
||||
cappedLen := len(p[n:])
|
||||
if toBoundary := int(c.shardingBlockSize - ((ofs + int64(n)) % c.shardingBlockSize)); cappedLen > toBoundary {
|
||||
cappedLen = toBoundary
|
||||
}
|
||||
numRead, err := shard.get(fileNum, p[n:n+cappedLen], ofs+int64(n))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += numRead
|
||||
if numRead < cappedLen {
|
||||
// We only read a prefix from this shard.
|
||||
return n, nil
|
||||
}
|
||||
if n == len(p) {
|
||||
// We are done.
|
||||
return n, nil
|
||||
}
|
||||
// Data extent crosses shard boundary, continue with next shard.
|
||||
if !multiShard {
|
||||
c.metrics.multiShardReads.Add(1)
|
||||
multiShard = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set attempts to write the requested data to the cache. Both ofs & len(p) must
|
||||
// be multiples of the block size.
|
||||
//
|
||||
// If all of p is not written to the shard, set returns a non-nil error.
|
||||
func (c *Cache) set(fileNum base.DiskFileNum, p []byte, ofs int64) error {
|
||||
if invariants.Enabled {
|
||||
if c.bm.Remainder(ofs) != 0 || c.bm.Remainder(int64(len(p))) != 0 {
|
||||
panic(fmt.Sprintf("set with ofs & len not multiples of block size: %v %v", ofs, len(p)))
|
||||
}
|
||||
}
|
||||
|
||||
// The data extent might cross shard boundaries, hence the loop. In the hot
|
||||
// path, max two iterations of this loop will be executed, since reads are sized
|
||||
// in units of sstable block size.
|
||||
n := 0
|
||||
for {
|
||||
shard := c.getShard(fileNum, ofs+int64(n))
|
||||
cappedLen := len(p[n:])
|
||||
if toBoundary := int(c.shardingBlockSize - ((ofs + int64(n)) % c.shardingBlockSize)); cappedLen > toBoundary {
|
||||
cappedLen = toBoundary
|
||||
}
|
||||
err := shard.set(fileNum, p[n:n+cappedLen], ofs+int64(n))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// set returns an error if cappedLen bytes aren't written to the shard.
|
||||
n += cappedLen
|
||||
if n == len(p) {
|
||||
// We are done.
|
||||
return nil
|
||||
}
|
||||
// Data extent crosses shard boundary, continue with next shard.
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) getShard(fileNum base.DiskFileNum, ofs int64) *shard {
|
||||
const prime64 = 1099511628211
|
||||
hash := uint64(fileNum.FileNum())*prime64 + uint64(ofs/c.shardingBlockSize)
|
||||
// TODO(josh): Instance change ops are often run in production. Such an operation
|
||||
// updates len(c.shards); see openSharedCache. As a result, the behavior of this
|
||||
// function changes, and the cache empties out at restart time. We may want a better
|
||||
// story here eventually.
|
||||
return &c.shards[hash%uint64(len(c.shards))]
|
||||
}
|
||||
|
||||
type shard struct {
|
||||
cache *Cache
|
||||
file vfs.File
|
||||
sizeInBlocks int64
|
||||
bm blockMath
|
||||
shardingBlockSize int64
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
// TODO(josh): None of these datastructures are space-efficient.
|
||||
// Focusing on correctness to start.
|
||||
where whereMap
|
||||
blocks []cacheBlockState
|
||||
// Head of LRU list (doubly-linked circular).
|
||||
lruHead cacheBlockIndex
|
||||
// Head of free list (singly-linked chain).
|
||||
freeHead cacheBlockIndex
|
||||
}
|
||||
}
|
||||
|
||||
type cacheBlockState struct {
|
||||
lock lockState
|
||||
logical logicalBlockID
|
||||
|
||||
// next is the next block in the LRU or free list (or invalidBlockIndex if it
|
||||
// is the last block in the free list).
|
||||
next cacheBlockIndex
|
||||
|
||||
// prev is the previous block in the LRU list. It is not used when the block
|
||||
// is in the free list.
|
||||
prev cacheBlockIndex
|
||||
}
|
||||
|
||||
// Maps a logical block in an SST to an index of the cache block with the
|
||||
// file contents (to the "cache block index").
|
||||
type whereMap map[logicalBlockID]cacheBlockIndex
|
||||
|
||||
type logicalBlockID struct {
|
||||
filenum base.DiskFileNum
|
||||
cacheBlockIdx cacheBlockIndex
|
||||
}
|
||||
|
||||
type lockState int64
|
||||
|
||||
const (
|
||||
unlocked lockState = 0
|
||||
// >0 lockState tracks the number of distinct readers of some cache block / logical block
|
||||
// which is in the secondary cache. It is used to ensure that a cache block is not evicted
|
||||
// and overwritten, while there are active readers.
|
||||
readLockTakenInc = 1
|
||||
// -1 lockState indicates that some cache block is currently being populated with data from
|
||||
// blob storage. It is used to ensure that a cache block is not read or evicted again, while
|
||||
// it is being populated.
|
||||
writeLockTaken = -1
|
||||
)
|
||||
|
||||
func (s *shard) init(
|
||||
cache *Cache,
|
||||
fs vfs.FS,
|
||||
fsDir string,
|
||||
shardIdx int,
|
||||
sizeInBlocks int64,
|
||||
blockSize int,
|
||||
shardingBlockSize int64,
|
||||
) error {
|
||||
*s = shard{
|
||||
cache: cache,
|
||||
sizeInBlocks: sizeInBlocks,
|
||||
}
|
||||
if blockSize < 1024 || shardingBlockSize%int64(blockSize) != 0 {
|
||||
return errors.Newf("invalid block size %d (must divide %d)", blockSize, shardingBlockSize)
|
||||
}
|
||||
s.bm = makeBlockMath(blockSize)
|
||||
s.shardingBlockSize = shardingBlockSize
|
||||
file, err := fs.OpenReadWrite(fs.PathJoin(fsDir, fmt.Sprintf("SHARED-CACHE-%03d", shardIdx)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(radu): truncate file if necessary (especially important if we restart
|
||||
// with more shards).
|
||||
if err := file.Preallocate(0, int64(blockSize)*sizeInBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
s.file = file
|
||||
|
||||
// TODO(josh): Right now, the secondary cache is not persistent. All existing
|
||||
// cache contents will be over-written, since all metadata is only stored in
|
||||
// memory.
|
||||
s.mu.where = make(whereMap)
|
||||
s.mu.blocks = make([]cacheBlockState, sizeInBlocks)
|
||||
s.mu.lruHead = invalidBlockIndex
|
||||
s.mu.freeHead = invalidBlockIndex
|
||||
for i := range s.mu.blocks {
|
||||
s.freePush(cacheBlockIndex(i))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *shard) close() error {
|
||||
defer func() {
|
||||
s.file = nil
|
||||
}()
|
||||
return s.file.Close()
|
||||
}
|
||||
|
||||
// freePush pushes a block to the front of the free list.
|
||||
func (s *shard) freePush(index cacheBlockIndex) {
|
||||
s.mu.blocks[index].next = s.mu.freeHead
|
||||
s.mu.freeHead = index
|
||||
}
|
||||
|
||||
// freePop removes the block from the front of the free list. Must not be called
|
||||
// if the list is empty (i.e. freeHead = invalidBlockIndex).
|
||||
func (s *shard) freePop() cacheBlockIndex {
|
||||
index := s.mu.freeHead
|
||||
s.mu.freeHead = s.mu.blocks[index].next
|
||||
return index
|
||||
}
|
||||
|
||||
// lruInsertFront inserts a block at the front of the LRU list.
|
||||
func (s *shard) lruInsertFront(index cacheBlockIndex) {
|
||||
b := &s.mu.blocks[index]
|
||||
if s.mu.lruHead == invalidBlockIndex {
|
||||
b.next = index
|
||||
b.prev = index
|
||||
} else {
|
||||
b.next = s.mu.lruHead
|
||||
h := &s.mu.blocks[s.mu.lruHead]
|
||||
b.prev = h.prev
|
||||
s.mu.blocks[h.prev].next = index
|
||||
h.prev = index
|
||||
}
|
||||
s.mu.lruHead = index
|
||||
}
|
||||
|
||||
func (s *shard) lruNext(index cacheBlockIndex) cacheBlockIndex {
|
||||
return s.mu.blocks[index].next
|
||||
}
|
||||
|
||||
func (s *shard) lruPrev(index cacheBlockIndex) cacheBlockIndex {
|
||||
return s.mu.blocks[index].prev
|
||||
}
|
||||
|
||||
// lruUnlink removes a block from the LRU list.
|
||||
func (s *shard) lruUnlink(index cacheBlockIndex) {
|
||||
b := &s.mu.blocks[index]
|
||||
if b.next == index {
|
||||
s.mu.lruHead = invalidBlockIndex
|
||||
} else {
|
||||
s.mu.blocks[b.prev].next = b.next
|
||||
s.mu.blocks[b.next].prev = b.prev
|
||||
if s.mu.lruHead == index {
|
||||
s.mu.lruHead = b.next
|
||||
}
|
||||
}
|
||||
b.next, b.prev = invalidBlockIndex, invalidBlockIndex
|
||||
}
|
||||
|
||||
// get attempts to read the requested data from the shard. The data must not
|
||||
// cross a shard boundary.
|
||||
//
|
||||
// If all data is available, returns n = len(p).
|
||||
//
|
||||
// If data is partially available, a prefix of the data is read; returns n < len(p)
|
||||
// and no error. If no prefix is available, returns n = 0 and no error.
|
||||
//
|
||||
// TODO(josh): Today, if there are two cache blocks needed to satisfy a read, and the
|
||||
// first block is not in the cache and the second one is, we will read both from
|
||||
// blob storage. We should fix this. This is not an unlikely scenario if we are doing
|
||||
// a reverse scan, since those iterate over sstable blocks in reverse order and due to
|
||||
// cache block aligned reads will have read the suffix of the sstable block that will
|
||||
// be needed next.
|
||||
func (s *shard) get(fileNum base.DiskFileNum, p []byte, ofs int64) (n int, _ error) {
|
||||
if invariants.Enabled {
|
||||
if ofs/s.shardingBlockSize != (ofs+int64(len(p))-1)/s.shardingBlockSize {
|
||||
panic(fmt.Sprintf("get crosses shard boundary: %v %v", ofs, len(p)))
|
||||
}
|
||||
s.assertShardStateIsConsistent()
|
||||
}
|
||||
|
||||
// The data extent might cross cache block boundaries, hence the loop. In the hot
|
||||
// path, max two iterations of this loop will be executed, since reads are sized
|
||||
// in units of sstable block size.
|
||||
var multiBlock bool
|
||||
for {
|
||||
k := logicalBlockID{
|
||||
filenum: fileNum,
|
||||
cacheBlockIdx: s.bm.Block(ofs + int64(n)),
|
||||
}
|
||||
s.mu.Lock()
|
||||
cacheBlockIdx, ok := s.mu.where[k]
|
||||
// TODO(josh): Multiple reads within the same few milliseconds (anything that is smaller
|
||||
// than blob storage read latency) that miss on the same logical block ID will not necessarily
|
||||
// be rare. We may want to do only one read, with the later readers blocking on the first read
|
||||
// completing. This could be implemented either here or in the primary block cache. See
|
||||
// https://github.com/cockroachdb/pebble/pull/2586 for additional discussion.
|
||||
if !ok {
|
||||
s.mu.Unlock()
|
||||
return n, nil
|
||||
}
|
||||
if s.mu.blocks[cacheBlockIdx].lock == writeLockTaken {
|
||||
// In practice, if we have two reads of the same SST block in close succession, we
|
||||
// would expect the second to hit in the in-memory block cache. So it's not worth
|
||||
// optimizing this case here.
|
||||
s.mu.Unlock()
|
||||
return n, nil
|
||||
}
|
||||
s.mu.blocks[cacheBlockIdx].lock += readLockTakenInc
|
||||
// Move to front of the LRU list.
|
||||
s.lruUnlink(cacheBlockIdx)
|
||||
s.lruInsertFront(cacheBlockIdx)
|
||||
s.mu.Unlock()
|
||||
|
||||
readAt := s.bm.BlockOffset(cacheBlockIdx)
|
||||
readSize := s.bm.BlockSize()
|
||||
if n == 0 { // if first read
|
||||
rem := s.bm.Remainder(ofs)
|
||||
readAt += rem
|
||||
readSize -= int(rem)
|
||||
}
|
||||
|
||||
if len(p[n:]) <= readSize {
|
||||
start := time.Now()
|
||||
numRead, err := s.file.ReadAt(p[n:], readAt)
|
||||
s.cache.metrics.diskReadLatency.Observe(float64(time.Since(start)))
|
||||
s.dropReadLock(cacheBlockIdx)
|
||||
return n + numRead, err
|
||||
}
|
||||
start := time.Now()
|
||||
numRead, err := s.file.ReadAt(p[n:n+readSize], readAt)
|
||||
s.cache.metrics.diskReadLatency.Observe(float64(time.Since(start)))
|
||||
s.dropReadLock(cacheBlockIdx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Note that numRead == readSize, since we checked for an error above.
|
||||
n += numRead
|
||||
|
||||
if !multiBlock {
|
||||
s.cache.metrics.multiBlockReads.Add(1)
|
||||
multiBlock = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set attempts to write the requested data to the shard. The data must not
|
||||
// cross a shard boundary, and both ofs & len(p) must be multiples of the
|
||||
// block size.
|
||||
//
|
||||
// If all of p is not written to the shard, set returns a non-nil error.
|
||||
func (s *shard) set(fileNum base.DiskFileNum, p []byte, ofs int64) error {
|
||||
if invariants.Enabled {
|
||||
if ofs/s.shardingBlockSize != (ofs+int64(len(p))-1)/s.shardingBlockSize {
|
||||
panic(fmt.Sprintf("set crosses shard boundary: %v %v", ofs, len(p)))
|
||||
}
|
||||
if s.bm.Remainder(ofs) != 0 || s.bm.Remainder(int64(len(p))) != 0 {
|
||||
panic(fmt.Sprintf("set with ofs & len not multiples of block size: %v %v", ofs, len(p)))
|
||||
}
|
||||
s.assertShardStateIsConsistent()
|
||||
}
|
||||
|
||||
// The data extent might cross cache block boundaries, hence the loop. In the hot
|
||||
// path, max two iterations of this loop will be executed, since reads are sized
|
||||
// in units of sstable block size.
|
||||
n := 0
|
||||
for {
|
||||
if n == len(p) {
|
||||
return nil
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if n > len(p) {
|
||||
panic(fmt.Sprintf("set with n greater than len(p): %v %v", n, len(p)))
|
||||
}
|
||||
}
|
||||
|
||||
// If the logical block is already in the cache, we should skip doing a set.
|
||||
k := logicalBlockID{
|
||||
filenum: fileNum,
|
||||
cacheBlockIdx: s.bm.Block(ofs + int64(n)),
|
||||
}
|
||||
s.mu.Lock()
|
||||
if _, ok := s.mu.where[k]; ok {
|
||||
s.mu.Unlock()
|
||||
n += s.bm.BlockSize()
|
||||
continue
|
||||
}
|
||||
|
||||
var cacheBlockIdx cacheBlockIndex
|
||||
if s.mu.freeHead == invalidBlockIndex {
|
||||
if invariants.Enabled && s.mu.lruHead == invalidBlockIndex {
|
||||
panic("both LRU and free lists empty")
|
||||
}
|
||||
|
||||
// Find the last element in the LRU list which is not locked.
|
||||
for idx := s.lruPrev(s.mu.lruHead); ; idx = s.lruPrev(idx) {
|
||||
if lock := s.mu.blocks[idx].lock; lock == unlocked {
|
||||
cacheBlockIdx = idx
|
||||
break
|
||||
}
|
||||
if idx == s.mu.lruHead {
|
||||
// No unlocked block to evict.
|
||||
//
|
||||
// TODO(josh): We may want to block until a block frees up, instead of returning
|
||||
// an error here. But I think we can do that later on, e.g. after running some production
|
||||
// experiments.
|
||||
s.mu.Unlock()
|
||||
return errors.New("no block to evict so skipping write to cache")
|
||||
}
|
||||
}
|
||||
s.cache.metrics.evictions.Add(1)
|
||||
s.lruUnlink(cacheBlockIdx)
|
||||
delete(s.mu.where, s.mu.blocks[cacheBlockIdx].logical)
|
||||
} else {
|
||||
s.cache.metrics.count.Add(1)
|
||||
cacheBlockIdx = s.freePop()
|
||||
}
|
||||
|
||||
s.lruInsertFront(cacheBlockIdx)
|
||||
s.mu.where[k] = cacheBlockIdx
|
||||
s.mu.blocks[cacheBlockIdx].logical = k
|
||||
s.mu.blocks[cacheBlockIdx].lock = writeLockTaken
|
||||
s.mu.Unlock()
|
||||
|
||||
writeAt := s.bm.BlockOffset(cacheBlockIdx)
|
||||
|
||||
writeSize := s.bm.BlockSize()
|
||||
if len(p[n:]) <= writeSize {
|
||||
writeSize = len(p[n:])
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
_, err := s.file.WriteAt(p[n:n+writeSize], writeAt)
|
||||
s.cache.metrics.diskWriteLatency.Observe(float64(time.Since(start)))
|
||||
if err != nil {
|
||||
// Free the block.
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.mu.where, k)
|
||||
s.lruUnlink(cacheBlockIdx)
|
||||
s.freePush(cacheBlockIdx)
|
||||
return err
|
||||
}
|
||||
s.dropWriteLock(cacheBlockIdx)
|
||||
n += writeSize
|
||||
}
|
||||
}
|
||||
|
||||
// Doesn't inline currently. This might be okay, but something to keep in mind.
|
||||
func (s *shard) dropReadLock(cacheBlockInd cacheBlockIndex) {
|
||||
s.mu.Lock()
|
||||
s.mu.blocks[cacheBlockInd].lock -= readLockTakenInc
|
||||
if invariants.Enabled && s.mu.blocks[cacheBlockInd].lock < 0 {
|
||||
panic(fmt.Sprintf("unexpected lock state %v in dropReadLock", s.mu.blocks[cacheBlockInd].lock))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Doesn't inline currently. This might be okay, but something to keep in mind.
|
||||
func (s *shard) dropWriteLock(cacheBlockInd cacheBlockIndex) {
|
||||
s.mu.Lock()
|
||||
if invariants.Enabled && s.mu.blocks[cacheBlockInd].lock != writeLockTaken {
|
||||
panic(fmt.Sprintf("unexpected lock state %v in dropWriteLock", s.mu.blocks[cacheBlockInd].lock))
|
||||
}
|
||||
s.mu.blocks[cacheBlockInd].lock = unlocked
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *shard) assertShardStateIsConsistent() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
lruLen := 0
|
||||
if s.mu.lruHead != invalidBlockIndex {
|
||||
for b := s.mu.lruHead; ; {
|
||||
lruLen++
|
||||
if idx, ok := s.mu.where[s.mu.blocks[b].logical]; !ok || idx != b {
|
||||
panic("block in LRU list with no entry in where map")
|
||||
}
|
||||
b = s.lruNext(b)
|
||||
if b == s.mu.lruHead {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if lruLen != len(s.mu.where) {
|
||||
panic(fmt.Sprintf("lru list len is %d but where map has %d entries", lruLen, len(s.mu.where)))
|
||||
}
|
||||
freeLen := 0
|
||||
for n := s.mu.freeHead; n != invalidBlockIndex; n = s.mu.blocks[n].next {
|
||||
freeLen++
|
||||
}
|
||||
|
||||
if lruLen+freeLen != int(s.sizeInBlocks) {
|
||||
panic(fmt.Sprintf("%d lru blocks and %d free blocks don't add up to %d", lruLen, freeLen, s.sizeInBlocks))
|
||||
}
|
||||
for i := range s.mu.blocks {
|
||||
if state := s.mu.blocks[i].lock; state < writeLockTaken {
|
||||
panic(fmt.Sprintf("lock state %v is not allowed", state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cacheBlockIndex is the index of a blockSize-aligned cache block.
|
||||
type cacheBlockIndex int64
|
||||
|
||||
// invalidBlockIndex is used for the head of a list when the list is empty.
|
||||
const invalidBlockIndex cacheBlockIndex = -1
|
||||
|
||||
// blockMath is a helper type for performing conversions between offsets and
|
||||
// block indexes.
|
||||
type blockMath struct {
|
||||
blockSizeBits int8
|
||||
}
|
||||
|
||||
func makeBlockMath(blockSize int) blockMath {
|
||||
bm := blockMath{
|
||||
blockSizeBits: int8(bits.Len64(uint64(blockSize)) - 1),
|
||||
}
|
||||
if blockSize != (1 << bm.blockSizeBits) {
|
||||
panic(fmt.Sprintf("blockSize %d is not a power of 2", blockSize))
|
||||
}
|
||||
return bm
|
||||
}
|
||||
|
||||
func (bm blockMath) mask() int64 {
|
||||
return (1 << bm.blockSizeBits) - 1
|
||||
}
|
||||
|
||||
// BlockSize returns the block size.
|
||||
func (bm blockMath) BlockSize() int {
|
||||
return 1 << bm.blockSizeBits
|
||||
}
|
||||
|
||||
// Block returns the block index containing the given offset.
|
||||
func (bm blockMath) Block(offset int64) cacheBlockIndex {
|
||||
return cacheBlockIndex(offset >> bm.blockSizeBits)
|
||||
}
|
||||
|
||||
// Remainder returns the offset relative to the start of the cache block.
|
||||
func (bm blockMath) Remainder(offset int64) int64 {
|
||||
return offset & bm.mask()
|
||||
}
|
||||
|
||||
// BlockOffset returns the object offset where the given block starts.
|
||||
func (bm blockMath) BlockOffset(block cacheBlockIndex) int64 {
|
||||
return int64(block) << bm.blockSizeBits
|
||||
}
|
||||
|
||||
// RoundUp rounds up the given value to the closest multiple of block size.
|
||||
func (bm blockMath) RoundUp(x int64) int64 {
|
||||
return (x + bm.mask()) & ^(bm.mask())
|
||||
}
|
||||
|
||||
type writeWorkers struct {
|
||||
doneCh chan struct{}
|
||||
doneWaitGroup sync.WaitGroup
|
||||
|
||||
numWorkers int
|
||||
tasksCh chan writeTask
|
||||
}
|
||||
|
||||
type writeTask struct {
|
||||
fileNum base.DiskFileNum
|
||||
p []byte
|
||||
offset int64
|
||||
}
|
||||
|
||||
// Start starts the worker goroutines.
|
||||
func (w *writeWorkers) Start(c *Cache, numWorkers int) {
|
||||
doneCh := make(chan struct{})
|
||||
tasksCh := make(chan writeTask, numWorkers*writeTasksPerWorker)
|
||||
|
||||
w.numWorkers = numWorkers
|
||||
w.doneCh = doneCh
|
||||
w.tasksCh = tasksCh
|
||||
w.doneWaitGroup.Add(numWorkers)
|
||||
for i := 0; i < numWorkers; i++ {
|
||||
go func() {
|
||||
defer w.doneWaitGroup.Done()
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
return
|
||||
case task, ok := <-tasksCh:
|
||||
if !ok {
|
||||
// The tasks channel was closed; this is used in testing code to
|
||||
// ensure all writes are completed.
|
||||
return
|
||||
}
|
||||
// TODO(radu): set() can perform multiple writes; perhaps each one
|
||||
// should be its own task.
|
||||
start := time.Now()
|
||||
err := c.set(task.fileNum, task.p, task.offset)
|
||||
c.metrics.putLatency.Observe(float64(time.Since(start)))
|
||||
if err != nil {
|
||||
c.metrics.writeBackFailures.Add(1)
|
||||
// TODO(radu): throttle logs.
|
||||
c.logger.Infof("writing back to cache after miss failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop waits for any in-progress writes to complete and stops the worker
|
||||
// goroutines and waits for any in-pro. Any queued writes not yet started are
|
||||
// discarded.
|
||||
func (w *writeWorkers) Stop() {
|
||||
close(w.doneCh)
|
||||
w.doneCh = nil
|
||||
w.tasksCh = nil
|
||||
w.doneWaitGroup.Wait()
|
||||
}
|
||||
|
||||
// QueueWrite adds a write task to the queue. Can block if the queue is full.
|
||||
func (w *writeWorkers) QueueWrite(fileNum base.DiskFileNum, p []byte, offset int64) {
|
||||
w.tasksCh <- writeTask{
|
||||
fileNum: fileNum,
|
||||
p: p,
|
||||
offset: offset,
|
||||
}
|
||||
}
|
||||
6
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/vfs.go
generated
vendored
6
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/vfs.go
generated
vendored
@@ -31,7 +31,11 @@ func (p *provider) vfsOpenForReading(
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return newFileReadable(file, p.st.FS, filename)
|
||||
readaheadConfig := DefaultReadaheadConfig
|
||||
if f := p.st.Local.ReadaheadConfigFn; f != nil {
|
||||
readaheadConfig = f()
|
||||
}
|
||||
return newFileReadable(file, p.st.FS, readaheadConfig, filename)
|
||||
}
|
||||
|
||||
func (p *provider) vfsCreate(
|
||||
|
||||
74
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/vfs_readable.go
generated
vendored
74
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/vfs_readable.go
generated
vendored
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
const fileMaxReadaheadSize = 256 * 1024 /* 256KB */
|
||||
|
||||
// fileReadable implements objstorage.Readable on top of a vfs.File.
|
||||
//
|
||||
// The implementation might use Prealloc and might reopen the file with
|
||||
@@ -25,22 +27,26 @@ type fileReadable struct {
|
||||
|
||||
// The following fields are used to possibly open the file again using the
|
||||
// sequential reads option (see vfsReadHandle).
|
||||
filename string
|
||||
fs vfs.FS
|
||||
filename string
|
||||
fs vfs.FS
|
||||
readaheadConfig ReadaheadConfig
|
||||
}
|
||||
|
||||
var _ objstorage.Readable = (*fileReadable)(nil)
|
||||
|
||||
func newFileReadable(file vfs.File, fs vfs.FS, filename string) (*fileReadable, error) {
|
||||
func newFileReadable(
|
||||
file vfs.File, fs vfs.FS, readaheadConfig ReadaheadConfig, filename string,
|
||||
) (*fileReadable, error) {
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &fileReadable{
|
||||
file: file,
|
||||
size: info.Size(),
|
||||
filename: filename,
|
||||
fs: fs,
|
||||
file: file,
|
||||
size: info.Size(),
|
||||
filename: filename,
|
||||
fs: fs,
|
||||
readaheadConfig: readaheadConfig,
|
||||
}
|
||||
invariants.SetFinalizer(r, func(obj interface{}) {
|
||||
if obj.(*fileReadable).file != nil {
|
||||
@@ -74,14 +80,14 @@ func (r *fileReadable) Size() int64 {
|
||||
// NewReadHandle is part of the objstorage.Readable interface.
|
||||
func (r *fileReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
|
||||
rh := readHandlePool.Get().(*vfsReadHandle)
|
||||
rh.r = r
|
||||
rh.rs = makeReadaheadState()
|
||||
rh.init(r)
|
||||
return rh
|
||||
}
|
||||
|
||||
type vfsReadHandle struct {
|
||||
r *fileReadable
|
||||
rs readaheadState
|
||||
r *fileReadable
|
||||
rs readaheadState
|
||||
readaheadMode ReadaheadMode
|
||||
|
||||
// sequentialFile holds a file descriptor to the same underlying File,
|
||||
// except with fadvise(FADV_SEQUENTIAL) called on it to take advantage of
|
||||
@@ -106,6 +112,14 @@ var readHandlePool = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
func (rh *vfsReadHandle) init(r *fileReadable) {
|
||||
*rh = vfsReadHandle{
|
||||
r: r,
|
||||
rs: makeReadaheadState(fileMaxReadaheadSize),
|
||||
readaheadMode: r.readaheadConfig.Speculative,
|
||||
}
|
||||
}
|
||||
|
||||
// Close is part of the objstorage.ReadHandle interface.
|
||||
func (rh *vfsReadHandle) Close() error {
|
||||
var err error
|
||||
@@ -119,31 +133,44 @@ func (rh *vfsReadHandle) Close() error {
|
||||
|
||||
// ReadAt is part of the objstorage.ReadHandle interface.
|
||||
func (rh *vfsReadHandle) ReadAt(_ context.Context, p []byte, offset int64) error {
|
||||
var n int
|
||||
var err error
|
||||
if rh.sequentialFile != nil {
|
||||
// Use OS-level read-ahead.
|
||||
n, err = rh.sequentialFile.ReadAt(p, offset)
|
||||
} else {
|
||||
n, err := rh.sequentialFile.ReadAt(p, offset)
|
||||
if invariants.Enabled && err == nil && n != len(p) {
|
||||
panic("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if rh.readaheadMode != NoReadahead {
|
||||
if readaheadSize := rh.rs.maybeReadahead(offset, int64(len(p))); readaheadSize > 0 {
|
||||
if readaheadSize >= maxReadaheadSize {
|
||||
if rh.readaheadMode == FadviseSequential && readaheadSize >= fileMaxReadaheadSize {
|
||||
// We've reached the maximum readahead size. Beyond this point, rely on
|
||||
// OS-level readahead.
|
||||
rh.MaxReadahead()
|
||||
rh.switchToOSReadahead()
|
||||
} else {
|
||||
_ = rh.r.file.Prefetch(offset, readaheadSize)
|
||||
}
|
||||
}
|
||||
n, err = rh.r.file.ReadAt(p, offset)
|
||||
}
|
||||
n, err := rh.r.file.ReadAt(p, offset)
|
||||
if invariants.Enabled && err == nil && n != len(p) {
|
||||
panic("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MaxReadahead is part of the objstorage.ReadHandle interface.
|
||||
func (rh *vfsReadHandle) MaxReadahead() {
|
||||
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||
func (rh *vfsReadHandle) SetupForCompaction() {
|
||||
rh.readaheadMode = rh.r.readaheadConfig.Informed
|
||||
if rh.readaheadMode == FadviseSequential {
|
||||
rh.switchToOSReadahead()
|
||||
}
|
||||
}
|
||||
|
||||
func (rh *vfsReadHandle) switchToOSReadahead() {
|
||||
if invariants.Enabled && rh.readaheadMode != FadviseSequential {
|
||||
panic("readheadMode not respected")
|
||||
}
|
||||
if rh.sequentialFile != nil {
|
||||
return
|
||||
}
|
||||
@@ -158,8 +185,8 @@ func (rh *vfsReadHandle) MaxReadahead() {
|
||||
|
||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||
func (rh *vfsReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
|
||||
if rh.sequentialFile != nil {
|
||||
// Using OS-level readahead, so do nothing.
|
||||
if rh.sequentialFile != nil || rh.readaheadMode == NoReadahead {
|
||||
// Using OS-level or no readahead, so do nothing.
|
||||
return
|
||||
}
|
||||
rh.rs.recordCacheHit(offset, size)
|
||||
@@ -202,8 +229,7 @@ func UsePreallocatedReadHandle(
|
||||
ctx context.Context, readable objstorage.Readable, rh *PreallocatedReadHandle,
|
||||
) objstorage.ReadHandle {
|
||||
if r, ok := readable.(*fileReadable); ok {
|
||||
// See fileReadable.NewReadHandle.
|
||||
rh.vfsReadHandle = vfsReadHandle{r: r}
|
||||
rh.init(r)
|
||||
return rh
|
||||
}
|
||||
return readable.NewReadHandle(ctx)
|
||||
|
||||
25
vendor/github.com/cockroachdb/pebble/objstorage/remote/factory.go
generated
vendored
Normal file
25
vendor/github.com/cockroachdb/pebble/objstorage/remote/factory.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package remote
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// MakeSimpleFactory returns a StorageFactory implementation that produces the given
|
||||
// Storage objects.
|
||||
func MakeSimpleFactory(m map[Locator]Storage) StorageFactory {
|
||||
return simpleFactory(m)
|
||||
}
|
||||
|
||||
type simpleFactory map[Locator]Storage
|
||||
|
||||
var _ StorageFactory = simpleFactory{}
|
||||
|
||||
// CreateStorage is part of the StorageFactory interface.
|
||||
func (sf simpleFactory) CreateStorage(locator Locator) (Storage, error) {
|
||||
if s, ok := sf[locator]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return nil, errors.Errorf("unknown locator '%s'", locator)
|
||||
}
|
||||
118
vendor/github.com/cockroachdb/pebble/objstorage/remote/localfs.go
generated
vendored
Normal file
118
vendor/github.com/cockroachdb/pebble/objstorage/remote/localfs.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
// NewLocalFS returns a vfs-backed implementation of the remote.Storage
|
||||
// interface (for testing). All objects will be stored at the directory
|
||||
// dirname.
|
||||
func NewLocalFS(dirname string, fs vfs.FS) Storage {
|
||||
store := &localFSStore{
|
||||
dirname: dirname,
|
||||
vfs: fs,
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
// localFSStore is a vfs-backed implementation of the remote.Storage
|
||||
// interface (for testing).
|
||||
type localFSStore struct {
|
||||
dirname string
|
||||
vfs vfs.FS
|
||||
}
|
||||
|
||||
var _ Storage = (*localFSStore)(nil)
|
||||
|
||||
// Close is part of the remote.Storage interface.
|
||||
func (s *localFSStore) Close() error {
|
||||
*s = localFSStore{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadObject is part of the remote.Storage interface.
|
||||
func (s *localFSStore) ReadObject(
|
||||
ctx context.Context, objName string,
|
||||
) (_ ObjectReader, objSize int64, _ error) {
|
||||
f, err := s.vfs.Open(path.Join(s.dirname, objName))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return &localFSReader{f}, stat.Size(), nil
|
||||
}
|
||||
|
||||
type localFSReader struct {
|
||||
file vfs.File
|
||||
}
|
||||
|
||||
var _ ObjectReader = (*localFSReader)(nil)
|
||||
|
||||
// ReadAt is part of the shared.ObjectReader interface.
|
||||
func (r *localFSReader) ReadAt(_ context.Context, p []byte, offset int64) error {
|
||||
n, err := r.file.ReadAt(p, offset)
|
||||
// https://pkg.go.dev/io#ReaderAt
|
||||
if err == io.EOF && n == len(p) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Close is part of the shared.ObjectReader interface.
|
||||
func (r *localFSReader) Close() error {
|
||||
r.file.Close()
|
||||
r.file = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateObject is part of the remote.Storage interface.
|
||||
func (s *localFSStore) CreateObject(objName string) (io.WriteCloser, error) {
|
||||
file, err := s.vfs.Create(path.Join(s.dirname, objName))
|
||||
return file, err
|
||||
}
|
||||
|
||||
// List is part of the remote.Storage interface.
|
||||
func (s *localFSStore) List(prefix, delimiter string) ([]string, error) {
|
||||
// TODO(josh): For the intended use case of localfs.go of running 'pebble bench',
|
||||
// List can always return <nil, nil>, since this indicates a file has only one ref,
|
||||
// and since `pebble bench` implies running in a single-pebble-instance context.
|
||||
// https://github.com/cockroachdb/pebble/blob/a9a079d4fb6bf4a9ebc52e4d83a76ad4cbf676cb/objstorage/objstorageprovider/shared.go#L292
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Delete is part of the remote.Storage interface.
|
||||
func (s *localFSStore) Delete(objName string) error {
|
||||
return s.vfs.Remove(path.Join(s.dirname, objName))
|
||||
}
|
||||
|
||||
// Size is part of the remote.Storage interface.
|
||||
func (s *localFSStore) Size(objName string) (int64, error) {
|
||||
f, err := s.vfs.Open(path.Join(s.dirname, objName))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return stat.Size(), nil
|
||||
}
|
||||
|
||||
// IsNotExistError is part of the remote.Storage interface.
|
||||
func (s *localFSStore) IsNotExistError(err error) bool {
|
||||
return err == os.ErrNotExist
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package shared
|
||||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -20,7 +20,7 @@ func WithLogging(wrapped Storage, logf func(fmt string, args ...interface{})) St
|
||||
}
|
||||
}
|
||||
|
||||
// loggingStore wraps a shared.Storage implementation and emits logs of the
|
||||
// loggingStore wraps a remote.Storage implementation and emits logs of the
|
||||
// operations.
|
||||
type loggingStore struct {
|
||||
logf func(fmt string, args ...interface{})
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package shared
|
||||
package remote
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewInMem returns an in-memory implementation of the shared.Storage
|
||||
// NewInMem returns an in-memory implementation of the remote.Storage
|
||||
// interface (for testing).
|
||||
func NewInMem() Storage {
|
||||
store := &inMemStore{}
|
||||
@@ -21,7 +21,7 @@ func NewInMem() Storage {
|
||||
return store
|
||||
}
|
||||
|
||||
// inMemStore is an in-memory implementation of the shared.Storage interface
|
||||
// inMemStore is an in-memory implementation of the remote.Storage interface
|
||||
// (for testing).
|
||||
type inMemStore struct {
|
||||
mu struct {
|
||||
@@ -2,13 +2,62 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package shared
|
||||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Locator is an opaque string identifying a remote.Storage implementation.
|
||||
//
|
||||
// The Locator must not contain secrets (like authentication keys). Locators are
|
||||
// stored on disk in the shared object catalog and are passed around as part of
|
||||
// RemoteObjectBacking; they can also appear in error messages.
|
||||
type Locator string
|
||||
|
||||
// StorageFactory is used to return Storage implementations based on locators. A
|
||||
// Pebble store that uses shared storage is configured with a StorageFactory.
|
||||
type StorageFactory interface {
|
||||
CreateStorage(locator Locator) (Storage, error)
|
||||
}
|
||||
|
||||
// SharedLevelsStart denotes the highest (i.e. lowest numbered) level that will
|
||||
// have sstables shared across Pebble instances when doing skip-shared
|
||||
// iteration (see db.ScanInternal) or shared file ingestion (see
|
||||
// db.IngestAndExcise).
|
||||
const SharedLevelsStart = 5
|
||||
|
||||
// CreateOnSharedStrategy specifies what table files should be created on shared
|
||||
// storage. For use with CreateOnShared in options.
|
||||
type CreateOnSharedStrategy int
|
||||
|
||||
const (
|
||||
// CreateOnSharedNone denotes no files being created on shared storage.
|
||||
CreateOnSharedNone CreateOnSharedStrategy = iota
|
||||
// CreateOnSharedLower denotes the creation of files in lower levels of the
|
||||
// LSM (specifically, L5 and L6 as they're below SharedLevelsStart) on
|
||||
// shared storage, and higher levels on local storage.
|
||||
CreateOnSharedLower
|
||||
// CreateOnSharedAll denotes the creation of all sstables on shared storage.
|
||||
CreateOnSharedAll
|
||||
)
|
||||
|
||||
// ShouldCreateShared returns whether new table files at the specified level
|
||||
// should be created on shared storage.
|
||||
func ShouldCreateShared(strategy CreateOnSharedStrategy, level int) bool {
|
||||
switch strategy {
|
||||
case CreateOnSharedAll:
|
||||
return true
|
||||
case CreateOnSharedNone:
|
||||
return false
|
||||
case CreateOnSharedLower:
|
||||
return level >= SharedLevelsStart
|
||||
default:
|
||||
panic("unexpected CreateOnSharedStrategy value")
|
||||
}
|
||||
}
|
||||
|
||||
// Storage is an interface for a blob storage driver. This is lower-level
|
||||
// than an FS-like interface, however FS/File-like abstractions can be built on
|
||||
// top of these methods.
|
||||
@@ -66,8 +115,11 @@ type Storage interface {
|
||||
type ObjectReader interface {
|
||||
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||
//
|
||||
// Clients of ReadAt can execute parallel ReadAt calls on the
|
||||
// same ObjectReader.
|
||||
// Does not return partial results; if offset + len(p) is past the end of the
|
||||
// object, an error is returned.
|
||||
//
|
||||
// Clients of ReadAt can execute parallel ReadAt calls on the same
|
||||
// ObjectReader.
|
||||
ReadAt(ctx context.Context, p []byte, offset int64) error
|
||||
|
||||
Close() error
|
||||
Reference in New Issue
Block a user