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

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package objstorageprovider
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"runtime"
    11  	"sync"
    12  	"sync/atomic"
    13  
    14  	"github.com/cockroachdb/errors"
    15  	"github.com/cockroachdb/pebble/internal/base"
    16  	"github.com/cockroachdb/pebble/internal/invariants"
    17  	"github.com/cockroachdb/pebble/objstorage"
    18  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
    19  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
    20  	"github.com/cockroachdb/pebble/objstorage/remote"
    21  )
    22  
    23  // remoteSubsystem contains the provider fields related to remote storage.
    24  // All fields remain unset if remote storage is not configured.
    25  type remoteSubsystem struct {
    26  	catalog *remoteobjcat.Catalog
    27  	// catalogSyncMutex is used to correctly serialize two sharedSync operations.
    28  	// It must be acquired before the provider mutex.
    29  	catalogSyncMutex sync.Mutex
    30  
    31  	cache *sharedcache.Cache
    32  
    33  	// shared contains the fields relevant to shared objects, i.e. objects that
    34  	// are created by Pebble and potentially shared between Pebble instances.
    35  	shared struct {
    36  		// initialized guards access to the creatorID field.
    37  		initialized atomic.Bool
    38  		creatorID   objstorage.CreatorID
    39  		initOnce    sync.Once
    40  
    41  		// checkRefsOnOpen controls whether we check the ref marker file when opening
    42  		// an object. Normally this is true when invariants are enabled (but the provider
    43  		// test tweaks this field).
    44  		checkRefsOnOpen bool
    45  	}
    46  }
    47  
    48  // remoteInit initializes the remote object subsystem (if configured) and finds
    49  // any remote objects.
    50  func (p *provider) remoteInit() error {
    51  	if p.st.Remote.StorageFactory == nil {
    52  		return nil
    53  	}
    54  	catalog, contents, err := remoteobjcat.Open(p.st.FS, p.st.FSDirName)
    55  	if err != nil {
    56  		return errors.Wrapf(err, "pebble: could not open remote object catalog")
    57  	}
    58  	p.remote.catalog = catalog
    59  	p.remote.shared.checkRefsOnOpen = invariants.Enabled
    60  
    61  	// The creator ID may or may not be initialized yet.
    62  	if contents.CreatorID.IsSet() {
    63  		p.remote.initShared(contents.CreatorID)
    64  		p.st.Logger.Infof("remote storage configured; creatorID = %s", contents.CreatorID)
    65  	} else {
    66  		p.st.Logger.Infof("remote storage configured; no creatorID yet")
    67  	}
    68  
    69  	if p.st.Remote.CacheSizeBytes > 0 {
    70  		const defaultBlockSize = 32 * 1024
    71  		blockSize := p.st.Remote.CacheBlockSize
    72  		if blockSize == 0 {
    73  			blockSize = defaultBlockSize
    74  		}
    75  
    76  		const defaultShardingBlockSize = 1024 * 1024
    77  		shardingBlockSize := p.st.Remote.ShardingBlockSize
    78  		if shardingBlockSize == 0 {
    79  			shardingBlockSize = defaultShardingBlockSize
    80  		}
    81  
    82  		numShards := p.st.Remote.CacheShardCount
    83  		if numShards == 0 {
    84  			numShards = 2 * runtime.GOMAXPROCS(0)
    85  		}
    86  
    87  		p.remote.cache, err = sharedcache.Open(
    88  			p.st.FS, p.st.Logger, p.st.FSDirName, blockSize, shardingBlockSize, p.st.Remote.CacheSizeBytes, numShards)
    89  		if err != nil {
    90  			return errors.Wrapf(err, "pebble: could not open remote object cache")
    91  		}
    92  	}
    93  
    94  	for _, meta := range contents.Objects {
    95  		o := objstorage.ObjectMetadata{
    96  			DiskFileNum: meta.FileNum,
    97  			FileType:    meta.FileType,
    98  		}
    99  		o.Remote.CreatorID = meta.CreatorID
   100  		o.Remote.CreatorFileNum = meta.CreatorFileNum
   101  		o.Remote.CleanupMethod = meta.CleanupMethod
   102  		o.Remote.Locator = meta.Locator
   103  		o.Remote.CustomObjectName = meta.CustomObjectName
   104  		o.Remote.Storage, err = p.ensureStorageLocked(o.Remote.Locator)
   105  		if err != nil {
   106  			return errors.Wrapf(err, "creating remote.Storage object for locator '%s'", o.Remote.Locator)
   107  		}
   108  		if invariants.Enabled {
   109  			o.AssertValid()
   110  		}
   111  		p.mu.knownObjects[o.DiskFileNum] = o
   112  	}
   113  	return nil
   114  }
   115  
   116  // initShared initializes the creator ID, allowing use of shared objects.
   117  func (ss *remoteSubsystem) initShared(creatorID objstorage.CreatorID) {
   118  	ss.shared.initOnce.Do(func() {
   119  		ss.shared.creatorID = creatorID
   120  		ss.shared.initialized.Store(true)
   121  	})
   122  }
   123  
   124  func (p *provider) sharedClose() error {
   125  	if p.st.Remote.StorageFactory == nil {
   126  		return nil
   127  	}
   128  	var err error
   129  	if p.remote.cache != nil {
   130  		err = p.remote.cache.Close()
   131  		p.remote.cache = nil
   132  	}
   133  	if p.remote.catalog != nil {
   134  		err = firstError(err, p.remote.catalog.Close())
   135  		p.remote.catalog = nil
   136  	}
   137  	return err
   138  }
   139  
   140  // SetCreatorID is part of the objstorage.Provider interface.
   141  func (p *provider) SetCreatorID(creatorID objstorage.CreatorID) error {
   142  	if p.st.Remote.StorageFactory == nil {
   143  		return errors.AssertionFailedf("attempt to set CreatorID but remote storage not enabled")
   144  	}
   145  	// Note: this call is a cheap no-op if the creator ID was already set. This
   146  	// call also checks if we are trying to change the ID.
   147  	if err := p.remote.catalog.SetCreatorID(creatorID); err != nil {
   148  		return err
   149  	}
   150  	if !p.remote.shared.initialized.Load() {
   151  		p.st.Logger.Infof("remote storage creatorID set to %s", creatorID)
   152  		p.remote.initShared(creatorID)
   153  	}
   154  	return nil
   155  }
   156  
   157  // IsSharedForeign is part of the objstorage.Provider interface.
   158  func (p *provider) IsSharedForeign(meta objstorage.ObjectMetadata) bool {
   159  	if !p.remote.shared.initialized.Load() {
   160  		return false
   161  	}
   162  	return meta.IsShared() && (meta.Remote.CreatorID != p.remote.shared.creatorID)
   163  }
   164  
   165  func (p *provider) remoteCheckInitialized() error {
   166  	if p.st.Remote.StorageFactory == nil {
   167  		return errors.Errorf("remote object support not configured")
   168  	}
   169  	return nil
   170  }
   171  
   172  func (p *provider) sharedCheckInitialized() error {
   173  	if err := p.remoteCheckInitialized(); err != nil {
   174  		return err
   175  	}
   176  	if !p.remote.shared.initialized.Load() {
   177  		return errors.Errorf("remote object support not available: remote creator ID not yet set")
   178  	}
   179  	return nil
   180  }
   181  
   182  func (p *provider) sharedSync() error {
   183  	// Serialize parallel sync operations. Note that ApplyBatch is already
   184  	// serialized internally, but we want to make sure they get called with
   185  	// batches in the right order.
   186  	p.remote.catalogSyncMutex.Lock()
   187  	defer p.remote.catalogSyncMutex.Unlock()
   188  
   189  	batch := func() remoteobjcat.Batch {
   190  		p.mu.Lock()
   191  		defer p.mu.Unlock()
   192  		res := p.mu.remote.catalogBatch.Copy()
   193  		p.mu.remote.catalogBatch.Reset()
   194  		return res
   195  	}()
   196  
   197  	if batch.IsEmpty() {
   198  		return nil
   199  	}
   200  
   201  	if err := p.remote.catalog.ApplyBatch(batch); err != nil {
   202  		// Put back the batch (for the next Sync), appending any operations that
   203  		// happened in the meantime.
   204  		p.mu.Lock()
   205  		defer p.mu.Unlock()
   206  		batch.Append(p.mu.remote.catalogBatch)
   207  		p.mu.remote.catalogBatch = batch
   208  		return err
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (p *provider) remotePath(meta objstorage.ObjectMetadata) string {
   215  	if meta.Remote.Locator != "" {
   216  		return fmt.Sprintf("remote-%s://%s", meta.Remote.Locator, remoteObjectName(meta))
   217  	}
   218  	return "remote://" + remoteObjectName(meta)
   219  }
   220  
   221  // sharedCreateRef creates a reference marker object.
   222  func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
   223  	if err := p.sharedCheckInitialized(); err != nil {
   224  		return err
   225  	}
   226  	if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
   227  		return nil
   228  	}
   229  	refName := p.sharedObjectRefName(meta)
   230  	writer, err := meta.Remote.Storage.CreateObject(refName)
   231  	if err == nil {
   232  		// The object is empty, just close the writer.
   233  		err = writer.Close()
   234  	}
   235  	if err != nil {
   236  		return errors.Wrapf(err, "creating marker object %q", refName)
   237  	}
   238  	return nil
   239  }
   240  
   241  func (p *provider) sharedCreate(
   242  	_ context.Context,
   243  	fileType base.FileType,
   244  	fileNum base.DiskFileNum,
   245  	locator remote.Locator,
   246  	opts objstorage.CreateOptions,
   247  ) (objstorage.Writable, objstorage.ObjectMetadata, error) {
   248  	if err := p.sharedCheckInitialized(); err != nil {
   249  		return nil, objstorage.ObjectMetadata{}, err
   250  	}
   251  	storage, err := p.ensureStorage(locator)
   252  	if err != nil {
   253  		return nil, objstorage.ObjectMetadata{}, err
   254  	}
   255  	meta := objstorage.ObjectMetadata{
   256  		DiskFileNum: fileNum,
   257  		FileType:    fileType,
   258  	}
   259  	meta.Remote.CreatorID = p.remote.shared.creatorID
   260  	meta.Remote.CreatorFileNum = fileNum
   261  	meta.Remote.CleanupMethod = opts.SharedCleanupMethod
   262  	meta.Remote.Locator = locator
   263  	meta.Remote.Storage = storage
   264  
   265  	objName := remoteObjectName(meta)
   266  	writer, err := storage.CreateObject(objName)
   267  	if err != nil {
   268  		return nil, objstorage.ObjectMetadata{}, errors.Wrapf(err, "creating object %q", objName)
   269  	}
   270  	return &sharedWritable{
   271  		p:             p,
   272  		meta:          meta,
   273  		storageWriter: writer,
   274  	}, meta, nil
   275  }
   276  
   277  func (p *provider) remoteOpenForReading(
   278  	ctx context.Context, meta objstorage.ObjectMetadata, opts objstorage.OpenOptions,
   279  ) (objstorage.Readable, error) {
   280  	if err := p.remoteCheckInitialized(); err != nil {
   281  		return nil, err
   282  	}
   283  	// Verify we have a reference on this object; for performance reasons, we only
   284  	// do this in testing scenarios.
   285  	if p.remote.shared.checkRefsOnOpen && meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
   286  		if err := p.sharedCheckInitialized(); err != nil {
   287  			return nil, err
   288  		}
   289  		refName := p.sharedObjectRefName(meta)
   290  		if _, err := meta.Remote.Storage.Size(refName); err != nil {
   291  			if meta.Remote.Storage.IsNotExistError(err) {
   292  				if opts.MustExist {
   293  					p.st.Logger.Fatalf("marker object %q does not exist", refName)
   294  					// TODO(radu): maybe list references for the object.
   295  				}
   296  				return nil, errors.Errorf("marker object %q does not exist", refName)
   297  			}
   298  			return nil, errors.Wrapf(err, "checking marker object %q", refName)
   299  		}
   300  	}
   301  	objName := remoteObjectName(meta)
   302  	reader, size, err := meta.Remote.Storage.ReadObject(ctx, objName)
   303  	if err != nil {
   304  		if opts.MustExist && meta.Remote.Storage.IsNotExistError(err) {
   305  			p.st.Logger.Fatalf("object %q does not exist", objName)
   306  			// TODO(radu): maybe list references for the object.
   307  		}
   308  		return nil, err
   309  	}
   310  	return p.newRemoteReadable(reader, size, meta.DiskFileNum), nil
   311  }
   312  
   313  func (p *provider) remoteSize(meta objstorage.ObjectMetadata) (int64, error) {
   314  	if err := p.remoteCheckInitialized(); err != nil {
   315  		return 0, err
   316  	}
   317  	objName := remoteObjectName(meta)
   318  	return meta.Remote.Storage.Size(objName)
   319  }
   320  
   321  // sharedUnref implements object "removal" with the remote backend. The ref
   322  // marker object is removed and the backing object is removed only if there are
   323  // no other ref markers.
   324  func (p *provider) sharedUnref(meta objstorage.ObjectMetadata) error {
   325  	if meta.Remote.CleanupMethod == objstorage.SharedNoCleanup {
   326  		// Never delete objects in this mode.
   327  		return nil
   328  	}
   329  	if p.isProtected(meta.DiskFileNum) {
   330  		// TODO(radu): we need a mechanism to unref the object when it becomes
   331  		// unprotected.
   332  		return nil
   333  	}
   334  
   335  	refName := p.sharedObjectRefName(meta)
   336  	// Tolerate a not-exists error.
   337  	if err := meta.Remote.Storage.Delete(refName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
   338  		return err
   339  	}
   340  	otherRefs, err := meta.Remote.Storage.List(sharedObjectRefPrefix(meta), "" /* delimiter */)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	if len(otherRefs) == 0 {
   345  		objName := remoteObjectName(meta)
   346  		if err := meta.Remote.Storage.Delete(objName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
   347  			return err
   348  		}
   349  	}
   350  	return nil
   351  }
   352  
   353  // ensureStorageLocked populates the remote.Storage object for the given
   354  // locator, if necessary. p.mu must be held.
   355  func (p *provider) ensureStorageLocked(locator remote.Locator) (remote.Storage, error) {
   356  	if p.mu.remote.storageObjects == nil {
   357  		p.mu.remote.storageObjects = make(map[remote.Locator]remote.Storage)
   358  	}
   359  	if res, ok := p.mu.remote.storageObjects[locator]; ok {
   360  		return res, nil
   361  	}
   362  	res, err := p.st.Remote.StorageFactory.CreateStorage(locator)
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  
   367  	p.mu.remote.storageObjects[locator] = res
   368  	return res, nil
   369  }
   370  
   371  // ensureStorage populates the remote.Storage object for the given locator, if necessary.
   372  func (p *provider) ensureStorage(locator remote.Locator) (remote.Storage, error) {
   373  	p.mu.Lock()
   374  	defer p.mu.Unlock()
   375  	return p.ensureStorageLocked(locator)
   376  }