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 }