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  }