github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/chunk/composite_store.go (about)

     1  package chunk
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sort"
     7  	"time"
     8  
     9  	"github.com/prometheus/common/model"
    10  	"github.com/prometheus/prometheus/pkg/labels"
    11  
    12  	"github.com/cortexproject/cortex/pkg/chunk/cache"
    13  )
    14  
    15  // StoreLimits helps get Limits specific to Queries for Stores
    16  type StoreLimits interface {
    17  	MaxChunksPerQueryFromStore(userID string) int
    18  	MaxQueryLength(userID string) time.Duration
    19  }
    20  
    21  type CacheGenNumLoader interface {
    22  	GetStoreCacheGenNumber(tenantIDs []string) string
    23  }
    24  
    25  // Store for chunks.
    26  type Store interface {
    27  	Put(ctx context.Context, chunks []Chunk) error
    28  	PutOne(ctx context.Context, from, through model.Time, chunk Chunk) error
    29  	Get(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([]Chunk, error)
    30  	// GetChunkRefs returns the un-loaded chunks and the fetchers to be used to load them. You can load each slice of chunks ([]Chunk),
    31  	// using the corresponding Fetcher (fetchers[i].FetchChunks(ctx, chunks[i], ...)
    32  	GetChunkRefs(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([][]Chunk, []*Fetcher, error)
    33  	LabelValuesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string, labelName string) ([]string, error)
    34  	LabelNamesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string) ([]string, error)
    35  	GetChunkFetcher(tm model.Time) *Fetcher
    36  
    37  	// DeleteChunk deletes a chunks index entry and then deletes the actual chunk from chunk storage.
    38  	// It takes care of chunks which are deleting partially by creating and inserting a new chunk first and then deleting the original chunk
    39  	DeleteChunk(ctx context.Context, from, through model.Time, userID, chunkID string, metric labels.Labels, partiallyDeletedInterval *model.Interval) error
    40  	// DeleteSeriesIDs is only relevant for SeriesStore.
    41  	DeleteSeriesIDs(ctx context.Context, from, through model.Time, userID string, metric labels.Labels) error
    42  	Stop()
    43  }
    44  
    45  // CompositeStore is a Store which delegates to various stores depending
    46  // on when they were activated.
    47  type CompositeStore struct {
    48  	compositeStore
    49  }
    50  
    51  type compositeStore struct {
    52  	cacheGenNumLoader CacheGenNumLoader
    53  	stores            []compositeStoreEntry
    54  }
    55  
    56  type compositeStoreEntry struct {
    57  	start model.Time
    58  	Store
    59  }
    60  
    61  // NewCompositeStore creates a new Store which delegates to different stores depending
    62  // on time.
    63  func NewCompositeStore(cacheGenNumLoader CacheGenNumLoader) CompositeStore {
    64  	return CompositeStore{compositeStore{cacheGenNumLoader: cacheGenNumLoader}}
    65  }
    66  
    67  // AddPeriod adds the configuration for a period of time to the CompositeStore
    68  func (c *CompositeStore) AddPeriod(storeCfg StoreConfig, cfg PeriodConfig, index IndexClient, chunks Client, limits StoreLimits, chunksCache, writeDedupeCache cache.Cache) error {
    69  	schema, err := cfg.CreateSchema()
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	return c.addSchema(storeCfg, schema, cfg.From.Time, index, chunks, limits, chunksCache, writeDedupeCache)
    75  }
    76  
    77  func (c *CompositeStore) addSchema(storeCfg StoreConfig, schema BaseSchema, start model.Time, index IndexClient, chunks Client, limits StoreLimits, chunksCache, writeDedupeCache cache.Cache) error {
    78  	var (
    79  		err   error
    80  		store Store
    81  	)
    82  
    83  	switch s := schema.(type) {
    84  	case SeriesStoreSchema:
    85  		store, err = newSeriesStore(storeCfg, s, index, chunks, limits, chunksCache, writeDedupeCache)
    86  	case StoreSchema:
    87  		store, err = newStore(storeCfg, s, index, chunks, limits, chunksCache)
    88  	default:
    89  		err = errors.New("invalid schema type")
    90  	}
    91  	if err != nil {
    92  		return err
    93  	}
    94  	c.stores = append(c.stores, compositeStoreEntry{start: start, Store: store})
    95  	return nil
    96  }
    97  
    98  func (c compositeStore) Put(ctx context.Context, chunks []Chunk) error {
    99  	for _, chunk := range chunks {
   100  		err := c.forStores(ctx, chunk.UserID, chunk.From, chunk.Through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   101  			return store.PutOne(innerCtx, from, through, chunk)
   102  		})
   103  		if err != nil {
   104  			return err
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  func (c compositeStore) PutOne(ctx context.Context, from, through model.Time, chunk Chunk) error {
   111  	return c.forStores(ctx, chunk.UserID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   112  		return store.PutOne(innerCtx, from, through, chunk)
   113  	})
   114  }
   115  
   116  func (c compositeStore) Get(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([]Chunk, error) {
   117  	var results []Chunk
   118  	err := c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   119  		chunks, err := store.Get(innerCtx, userID, from, through, matchers...)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		results = append(results, chunks...)
   124  		return nil
   125  	})
   126  	return results, err
   127  }
   128  
   129  // LabelValuesForMetricName retrieves all label values for a single label name and metric name.
   130  func (c compositeStore) LabelValuesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string, labelName string) ([]string, error) {
   131  	var result UniqueStrings
   132  	err := c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   133  		labelValues, err := store.LabelValuesForMetricName(innerCtx, userID, from, through, metricName, labelName)
   134  		if err != nil {
   135  			return err
   136  		}
   137  		result.Add(labelValues...)
   138  		return nil
   139  	})
   140  	return result.Strings(), err
   141  }
   142  
   143  // LabelNamesForMetricName retrieves all label names for a metric name.
   144  func (c compositeStore) LabelNamesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string) ([]string, error) {
   145  	var result UniqueStrings
   146  	err := c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   147  		labelNames, err := store.LabelNamesForMetricName(innerCtx, userID, from, through, metricName)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		result.Add(labelNames...)
   152  		return nil
   153  	})
   154  	return result.Strings(), err
   155  }
   156  
   157  func (c compositeStore) GetChunkRefs(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([][]Chunk, []*Fetcher, error) {
   158  	chunkIDs := [][]Chunk{}
   159  	fetchers := []*Fetcher{}
   160  	err := c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   161  		ids, fetcher, err := store.GetChunkRefs(innerCtx, userID, from, through, matchers...)
   162  		if err != nil {
   163  			return err
   164  		}
   165  
   166  		// Skip it if there are no chunks.
   167  		if len(ids) == 0 {
   168  			return nil
   169  		}
   170  
   171  		chunkIDs = append(chunkIDs, ids...)
   172  		fetchers = append(fetchers, fetcher...)
   173  		return nil
   174  	})
   175  	return chunkIDs, fetchers, err
   176  }
   177  
   178  func (c compositeStore) GetChunkFetcher(tm model.Time) *Fetcher {
   179  	// find the schema with the lowest start _after_ tm
   180  	j := sort.Search(len(c.stores), func(j int) bool {
   181  		return c.stores[j].start > tm
   182  	})
   183  
   184  	// reduce it by 1 because we want a schema with start <= tm
   185  	j--
   186  
   187  	if 0 <= j && j < len(c.stores) {
   188  		return c.stores[j].GetChunkFetcher(tm)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // DeleteSeriesIDs deletes series IDs from index in series store
   195  func (c CompositeStore) DeleteSeriesIDs(ctx context.Context, from, through model.Time, userID string, metric labels.Labels) error {
   196  	return c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   197  		return store.DeleteSeriesIDs(innerCtx, from, through, userID, metric)
   198  	})
   199  }
   200  
   201  // DeleteChunk deletes a chunks index entry and then deletes the actual chunk from chunk storage.
   202  // It takes care of chunks which are deleting partially by creating and inserting a new chunk first and then deleting the original chunk
   203  func (c CompositeStore) DeleteChunk(ctx context.Context, from, through model.Time, userID, chunkID string, metric labels.Labels, partiallyDeletedInterval *model.Interval) error {
   204  	return c.forStores(ctx, userID, from, through, func(innerCtx context.Context, from, through model.Time, store Store) error {
   205  		return store.DeleteChunk(innerCtx, from, through, userID, chunkID, metric, partiallyDeletedInterval)
   206  	})
   207  }
   208  
   209  func (c compositeStore) Stop() {
   210  	for _, store := range c.stores {
   211  		store.Stop()
   212  	}
   213  }
   214  
   215  func (c compositeStore) forStores(ctx context.Context, userID string, from, through model.Time, callback func(innerCtx context.Context, from, through model.Time, store Store) error) error {
   216  	if len(c.stores) == 0 {
   217  		return nil
   218  	}
   219  
   220  	ctx = c.injectCacheGen(ctx, []string{userID})
   221  
   222  	// first, find the schema with the highest start _before or at_ from
   223  	i := sort.Search(len(c.stores), func(i int) bool {
   224  		return c.stores[i].start > from
   225  	})
   226  	if i > 0 {
   227  		i--
   228  	} else {
   229  		// This could happen if we get passed a sample from before 1970.
   230  		i = 0
   231  		from = c.stores[0].start
   232  	}
   233  
   234  	// next, find the schema with the lowest start _after_ through
   235  	j := sort.Search(len(c.stores), func(j int) bool {
   236  		return c.stores[j].start > through
   237  	})
   238  
   239  	min := func(a, b model.Time) model.Time {
   240  		if a < b {
   241  			return a
   242  		}
   243  		return b
   244  	}
   245  
   246  	start := from
   247  	for ; i < j; i++ {
   248  		nextSchemaStarts := model.Latest
   249  		if i+1 < len(c.stores) {
   250  			nextSchemaStarts = c.stores[i+1].start
   251  		}
   252  
   253  		end := min(through, nextSchemaStarts-1)
   254  		err := callback(ctx, start, end, c.stores[i].Store)
   255  		if err != nil {
   256  			return err
   257  		}
   258  
   259  		start = nextSchemaStarts
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func (c compositeStore) injectCacheGen(ctx context.Context, tenantIDs []string) context.Context {
   266  	if c.cacheGenNumLoader == nil {
   267  		return ctx
   268  	}
   269  
   270  	return cache.InjectCacheGenNumber(ctx, c.cacheGenNumLoader.GetStoreCacheGenNumber(tenantIDs))
   271  }