github.com/thanos-io/thanos@v0.32.5/pkg/block/writer.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package block
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/go-kit/log"
    13  	"github.com/go-kit/log/level"
    14  	"github.com/pkg/errors"
    15  	"github.com/prometheus/prometheus/model/labels"
    16  	"github.com/prometheus/prometheus/storage"
    17  	"github.com/prometheus/prometheus/tsdb"
    18  	"github.com/prometheus/prometheus/tsdb/chunks"
    19  	tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
    20  	"github.com/prometheus/prometheus/tsdb/fileutil"
    21  	"github.com/prometheus/prometheus/tsdb/index"
    22  )
    23  
    24  // Reader is like tsdb.BlockReader but without tombstones and size methods.
    25  type Reader interface {
    26  	// Index returns an IndexReader over the block's data.
    27  	Index() (tsdb.IndexReader, error)
    28  
    29  	// Chunks returns a ChunkReader over the block's data.
    30  	Chunks() (tsdb.ChunkReader, error)
    31  
    32  	// Meta returns block metadata file.
    33  	Meta() tsdb.BlockMeta
    34  }
    35  
    36  // SeriesWriter is interface for writing series into one or multiple Blocks.
    37  // Statistics has to be counted by implementation.
    38  type SeriesWriter interface {
    39  	tsdb.IndexWriter
    40  	tsdb.ChunkWriter
    41  }
    42  
    43  // Writer is interface for creating block(s).
    44  type Writer interface {
    45  	SeriesWriter
    46  
    47  	Flush() (tsdb.BlockStats, error)
    48  }
    49  
    50  type DiskWriter struct {
    51  	statsGatheringSeriesWriter
    52  
    53  	bTmp, bDir string
    54  	logger     log.Logger
    55  	closers    []io.Closer
    56  }
    57  
    58  const tmpForCreationBlockDirSuffix = ".tmp-for-creation"
    59  
    60  // NewDiskWriter allows to write single TSDB block to disk and returns statistics.
    61  // Destination block directory has to exists.
    62  func NewDiskWriter(ctx context.Context, logger log.Logger, bDir string) (_ *DiskWriter, err error) {
    63  	bTmp := bDir + tmpForCreationBlockDirSuffix
    64  
    65  	d := &DiskWriter{
    66  		bTmp:   bTmp,
    67  		bDir:   bDir,
    68  		logger: logger,
    69  	}
    70  	defer func() {
    71  		if err != nil {
    72  			err = tsdb_errors.NewMulti(err, tsdb_errors.CloseAll(d.closers)).Err()
    73  			if err := os.RemoveAll(bTmp); err != nil {
    74  				level.Error(logger).Log("msg", "removed tmp folder after failed compaction", "err", err.Error())
    75  			}
    76  		}
    77  	}()
    78  
    79  	if err = os.RemoveAll(bTmp); err != nil {
    80  		return nil, err
    81  	}
    82  	if err = os.MkdirAll(bTmp, 0750); err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	chunkw, err := chunks.NewWriter(filepath.Join(bTmp, ChunksDirname))
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "open chunk writer")
    89  	}
    90  	d.closers = append(d.closers, chunkw)
    91  
    92  	// TODO(bwplotka): Setup instrumentedChunkWriter if we want to upstream this code.
    93  
    94  	indexw, err := index.NewWriter(ctx, filepath.Join(bTmp, IndexFilename))
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "open index writer")
    97  	}
    98  	d.closers = append(d.closers, indexw)
    99  	d.statsGatheringSeriesWriter = statsGatheringSeriesWriter{iw: indexw, cw: chunkw}
   100  	return d, nil
   101  }
   102  
   103  func (d *DiskWriter) Flush() (_ tsdb.BlockStats, err error) {
   104  	defer func() {
   105  		if err != nil {
   106  			err = tsdb_errors.NewMulti(err, tsdb_errors.CloseAll(d.closers)).Err()
   107  			if err := os.RemoveAll(d.bTmp); err != nil {
   108  				level.Error(d.logger).Log("msg", "removed tmp folder failed after block(s) write", "err", err.Error())
   109  			}
   110  		}
   111  	}()
   112  	df, err := fileutil.OpenDir(d.bTmp)
   113  	if err != nil {
   114  		return tsdb.BlockStats{}, errors.Wrap(err, "open temporary block dir")
   115  	}
   116  	defer func() {
   117  		if df != nil {
   118  			err = tsdb_errors.NewMulti(err, df.Close()).Err()
   119  		}
   120  	}()
   121  
   122  	if err := df.Sync(); err != nil {
   123  		return tsdb.BlockStats{}, errors.Wrap(err, "sync temporary dir file")
   124  	}
   125  
   126  	// Close temp dir before rename block dir (for windows platform).
   127  	if err = df.Close(); err != nil {
   128  		return tsdb.BlockStats{}, errors.Wrap(err, "close temporary dir")
   129  	}
   130  	df = nil
   131  
   132  	if err := tsdb_errors.CloseAll(d.closers); err != nil {
   133  		d.closers = nil
   134  		return tsdb.BlockStats{}, err
   135  	}
   136  	d.closers = nil
   137  
   138  	// Block files successfully written, make them visible by moving files from tmp dir.
   139  	if err := fileutil.Replace(filepath.Join(d.bTmp, IndexFilename), filepath.Join(d.bDir, IndexFilename)); err != nil {
   140  		return tsdb.BlockStats{}, errors.Wrap(err, "replace index file")
   141  	}
   142  	if err := fileutil.Replace(filepath.Join(d.bTmp, ChunksDirname), filepath.Join(d.bDir, ChunksDirname)); err != nil {
   143  		return tsdb.BlockStats{}, errors.Wrap(err, "replace chunks dir")
   144  	}
   145  	return d.stats, nil
   146  }
   147  
   148  type statsGatheringSeriesWriter struct {
   149  	iw tsdb.IndexWriter
   150  	cw tsdb.ChunkWriter
   151  
   152  	stats   tsdb.BlockStats
   153  	symbols int64
   154  }
   155  
   156  func (s *statsGatheringSeriesWriter) AddSymbol(sym string) error {
   157  	if err := s.iw.AddSymbol(sym); err != nil {
   158  		return err
   159  	}
   160  	s.symbols++
   161  	return nil
   162  }
   163  
   164  func (s *statsGatheringSeriesWriter) AddSeries(ref storage.SeriesRef, l labels.Labels, chks ...chunks.Meta) error {
   165  	if err := s.iw.AddSeries(ref, l, chks...); err != nil {
   166  		return err
   167  	}
   168  	s.stats.NumSeries++
   169  	return nil
   170  }
   171  
   172  func (s *statsGatheringSeriesWriter) WriteChunks(chks ...chunks.Meta) error {
   173  	if err := s.cw.WriteChunks(chks...); err != nil {
   174  		return err
   175  	}
   176  	s.stats.NumChunks += uint64(len(chks))
   177  	for _, chk := range chks {
   178  		s.stats.NumSamples += uint64(chk.Chunk.NumSamples())
   179  	}
   180  	return nil
   181  }
   182  
   183  func (s statsGatheringSeriesWriter) Close() error {
   184  	return tsdb_errors.NewMulti(s.iw.Close(), s.cw.Close()).Err()
   185  }