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

     1  package index
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/go-kit/log/level"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"go.etcd.io/bbolt"
    17  
    18  	"github.com/grafana/loki/pkg/storage/chunk/client/local"
    19  	chunk_util "github.com/grafana/loki/pkg/storage/chunk/client/util"
    20  	"github.com/grafana/loki/pkg/storage/stores/indexshipper"
    21  	shipper_index "github.com/grafana/loki/pkg/storage/stores/indexshipper/index"
    22  	"github.com/grafana/loki/pkg/storage/stores/series/index"
    23  	util_log "github.com/grafana/loki/pkg/util/log"
    24  )
    25  
    26  type Config struct {
    27  	Uploader             string
    28  	IndexDir             string
    29  	DBRetainPeriod       time.Duration
    30  	MakePerTenantBuckets bool
    31  }
    32  
    33  type TableManager struct {
    34  	cfg          Config
    35  	indexShipper Shipper
    36  
    37  	metrics   *metrics
    38  	tables    map[string]*Table
    39  	tablesMtx sync.RWMutex
    40  
    41  	ctx    context.Context
    42  	cancel context.CancelFunc
    43  	wg     sync.WaitGroup
    44  }
    45  
    46  type Shipper interface {
    47  	AddIndex(tableName, userID string, index shipper_index.Index) error
    48  	ForEach(ctx context.Context, tableName, userID string, callback shipper_index.ForEachIndexCallback) error
    49  }
    50  
    51  func NewTableManager(cfg Config, indexShipper Shipper, registerer prometheus.Registerer) (*TableManager, error) {
    52  	err := chunk_util.EnsureDirectory(cfg.IndexDir)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	ctx, cancel := context.WithCancel(context.Background())
    58  	tm := TableManager{
    59  		cfg:          cfg,
    60  		indexShipper: indexShipper,
    61  		metrics:      newMetrics(registerer),
    62  		ctx:          ctx,
    63  		cancel:       cancel,
    64  	}
    65  
    66  	tables, err := tm.loadTables()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	tm.tables = tables
    72  	go tm.loop()
    73  	return &tm, nil
    74  }
    75  
    76  func (tm *TableManager) loop() {
    77  	tm.wg.Add(1)
    78  	defer tm.wg.Done()
    79  
    80  	tm.handoverIndexesToShipper(false)
    81  
    82  	syncTicker := time.NewTicker(indexshipper.UploadInterval)
    83  	defer syncTicker.Stop()
    84  
    85  	for {
    86  		select {
    87  		case <-syncTicker.C:
    88  			tm.handoverIndexesToShipper(false)
    89  		case <-tm.ctx.Done():
    90  			return
    91  		}
    92  	}
    93  }
    94  
    95  func (tm *TableManager) Stop() {
    96  	level.Info(util_log.Logger).Log("msg", "stopping table manager")
    97  
    98  	tm.cancel()
    99  	tm.wg.Wait()
   100  
   101  	tm.handoverIndexesToShipper(true)
   102  }
   103  
   104  func (tm *TableManager) ForEach(ctx context.Context, tableName string, callback func(boltdb *bbolt.DB) error) error {
   105  	table, ok := tm.getTable(tableName)
   106  	if !ok {
   107  		return nil
   108  	}
   109  
   110  	return table.ForEach(ctx, callback)
   111  }
   112  
   113  func (tm *TableManager) getTable(tableName string) (*Table, bool) {
   114  	tm.tablesMtx.RLock()
   115  	defer tm.tablesMtx.RUnlock()
   116  	table, ok := tm.tables[tableName]
   117  	return table, ok
   118  }
   119  
   120  func (tm *TableManager) BatchWrite(ctx context.Context, batch index.WriteBatch) error {
   121  	boltWriteBatch, ok := batch.(*local.BoltWriteBatch)
   122  	if !ok {
   123  		return errors.New("invalid write batch")
   124  	}
   125  
   126  	for tableName, tableWrites := range boltWriteBatch.Writes {
   127  		table, err := tm.getOrCreateTable(tableName)
   128  		if err != nil {
   129  			return err
   130  		}
   131  
   132  		err = table.Write(ctx, tableWrites)
   133  		if err != nil {
   134  			return err
   135  		}
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (tm *TableManager) getOrCreateTable(tableName string) (*Table, error) {
   142  	table, ok := tm.getTable(tableName)
   143  
   144  	if !ok {
   145  		tm.tablesMtx.Lock()
   146  		defer tm.tablesMtx.Unlock()
   147  
   148  		table, ok = tm.tables[tableName]
   149  		if !ok {
   150  			var err error
   151  			table, err = NewTable(filepath.Join(tm.cfg.IndexDir, tableName), tm.cfg.Uploader, tm.indexShipper, tm.cfg.MakePerTenantBuckets)
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  
   156  			tm.tables[tableName] = table
   157  		}
   158  	}
   159  
   160  	return table, nil
   161  }
   162  
   163  func (tm *TableManager) handoverIndexesToShipper(force bool) {
   164  	tm.tablesMtx.RLock()
   165  	defer tm.tablesMtx.RUnlock()
   166  
   167  	level.Info(util_log.Logger).Log("msg", "handing over indexes to shipper")
   168  
   169  	for _, table := range tm.tables {
   170  		err := table.HandoverIndexesToShipper(force)
   171  		if err != nil {
   172  			// continue handing over other tables while skipping cleanup for a failed one.
   173  			level.Error(util_log.Logger).Log("msg", "failed to handover index", "table", table.name, "err", err)
   174  			continue
   175  		}
   176  
   177  		err = table.Snapshot()
   178  		if err != nil {
   179  			// we do not want to stop handing over of index due to failures in snapshotting them so logging just the error here.
   180  			level.Error(util_log.Logger).Log("msg", "failed to snapshot table for reads", "table", table.name, "err", err)
   181  		}
   182  	}
   183  }
   184  
   185  func (tm *TableManager) loadTables() (map[string]*Table, error) {
   186  	localTables := make(map[string]*Table)
   187  	filesInfo, err := ioutil.ReadDir(tm.cfg.IndexDir)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	// regex matching table name patters, i.e prefix+period_number
   193  	re, err := regexp.Compile(`.+[0-9]+$`)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	for _, fileInfo := range filesInfo {
   199  		if !re.MatchString(fileInfo.Name()) {
   200  			continue
   201  		}
   202  
   203  		// since we are moving to keeping files for same table in a folder, if current element is a file we need to move it inside a directory with the same name
   204  		// i.e file index_123 would be moved to path index_123/index_123.
   205  		if !fileInfo.IsDir() {
   206  			level.Info(util_log.Logger).Log("msg", fmt.Sprintf("found a legacy file %s, moving it to folder with same name", fileInfo.Name()))
   207  			filePath := filepath.Join(tm.cfg.IndexDir, fileInfo.Name())
   208  
   209  			// create a folder with .temp suffix since we can't create a directory with same name as file.
   210  			tempDirPath := filePath + ".temp"
   211  			if err := chunk_util.EnsureDirectory(tempDirPath); err != nil {
   212  				return nil, err
   213  			}
   214  
   215  			// move the file to temp dir.
   216  			if err := os.Rename(filePath, filepath.Join(tempDirPath, fileInfo.Name())); err != nil {
   217  				return nil, err
   218  			}
   219  
   220  			// rename the directory to name of the file
   221  			if err := os.Rename(tempDirPath, filePath); err != nil {
   222  				return nil, err
   223  			}
   224  		}
   225  
   226  		level.Info(util_log.Logger).Log("msg", fmt.Sprintf("loading table %s", fileInfo.Name()))
   227  		table, err := LoadTable(filepath.Join(tm.cfg.IndexDir, fileInfo.Name()), tm.cfg.Uploader, tm.indexShipper, tm.cfg.MakePerTenantBuckets, tm.metrics)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  
   232  		if table == nil {
   233  			// if table is nil it means it has no files in it so remove the folder for that table.
   234  			err := os.Remove(filepath.Join(tm.cfg.IndexDir, fileInfo.Name()))
   235  			if err != nil {
   236  				level.Error(util_log.Logger).Log("msg", "failed to remove empty table folder", "table", fileInfo.Name(), "err", err)
   237  			}
   238  			continue
   239  		}
   240  
   241  		// handover indexes to shipper since we won't modify them anymore.
   242  		err = table.HandoverIndexesToShipper(true)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		// Queries are only done against table snapshots so it's important we snapshot as soon as the table is loaded.
   248  		err = table.Snapshot()
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  
   253  		localTables[fileInfo.Name()] = table
   254  	}
   255  
   256  	return localTables, nil
   257  }