github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/storage/instance/instance.go (about)

     1  // This directory was copied and adapted from https://github.com/grafana/agent/tree/main/pkg/metrics.
     2  // We cannot vendor the agent in since the agent vendors loki in, which would cause a cyclic dependency.
     3  // NOTE: many changes have been made to the original code for our use-case.
     4  package instance
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"math"
    13  	"path/filepath"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/go-kit/log"
    19  	"github.com/go-kit/log/level"
    20  	"github.com/oklog/run"
    21  	"github.com/prometheus/client_golang/prometheus"
    22  	"github.com/prometheus/prometheus/config"
    23  	"github.com/prometheus/prometheus/model/timestamp"
    24  	"github.com/prometheus/prometheus/scrape"
    25  	"github.com/prometheus/prometheus/storage"
    26  	"github.com/prometheus/prometheus/storage/remote"
    27  	"gopkg.in/yaml.v2"
    28  
    29  	"github.com/grafana/loki/pkg/ruler/storage/util"
    30  	"github.com/grafana/loki/pkg/ruler/storage/wal"
    31  	"github.com/grafana/loki/pkg/util/build"
    32  )
    33  
    34  func init() {
    35  	remote.UserAgent = fmt.Sprintf("LokiRulerWAL/%s", build.Version)
    36  }
    37  
    38  var (
    39  	remoteWriteMetricName = "queue_highest_sent_timestamp_seconds"
    40  )
    41  
    42  // Default configuration values
    43  var (
    44  	DefaultConfig = Config{
    45  		Dir:                 "ruler-wal",
    46  		TruncateFrequency:   60 * time.Minute,
    47  		MinAge:              5 * time.Minute,
    48  		MaxAge:              4 * time.Hour,
    49  		RemoteFlushDeadline: 1 * time.Minute,
    50  	}
    51  )
    52  
    53  // Config is a specific agent that runs within the overall Prometheus
    54  // agent. It has its own set of scrape_configs and remote_write rules.
    55  type Config struct {
    56  	Tenant      string
    57  	Name        string
    58  	RemoteWrite []*config.RemoteWriteConfig
    59  
    60  	Dir string `yaml:"dir"`
    61  
    62  	// How frequently the WAL should be truncated.
    63  	TruncateFrequency time.Duration `yaml:"truncate_frequency,omitempty"`
    64  
    65  	// Minimum and maximum time series should exist in the WAL for.
    66  	MinAge time.Duration `yaml:"min_age,omitempty"`
    67  	MaxAge time.Duration `yaml:"max_age,omitempty"`
    68  
    69  	RemoteFlushDeadline time.Duration `yaml:"remote_flush_deadline,omitempty"`
    70  }
    71  
    72  // UnmarshalYAML implements yaml.Unmarshaler.
    73  func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
    74  	*c = DefaultConfig
    75  
    76  	type plain Config
    77  	return unmarshal((*plain)(c))
    78  }
    79  
    80  // MarshalYAML implements yaml.Marshaler.
    81  func (c Config) MarshalYAML() (interface{}, error) {
    82  	// We want users to be able to marshal instance.Configs directly without
    83  	// *needing* to call instance.MarshalConfig, so we call it internally
    84  	// here and return a map.
    85  	bb, err := MarshalConfig(&c, false)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	// Use a yaml.MapSlice rather than a map[string]interface{} so
    91  	// order of keys is retained compared to just calling MarshalConfig.
    92  	var m yaml.MapSlice
    93  	if err := yaml.Unmarshal(bb, &m); err != nil {
    94  		return nil, err
    95  	}
    96  	return m, nil
    97  }
    98  
    99  // ApplyDefaults applies default configurations to the configuration to all
   100  // values that have not been changed to their non-zero value. ApplyDefaults
   101  // also validates the config.
   102  //
   103  // The value for global will saved.
   104  func (c *Config) ApplyDefaults() error {
   105  	switch {
   106  	case c.Name == "":
   107  		return errors.New("missing instance name")
   108  	case c.TruncateFrequency <= 0:
   109  		return errors.New("wal_truncate_frequency must be greater than 0s")
   110  	case c.RemoteFlushDeadline <= 0:
   111  		return errors.New("remote_flush_deadline must be greater than 0s")
   112  	case c.MinAge > c.MaxAge:
   113  		return errors.New("min_wal_time must be less than max_wal_time")
   114  	}
   115  
   116  	for _, cfg := range c.RemoteWrite {
   117  		if cfg == nil {
   118  			return fmt.Errorf("empty or null remote write config section")
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  // Clone makes a deep copy of the config along with global settings.
   125  func (c *Config) Clone() (Config, error) {
   126  	bb, err := MarshalConfig(c, false)
   127  	if err != nil {
   128  		return Config{}, err
   129  	}
   130  	cp, err := UnmarshalConfig(bytes.NewReader(bb))
   131  	if err != nil {
   132  		return Config{}, err
   133  	}
   134  
   135  	// Some tests will trip up on this; the marshal/unmarshal cycle might set
   136  	// an empty slice to nil. Set it back to an empty slice if we detect this
   137  	// happening.
   138  	if cp.RemoteWrite == nil && c.RemoteWrite != nil {
   139  		cp.RemoteWrite = []*config.RemoteWriteConfig{}
   140  	}
   141  
   142  	return *cp, nil
   143  }
   144  
   145  func (c *Config) RegisterFlags(f *flag.FlagSet) {
   146  	f.StringVar(&c.Dir, "ruler.wal.dir", DefaultConfig.Dir, "Directory to store the WAL and/or recover from WAL.")
   147  	f.DurationVar(&c.TruncateFrequency, "ruler.wal.truncate-frequency", DefaultConfig.TruncateFrequency, "How often to run the WAL truncation.")
   148  	f.DurationVar(&c.MinAge, "ruler.wal.min-age", DefaultConfig.MinAge, "Minimum age that samples must exist in the WAL before being truncated.")
   149  	f.DurationVar(&c.MaxAge, "ruler.wal.max-age", DefaultConfig.MaxAge, "Maximum age that samples must exist in the WAL before being truncated.")
   150  }
   151  
   152  type walStorageFactory func(reg prometheus.Registerer) (walStorage, error)
   153  
   154  // Instance is an individual metrics collector and remote_writer.
   155  type Instance struct {
   156  	initialized bool
   157  
   158  	// All fields in the following block may be accessed and modified by
   159  	// concurrently running goroutines.
   160  	//
   161  	// Note that all Prometheus components listed here may be nil at any
   162  	// given time; methods reading them should take care to do nil checks.
   163  	mut         sync.Mutex
   164  	cfg         Config
   165  	wal         walStorage
   166  	remoteStore *remote.Storage
   167  	storage     storage.Storage
   168  
   169  	logger log.Logger
   170  
   171  	reg    prometheus.Registerer
   172  	newWal walStorageFactory
   173  
   174  	vc     *MetricValueCollector
   175  	tenant string
   176  }
   177  
   178  // New creates a new Instance with a directory for storing the WAL. The instance
   179  // will not start until Run is called on the instance.
   180  func New(reg prometheus.Registerer, cfg Config, metrics *wal.Metrics, logger log.Logger) (*Instance, error) {
   181  	logger = log.With(logger, "instance", cfg.Name)
   182  
   183  	instWALDir := filepath.Join(cfg.Dir, cfg.Tenant)
   184  
   185  	newWal := func(reg prometheus.Registerer) (walStorage, error) {
   186  		return wal.NewStorage(logger, metrics, reg, instWALDir)
   187  	}
   188  
   189  	return newInstance(cfg, reg, logger, newWal, cfg.Tenant)
   190  }
   191  
   192  func newInstance(cfg Config, reg prometheus.Registerer, logger log.Logger, newWal walStorageFactory, tenant string) (*Instance, error) {
   193  	vc := NewMetricValueCollector(prometheus.DefaultGatherer, remoteWriteMetricName)
   194  
   195  	i := &Instance{
   196  		cfg:    cfg,
   197  		logger: logger,
   198  		vc:     vc,
   199  
   200  		reg:    reg,
   201  		newWal: newWal,
   202  
   203  		tenant: tenant,
   204  	}
   205  
   206  	return i, nil
   207  }
   208  
   209  func (i *Instance) Storage() storage.Storage {
   210  	i.mut.Lock()
   211  	defer i.mut.Unlock()
   212  
   213  	return i.storage
   214  }
   215  
   216  // Run starts the instance, initializing Prometheus components, and will
   217  // continue to run until an error happens during execution or the provided
   218  // context is cancelled.
   219  //
   220  // Run may be re-called after exiting, as components will be reinitialized each
   221  // time Run is called.
   222  func (i *Instance) Run(ctx context.Context) error {
   223  	// i.cfg may change at any point in the middle of this method but not in a way
   224  	// that affects any of the code below; rather than grabbing a mutex every time
   225  	// we want to read the config, we'll simplify the access and just grab a copy
   226  	// now.
   227  	i.mut.Lock()
   228  	cfg := i.cfg
   229  	i.mut.Unlock()
   230  
   231  	level.Debug(i.logger).Log("msg", "initializing instance", "name", cfg.Name)
   232  
   233  	// trackingReg wraps the register for the instance to make sure that if Run
   234  	// exits, any metrics Prometheus registers are removed and can be
   235  	// re-registered if Run is called again.
   236  	trackingReg := util.WrapWithUnregisterer(i.reg)
   237  	defer trackingReg.UnregisterAll()
   238  
   239  	if err := i.initialize(ctx, trackingReg, &cfg); err != nil {
   240  		level.Error(i.logger).Log("msg", "failed to initialize instance", "err", err)
   241  		return fmt.Errorf("failed to initialize instance: %w", err)
   242  	}
   243  
   244  	// The actors defined here are defined in the order we want them to shut down.
   245  	// Primarily, we want to ensure that the following shutdown order is
   246  	// maintained:
   247  	//		1. The scrape manager stops
   248  	//    2. WAL storage is closed
   249  	//    3. Remote write storage is closed
   250  	// This is done to allow the instance to write stale markers for all active
   251  	// series.
   252  	rg := runGroupWithContext(ctx)
   253  
   254  	{
   255  		// Truncation loop
   256  		ctx, contextCancel := context.WithCancel(context.Background())
   257  		defer contextCancel()
   258  		rg.Add(
   259  			func() error {
   260  				i.truncateLoop(ctx, i.wal, &cfg)
   261  				level.Info(i.logger).Log("msg", "truncation loop stopped")
   262  				return nil
   263  			},
   264  			func(err error) {
   265  				level.Info(i.logger).Log("msg", "stopping truncation loop...")
   266  				contextCancel()
   267  			},
   268  		)
   269  	}
   270  
   271  	level.Debug(i.logger).Log("msg", "running instance", "name", cfg.Name)
   272  	err := rg.Run()
   273  	if err != nil {
   274  		level.Error(i.logger).Log("msg", "agent instance stopped with error", "err", err)
   275  	}
   276  	return err
   277  }
   278  
   279  type noopScrapeManager struct{}
   280  
   281  func (n noopScrapeManager) Get() (*scrape.Manager, error) {
   282  	return nil, nil
   283  }
   284  
   285  // initialize sets up the various Prometheus components with their initial
   286  // settings. initialize will be called each time the Instance is run. Prometheus
   287  // components cannot be reused after they are stopped so we need to recreate them
   288  // each run.
   289  func (i *Instance) initialize(_ context.Context, reg prometheus.Registerer, cfg *Config) error {
   290  	i.mut.Lock()
   291  	defer i.mut.Unlock()
   292  
   293  	// explicitly set this in case this function is called multiple times
   294  	i.initialized = false
   295  
   296  	var err error
   297  
   298  	i.wal, err = i.newWal(reg)
   299  	if err != nil {
   300  		return fmt.Errorf("error creating WAL: %w", err)
   301  	}
   302  
   303  	// Setup the remote storage
   304  	remoteLogger := log.With(i.logger, "component", "remote")
   305  	i.remoteStore = remote.NewStorage(remoteLogger, reg, i.wal.StartTime, i.wal.Directory(), cfg.RemoteFlushDeadline, noopScrapeManager{})
   306  	err = i.remoteStore.ApplyConfig(&config.Config{
   307  		RemoteWriteConfigs: cfg.RemoteWrite,
   308  	})
   309  	if err != nil {
   310  		return fmt.Errorf("failed applying config to remote storage: %w", err)
   311  	}
   312  
   313  	i.storage = storage.NewFanout(i.logger, i.wal, i.remoteStore)
   314  	i.initialized = true
   315  
   316  	return nil
   317  }
   318  
   319  // Update accepts a new Config for the Instance and will dynamically update any
   320  // running Prometheus components with the new values from Config. Update will
   321  // return an ErrInvalidUpdate if the Update could not be applied.
   322  func (i *Instance) Update(c Config) (err error) {
   323  	i.mut.Lock()
   324  	defer i.mut.Unlock()
   325  
   326  	// It's only (currently) valid to update scrape_configs and remote_write, so
   327  	// if any other field has changed here, return the error.
   328  	switch {
   329  	// This first case should never happen in practice but it's included here for
   330  	// completions sake.
   331  	case i.cfg.Name != c.Name:
   332  		err = errImmutableField{Field: "name"}
   333  	case i.cfg.TruncateFrequency != c.TruncateFrequency:
   334  		err = errImmutableField{Field: "wal_truncate_frequency"}
   335  	case i.cfg.RemoteFlushDeadline != c.RemoteFlushDeadline:
   336  		err = errImmutableField{Field: "remote_flush_deadline"}
   337  	}
   338  	if err != nil {
   339  		return ErrInvalidUpdate{Inner: err}
   340  	}
   341  
   342  	// Check to see if the components exist yet.
   343  	if i.remoteStore == nil {
   344  		return ErrInvalidUpdate{
   345  			Inner: fmt.Errorf("cannot dynamically update because instance is not running"),
   346  		}
   347  	}
   348  
   349  	// NOTE(rfratto): Prometheus applies configs in a specific order to ensure
   350  	// flow from service discovery down to the WAL continues working properly.
   351  	//
   352  	// Keep the following order below:
   353  	//
   354  	// 1. Local config
   355  	// 2. Remote Store
   356  	// 3. Scrape Manager
   357  	// 4. Discovery Manager
   358  
   359  	originalConfig := i.cfg
   360  	defer func() {
   361  		if err != nil {
   362  			i.cfg = originalConfig
   363  		}
   364  	}()
   365  	i.cfg = c
   366  
   367  	err = i.remoteStore.ApplyConfig(&config.Config{
   368  		RemoteWriteConfigs: c.RemoteWrite,
   369  	})
   370  	if err != nil {
   371  		return fmt.Errorf("error applying new remote_write configs: %w", err)
   372  	}
   373  
   374  	return nil
   375  }
   376  
   377  // Ready indicates if the instance is ready for processing.
   378  func (i *Instance) Ready() bool {
   379  	i.mut.Lock()
   380  	defer i.mut.Unlock()
   381  
   382  	return i.initialized
   383  }
   384  
   385  // StorageDirectory returns the directory where this Instance is writing series
   386  // and samples to for the WAL.
   387  func (i *Instance) StorageDirectory() string {
   388  	return i.wal.Directory()
   389  }
   390  
   391  // Appender returns a storage.Appender from the instance's WAL
   392  func (i *Instance) Appender(ctx context.Context) storage.Appender {
   393  	return i.wal.Appender(ctx)
   394  }
   395  
   396  // Stop stops the WAL
   397  func (i *Instance) Stop() error {
   398  	level.Info(i.logger).Log("msg", "stopping WAL instance", "user", i.Tenant())
   399  
   400  	// close WAL first to prevent any further appends
   401  	if err := i.wal.Close(); err != nil {
   402  		level.Error(i.logger).Log("msg", "error stopping WAL instance", "user", i.Tenant(), "err", err)
   403  		return err
   404  	}
   405  
   406  	if err := i.remoteStore.Close(); err != nil {
   407  		level.Error(i.logger).Log("msg", "error stopping remote storage instance", "user", i.Tenant(), "err", err)
   408  		return err
   409  	}
   410  
   411  	return nil
   412  }
   413  
   414  // Tenant returns the tenant name of the instance
   415  func (i *Instance) Tenant() string {
   416  	return i.tenant
   417  }
   418  
   419  func (i *Instance) truncateLoop(ctx context.Context, wal walStorage, cfg *Config) {
   420  	// Track the last timestamp we truncated for to prevent segments from getting
   421  	// deleted until at least some new data has been sent.
   422  	var lastTs int64 = math.MinInt64
   423  
   424  	for {
   425  		select {
   426  		case <-ctx.Done():
   427  			return
   428  		case <-time.After(cfg.TruncateFrequency):
   429  			// The timestamp ts is used to determine which series are not receiving
   430  			// samples and may be deleted from the WAL. Their most recent append
   431  			// timestamp is compared to ts, and if that timestamp is older then ts,
   432  			// they are considered inactive and may be deleted.
   433  			//
   434  			// Subtracting a duration from ts will delay when it will be considered
   435  			// inactive and scheduled for deletion.
   436  			ts := i.getRemoteWriteTimestamp() - i.cfg.MinAge.Milliseconds()
   437  			if ts < 0 {
   438  				ts = 0
   439  			}
   440  
   441  			// Network issues can prevent the result of getRemoteWriteTimestamp from
   442  			// changing. We don't want data in the WAL to grow forever, so we set a cap
   443  			// on the maximum age data can be. If our ts is older than this cutoff point,
   444  			// we'll shift it forward to start deleting very stale data.
   445  			if maxTS := timestamp.FromTime(time.Now().Add(-i.cfg.MaxAge)); ts < maxTS {
   446  				ts = maxTS
   447  			}
   448  
   449  			if ts == lastTs {
   450  				level.Debug(i.logger).Log("msg", "not truncating the WAL, remote_write timestamp is unchanged", "ts", ts)
   451  				continue
   452  			}
   453  			lastTs = ts
   454  
   455  			level.Debug(i.logger).Log("msg", "truncating the WAL", "ts", ts)
   456  			err := wal.Truncate(ts)
   457  			if err != nil {
   458  				// The only issue here is larger disk usage and a greater replay time,
   459  				// so we'll only log this as a warning.
   460  				level.Warn(i.logger).Log("msg", "could not truncate WAL", "err", err)
   461  			}
   462  		}
   463  	}
   464  }
   465  
   466  // getRemoteWriteTimestamp looks up the last successful remote write timestamp.
   467  // This is passed to wal.Storage for its truncation. If no remote write sections
   468  // are configured, getRemoteWriteTimestamp returns the current time.
   469  func (i *Instance) getRemoteWriteTimestamp() int64 {
   470  	i.mut.Lock()
   471  	defer i.mut.Unlock()
   472  
   473  	if len(i.cfg.RemoteWrite) == 0 {
   474  		return timestamp.FromTime(time.Now())
   475  	}
   476  
   477  	lbls := make([]string, len(i.cfg.RemoteWrite))
   478  	for idx := 0; idx < len(lbls); idx++ {
   479  		lbls[idx] = i.cfg.RemoteWrite[idx].Name
   480  	}
   481  
   482  	vals, err := i.vc.GetValues("remote_name", lbls...)
   483  	if err != nil {
   484  		level.Error(i.logger).Log("msg", "could not get remote write timestamps", "err", err)
   485  		return 0
   486  	}
   487  	if len(vals) == 0 {
   488  		return 0
   489  	}
   490  
   491  	// We use the lowest value from the metric since we don't want to delete any
   492  	// segments from the WAL until they've been written by all of the remote_write
   493  	// configurations.
   494  	ts := int64(math.MaxInt64)
   495  	for _, val := range vals {
   496  		ival := int64(val)
   497  		if ival < ts {
   498  			ts = ival
   499  		}
   500  	}
   501  
   502  	// Convert to the millisecond precision which is used by the WAL
   503  	return ts * 1000
   504  }
   505  
   506  // walStorage is an interface satisfied by wal.Storage, and created for testing.
   507  type walStorage interface {
   508  	// walStorage implements Queryable/ChunkQueryable for compatibility, but is unused.
   509  	storage.Queryable
   510  	storage.ChunkQueryable
   511  
   512  	Directory() string
   513  
   514  	StartTime() (int64, error)
   515  	WriteStalenessMarkers(remoteTsFunc func() int64) error
   516  	Appender(context.Context) storage.Appender
   517  	Truncate(mint int64) error
   518  
   519  	Close() error
   520  }
   521  
   522  // MetricValueCollector wraps around a Gatherer and provides utilities for
   523  // pulling metric values from a given metric name and label matchers.
   524  //
   525  // This is used by the agent instances to find the most recent timestamp
   526  // successfully remote_written to for purposes of safely truncating the WAL.
   527  //
   528  // MetricValueCollector is only intended for use with Gauges and Counters.
   529  type MetricValueCollector struct {
   530  	g     prometheus.Gatherer
   531  	match string
   532  }
   533  
   534  // NewMetricValueCollector creates a new MetricValueCollector.
   535  func NewMetricValueCollector(g prometheus.Gatherer, match string) *MetricValueCollector {
   536  	return &MetricValueCollector{
   537  		g:     g,
   538  		match: match,
   539  	}
   540  }
   541  
   542  // GetValues looks through all the tracked metrics and returns all values
   543  // for metrics that match some key value pair.
   544  func (vc *MetricValueCollector) GetValues(label string, labelValues ...string) ([]float64, error) {
   545  	vals := []float64{}
   546  
   547  	families, err := vc.g.Gather()
   548  	if err != nil {
   549  		return nil, err
   550  	}
   551  
   552  	for _, family := range families {
   553  		if !strings.Contains(family.GetName(), vc.match) {
   554  			continue
   555  		}
   556  
   557  		for _, m := range family.GetMetric() {
   558  			matches := false
   559  			for _, l := range m.GetLabel() {
   560  				if l.GetName() != label {
   561  					continue
   562  				}
   563  
   564  				v := l.GetValue()
   565  				for _, match := range labelValues {
   566  					if match == v {
   567  						matches = true
   568  						break
   569  					}
   570  				}
   571  				break
   572  			}
   573  			if !matches {
   574  				continue
   575  			}
   576  
   577  			var value float64
   578  			if m.Gauge != nil {
   579  				value = m.Gauge.GetValue()
   580  			} else if m.Counter != nil {
   581  				value = m.Counter.GetValue()
   582  			} else if m.Untyped != nil {
   583  				value = m.Untyped.GetValue()
   584  			} else {
   585  				return nil, errors.New("tracking unexpected metric type")
   586  			}
   587  
   588  			vals = append(vals, value)
   589  		}
   590  	}
   591  
   592  	return vals, nil
   593  }
   594  
   595  type runGroupContext struct {
   596  	cancel context.CancelFunc
   597  
   598  	g *run.Group
   599  }
   600  
   601  // runGroupWithContext creates a new run.Group that will be stopped if the
   602  // context gets canceled in addition to the normal behavior of stopping
   603  // when any of the actors stop.
   604  func runGroupWithContext(ctx context.Context) *runGroupContext {
   605  	ctx, cancel := context.WithCancel(ctx)
   606  
   607  	var g run.Group
   608  	g.Add(func() error {
   609  		<-ctx.Done()
   610  		return nil
   611  	}, func(_ error) {
   612  		cancel()
   613  	})
   614  
   615  	return &runGroupContext{cancel: cancel, g: &g}
   616  }
   617  
   618  func (rg *runGroupContext) Add(execute func() error, interrupt func(error)) {
   619  	rg.g.Add(execute, interrupt)
   620  }
   621  
   622  func (rg *runGroupContext) Run() error   { return rg.g.Run() }
   623  func (rg *runGroupContext) Stop(_ error) { rg.cancel() }