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 }