[WIP] Partial bot skeleton, non-functional though
This commit is contained in:
446
vendor/github.com/cockroachdb/pebble/objstorage/provider.go
generated
vendored
Normal file
446
vendor/github.com/cockroachdb/pebble/objstorage/provider.go
generated
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
// 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 objstorage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/errors/oserror"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
"github.com/cockroachdb/pebble/objstorage/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
// Provider is a singleton object used to access and manage objects.
|
||||
//
|
||||
// An object is conceptually like a large immutable file. The main use of
|
||||
// objects is for storing sstables; in the future it could also be used for blob
|
||||
// storage.
|
||||
//
|
||||
// The Provider can only manage objects that it knows about - either objects
|
||||
// created by the provider, or existing objects the Provider was informed about
|
||||
// via AddObjects.
|
||||
//
|
||||
// Objects are currently backed by a vfs.File.
|
||||
type Provider struct {
|
||||
st Settings
|
||||
|
||||
fsDir vfs.File
|
||||
|
||||
shared sharedSubsystem
|
||||
|
||||
mu struct {
|
||||
sync.RWMutex
|
||||
|
||||
shared struct {
|
||||
// catalogBatch accumulates shared object creations and deletions until
|
||||
// Sync is called.
|
||||
catalogBatch sharedobjcat.Batch
|
||||
}
|
||||
|
||||
// localObjectsChanged is set if non-shared objects were created or deleted
|
||||
// but Sync was not yet called.
|
||||
localObjectsChanged bool
|
||||
|
||||
// knownObjects maintains information about objects that are known to the provider.
|
||||
// It is initialized with the list of files in the manifest when we open a DB.
|
||||
knownObjects map[base.FileNum]ObjectMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// Readable is the handle for an object that is open for reading.
|
||||
type Readable interface {
|
||||
io.ReaderAt
|
||||
io.Closer
|
||||
|
||||
// Size returns the size of the object.
|
||||
Size() int64
|
||||
|
||||
// NewReadHandle creates a read handle for ReadAt requests that are related
|
||||
// and can benefit from optimizations like read-ahead.
|
||||
//
|
||||
// The ReadHandle must be closed before the Readable is closed.
|
||||
//
|
||||
// Multiple separate ReadHandles can be used.
|
||||
NewReadHandle() ReadHandle
|
||||
}
|
||||
|
||||
// ReadHandle is used to perform reads that are related and might benefit from
|
||||
// optimizations like read-ahead.
|
||||
type ReadHandle interface {
|
||||
io.ReaderAt
|
||||
io.Closer
|
||||
|
||||
// MaxReadahead configures the implementation to expect large sequential
|
||||
// reads. Used to skip any initial read-ahead ramp-up.
|
||||
MaxReadahead()
|
||||
|
||||
// RecordCacheHit informs the implementation that we were able to retrieve a
|
||||
// block from cache.
|
||||
RecordCacheHit(offset, size int64)
|
||||
}
|
||||
|
||||
// Writable is the handle for an object that is open for writing.
|
||||
// Either Finish or Abort must be called.
|
||||
type Writable interface {
|
||||
// Write writes len(p) bytes from p to the underlying object. The data is not
|
||||
// guaranteed to be durable until Finish is called.
|
||||
//
|
||||
// Note that Write *is* allowed to modify the slice passed in, whether
|
||||
// temporarily or permanently. Callers of Write need to take this into
|
||||
// account.
|
||||
Write(p []byte) error
|
||||
|
||||
// Finish completes the object and makes the data durable.
|
||||
// No further calls are allowed after calling Finish.
|
||||
Finish() error
|
||||
|
||||
// Abort gives up on finishing the object. There is no guarantee about whether
|
||||
// the object exists after calling Abort.
|
||||
// No further calls are allowed after calling Abort.
|
||||
Abort()
|
||||
}
|
||||
|
||||
// Settings that must be specified when creating the Provider.
|
||||
type Settings struct {
|
||||
Logger base.Logger
|
||||
|
||||
// Local filesystem configuration.
|
||||
FS vfs.FS
|
||||
FSDirName string
|
||||
|
||||
// FSDirInitialListing is a listing of FSDirName at the time of calling Open.
|
||||
//
|
||||
// This is an optional optimization to avoid double listing on Open when the
|
||||
// higher layer already has a listing. When nil, we obtain the listing on
|
||||
// Open.
|
||||
FSDirInitialListing []string
|
||||
|
||||
// Cleaner cleans obsolete files from the local filesystem.
|
||||
//
|
||||
// The default cleaner uses the DeleteCleaner.
|
||||
FSCleaner base.Cleaner
|
||||
|
||||
// NoSyncOnClose decides whether the implementation will enforce a
|
||||
// close-time synchronization (e.g., fdatasync() or sync_file_range())
|
||||
// on files it writes to. Setting this to true removes the guarantee for a
|
||||
// sync on close. Some implementations can still issue a non-blocking sync.
|
||||
NoSyncOnClose bool
|
||||
|
||||
// BytesPerSync enables periodic syncing of files in order to smooth out
|
||||
// writes to disk. This option does not provide any persistence guarantee, but
|
||||
// is used to avoid latency spikes if the OS automatically decides to write
|
||||
// out a large chunk of dirty filesystem buffers.
|
||||
BytesPerSync int
|
||||
|
||||
// Fields here are set only if the provider is to support shared objects
|
||||
// (experimental).
|
||||
Shared struct {
|
||||
Storage shared.Storage
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSettings initializes default settings (with no shared storage),
|
||||
// suitable for tests and tools.
|
||||
func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||
return Settings{
|
||||
Logger: base.DefaultLogger,
|
||||
FS: fs,
|
||||
FSDirName: dirName,
|
||||
FSCleaner: base.DeleteCleaner{},
|
||||
NoSyncOnClose: false,
|
||||
BytesPerSync: 512 * 1024, // 512KB
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectMetadata contains the metadata required to be able to access an object.
|
||||
type ObjectMetadata struct {
|
||||
FileNum base.FileNum
|
||||
FileType base.FileType
|
||||
|
||||
// The fields below are only set if the object is on shared storage.
|
||||
Shared struct {
|
||||
// CreatorID identifies the DB instance that originally created the object.
|
||||
CreatorID CreatorID
|
||||
// CreatorFileNum is the identifier for the object within the context of the
|
||||
// DB instance that originally created the object.
|
||||
CreatorFileNum base.FileNum
|
||||
}
|
||||
}
|
||||
|
||||
// CreatorID identifies the DB instance that originally created a shared object.
|
||||
// This ID is incorporated in backing object names.
|
||||
// Must be non-zero.
|
||||
type CreatorID = sharedobjcat.CreatorID
|
||||
|
||||
// IsShared returns true if the object is on shared storage.
|
||||
func (meta *ObjectMetadata) IsShared() bool {
|
||||
return meta.Shared.CreatorID.IsSet()
|
||||
}
|
||||
|
||||
// Open creates the Provider.
|
||||
func Open(settings Settings) (p *Provider, _ error) {
|
||||
fsDir, err := settings.FS.OpenDir(settings.FSDirName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p == nil {
|
||||
fsDir.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
p = &Provider{
|
||||
st: settings,
|
||||
fsDir: fsDir,
|
||||
}
|
||||
p.mu.knownObjects = make(map[base.FileNum]ObjectMetadata)
|
||||
|
||||
// Add local FS objects.
|
||||
if err := p.vfsInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize shared subsystem (if configured) and add shared objects.
|
||||
if err := p.sharedInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Close the provider.
|
||||
func (p *Provider) Close() error {
|
||||
var err error
|
||||
if p.fsDir != nil {
|
||||
err = p.fsDir.Close()
|
||||
p.fsDir = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenForReading opens an existing object.
|
||||
func (p *Provider) OpenForReading(fileType base.FileType, fileNum base.FileNum) (Readable, error) {
|
||||
meta, err := p.Lookup(fileType, fileNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !meta.IsShared() {
|
||||
return p.vfsOpenForReading(fileType, fileNum, false /* mustExist */)
|
||||
}
|
||||
return p.sharedOpenForReading(meta)
|
||||
}
|
||||
|
||||
// OpenForReadingMustExist is a variant of OpenForReading which causes a fatal
|
||||
// error if the file does not exist. The fatal error message contains
|
||||
// information helpful for debugging.
|
||||
func (p *Provider) OpenForReadingMustExist(
|
||||
fileType base.FileType, fileNum base.FileNum,
|
||||
) (Readable, error) {
|
||||
meta, err := p.Lookup(fileType, fileNum)
|
||||
if err != nil {
|
||||
p.st.Logger.Fatalf("%v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !meta.IsShared() {
|
||||
return p.vfsOpenForReading(fileType, fileNum, true /* mustExist */)
|
||||
}
|
||||
|
||||
// TODO(radu): implement "must exist" behavior.
|
||||
return p.sharedOpenForReading(meta)
|
||||
}
|
||||
|
||||
// CreateOptions contains optional arguments for Create.
|
||||
type CreateOptions struct {
|
||||
// PreferSharedStorage causes the object to be created on shared storage if
|
||||
// the provider has shared storage configured.
|
||||
PreferSharedStorage bool
|
||||
}
|
||||
|
||||
// Create creates a new object and opens it for writing.
|
||||
//
|
||||
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||
// until Sync is called.
|
||||
func (p *Provider) Create(
|
||||
fileType base.FileType, fileNum base.FileNum, opts CreateOptions,
|
||||
) (w Writable, meta ObjectMetadata, err error) {
|
||||
if opts.PreferSharedStorage && p.st.Shared.Storage != nil {
|
||||
w, meta, err = p.sharedCreate(fileType, fileNum)
|
||||
} else {
|
||||
w, meta, err = p.vfsCreate(fileType, fileNum)
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "creating object %s", errors.Safe(fileNum))
|
||||
return nil, ObjectMetadata{}, err
|
||||
}
|
||||
p.addMetadata(meta)
|
||||
return w, meta, nil
|
||||
}
|
||||
|
||||
// Remove removes an object.
|
||||
//
|
||||
// The object is not guaranteed to be durably removed until Sync is called.
|
||||
func (p *Provider) Remove(fileType base.FileType, fileNum base.FileNum) error {
|
||||
meta, err := p.Lookup(fileType, fileNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !meta.IsShared() {
|
||||
err = p.vfsRemove(fileType, fileNum)
|
||||
}
|
||||
// TODO(radu): implement shared object removal (i.e. deref).
|
||||
|
||||
if err != nil && !IsNotExistError(err) {
|
||||
// We want to be able to retry a Remove, so we keep the object in our list.
|
||||
// TODO(radu): we should mark the object as "zombie" and not allow any other
|
||||
// operations.
|
||||
return errors.Wrapf(err, "removing object %s", errors.Safe(fileNum))
|
||||
}
|
||||
p.removeMetadata(fileNum)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsNotExistError indicates whether the error is known to report that a file or
|
||||
// directory does not exist.
|
||||
func IsNotExistError(err error) bool {
|
||||
return oserror.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Sync flushes the metadata from creation or removal of objects since the last Sync.
|
||||
func (p *Provider) Sync() error {
|
||||
if err := p.vfsSync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.sharedSync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LinkOrCopyFromLocal creates a new object that is either a copy of a given
|
||||
// local file or a hard link (if the new object is created on the same FS, and
|
||||
// if the FS supports it).
|
||||
//
|
||||
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||
// until Sync is called.
|
||||
func (p *Provider) LinkOrCopyFromLocal(
|
||||
srcFS vfs.FS, srcFilePath string, dstFileType base.FileType, dstFileNum base.FileNum,
|
||||
) (ObjectMetadata, error) {
|
||||
if srcFS == p.st.FS {
|
||||
// Wrap the normal filesystem with one which wraps newly created files with
|
||||
// vfs.NewSyncingFile.
|
||||
fs := vfs.NewSyncingFS(p.st.FS, vfs.SyncingFileOptions{
|
||||
NoSyncOnClose: p.st.NoSyncOnClose,
|
||||
BytesPerSync: p.st.BytesPerSync,
|
||||
})
|
||||
dstPath := p.vfsPath(dstFileType, dstFileNum)
|
||||
if err := vfs.LinkOrCopy(fs, srcFilePath, dstPath); err != nil {
|
||||
return ObjectMetadata{}, err
|
||||
}
|
||||
|
||||
meta := ObjectMetadata{
|
||||
FileNum: dstFileNum,
|
||||
FileType: dstFileType,
|
||||
}
|
||||
p.addMetadata(meta)
|
||||
return meta, nil
|
||||
}
|
||||
// TODO(radu): for the copy case, we should use `p.Create` and do the copy ourselves.
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Lookup returns the metadata of an object that is already known to the Provider.
|
||||
// Does not perform any I/O.
|
||||
func (p *Provider) Lookup(fileType base.FileType, fileNum base.FileNum) (ObjectMetadata, error) {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
meta, ok := p.mu.knownObjects[fileNum]
|
||||
if !ok {
|
||||
return ObjectMetadata{}, errors.Wrapf(
|
||||
os.ErrNotExist,
|
||||
"file %s (type %d) unknown to the objstorage provider",
|
||||
errors.Safe(fileNum), errors.Safe(fileType),
|
||||
)
|
||||
}
|
||||
if meta.FileType != fileType {
|
||||
return ObjectMetadata{}, errors.AssertionFailedf(
|
||||
"file %s type mismatch (known type %d, expected type %d)",
|
||||
errors.Safe(fileNum), errors.Safe(meta.FileType), errors.Safe(fileType),
|
||||
)
|
||||
}
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// Path returns an internal, implementation-dependent path for the object. It is
|
||||
// meant to be used for informational purposes (like logging).
|
||||
func (p *Provider) Path(meta ObjectMetadata) string {
|
||||
if !meta.IsShared() {
|
||||
return p.vfsPath(meta.FileType, meta.FileNum)
|
||||
}
|
||||
return p.sharedPath(meta)
|
||||
}
|
||||
|
||||
// Size returns the size of the object.
|
||||
func (p *Provider) Size(meta ObjectMetadata) (int64, error) {
|
||||
if !meta.IsShared() {
|
||||
return p.vfsSize(meta.FileType, meta.FileNum)
|
||||
}
|
||||
return p.sharedSize(meta)
|
||||
}
|
||||
|
||||
// List returns the objects currently known to the provider. Does not perform any I/O.
|
||||
func (p *Provider) List() []ObjectMetadata {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
res := make([]ObjectMetadata, 0, len(p.mu.knownObjects))
|
||||
for _, meta := range p.mu.knownObjects {
|
||||
res = append(res, meta)
|
||||
}
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].FileNum < res[j].FileNum
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *Provider) addMetadata(meta ObjectMetadata) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.mu.knownObjects[meta.FileNum] = meta
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
||||
FileNum: meta.FileNum,
|
||||
FileType: meta.FileType,
|
||||
CreatorID: meta.Shared.CreatorID,
|
||||
CreatorFileNum: meta.Shared.CreatorFileNum,
|
||||
})
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) removeMetadata(fileNum base.FileNum) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
meta, ok := p.mu.knownObjects[fileNum]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(p.mu.knownObjects, fileNum)
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.DeleteObject(fileNum)
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user