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

     1  package uploads
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  
    14  	"github.com/grafana/loki/pkg/chunkenc"
    15  	"github.com/grafana/loki/pkg/storage/stores/indexshipper/index"
    16  	"github.com/grafana/loki/pkg/storage/stores/indexshipper/storage"
    17  	util_log "github.com/grafana/loki/pkg/util/log"
    18  )
    19  
    20  type IndexSet interface {
    21  	Add(idx index.Index)
    22  	Upload(ctx context.Context) error
    23  	Cleanup(indexRetainPeriod time.Duration) error
    24  	ForEach(callback index.ForEachIndexCallback) error
    25  	Close()
    26  }
    27  
    28  // indexSet is a collection of multiple files created for a same table by various ingesters.
    29  // All the public methods are concurrency safe and take care of mutexes to avoid any data race.
    30  type indexSet struct {
    31  	storageIndexSet   storage.IndexSet
    32  	tableName, userID string
    33  	logger            log.Logger
    34  
    35  	index    map[string]index.Index
    36  	indexMtx sync.RWMutex
    37  
    38  	indexUploadTime    map[string]time.Time
    39  	indexUploadTimeMtx sync.RWMutex
    40  }
    41  
    42  func NewIndexSet(tableName, userID string, baseIndexSet storage.IndexSet, logger log.Logger) (IndexSet, error) {
    43  	if baseIndexSet.IsUserBasedIndexSet() && userID == "" {
    44  		return nil, fmt.Errorf("userID must not be empty")
    45  	} else if !baseIndexSet.IsUserBasedIndexSet() && userID != "" {
    46  		return nil, fmt.Errorf("userID must be empty")
    47  	}
    48  
    49  	is := indexSet{
    50  		storageIndexSet: baseIndexSet,
    51  		tableName:       tableName,
    52  		index:           map[string]index.Index{},
    53  		indexUploadTime: map[string]time.Time{},
    54  		userID:          userID,
    55  		logger:          logger,
    56  	}
    57  
    58  	return &is, nil
    59  }
    60  
    61  func (t *indexSet) Add(idx index.Index) {
    62  	t.indexMtx.Lock()
    63  	defer t.indexMtx.Unlock()
    64  
    65  	t.index[idx.Name()] = idx
    66  }
    67  
    68  func (t *indexSet) ForEach(callback index.ForEachIndexCallback) error {
    69  	t.indexMtx.RLock()
    70  	defer t.indexMtx.RUnlock()
    71  
    72  	for _, idx := range t.index {
    73  		if err := callback(t.userID == "", idx); err != nil {
    74  			return err
    75  		}
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // Upload uploads all the dbs which are never uploaded or have been modified since the last batch was uploaded.
    82  func (t *indexSet) Upload(ctx context.Context) error {
    83  	t.indexMtx.RLock()
    84  	defer t.indexMtx.RUnlock()
    85  
    86  	level.Info(util_log.Logger).Log("msg", fmt.Sprintf("uploading table %s", t.tableName))
    87  
    88  	for name, idx := range t.index {
    89  		// if the file is uploaded already do not upload it again.
    90  		t.indexUploadTimeMtx.RLock()
    91  		_, ok := t.indexUploadTime[name]
    92  		t.indexUploadTimeMtx.RUnlock()
    93  
    94  		if ok {
    95  			continue
    96  		}
    97  
    98  		if err := t.uploadIndex(ctx, idx); err != nil {
    99  			return err
   100  		}
   101  
   102  		t.indexUploadTimeMtx.Lock()
   103  		t.indexUploadTime[name] = time.Now()
   104  		t.indexUploadTimeMtx.Unlock()
   105  	}
   106  
   107  	level.Info(util_log.Logger).Log("msg", fmt.Sprintf("finished uploading table %s", t.tableName))
   108  
   109  	return nil
   110  }
   111  
   112  // Close Closes references to all the indexes.
   113  func (t *indexSet) Close() {
   114  	t.indexMtx.Lock()
   115  	defer t.indexMtx.Unlock()
   116  
   117  	for name, idx := range t.index {
   118  		if err := idx.Close(); err != nil {
   119  			level.Error(t.logger).Log("msg", fmt.Sprintf("failed to close index %s", name), "err", err)
   120  		}
   121  	}
   122  
   123  	t.index = map[string]index.Index{}
   124  }
   125  
   126  func (t *indexSet) uploadIndex(ctx context.Context, idx index.Index) error {
   127  	fileName := idx.Name()
   128  	level.Debug(t.logger).Log("msg", fmt.Sprintf("uploading index %s", fileName))
   129  
   130  	idxPath := idx.Path()
   131  
   132  	filePath := fmt.Sprintf("%s%s", idxPath, tempFileSuffix)
   133  	f, err := os.Create(filePath)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	defer func() {
   139  		if err := f.Close(); err != nil {
   140  			level.Error(util_log.Logger).Log("msg", "failed to close temp file", "path", filePath, "err", err)
   141  		}
   142  
   143  		if err := os.Remove(filePath); err != nil {
   144  			level.Error(util_log.Logger).Log("msg", "failed to remove temp file", "path", filePath, "err", err)
   145  		}
   146  	}()
   147  
   148  	compressedWriter := chunkenc.Gzip.GetWriter(f)
   149  	defer chunkenc.Gzip.PutWriter(compressedWriter)
   150  
   151  	idxReader, err := idx.Reader()
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	_, err = idxReader.Seek(0, 0)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	_, err = io.Copy(compressedWriter, idxReader)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	err = compressedWriter.Close()
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	// flush the file to disk and seek the file to the beginning.
   172  	if err := f.Sync(); err != nil {
   173  		return err
   174  	}
   175  
   176  	if _, err := f.Seek(0, 0); err != nil {
   177  		return err
   178  	}
   179  
   180  	return t.storageIndexSet.PutFile(ctx, t.tableName, t.userID, t.buildFileName(fileName), f)
   181  }
   182  
   183  // Cleanup removes indexes which are already uploaded and have been retained for period longer than indexRetainPeriod since they were uploaded.
   184  func (t *indexSet) Cleanup(indexRetainPeriod time.Duration) error {
   185  	level.Info(util_log.Logger).Log("msg", fmt.Sprintf("cleaning up unwanted indexes from table %s", t.tableName))
   186  
   187  	var filesToCleanup []string
   188  	cutoffTime := time.Now().Add(-indexRetainPeriod)
   189  
   190  	t.indexMtx.RLock()
   191  
   192  	for name := range t.index {
   193  		t.indexUploadTimeMtx.RLock()
   194  		indexUploadTime, ok := t.indexUploadTime[name]
   195  		t.indexUploadTimeMtx.RUnlock()
   196  
   197  		if ok && indexUploadTime.Before(cutoffTime) {
   198  			filesToCleanup = append(filesToCleanup, name)
   199  		}
   200  	}
   201  
   202  	t.indexMtx.RUnlock()
   203  
   204  	for i := range filesToCleanup {
   205  		level.Debug(util_log.Logger).Log("msg", fmt.Sprintf("dropping uploaded index %s from table %s", filesToCleanup[i], t.tableName))
   206  
   207  		if err := t.removeIndex(filesToCleanup[i]); err != nil {
   208  			return err
   209  		}
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // removeIndex closes the index and removes the file locally.
   216  func (t *indexSet) removeIndex(name string) error {
   217  	t.indexMtx.Lock()
   218  	defer t.indexMtx.Unlock()
   219  
   220  	idx, ok := t.index[name]
   221  	if !ok {
   222  		return nil
   223  	}
   224  
   225  	err := idx.Close()
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	delete(t.index, name)
   231  
   232  	t.indexUploadTimeMtx.Lock()
   233  	delete(t.indexUploadTime, name)
   234  	t.indexUploadTimeMtx.Unlock()
   235  
   236  	return os.Remove(idx.Path())
   237  }
   238  
   239  func (t *indexSet) buildFileName(indexName string) string {
   240  	return fmt.Sprintf("%s.gz", indexName)
   241  }