github.com/grafana/pyroscope@v1.18.0/pkg/metastore/index_service.go (about) 1 package metastore 2 3 import ( 4 "context" 5 goiter "iter" 6 7 "github.com/go-kit/log" 8 "github.com/go-kit/log/level" 9 "github.com/opentracing/opentracing-go" 10 "github.com/opentracing/opentracing-go/ext" 11 "go.etcd.io/bbolt" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/status" 14 15 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 16 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1/raft_log" 17 "github.com/grafana/pyroscope/pkg/block/metadata" 18 "github.com/grafana/pyroscope/pkg/iter" 19 "github.com/grafana/pyroscope/pkg/metastore/fsm" 20 "github.com/grafana/pyroscope/pkg/metastore/index/cleaner/retention" 21 indexstore "github.com/grafana/pyroscope/pkg/metastore/index/store" 22 "github.com/grafana/pyroscope/pkg/metastore/raftnode" 23 placement "github.com/grafana/pyroscope/pkg/segmentwriter/client/distributor/placement/adaptiveplacement" 24 ) 25 26 type PlacementStats interface { 27 RecordStats(iter.Iterator[placement.Sample]) 28 } 29 30 type IndexBlockFinder interface { 31 GetBlocks(*bbolt.Tx, *metastorev1.BlockList) ([]*metastorev1.BlockMeta, error) 32 } 33 34 type IndexPartitionLister interface { 35 // Partitions provide access to all partitions in the index. 36 // They are iterated in the order of their creation and are 37 // guaranteed to be thread-safe for reads. 38 Partitions(*bbolt.Tx) goiter.Seq[indexstore.Partition] 39 } 40 41 type IndexReader interface { 42 IndexBlockFinder 43 IndexPartitionLister 44 } 45 46 func NewIndexService( 47 logger log.Logger, 48 raft Raft, 49 state State, 50 index IndexReader, 51 stats PlacementStats, 52 ) *IndexService { 53 return &IndexService{ 54 logger: logger, 55 raft: raft, 56 state: state, 57 index: index, 58 stats: stats, 59 } 60 } 61 62 type IndexService struct { 63 metastorev1.IndexServiceServer 64 65 logger log.Logger 66 raft Raft 67 state State 68 index IndexReader 69 stats PlacementStats 70 } 71 72 func (svc *IndexService) AddBlock( 73 ctx context.Context, 74 req *metastorev1.AddBlockRequest, 75 ) (resp *metastorev1.AddBlockResponse, err error) { 76 span, ctx := opentracing.StartSpanFromContext(ctx, "IndexService.AddBlock") 77 defer func() { 78 if err != nil { 79 ext.LogError(span, err) 80 } 81 span.Finish() 82 }() 83 84 if block := req.GetBlock(); block != nil { 85 span.SetTag("block_id", block.GetId()) 86 span.SetTag("shard", block.GetShard()) 87 span.SetTag("compaction_level", block.GetCompactionLevel()) 88 } 89 90 defer func() { 91 if err == nil { 92 svc.stats.RecordStats(statsFromMetadata(req.Block)) 93 } 94 }() 95 96 return svc.addBlockMetadata(ctx, req) 97 } 98 99 func (svc *IndexService) AddRecoveredBlock( 100 ctx context.Context, 101 req *metastorev1.AddBlockRequest, 102 ) (resp *metastorev1.AddBlockResponse, err error) { 103 span, ctx := opentracing.StartSpanFromContext(ctx, "IndexService.AddRecoveredBlock") 104 defer func() { 105 if err != nil { 106 ext.LogError(span, err) 107 } 108 span.Finish() 109 }() 110 111 if block := req.GetBlock(); block != nil { 112 span.SetTag("block_id", block.GetId()) 113 span.SetTag("shard", block.GetShard()) 114 span.SetTag("compaction_level", block.GetCompactionLevel()) 115 } 116 117 return svc.addBlockMetadata(ctx, req) 118 } 119 120 func (svc *IndexService) addBlockMetadata( 121 ctx context.Context, 122 req *metastorev1.AddBlockRequest, 123 ) (resp *metastorev1.AddBlockResponse, err error) { 124 if err = metadata.Sanitize(req.Block); err != nil { 125 level.Warn(svc.logger).Log("invalid metadata", "block", req.Block.Id, "err", err) 126 return nil, status.Error(codes.InvalidArgument, err.Error()) 127 } 128 _, err = svc.raft.Propose( 129 ctx, 130 fsm.RaftLogEntryType(raft_log.RaftCommand_RAFT_COMMAND_ADD_BLOCK_METADATA), 131 &raft_log.AddBlockMetadataRequest{Metadata: req.Block}, 132 ) 133 if err != nil { 134 if !raftnode.IsRaftLeadershipError(err) { 135 level.Error(svc.logger).Log("msg", "failed to add block", "block", req.Block.Id, "err", err) 136 } 137 return nil, err 138 } 139 return new(metastorev1.AddBlockResponse), nil 140 } 141 142 func (svc *IndexService) GetBlockMetadata( 143 ctx context.Context, 144 req *metastorev1.GetBlockMetadataRequest, 145 ) (resp *metastorev1.GetBlockMetadataResponse, err error) { 146 span, ctx := opentracing.StartSpanFromContext(ctx, "IndexService.GetBlockMetadata") 147 defer func() { 148 if err != nil { 149 ext.LogError(span, err) 150 } 151 span.Finish() 152 }() 153 154 if list := req.GetBlocks(); list != nil { 155 span.SetTag("tenant_id", list.GetTenant()) 156 span.SetTag("shard", list.GetShard()) 157 span.SetTag("requested_blocks", len(list.GetBlocks())) 158 } 159 160 read := func(tx *bbolt.Tx, _ raftnode.ReadIndex) { 161 resp, err = svc.getBlockMetadata(tx, req.GetBlocks()) 162 } 163 if readErr := svc.state.ConsistentRead(ctx, read); readErr != nil { 164 return nil, status.Error(codes.Unavailable, readErr.Error()) 165 } 166 return resp, err 167 } 168 169 func (svc *IndexService) getBlockMetadata(tx *bbolt.Tx, list *metastorev1.BlockList) (*metastorev1.GetBlockMetadataResponse, error) { 170 found, err := svc.index.GetBlocks(tx, list) 171 if err != nil { 172 return nil, err 173 } 174 return &metastorev1.GetBlockMetadataResponse{Blocks: found}, nil 175 } 176 177 func statsFromMetadata(md *metastorev1.BlockMeta) iter.Iterator[placement.Sample] { 178 return &sampleIterator{md: md} 179 } 180 181 type sampleIterator struct { 182 md *metastorev1.BlockMeta 183 cur int 184 } 185 186 func (s *sampleIterator) Err() error { return nil } 187 func (s *sampleIterator) Close() error { return nil } 188 189 func (s *sampleIterator) Next() bool { 190 if s.cur >= len(s.md.Datasets) { 191 return false 192 } 193 s.cur++ 194 return true 195 } 196 197 func (s *sampleIterator) At() placement.Sample { 198 ds := s.md.Datasets[s.cur-1] 199 return placement.Sample{ 200 TenantID: s.md.StringTable[ds.Tenant], 201 DatasetName: s.md.StringTable[ds.Name], 202 ShardOwner: s.md.StringTable[s.md.CreatedBy], 203 ShardID: s.md.Shard, 204 Size: ds.Size, 205 } 206 } 207 208 func (svc *IndexService) TruncateIndex(ctx context.Context, rp retention.Policy) (err error) { 209 span, ctx := opentracing.StartSpanFromContext(ctx, "IndexService.TruncateIndex") 210 defer func() { 211 if err != nil { 212 ext.LogError(span, err) 213 } 214 span.Finish() 215 }() 216 217 var req raft_log.TruncateIndexRequest 218 read := func(tx *bbolt.Tx, r raftnode.ReadIndex) { 219 req.Tombstones = rp.CreateTombstones(tx, svc.index.Partitions(tx)) 220 req.Term = r.Term // The leader may change after we read the index. 221 } 222 if readErr := svc.state.ConsistentRead(ctx, read); readErr != nil { 223 return status.Error(codes.Unavailable, readErr.Error()) 224 } 225 226 span.SetTag("tombstone_count", len(req.Tombstones)) 227 span.SetTag("term", req.Term) 228 229 if len(req.Tombstones) == 0 { 230 return nil 231 } 232 if _, err = svc.raft.Propose(ctx, fsm.RaftLogEntryType(raft_log.RaftCommand_RAFT_COMMAND_TRUNCATE_INDEX), &req); err != nil { 233 if !raftnode.IsRaftLeadershipError(err) { 234 level.Error(svc.logger).Log("msg", "failed to truncate index", "err", err) 235 } 236 return err 237 } 238 return nil 239 }