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 }