github.com/thanos-io/thanos@v0.32.5/pkg/replicate/scheme.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package replicate 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "path" 12 "sort" 13 14 "github.com/go-kit/log" 15 "github.com/go-kit/log/level" 16 "github.com/oklog/ulid" 17 "github.com/pkg/errors" 18 "github.com/prometheus/client_golang/prometheus" 19 "github.com/prometheus/client_golang/prometheus/promauto" 20 "github.com/prometheus/prometheus/model/labels" 21 22 "github.com/thanos-io/objstore" 23 24 thanosblock "github.com/thanos-io/thanos/pkg/block" 25 "github.com/thanos-io/thanos/pkg/block/metadata" 26 "github.com/thanos-io/thanos/pkg/compact" 27 "github.com/thanos-io/thanos/pkg/runutil" 28 "github.com/thanos-io/thanos/pkg/store/storepb" 29 ) 30 31 // BlockFilter is block filter that filters out compacted and unselected blocks. 32 type BlockFilter struct { 33 logger log.Logger 34 labelSelector labels.Selector 35 labelSelectorStr string 36 resolutionLevels map[compact.ResolutionLevel]struct{} 37 compactionLevels map[int]struct{} 38 blockIDs []ulid.ULID 39 } 40 41 // NewBlockFilter returns block filter. 42 func NewBlockFilter( 43 logger log.Logger, 44 labelSelector labels.Selector, 45 resolutionLevels []compact.ResolutionLevel, 46 compactionLevels []int, 47 blockIDs []ulid.ULID, 48 ) *BlockFilter { 49 allowedResolutions := make(map[compact.ResolutionLevel]struct{}) 50 for _, resolutionLevel := range resolutionLevels { 51 allowedResolutions[resolutionLevel] = struct{}{} 52 } 53 allowedCompactions := make(map[int]struct{}) 54 for _, compactionLevel := range compactionLevels { 55 allowedCompactions[compactionLevel] = struct{}{} 56 } 57 58 return &BlockFilter{ 59 labelSelector: labelSelector, 60 labelSelectorStr: storepb.PromMatchersToString(labelSelector...), 61 logger: logger, 62 resolutionLevels: allowedResolutions, 63 compactionLevels: allowedCompactions, 64 blockIDs: blockIDs, 65 } 66 } 67 68 // Filter return true if block is non-compacted and matches selector. 69 func (bf *BlockFilter) Filter(b *metadata.Meta) bool { 70 if len(b.Thanos.Labels) == 0 { 71 level.Error(bf.logger).Log("msg", "filtering block", "reason", "labels should not be empty") 72 return false 73 } 74 75 // If required block IDs are set, we only match required blocks and ignore others. 76 if len(bf.blockIDs) > 0 { 77 for _, id := range bf.blockIDs { 78 if b.ULID == id { 79 return true 80 } 81 } 82 return false 83 } 84 85 blockLabels := labels.FromMap(b.Thanos.Labels) 86 87 labelMatch := bf.labelSelector.Matches(blockLabels) 88 if !labelMatch { 89 level.Debug(bf.logger).Log("msg", "filtering block", "reason", "labels don't match", "block_labels", blockLabels.String(), "selector", bf.labelSelectorStr) 90 return false 91 } 92 93 gotResolution := compact.ResolutionLevel(b.Thanos.Downsample.Resolution) 94 if _, ok := bf.resolutionLevels[gotResolution]; !ok { 95 level.Info(bf.logger).Log("msg", "filtering block", "reason", "resolution doesn't match allowed resolutions", "got_resolution", gotResolution, "allowed_resolutions", fmt.Sprintf("%v", bf.resolutionLevels)) 96 return false 97 } 98 99 gotCompactionLevel := b.BlockMeta.Compaction.Level 100 if _, ok := bf.compactionLevels[gotCompactionLevel]; !ok { 101 level.Info(bf.logger).Log("msg", "filtering block", "reason", "compaction level doesn't match allowed levels", "got_compaction_level", gotCompactionLevel, "allowed_compaction_levels", fmt.Sprintf("%v", bf.compactionLevels)) 102 return false 103 } 104 105 return true 106 } 107 108 type blockFilterFunc func(b *metadata.Meta) bool 109 110 // TODO: Add filters field. 111 type replicationScheme struct { 112 fromBkt objstore.InstrumentedBucketReader 113 toBkt objstore.Bucket 114 115 blockFilter blockFilterFunc 116 fetcher thanosblock.MetadataFetcher 117 118 logger log.Logger 119 metrics *replicationMetrics 120 121 reg prometheus.Registerer 122 } 123 124 type replicationMetrics struct { 125 blocksAlreadyReplicated prometheus.Counter 126 blocksReplicated prometheus.Counter 127 objectsReplicated prometheus.Counter 128 } 129 130 func newReplicationMetrics(reg prometheus.Registerer) *replicationMetrics { 131 m := &replicationMetrics{ 132 blocksAlreadyReplicated: promauto.With(reg).NewCounter(prometheus.CounterOpts{ 133 Name: "thanos_replicate_blocks_already_replicated_total", 134 Help: "Total number of blocks skipped due to already being replicated.", 135 }), 136 blocksReplicated: promauto.With(reg).NewCounter(prometheus.CounterOpts{ 137 Name: "thanos_replicate_blocks_replicated_total", 138 Help: "Total number of blocks replicated.", 139 }), 140 objectsReplicated: promauto.With(reg).NewCounter(prometheus.CounterOpts{ 141 Name: "thanos_replicate_objects_replicated_total", 142 Help: "Total number of objects replicated.", 143 }), 144 } 145 return m 146 } 147 148 func newReplicationScheme( 149 logger log.Logger, 150 metrics *replicationMetrics, 151 blockFilter blockFilterFunc, 152 fetcher thanosblock.MetadataFetcher, 153 from objstore.InstrumentedBucketReader, 154 to objstore.Bucket, 155 reg prometheus.Registerer, 156 ) *replicationScheme { 157 if logger == nil { 158 logger = log.NewNopLogger() 159 } 160 161 return &replicationScheme{ 162 logger: logger, 163 blockFilter: blockFilter, 164 fetcher: fetcher, 165 fromBkt: from, 166 toBkt: to, 167 metrics: metrics, 168 reg: reg, 169 } 170 } 171 172 func (rs *replicationScheme) execute(ctx context.Context) error { 173 availableBlocks := []*metadata.Meta{} 174 175 metas, partials, err := rs.fetcher.Fetch(ctx) 176 if err != nil { 177 return err 178 } 179 180 for id := range partials { 181 level.Info(rs.logger).Log("msg", "block meta not uploaded yet. Skipping.", "block_uuid", id.String()) 182 } 183 184 for id, meta := range metas { 185 if rs.blockFilter(meta) { 186 level.Info(rs.logger).Log("msg", "adding block to be replicated", "block_uuid", id.String()) 187 availableBlocks = append(availableBlocks, meta) 188 } 189 } 190 191 // In order to prevent races in compactions by the target environment, we 192 // need to replicate oldest start timestamp first. 193 sort.Slice(availableBlocks, func(i, j int) bool { 194 return availableBlocks[i].BlockMeta.MinTime < availableBlocks[j].BlockMeta.MinTime 195 }) 196 197 for _, b := range availableBlocks { 198 if err := rs.ensureBlockIsReplicated(ctx, b.BlockMeta.ULID); err != nil { 199 return errors.Wrapf(err, "ensure block %v is replicated", b.BlockMeta.ULID.String()) 200 } 201 } 202 203 return nil 204 } 205 206 // ensureBlockIsReplicated ensures that a block present in the origin bucket is 207 // present in the target bucket. 208 func (rs *replicationScheme) ensureBlockIsReplicated(ctx context.Context, id ulid.ULID) error { 209 blockID := id.String() 210 chunksDir := path.Join(blockID, thanosblock.ChunksDirname) 211 indexFile := path.Join(blockID, thanosblock.IndexFilename) 212 metaFile := path.Join(blockID, thanosblock.MetaFilename) 213 214 level.Debug(rs.logger).Log("msg", "ensuring block is replicated", "block_uuid", blockID) 215 216 originMetaFile, err := rs.fromBkt.ReaderWithExpectedErrs(rs.fromBkt.IsObjNotFoundErr).Get(ctx, metaFile) 217 if err != nil { 218 return errors.Wrap(err, "get meta file from origin bucket") 219 } 220 221 defer runutil.CloseWithLogOnErr(rs.logger, originMetaFile, "close original meta file") 222 223 targetMetaFile, err := rs.toBkt.Get(ctx, metaFile) 224 225 if targetMetaFile != nil { 226 defer runutil.CloseWithLogOnErr(rs.logger, targetMetaFile, "close target meta file") 227 } 228 229 if err != nil && !rs.toBkt.IsObjNotFoundErr(err) && err != io.EOF { 230 return errors.Wrap(err, "get meta file from target bucket") 231 } 232 233 // TODO(bwplotka): Allow injecting custom labels as shipper does. 234 originMetaFileContent, err := io.ReadAll(originMetaFile) 235 if err != nil { 236 return errors.Wrap(err, "read origin meta file") 237 } 238 239 if targetMetaFile != nil && !rs.toBkt.IsObjNotFoundErr(err) { 240 targetMetaFileContent, err := io.ReadAll(targetMetaFile) 241 if err != nil { 242 return errors.Wrap(err, "read target meta file") 243 } 244 245 if bytes.Equal(originMetaFileContent, targetMetaFileContent) { 246 // If the origin meta file content and target meta file content is 247 // equal, we know we have already successfully replicated 248 // previously. 249 level.Debug(rs.logger).Log("msg", "skipping block as already replicated", "block_uuid", blockID) 250 rs.metrics.blocksAlreadyReplicated.Inc() 251 252 return nil 253 } 254 } 255 256 if err := rs.fromBkt.Iter(ctx, chunksDir, func(objectName string) error { 257 err := rs.ensureObjectReplicated(ctx, objectName) 258 if err != nil { 259 return errors.Wrapf(err, "replicate object %v", objectName) 260 } 261 262 return nil 263 }); err != nil { 264 return err 265 } 266 267 if err := rs.ensureObjectReplicated(ctx, indexFile); err != nil { 268 return errors.Wrap(err, "replicate index file") 269 } 270 271 level.Debug(rs.logger).Log("msg", "replicating meta file", "object", metaFile) 272 273 if err := rs.toBkt.Upload(ctx, metaFile, bytes.NewBuffer(originMetaFileContent)); err != nil { 274 return errors.Wrap(err, "upload meta file") 275 } 276 277 rs.metrics.blocksReplicated.Inc() 278 279 return nil 280 } 281 282 // ensureBlockIsReplicated ensures that an object present in the origin bucket 283 // is present in the target bucket. 284 func (rs *replicationScheme) ensureObjectReplicated(ctx context.Context, objectName string) error { 285 level.Debug(rs.logger).Log("msg", "ensuring object is replicated", "object", objectName) 286 287 exists, err := rs.toBkt.Exists(ctx, objectName) 288 if err != nil { 289 return errors.Wrapf(err, "check if %v exists in target bucket", objectName) 290 } 291 292 // skip if already exists. 293 if exists { 294 level.Debug(rs.logger).Log("msg", "skipping object as already replicated", "object", objectName) 295 return nil 296 } 297 298 level.Debug(rs.logger).Log("msg", "object not present in target bucket, replicating", "object", objectName) 299 300 r, err := rs.fromBkt.Get(ctx, objectName) 301 if err != nil { 302 return errors.Wrapf(err, "get %v from origin bucket", objectName) 303 } 304 305 defer r.Close() 306 307 if err = rs.toBkt.Upload(ctx, objectName, r); err != nil { 308 return errors.Wrapf(err, "upload %v to target bucket", objectName) 309 } 310 311 level.Info(rs.logger).Log("msg", "object replicated", "object", objectName) 312 rs.metrics.objectsReplicated.Inc() 313 314 return nil 315 }