github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/block/global_markers_bucket_client.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/storage/tsdb/bucketindex/markers_bucket_client.go
     3  // Provenance-includes-license: Apache-2.0
     4  // Provenance-includes-copyright: The Cortex Authors.
     5  
     6  package block
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"io"
    12  	"path"
    13  
    14  	"github.com/grafana/dskit/multierror"
    15  	"github.com/oklog/ulid/v2"
    16  	thanosobjstore "github.com/thanos-io/objstore"
    17  
    18  	"github.com/grafana/pyroscope/pkg/objstore"
    19  )
    20  
    21  // globalMarkersBucket is a bucket client which stores markers (eg. block deletion marks) in a per-tenant
    22  // global location too.
    23  type globalMarkersBucket struct {
    24  	parent objstore.Bucket
    25  }
    26  
    27  // BucketWithGlobalMarkers wraps the input bucket into a bucket which also keeps track of markers
    28  // in the global markers location.
    29  func BucketWithGlobalMarkers(b objstore.Bucket) objstore.Bucket {
    30  	return &globalMarkersBucket{
    31  		parent: b,
    32  	}
    33  }
    34  
    35  // Upload implements objstore.Bucket.
    36  func (b *globalMarkersBucket) Upload(ctx context.Context, name string, r io.Reader, opts ...thanosobjstore.ObjectUploadOption) error {
    37  	globalMarkPath := getGlobalMarkPathFromBlockMark(name)
    38  	if globalMarkPath == "" {
    39  		return b.parent.Upload(ctx, name, r, opts...)
    40  	}
    41  
    42  	// Read the marker.
    43  	body, err := io.ReadAll(r)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	// Upload it to the original location.
    49  	if err := b.parent.Upload(ctx, name, bytes.NewBuffer(body), opts...); err != nil {
    50  		return err
    51  	}
    52  
    53  	// Upload it to the global markers location too.
    54  	return b.parent.Upload(ctx, globalMarkPath, bytes.NewBuffer(body), opts...)
    55  }
    56  
    57  // Delete implements objstore.Bucket.
    58  func (b *globalMarkersBucket) Delete(ctx context.Context, name string) error {
    59  	// Call the parent. Only return error here (without deleting global marker too) if error is different than "not found".
    60  	err1 := b.parent.Delete(ctx, name)
    61  	if err1 != nil && !b.parent.IsObjNotFoundErr(err1) {
    62  		return err1
    63  	}
    64  
    65  	// Delete the marker in the global markers location too.
    66  	globalMarkPath := getGlobalMarkPathFromBlockMark(name)
    67  	if globalMarkPath == "" {
    68  		return err1
    69  	}
    70  
    71  	var err2 error
    72  	if err := b.parent.Delete(ctx, globalMarkPath); err != nil {
    73  		if !b.parent.IsObjNotFoundErr(err) {
    74  			err2 = err
    75  		}
    76  	}
    77  
    78  	if err1 != nil {
    79  		// In this case err1 is "ObjNotFound". If we tried to wrap it together with err2, we would need to
    80  		// handle this possibility in globalMarkersBucket.IsObjNotFoundErr(). Instead we just ignore err2, if any.
    81  		return err1
    82  	}
    83  
    84  	return err2
    85  }
    86  
    87  // Name implements objstore.Bucket.
    88  func (b *globalMarkersBucket) Name() string {
    89  	return b.parent.Name()
    90  }
    91  
    92  // Close implements objstore.Bucket.
    93  func (b *globalMarkersBucket) Close() error {
    94  	return b.parent.Close()
    95  }
    96  
    97  // Iter implements objstore.Bucket.
    98  func (b *globalMarkersBucket) Iter(ctx context.Context, dir string, f func(string) error, options ...thanosobjstore.IterOption) error {
    99  	return b.parent.Iter(ctx, dir, f, options...)
   100  }
   101  
   102  func (b *globalMarkersBucket) IterWithAttributes(ctx context.Context, dir string, f func(attrs thanosobjstore.IterObjectAttributes) error, options ...thanosobjstore.IterOption) error {
   103  	return b.parent.IterWithAttributes(ctx, dir, f, options...)
   104  }
   105  
   106  func (b *globalMarkersBucket) SupportedIterOptions() []thanosobjstore.IterOptionType {
   107  	return b.parent.SupportedIterOptions()
   108  }
   109  
   110  // Get implements objstore.Bucket.
   111  func (b *globalMarkersBucket) Get(ctx context.Context, name string) (io.ReadCloser, error) {
   112  	return b.parent.Get(ctx, name)
   113  }
   114  
   115  // GetRange implements objstore.Bucket.
   116  func (b *globalMarkersBucket) GetRange(ctx context.Context, name string, off, length int64) (io.ReadCloser, error) {
   117  	return b.parent.GetRange(ctx, name, off, length)
   118  }
   119  
   120  // Exists implements objstore.Bucket.
   121  func (b *globalMarkersBucket) Exists(ctx context.Context, name string) (bool, error) {
   122  	globalMarkPath := getGlobalMarkPathFromBlockMark(name)
   123  	if globalMarkPath == "" {
   124  		return b.parent.Exists(ctx, name)
   125  	}
   126  
   127  	// Report "exists" only if BOTH (block-local, and global) files exist, otherwise Thanos
   128  	// code will never try to upload the file again, if it finds that it exist.
   129  	ok1, err1 := b.parent.Exists(ctx, name)
   130  	ok2, err2 := b.parent.Exists(ctx, globalMarkPath)
   131  
   132  	var me multierror.MultiError
   133  	me.Add(err1)
   134  	me.Add(err2)
   135  
   136  	return ok1 && ok2, me.Err()
   137  }
   138  
   139  // IsObjNotFoundErr implements objstore.Bucket.
   140  func (b *globalMarkersBucket) IsObjNotFoundErr(err error) bool {
   141  	return b.parent.IsObjNotFoundErr(err)
   142  }
   143  
   144  // IsAccessDeniedErr returns true if acces to object is denied.
   145  func (b *globalMarkersBucket) IsAccessDeniedErr(err error) bool {
   146  	return b.parent.IsAccessDeniedErr(err)
   147  }
   148  
   149  // Attributes implements objstore.Bucket.
   150  func (b *globalMarkersBucket) Attributes(ctx context.Context, name string) (thanosobjstore.ObjectAttributes, error) {
   151  	return b.parent.Attributes(ctx, name)
   152  }
   153  
   154  // Attributes implements objstore.ReaderAt.
   155  func (b *globalMarkersBucket) ReaderAt(ctx context.Context, filename string) (objstore.ReaderAtCloser, error) {
   156  	return b.parent.ReaderAt(ctx, filename)
   157  }
   158  
   159  // ReaderWithExpectedErrs implements objstore.Bucket.
   160  func (b *globalMarkersBucket) ReaderWithExpectedErrs(fn objstore.IsOpFailureExpectedFunc) objstore.BucketReader {
   161  	return b.WithExpectedErrs(fn)
   162  }
   163  
   164  // WithExpectedErrs implements objstore.Bucket.
   165  func (b *globalMarkersBucket) WithExpectedErrs(fn objstore.IsOpFailureExpectedFunc) objstore.Bucket {
   166  	if ib, ok := b.parent.(objstore.InstrumentedBucket); ok {
   167  		return &globalMarkersBucket{
   168  			parent: ib.WithExpectedErrs(fn),
   169  		}
   170  	}
   171  
   172  	return b
   173  }
   174  
   175  func (b *globalMarkersBucket) Provider() thanosobjstore.ObjProvider {
   176  	return b.parent.Provider()
   177  }
   178  
   179  // getGlobalMarkPathFromBlockMark returns path to global mark, if name points to a block-local mark file. If name
   180  // doesn't point to a block-local mark file, returns empty string.
   181  func getGlobalMarkPathFromBlockMark(name string) string {
   182  	if blockID, ok := isDeletionMark(name); ok {
   183  		return path.Clean(path.Join(path.Dir(name), "../", DeletionMarkFilepath(blockID)))
   184  	}
   185  
   186  	if blockID, ok := isNoCompactMark(name); ok {
   187  		return path.Clean(path.Join(path.Dir(name), "../", NoCompactMarkFilepath(blockID)))
   188  	}
   189  
   190  	return ""
   191  }
   192  
   193  func isDeletionMark(name string) (ulid.ULID, bool) {
   194  	if path.Base(name) != DeletionMarkFilename {
   195  		return ulid.ULID{}, false
   196  	}
   197  
   198  	// Parse the block ID in the path. If there's no block ID, then it's not the per-block
   199  	// deletion mark.
   200  	return IsBlockDir(path.Dir(name))
   201  }
   202  
   203  func isNoCompactMark(name string) (ulid.ULID, bool) {
   204  	if path.Base(name) != NoCompactMarkFilename {
   205  		return ulid.ULID{}, false
   206  	}
   207  
   208  	// Parse the block ID in the path. If there's no block ID, then it's not the per-block
   209  	// no-compact mark.
   210  	return IsBlockDir(path.Dir(name))
   211  }