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

     1  package ingester
     2  
     3  import (
     4  	"hash"
     5  	"math"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/cespare/xxhash"
    10  	"github.com/prometheus/common/model"
    11  	"github.com/prometheus/prometheus/pkg/labels"
    12  	"go.uber.org/atomic"
    13  
    14  	"github.com/cortexproject/cortex/pkg/util"
    15  )
    16  
    17  const (
    18  	numActiveSeriesStripes = 512
    19  )
    20  
    21  // ActiveSeries is keeping track of recently active series for a single tenant.
    22  type ActiveSeries struct {
    23  	stripes [numActiveSeriesStripes]activeSeriesStripe
    24  }
    25  
    26  // activeSeriesStripe holds a subset of the series timestamps for a single tenant.
    27  type activeSeriesStripe struct {
    28  	// Unix nanoseconds. Only used by purge. Zero = unknown.
    29  	// Updated in purge and when old timestamp is used when updating series (in this case, oldestEntryTs is updated
    30  	// without holding the lock -- hence the atomic).
    31  	oldestEntryTs atomic.Int64
    32  
    33  	mu     sync.RWMutex
    34  	refs   map[uint64][]activeSeriesEntry
    35  	active int // Number of active entries in this stripe. Only decreased during purge or clear.
    36  }
    37  
    38  // activeSeriesEntry holds a timestamp for single series.
    39  type activeSeriesEntry struct {
    40  	lbs   labels.Labels
    41  	nanos *atomic.Int64 // Unix timestamp in nanoseconds. Needs to be a pointer because we don't store pointers to entries in the stripe.
    42  }
    43  
    44  func NewActiveSeries() *ActiveSeries {
    45  	c := &ActiveSeries{}
    46  
    47  	// Stripes are pre-allocated so that we only read on them and no lock is required.
    48  	for i := 0; i < numActiveSeriesStripes; i++ {
    49  		c.stripes[i].refs = map[uint64][]activeSeriesEntry{}
    50  	}
    51  
    52  	return c
    53  }
    54  
    55  // Updates series timestamp to 'now'. Function is called to make a copy of labels if entry doesn't exist yet.
    56  func (c *ActiveSeries) UpdateSeries(series labels.Labels, now time.Time, labelsCopy func(labels.Labels) labels.Labels) {
    57  	fp := fingerprint(series)
    58  	stripeID := fp % numActiveSeriesStripes
    59  
    60  	c.stripes[stripeID].updateSeriesTimestamp(now, series, fp, labelsCopy)
    61  }
    62  
    63  var sep = []byte{model.SeparatorByte}
    64  
    65  var hashPool = sync.Pool{New: func() interface{} { return xxhash.New() }}
    66  
    67  func fingerprint(series labels.Labels) uint64 {
    68  	sum := hashPool.Get().(hash.Hash64)
    69  	defer hashPool.Put(sum)
    70  
    71  	sum.Reset()
    72  	for _, label := range series {
    73  		_, _ = sum.Write(util.YoloBuf(label.Name))
    74  		_, _ = sum.Write(sep)
    75  		_, _ = sum.Write(util.YoloBuf(label.Value))
    76  		_, _ = sum.Write(sep)
    77  	}
    78  
    79  	return sum.Sum64()
    80  }
    81  
    82  // Purge removes expired entries from the cache. This function should be called
    83  // periodically to avoid memory leaks.
    84  func (c *ActiveSeries) Purge(keepUntil time.Time) {
    85  	for s := 0; s < numActiveSeriesStripes; s++ {
    86  		c.stripes[s].purge(keepUntil)
    87  	}
    88  }
    89  
    90  //nolint // Linter reports that this method is unused, but it is.
    91  func (c *ActiveSeries) clear() {
    92  	for s := 0; s < numActiveSeriesStripes; s++ {
    93  		c.stripes[s].clear()
    94  	}
    95  }
    96  
    97  func (c *ActiveSeries) Active() int {
    98  	total := 0
    99  	for s := 0; s < numActiveSeriesStripes; s++ {
   100  		total += c.stripes[s].getActive()
   101  	}
   102  	return total
   103  }
   104  
   105  func (s *activeSeriesStripe) updateSeriesTimestamp(now time.Time, series labels.Labels, fingerprint uint64, labelsCopy func(labels.Labels) labels.Labels) {
   106  	nowNanos := now.UnixNano()
   107  
   108  	e := s.findEntryForSeries(fingerprint, series)
   109  	entryTimeSet := false
   110  	if e == nil {
   111  		e, entryTimeSet = s.findOrCreateEntryForSeries(fingerprint, series, nowNanos, labelsCopy)
   112  	}
   113  
   114  	if !entryTimeSet {
   115  		if prev := e.Load(); nowNanos > prev {
   116  			entryTimeSet = e.CAS(prev, nowNanos)
   117  		}
   118  	}
   119  
   120  	if entryTimeSet {
   121  		for prevOldest := s.oldestEntryTs.Load(); nowNanos < prevOldest; {
   122  			// If recent purge already removed entries older than "oldest entry timestamp", setting this to 0 will make
   123  			// sure that next purge doesn't take the shortcut route.
   124  			if s.oldestEntryTs.CAS(prevOldest, 0) {
   125  				break
   126  			}
   127  		}
   128  	}
   129  }
   130  
   131  func (s *activeSeriesStripe) findEntryForSeries(fingerprint uint64, series labels.Labels) *atomic.Int64 {
   132  	s.mu.RLock()
   133  	defer s.mu.RUnlock()
   134  
   135  	// Check if already exists within the entries.
   136  	for ix, entry := range s.refs[fingerprint] {
   137  		if labels.Equal(entry.lbs, series) {
   138  			return s.refs[fingerprint][ix].nanos
   139  		}
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  func (s *activeSeriesStripe) findOrCreateEntryForSeries(fingerprint uint64, series labels.Labels, nowNanos int64, labelsCopy func(labels.Labels) labels.Labels) (*atomic.Int64, bool) {
   146  	s.mu.Lock()
   147  	defer s.mu.Unlock()
   148  
   149  	// Check if already exists within the entries.
   150  	for ix, entry := range s.refs[fingerprint] {
   151  		if labels.Equal(entry.lbs, series) {
   152  			return s.refs[fingerprint][ix].nanos, false
   153  		}
   154  	}
   155  
   156  	s.active++
   157  	e := activeSeriesEntry{
   158  		lbs:   labelsCopy(series),
   159  		nanos: atomic.NewInt64(nowNanos),
   160  	}
   161  
   162  	s.refs[fingerprint] = append(s.refs[fingerprint], e)
   163  
   164  	return e.nanos, true
   165  }
   166  
   167  //nolint // Linter reports that this method is unused, but it is.
   168  func (s *activeSeriesStripe) clear() {
   169  	s.mu.Lock()
   170  	defer s.mu.Unlock()
   171  
   172  	s.oldestEntryTs.Store(0)
   173  	s.refs = map[uint64][]activeSeriesEntry{}
   174  	s.active = 0
   175  }
   176  
   177  func (s *activeSeriesStripe) purge(keepUntil time.Time) {
   178  	keepUntilNanos := keepUntil.UnixNano()
   179  	if oldest := s.oldestEntryTs.Load(); oldest > 0 && keepUntilNanos <= oldest {
   180  		// Nothing to do.
   181  		return
   182  	}
   183  
   184  	s.mu.Lock()
   185  	defer s.mu.Unlock()
   186  
   187  	active := 0
   188  
   189  	oldest := int64(math.MaxInt64)
   190  	for fp, entries := range s.refs {
   191  		// Since we do expect very few fingerprint collisions, we
   192  		// have an optimized implementation for the common case.
   193  		if len(entries) == 1 {
   194  			ts := entries[0].nanos.Load()
   195  			if ts < keepUntilNanos {
   196  				delete(s.refs, fp)
   197  				continue
   198  			}
   199  
   200  			active++
   201  			if ts < oldest {
   202  				oldest = ts
   203  			}
   204  			continue
   205  		}
   206  
   207  		// We have more entries, which means there's a collision,
   208  		// so we have to iterate over the entries.
   209  		for i := 0; i < len(entries); {
   210  			ts := entries[i].nanos.Load()
   211  			if ts < keepUntilNanos {
   212  				entries = append(entries[:i], entries[i+1:]...)
   213  			} else {
   214  				if ts < oldest {
   215  					oldest = ts
   216  				}
   217  
   218  				i++
   219  			}
   220  		}
   221  
   222  		// Either update or delete the entries in the map
   223  		if cnt := len(entries); cnt == 0 {
   224  			delete(s.refs, fp)
   225  		} else {
   226  			active += cnt
   227  			s.refs[fp] = entries
   228  		}
   229  	}
   230  
   231  	if oldest == math.MaxInt64 {
   232  		s.oldestEntryTs.Store(0)
   233  	} else {
   234  		s.oldestEntryTs.Store(oldest)
   235  	}
   236  	s.active = active
   237  }
   238  
   239  func (s *activeSeriesStripe) getActive() int {
   240  	s.mu.RLock()
   241  	defer s.mu.RUnlock()
   242  
   243  	return s.active
   244  }