github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/tsdb/manager.go (about)

     1  package tsdb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strconv"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/go-kit/log/level"
    15  	"github.com/pkg/errors"
    16  	"github.com/prometheus/common/model"
    17  	"github.com/prometheus/prometheus/model/labels"
    18  
    19  	"github.com/grafana/loki/pkg/storage/config"
    20  	"github.com/grafana/loki/pkg/storage/stores/indexshipper"
    21  	"github.com/grafana/loki/pkg/storage/stores/tsdb/index"
    22  )
    23  
    24  // nolint:revive
    25  // TSDBManager wraps the index shipper and writes/manages
    26  // TSDB files on  disk
    27  type TSDBManager interface {
    28  	Start() error
    29  	// Builds a new TSDB file from a set of WALs
    30  	BuildFromWALs(time.Time, []WALIdentifier) error
    31  }
    32  
    33  /*
    34  tsdbManager is used for managing active index and is responsible for:
    35   * Turning WALs into optimized multi-tenant TSDBs when requested
    36   * Serving reads from these TSDBs
    37   * Shipping them to remote storage
    38   * Keeping them available for querying
    39   * Removing old TSDBs which are no longer needed
    40  */
    41  type tsdbManager struct {
    42  	nodeName    string // node name
    43  	log         log.Logger
    44  	dir         string
    45  	metrics     *Metrics
    46  	tableRanges config.TableRanges
    47  
    48  	sync.RWMutex
    49  
    50  	shipper indexshipper.IndexShipper
    51  }
    52  
    53  func NewTSDBManager(
    54  	nodeName,
    55  	dir string,
    56  	shipper indexshipper.IndexShipper,
    57  	tableRanges config.TableRanges,
    58  	logger log.Logger,
    59  	metrics *Metrics,
    60  ) TSDBManager {
    61  	return &tsdbManager{
    62  		nodeName:    nodeName,
    63  		log:         log.With(logger, "component", "tsdb-manager"),
    64  		dir:         dir,
    65  		metrics:     metrics,
    66  		tableRanges: tableRanges,
    67  		shipper:     shipper,
    68  	}
    69  }
    70  
    71  func (m *tsdbManager) Start() (err error) {
    72  	var (
    73  		buckets, indices, loadingErrors int
    74  	)
    75  
    76  	defer func() {
    77  		level.Info(m.log).Log(
    78  			"msg", "loaded leftover local indices",
    79  			"err", err,
    80  			"successful", err == nil,
    81  			"buckets", buckets,
    82  			"indices", indices,
    83  			"failures", loadingErrors,
    84  		)
    85  	}()
    86  
    87  	// regexp for finding the trailing index bucket number at the end of table name
    88  	extractBucketNumberRegex, err := regexp.Compile(`[0-9]+$`)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	// load list of multitenant tsdbs
    94  	mulitenantDir := managerMultitenantDir(m.dir)
    95  	files, err := ioutil.ReadDir(mulitenantDir)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	for _, f := range files {
   101  		if !f.IsDir() {
   102  			continue
   103  		}
   104  
   105  		bucket := f.Name()
   106  		if !extractBucketNumberRegex.MatchString(f.Name()) {
   107  			level.Warn(m.log).Log(
   108  				"msg", "directory name does not match expected bucket name pattern",
   109  				"name", bucket,
   110  				"err", err.Error(),
   111  			)
   112  			continue
   113  		}
   114  		buckets++
   115  
   116  		tsdbs, err := ioutil.ReadDir(filepath.Join(mulitenantDir, bucket))
   117  		if err != nil {
   118  			level.Warn(m.log).Log(
   119  				"msg", "failed to open period bucket dir",
   120  				"bucket", bucket,
   121  				"err", err.Error(),
   122  			)
   123  			continue
   124  		}
   125  
   126  		for _, db := range tsdbs {
   127  			id, ok := parseMultitenantTSDBPath(db.Name())
   128  			if !ok {
   129  				continue
   130  			}
   131  			indices++
   132  
   133  			prefixed := newPrefixedIdentifier(id, filepath.Join(mulitenantDir, bucket), "")
   134  			loaded, err := NewShippableTSDBFile(
   135  				prefixed,
   136  				false,
   137  			)
   138  
   139  			if err != nil {
   140  				level.Warn(m.log).Log(
   141  					"msg", "",
   142  					"tsdbPath", prefixed.Path(),
   143  					"err", err.Error(),
   144  				)
   145  				loadingErrors++
   146  			}
   147  
   148  			if err := m.shipper.AddIndex(bucket, "", loaded); err != nil {
   149  				loadingErrors++
   150  				return err
   151  			}
   152  		}
   153  
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (m *tsdbManager) BuildFromWALs(t time.Time, ids []WALIdentifier) (err error) {
   160  	level.Debug(m.log).Log("msg", "building WALs", "n", len(ids), "ts", t)
   161  	// get relevant wals
   162  	// iterate them, build tsdb in scratch dir
   163  	defer func() {
   164  		m.metrics.tsdbCreationsTotal.Inc()
   165  		if err != nil {
   166  			m.metrics.tsdbCreationFailures.Inc()
   167  		}
   168  	}()
   169  
   170  	level.Debug(m.log).Log("msg", "recovering tenant heads")
   171  	for _, id := range ids {
   172  		tmp := newTenantHeads(t, defaultHeadManagerStripeSize, m.metrics, m.log)
   173  		if err = recoverHead(m.dir, tmp, []WALIdentifier{id}); err != nil {
   174  			return errors.Wrap(err, "building TSDB from WALs")
   175  		}
   176  
   177  		periods := make(map[string]*Builder)
   178  
   179  		if err := tmp.forAll(func(user string, ls labels.Labels, chks index.ChunkMetas) error {
   180  
   181  			// chunks may overlap index period bounds, in which case they're written to multiple
   182  			pds := make(map[string]index.ChunkMetas)
   183  			for _, chk := range chks {
   184  				idxBuckets, err := indexBuckets(chk.From(), chk.Through(), m.tableRanges)
   185  				if err != nil {
   186  					return err
   187  				}
   188  
   189  				for _, bucket := range idxBuckets {
   190  					pds[bucket] = append(pds[bucket], chk)
   191  				}
   192  			}
   193  
   194  			// Embed the tenant label into TSDB
   195  			lb := labels.NewBuilder(ls)
   196  			lb.Set(TenantLabel, user)
   197  			withTenant := lb.Labels()
   198  
   199  			// Add the chunks to all relevant builders
   200  			for pd, matchingChks := range pds {
   201  				b, ok := periods[pd]
   202  				if !ok {
   203  					b = NewBuilder()
   204  					periods[pd] = b
   205  				}
   206  
   207  				b.AddSeries(
   208  					withTenant,
   209  					// use the fingerprint without the added tenant label
   210  					// so queries route to the chunks which actually exist.
   211  					model.Fingerprint(ls.Hash()),
   212  					matchingChks,
   213  				)
   214  			}
   215  
   216  			return nil
   217  		}); err != nil {
   218  			level.Error(m.log).Log("err", err.Error(), "msg", "building TSDB from WALs")
   219  			return err
   220  		}
   221  
   222  		for p, b := range periods {
   223  
   224  			dstDir := filepath.Join(managerMultitenantDir(m.dir), fmt.Sprint(p))
   225  			dst := newPrefixedIdentifier(
   226  				MultitenantTSDBIdentifier{
   227  					nodeName: m.nodeName,
   228  					ts:       id.ts,
   229  				},
   230  				dstDir,
   231  				"",
   232  			)
   233  
   234  			level.Debug(m.log).Log("msg", "building tsdb for period", "pd", p, "dst", dst.Path())
   235  			// build+move tsdb to multitenant dir
   236  			start := time.Now()
   237  			_, err = b.Build(
   238  				context.Background(),
   239  				managerScratchDir(m.dir),
   240  				func(from, through model.Time, checksum uint32) Identifier {
   241  					return dst
   242  				},
   243  			)
   244  			if err != nil {
   245  				return err
   246  			}
   247  
   248  			level.Debug(m.log).Log("msg", "finished building tsdb for period", "pd", p, "dst", dst.Path(), "duration", time.Since(start))
   249  
   250  			loaded, err := NewShippableTSDBFile(dst, false)
   251  			if err != nil {
   252  				return err
   253  			}
   254  
   255  			if err := m.shipper.AddIndex(p, "", loaded); err != nil {
   256  				return err
   257  			}
   258  		}
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  func indexBuckets(from, through model.Time, tableRanges config.TableRanges) (res []string, err error) {
   265  	start := from.Time().UnixNano() / int64(config.ObjectStorageIndexRequiredPeriod)
   266  	end := through.Time().UnixNano() / int64(config.ObjectStorageIndexRequiredPeriod)
   267  	for cur := start; cur <= end; cur++ {
   268  		cfg := tableRanges.ConfigForTableNumber(cur)
   269  		if cfg == nil {
   270  			return nil, fmt.Errorf("could not find config for table number %d", cur)
   271  		}
   272  		res = append(res, cfg.IndexTables.Prefix+strconv.Itoa(int(cur)))
   273  	}
   274  	return
   275  }