2017-04-10 11:59:45 -07:00
|
|
|
// Copyright 2017 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-01-02 01:34:55 -08:00
|
|
|
package tsdb
|
|
|
|
|
|
|
|
import (
|
2017-03-20 02:41:43 -07:00
|
|
|
"fmt"
|
2017-02-27 01:46:15 -08:00
|
|
|
"math/rand"
|
2017-01-02 01:34:55 -08:00
|
|
|
"os"
|
2017-01-02 05:41:13 -08:00
|
|
|
"path/filepath"
|
2017-05-18 08:30:52 -07:00
|
|
|
"sort"
|
2017-01-03 06:43:26 -08:00
|
|
|
"time"
|
2017-01-02 01:34:55 -08:00
|
|
|
|
2017-01-02 05:41:13 -08:00
|
|
|
"github.com/coreos/etcd/pkg/fileutil"
|
2017-03-20 02:41:43 -07:00
|
|
|
"github.com/go-kit/kit/log"
|
2017-02-27 01:46:15 -08:00
|
|
|
"github.com/oklog/ulid"
|
2017-01-03 06:43:26 -08:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2017-05-17 02:19:42 -07:00
|
|
|
"github.com/prometheus/tsdb/chunks"
|
2017-04-04 02:27:26 -07:00
|
|
|
"github.com/prometheus/tsdb/labels"
|
2017-01-02 01:34:55 -08:00
|
|
|
)
|
|
|
|
|
2017-07-07 04:46:41 -07:00
|
|
|
// ExponentialBlockRanges returns the time ranges based on the stepSize
|
|
|
|
func ExponentialBlockRanges(minSize int64, steps, stepSize int) []int64 {
|
|
|
|
ranges := make([]int64, 0, steps)
|
|
|
|
curRange := minSize
|
|
|
|
for i := 0; i < steps; i++ {
|
|
|
|
ranges = append(ranges, curRange)
|
|
|
|
curRange = curRange * int64(stepSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ranges
|
|
|
|
}
|
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
// Compactor provides compaction against an underlying storage
|
|
|
|
// of time series data.
|
2017-03-02 00:13:29 -08:00
|
|
|
type Compactor interface {
|
2017-03-02 05:32:09 -08:00
|
|
|
// Plan returns a set of non-overlapping directories that can
|
|
|
|
// be compacted concurrently.
|
|
|
|
// Results returned when compactions are in progress are undefined.
|
2017-05-18 07:09:30 -07:00
|
|
|
Plan() ([][]string, error)
|
2017-03-02 00:13:29 -08:00
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
// Write persists a Block into a directory.
|
2017-05-18 07:09:30 -07:00
|
|
|
Write(b Block) error
|
2017-03-02 00:13:29 -08:00
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
// Compact runs compaction against the provided directories. Must
|
|
|
|
// only be called concurrently with results of Plan().
|
2017-03-02 00:13:29 -08:00
|
|
|
Compact(dirs ...string) error
|
|
|
|
}
|
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
// compactor implements the Compactor interface.
|
2017-01-02 01:34:55 -08:00
|
|
|
type compactor struct {
|
2017-05-18 07:09:30 -07:00
|
|
|
dir string
|
2017-01-03 06:43:26 -08:00
|
|
|
metrics *compactorMetrics
|
2017-03-20 02:41:43 -07:00
|
|
|
logger log.Logger
|
2017-01-17 21:18:32 -08:00
|
|
|
opts *compactorOptions
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-01-03 06:43:26 -08:00
|
|
|
type compactorMetrics struct {
|
2017-01-06 03:37:28 -08:00
|
|
|
ran prometheus.Counter
|
|
|
|
failed prometheus.Counter
|
|
|
|
duration prometheus.Histogram
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
|
|
|
|
2017-01-06 02:40:09 -08:00
|
|
|
func newCompactorMetrics(r prometheus.Registerer) *compactorMetrics {
|
2017-01-03 06:43:26 -08:00
|
|
|
m := &compactorMetrics{}
|
|
|
|
|
|
|
|
m.ran = prometheus.NewCounter(prometheus.CounterOpts{
|
2017-01-06 02:40:09 -08:00
|
|
|
Name: "tsdb_compactions_total",
|
|
|
|
Help: "Total number of compactions that were executed for the partition.",
|
2017-01-03 06:43:26 -08:00
|
|
|
})
|
|
|
|
m.failed = prometheus.NewCounter(prometheus.CounterOpts{
|
2017-01-06 02:40:09 -08:00
|
|
|
Name: "tsdb_compactions_failed_total",
|
|
|
|
Help: "Total number of compactions that failed for the partition.",
|
2017-01-03 06:43:26 -08:00
|
|
|
})
|
2017-05-26 06:13:03 -07:00
|
|
|
m.duration = prometheus.NewSummary(prometheus.SummaryOpts{
|
2017-01-06 02:40:09 -08:00
|
|
|
Name: "tsdb_compaction_duration",
|
|
|
|
Help: "Duration of compaction runs.",
|
2017-01-03 06:43:26 -08:00
|
|
|
})
|
|
|
|
|
2017-01-06 02:40:09 -08:00
|
|
|
if r != nil {
|
|
|
|
r.MustRegister(
|
|
|
|
m.ran,
|
|
|
|
m.failed,
|
|
|
|
m.duration,
|
|
|
|
)
|
|
|
|
}
|
2017-01-03 06:43:26 -08:00
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2017-01-17 21:18:32 -08:00
|
|
|
type compactorOptions struct {
|
2017-07-06 12:29:26 -07:00
|
|
|
blockRanges []int64
|
2017-01-06 04:53:05 -08:00
|
|
|
}
|
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
func newCompactor(dir string, r prometheus.Registerer, l log.Logger, opts *compactorOptions) *compactor {
|
2017-01-17 21:18:32 -08:00
|
|
|
return &compactor{
|
2017-05-18 07:09:30 -07:00
|
|
|
dir: dir,
|
2017-01-17 21:18:32 -08:00
|
|
|
opts: opts,
|
2017-03-20 02:41:43 -07:00
|
|
|
logger: l,
|
2017-01-09 10:14:21 -08:00
|
|
|
metrics: newCompactorMetrics(r),
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-19 22:58:19 -08:00
|
|
|
type compactionInfo struct {
|
2017-02-27 23:40:51 -08:00
|
|
|
seq int
|
2017-01-19 22:58:19 -08:00
|
|
|
generation int
|
|
|
|
mint, maxt int64
|
|
|
|
}
|
|
|
|
|
2017-02-23 01:50:22 -08:00
|
|
|
const compactionBlocksLen = 3
|
2017-01-30 00:42:38 -08:00
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
type dirMeta struct {
|
|
|
|
dir string
|
|
|
|
meta *BlockMeta
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactor) Plan() ([][]string, error) {
|
|
|
|
dirs, err := blockDirs(c.dir)
|
2017-03-02 00:13:29 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
2017-01-17 21:18:32 -08:00
|
|
|
|
2017-05-18 08:30:52 -07:00
|
|
|
var dms []dirMeta
|
2017-03-02 00:13:29 -08:00
|
|
|
|
|
|
|
for _, dir := range dirs {
|
|
|
|
meta, err := readMetaFile(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-06 04:53:05 -08:00
|
|
|
}
|
2017-03-02 00:13:29 -08:00
|
|
|
if meta.Compaction.Generation > 0 {
|
2017-05-18 08:30:52 -07:00
|
|
|
dms = append(dms, dirMeta{dir, meta})
|
2017-01-30 00:42:38 -08:00
|
|
|
}
|
2017-03-02 00:13:29 -08:00
|
|
|
}
|
2017-05-18 08:30:52 -07:00
|
|
|
sort.Slice(dms, func(i, j int) bool {
|
|
|
|
return dms[i].meta.MinTime < dms[j].meta.MinTime
|
2017-05-18 07:09:30 -07:00
|
|
|
})
|
2017-03-02 00:13:29 -08:00
|
|
|
|
2017-07-06 12:29:26 -07:00
|
|
|
if len(dms) <= 1 {
|
2017-03-02 00:13:29 -08:00
|
|
|
return nil, nil
|
|
|
|
}
|
2017-01-30 00:42:38 -08:00
|
|
|
|
2017-07-06 12:29:26 -07:00
|
|
|
sliceDirs := func(dms []dirMeta) [][]string {
|
|
|
|
if len(dms) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2017-03-02 00:13:29 -08:00
|
|
|
var res []string
|
2017-07-06 12:29:26 -07:00
|
|
|
for _, dm := range dms {
|
|
|
|
res = append(res, dm.dir)
|
2017-03-02 00:13:29 -08:00
|
|
|
}
|
|
|
|
return [][]string{res}
|
2017-01-06 04:53:05 -08:00
|
|
|
}
|
2017-01-03 06:43:26 -08:00
|
|
|
|
2017-07-12 09:16:12 -07:00
|
|
|
planDirs := sliceDirs(c.selectDirs(dms))
|
|
|
|
if len(dirs) > 1 {
|
|
|
|
return planDirs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compact any blocks that have >5% tombstones.
|
|
|
|
for i := len(dms) - 1; i >= 0; i-- {
|
|
|
|
meta := dms[i].meta
|
|
|
|
if meta.MaxTime-meta.MinTime < c.opts.blockRanges[len(c.opts.blockRanges)/2] {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if meta.Stats.NumSeries/meta.Stats.NumTombstones <= 20 { // 5%
|
|
|
|
return [][]string{{dms[i].dir}}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
2017-07-06 12:29:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactor) selectDirs(ds []dirMeta) []dirMeta {
|
2017-07-07 04:46:41 -07:00
|
|
|
// The way to skip compaction is to not have blockRanges.
|
|
|
|
if len(c.opts.blockRanges) == 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-06 12:29:26 -07:00
|
|
|
return selectRecurse(ds, c.opts.blockRanges)
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectRecurse(dms []dirMeta, intervals []int64) []dirMeta {
|
|
|
|
if len(intervals) == 0 {
|
|
|
|
return dms
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the blocks by the max interval
|
|
|
|
blocks := splitByRange(dms, intervals[len(intervals)-1])
|
|
|
|
dirs := []dirMeta{}
|
|
|
|
for i := len(blocks) - 1; i >= 0; i-- {
|
|
|
|
// We need to choose the oldest blocks to compact. If there are a couple of blocks in
|
|
|
|
// the largest interval, we should compact those first.
|
|
|
|
if len(blocks[i]) > 1 {
|
|
|
|
dirs = blocks[i]
|
|
|
|
break
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
|
|
|
}
|
2017-01-30 00:42:38 -08:00
|
|
|
|
2017-07-06 12:29:26 -07:00
|
|
|
// If there are too many blocks, see if a smaller interval will catch them.
|
|
|
|
// i.e, if we have 0-20, 60-80, 80-100; all fall under 0-240, but we'd rather compact 60-100
|
|
|
|
// than all at once.
|
2017-07-07 04:46:41 -07:00
|
|
|
// Again if have 0-1d, 1d-2d, 3-6d we compact 0-1d, 1d-2d to compact it into the 0-3d block instead of compacting all three
|
|
|
|
// This is to honor the boundaries as much as possible.
|
2017-07-06 12:29:26 -07:00
|
|
|
if len(dirs) > 2 {
|
|
|
|
smallerDirs := selectRecurse(dirs, intervals[:len(intervals)-1])
|
|
|
|
if len(smallerDirs) > 1 {
|
|
|
|
return smallerDirs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dirs
|
2017-01-04 12:11:15 -08:00
|
|
|
}
|
|
|
|
|
2017-07-07 04:46:41 -07:00
|
|
|
// splitByRange splits the directories by the time range.
|
|
|
|
// for example if we have blocks 0-10, 10-20, 50-60, 90-100 and want to split them into 30 interval ranges
|
|
|
|
// splitByRange returns [0-10, 10-20], [50-60], [90-100].
|
2017-07-06 12:29:26 -07:00
|
|
|
func splitByRange(ds []dirMeta, tr int64) [][]dirMeta {
|
2017-07-07 04:46:41 -07:00
|
|
|
var splitDirs [][]dirMeta
|
2017-07-06 12:29:26 -07:00
|
|
|
|
2017-07-07 04:46:41 -07:00
|
|
|
for i := 0; i < len(ds); {
|
|
|
|
var group []dirMeta
|
|
|
|
// Compute start of aligned time range of size tr closest to the current block's start.
|
|
|
|
t0 := ds[i].meta.MinTime - (ds[i].meta.MinTime % tr)
|
2017-01-19 10:45:52 -08:00
|
|
|
|
2017-07-07 04:46:41 -07:00
|
|
|
// Add all dirs to the current group that are within [t0, t0+tr].
|
|
|
|
for ; i < len(ds); i++ {
|
|
|
|
if ds[i].meta.MinTime < t0 || ds[i].meta.MaxTime > t0+tr {
|
|
|
|
break
|
2017-07-06 12:29:26 -07:00
|
|
|
}
|
2017-07-07 04:46:41 -07:00
|
|
|
group = append(group, ds[i])
|
2017-01-19 10:45:52 -08:00
|
|
|
}
|
2017-07-06 12:29:26 -07:00
|
|
|
|
2017-07-07 04:46:41 -07:00
|
|
|
if len(group) > 0 {
|
|
|
|
splitDirs = append(splitDirs, group)
|
|
|
|
}
|
2017-07-06 12:29:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return splitDirs
|
2017-01-03 01:09:20 -08:00
|
|
|
}
|
|
|
|
|
2017-06-07 00:52:20 -07:00
|
|
|
func compactBlockMetas(blocks ...BlockMeta) (res BlockMeta) {
|
|
|
|
res.MinTime = blocks[0].MinTime
|
|
|
|
res.MaxTime = blocks[len(blocks)-1].MaxTime
|
2017-01-28 23:11:47 -08:00
|
|
|
|
2017-06-07 00:52:20 -07:00
|
|
|
sources := map[ulid.ULID]struct{}{}
|
2017-01-03 06:43:26 -08:00
|
|
|
|
|
|
|
for _, b := range blocks {
|
2017-06-07 00:52:20 -07:00
|
|
|
if b.Compaction.Generation > res.Compaction.Generation {
|
|
|
|
res.Compaction.Generation = b.Compaction.Generation
|
|
|
|
}
|
|
|
|
for _, s := range b.Compaction.Sources {
|
|
|
|
sources[s] = struct{}{}
|
|
|
|
}
|
|
|
|
// If it's an in memory block, its ULID goes into the sources.
|
|
|
|
if b.Compaction.Generation == 0 {
|
|
|
|
sources[b.ULID] = struct{}{}
|
|
|
|
}
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
2017-06-07 00:52:20 -07:00
|
|
|
res.Compaction.Generation++
|
|
|
|
|
|
|
|
for s := range sources {
|
|
|
|
res.Compaction.Sources = append(res.Compaction.Sources, s)
|
|
|
|
}
|
|
|
|
sort.Slice(res.Compaction.Sources, func(i, j int) bool {
|
|
|
|
return res.Compaction.Sources[i].Compare(res.Compaction.Sources[j]) < 0
|
|
|
|
})
|
|
|
|
|
2017-01-03 06:43:26 -08:00
|
|
|
return res
|
|
|
|
}
|
2017-01-02 01:34:55 -08:00
|
|
|
|
2017-03-02 00:13:29 -08:00
|
|
|
func (c *compactor) Compact(dirs ...string) (err error) {
|
|
|
|
var blocks []Block
|
2017-03-02 05:32:09 -08:00
|
|
|
|
2017-03-02 00:13:29 -08:00
|
|
|
for _, d := range dirs {
|
|
|
|
b, err := newPersistedBlock(d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-06 00:33:55 -08:00
|
|
|
defer b.Close()
|
|
|
|
|
2017-03-02 00:13:29 -08:00
|
|
|
blocks = append(blocks, b)
|
|
|
|
}
|
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
uid := ulid.MustNew(ulid.Now(), entropy)
|
|
|
|
|
|
|
|
return c.write(uid, blocks...)
|
2017-03-02 05:32:09 -08:00
|
|
|
}
|
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
func (c *compactor) Write(b Block) error {
|
2017-05-18 07:12:17 -07:00
|
|
|
// Buffering blocks might have been created that often have no data.
|
|
|
|
if b.Meta().Stats.NumSeries == 0 {
|
|
|
|
return errors.Wrap(os.RemoveAll(b.Dir()), "remove empty block")
|
|
|
|
}
|
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
uid := ulid.MustNew(ulid.Now(), entropy)
|
|
|
|
|
|
|
|
return c.write(uid, b)
|
2017-03-02 00:13:29 -08:00
|
|
|
}
|
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
// write creates a new block that is the union of the provided blocks into dir.
|
|
|
|
// It cleans up all files of the old blocks after completing successfully.
|
2017-05-18 07:09:30 -07:00
|
|
|
func (c *compactor) write(uid ulid.ULID, blocks ...Block) (err error) {
|
2017-03-20 02:41:43 -07:00
|
|
|
c.logger.Log("msg", "compact blocks", "blocks", fmt.Sprintf("%v", blocks))
|
|
|
|
|
2017-03-02 00:13:29 -08:00
|
|
|
defer func(t time.Time) {
|
2017-01-06 03:37:28 -08:00
|
|
|
if err != nil {
|
|
|
|
c.metrics.failed.Inc()
|
|
|
|
}
|
2017-05-26 06:13:03 -07:00
|
|
|
c.metrics.ran.Inc()
|
2017-03-02 00:13:29 -08:00
|
|
|
c.metrics.duration.Observe(time.Since(t).Seconds())
|
|
|
|
}(time.Now())
|
2017-01-03 06:43:26 -08:00
|
|
|
|
2017-05-18 07:09:30 -07:00
|
|
|
dir := filepath.Join(c.dir, uid.String())
|
2017-03-02 05:32:09 -08:00
|
|
|
tmp := dir + ".tmp"
|
|
|
|
|
|
|
|
if err = os.RemoveAll(tmp); err != nil {
|
2017-02-19 07:04:37 -08:00
|
|
|
return err
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
2017-02-19 07:04:37 -08:00
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
if err = os.MkdirAll(tmp, 0777); err != nil {
|
2017-01-02 01:34:55 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-03-02 05:35:06 -08:00
|
|
|
// Populate chunk and index files into temporary directory with
|
|
|
|
// data of all blocks.
|
2017-03-02 05:32:09 -08:00
|
|
|
chunkw, err := newChunkWriter(chunkDir(tmp))
|
2017-02-23 01:50:22 -08:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "open chunk writer")
|
|
|
|
}
|
2017-03-02 05:32:09 -08:00
|
|
|
indexw, err := newIndexWriter(tmp)
|
2017-02-24 22:24:20 -08:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "open index writer")
|
|
|
|
}
|
2017-01-03 06:43:26 -08:00
|
|
|
|
2017-06-05 01:18:31 -07:00
|
|
|
meta, err := populateBlock(blocks, indexw, chunkw)
|
2017-03-02 05:32:09 -08:00
|
|
|
if err != nil {
|
2017-01-03 06:43:26 -08:00
|
|
|
return errors.Wrap(err, "write compaction")
|
|
|
|
}
|
2017-05-18 07:09:30 -07:00
|
|
|
meta.ULID = uid
|
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
if err = writeMetaFile(tmp, meta); err != nil {
|
|
|
|
return errors.Wrap(err, "write merged meta")
|
|
|
|
}
|
2017-01-03 06:43:26 -08:00
|
|
|
|
2017-01-06 03:37:28 -08:00
|
|
|
if err = chunkw.Close(); err != nil {
|
2017-01-03 06:43:26 -08:00
|
|
|
return errors.Wrap(err, "close chunk writer")
|
|
|
|
}
|
2017-01-06 03:37:28 -08:00
|
|
|
if err = indexw.Close(); err != nil {
|
2017-01-03 06:43:26 -08:00
|
|
|
return errors.Wrap(err, "close index writer")
|
|
|
|
}
|
2017-03-02 05:32:09 -08:00
|
|
|
|
2017-05-14 02:06:26 -07:00
|
|
|
// Create an empty tombstones file.
|
2017-05-26 04:01:45 -07:00
|
|
|
if err := writeTombstoneFile(tmp, newEmptyTombstoneReader()); err != nil {
|
|
|
|
return errors.Wrap(err, "write new tombstones file")
|
2017-05-14 02:06:26 -07:00
|
|
|
}
|
|
|
|
|
2017-03-02 05:35:06 -08:00
|
|
|
// Block successfully written, make visible and remove old ones.
|
2017-03-02 05:32:09 -08:00
|
|
|
if err := renameFile(tmp, dir); err != nil {
|
|
|
|
return errors.Wrap(err, "rename block dir")
|
|
|
|
}
|
2017-05-18 07:09:30 -07:00
|
|
|
for _, b := range blocks {
|
2017-03-02 05:32:09 -08:00
|
|
|
if err := os.RemoveAll(b.Dir()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-03-02 05:35:06 -08:00
|
|
|
// Properly sync parent dir to ensure changes are visible.
|
|
|
|
df, err := fileutil.OpenDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "sync block dir")
|
|
|
|
}
|
2017-06-11 15:05:04 -07:00
|
|
|
defer df.Close()
|
|
|
|
|
2017-03-02 05:35:06 -08:00
|
|
|
if err := fileutil.Fsync(df); err != nil {
|
|
|
|
return errors.Wrap(err, "sync block dir")
|
|
|
|
}
|
2017-03-02 05:32:09 -08:00
|
|
|
|
2017-01-06 03:37:28 -08:00
|
|
|
return nil
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
|
|
|
|
2017-06-05 01:18:31 -07:00
|
|
|
// populateBlock fills the index and chunk writers with new data gathered as the union
|
2017-03-02 05:32:09 -08:00
|
|
|
// of the provided blocks. It returns meta information for the new block.
|
2017-06-05 01:18:31 -07:00
|
|
|
func populateBlock(blocks []Block, indexw IndexWriter, chunkw ChunkWriter) (*BlockMeta, error) {
|
2017-01-03 06:43:26 -08:00
|
|
|
var set compactionSet
|
2017-06-07 00:52:20 -07:00
|
|
|
var metas []BlockMeta
|
2017-01-17 21:18:32 -08:00
|
|
|
|
2017-01-03 06:43:26 -08:00
|
|
|
for i, b := range blocks {
|
2017-06-07 04:42:53 -07:00
|
|
|
metas = append(metas, b.Meta())
|
|
|
|
|
2017-01-10 06:28:22 -08:00
|
|
|
all, err := b.Index().Postings("", "")
|
2017-01-03 06:43:26 -08:00
|
|
|
if err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
2017-05-17 02:19:42 -07:00
|
|
|
s := newCompactionSeriesSet(b.Index(), b.Chunks(), b.Tombstones(), all)
|
2017-01-03 06:43:26 -08:00
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
set = s
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
set, err = newCompactionMerger(set, s)
|
|
|
|
if err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-03 06:43:26 -08:00
|
|
|
}
|
2017-01-02 02:12:28 -08:00
|
|
|
}
|
|
|
|
|
2017-01-02 01:34:55 -08:00
|
|
|
// We fully rebuild the postings list index from merged series.
|
|
|
|
var (
|
|
|
|
postings = &memPostings{m: make(map[term][]uint32, 512)}
|
|
|
|
values = map[string]stringset{}
|
|
|
|
i = uint32(0)
|
2017-06-07 00:52:20 -07:00
|
|
|
meta = compactBlockMetas(metas...)
|
2017-01-02 01:34:55 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
for set.Next() {
|
2017-05-22 04:12:36 -07:00
|
|
|
lset, chks, dranges := set.At() // The chunks here are not fully deleted.
|
2017-05-17 02:19:42 -07:00
|
|
|
|
2017-07-12 09:16:12 -07:00
|
|
|
// Skip the series with all deleted chunks.
|
|
|
|
if len(chks) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
if len(dranges) > 0 {
|
2017-05-17 02:19:42 -07:00
|
|
|
// Re-encode the chunk to not have deleted values.
|
|
|
|
for _, chk := range chks {
|
2017-05-22 04:12:36 -07:00
|
|
|
if intervalOverlap(dranges[0].mint, dranges[len(dranges)-1].maxt, chk.MinTime, chk.MaxTime) {
|
2017-05-17 02:19:42 -07:00
|
|
|
newChunk := chunks.NewXORChunk()
|
|
|
|
app, err := newChunk.Appender()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
it := &deletedIterator{it: chk.Chunk.Iterator(), intervals: dranges}
|
2017-05-17 02:19:42 -07:00
|
|
|
for it.Next() {
|
|
|
|
ts, v := it.At()
|
|
|
|
app.Append(ts, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
chk.Chunk = newChunk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := chunkw.WriteChunks(chks...); err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-05-17 02:19:42 -07:00
|
|
|
indexw.AddSeries(i, lset, chks...)
|
2017-02-19 02:27:31 -08:00
|
|
|
|
2017-05-17 02:19:42 -07:00
|
|
|
meta.Stats.NumChunks += uint64(len(chks))
|
2017-01-19 02:22:47 -08:00
|
|
|
meta.Stats.NumSeries++
|
2017-07-12 09:16:12 -07:00
|
|
|
for _, chk := range chks {
|
2017-07-12 09:31:26 -07:00
|
|
|
meta.Stats.NumSamples += uint64(chk.Chunk.NumSamples())
|
2017-07-12 09:16:12 -07:00
|
|
|
}
|
2017-01-02 01:34:55 -08:00
|
|
|
|
|
|
|
for _, l := range lset {
|
|
|
|
valset, ok := values[l.Name]
|
|
|
|
if !ok {
|
|
|
|
valset = stringset{}
|
|
|
|
values[l.Name] = valset
|
|
|
|
}
|
|
|
|
valset.set(l.Value)
|
|
|
|
|
|
|
|
postings.add(i, term{name: l.Name, value: l.Value})
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
if set.Err() != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, set.Err()
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
s := make([]string, 0, 256)
|
|
|
|
for n, v := range values {
|
|
|
|
s = s[:0]
|
|
|
|
|
|
|
|
for x := range v {
|
|
|
|
s = append(s, x)
|
|
|
|
}
|
2017-01-02 07:58:47 -08:00
|
|
|
if err := indexw.WriteLabelIndex([]string{n}, s); err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for t := range postings.m {
|
2017-01-02 07:58:47 -08:00
|
|
|
if err := indexw.WritePostings(t.name, t.value, postings.get(t)); err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Write a postings list containing all series.
|
|
|
|
all := make([]uint32, i)
|
|
|
|
for i := range all {
|
|
|
|
all[i] = uint32(i)
|
|
|
|
}
|
2017-01-02 07:58:47 -08:00
|
|
|
if err := indexw.WritePostings("", "", newListPostings(all)); err != nil {
|
2017-03-02 05:32:09 -08:00
|
|
|
return nil, err
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
2017-01-19 05:01:38 -08:00
|
|
|
|
2017-03-02 05:32:09 -08:00
|
|
|
return &meta, nil
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-01-03 06:43:26 -08:00
|
|
|
type compactionSet interface {
|
|
|
|
Next() bool
|
2017-05-22 04:12:36 -07:00
|
|
|
At() (labels.Labels, []*ChunkMeta, intervals)
|
2017-01-03 06:43:26 -08:00
|
|
|
Err() error
|
|
|
|
}
|
|
|
|
|
2017-01-02 01:34:55 -08:00
|
|
|
type compactionSeriesSet struct {
|
2017-05-17 02:19:42 -07:00
|
|
|
p Postings
|
|
|
|
index IndexReader
|
|
|
|
chunks ChunkReader
|
|
|
|
tombstones TombstoneReader
|
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
l labels.Labels
|
|
|
|
c []*ChunkMeta
|
|
|
|
intervals intervals
|
|
|
|
err error
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-05-17 02:19:42 -07:00
|
|
|
func newCompactionSeriesSet(i IndexReader, c ChunkReader, t TombstoneReader, p Postings) *compactionSeriesSet {
|
2017-01-02 01:34:55 -08:00
|
|
|
return &compactionSeriesSet{
|
2017-05-17 02:19:42 -07:00
|
|
|
index: i,
|
|
|
|
chunks: c,
|
|
|
|
tombstones: t,
|
|
|
|
p: p,
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactionSeriesSet) Next() bool {
|
|
|
|
if !c.p.Next() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-26 08:56:31 -07:00
|
|
|
c.intervals = c.tombstones.Get(c.p.At())
|
2017-05-23 22:54:24 -07:00
|
|
|
|
2017-01-02 04:27:52 -08:00
|
|
|
c.l, c.c, c.err = c.index.Series(c.p.At())
|
2017-01-02 01:34:55 -08:00
|
|
|
if c.err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2017-05-17 02:19:42 -07:00
|
|
|
|
2017-05-21 10:50:05 -07:00
|
|
|
// Remove completely deleted chunks.
|
2017-05-22 04:12:36 -07:00
|
|
|
if len(c.intervals) > 0 {
|
2017-05-17 02:19:42 -07:00
|
|
|
chks := make([]*ChunkMeta, 0, len(c.c))
|
|
|
|
for _, chk := range c.c {
|
2017-05-22 04:12:36 -07:00
|
|
|
if !(interval{chk.MinTime, chk.MaxTime}.isSubrange(c.intervals)) {
|
2017-05-17 02:19:42 -07:00
|
|
|
chks = append(chks, chk)
|
|
|
|
}
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
2017-05-17 02:19:42 -07:00
|
|
|
|
|
|
|
c.c = chks
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-03-14 07:40:16 -07:00
|
|
|
for _, chk := range c.c {
|
2017-02-23 01:50:22 -08:00
|
|
|
chk.Chunk, c.err = c.chunks.Chunk(chk.Ref)
|
2017-01-02 01:34:55 -08:00
|
|
|
if c.err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactionSeriesSet) Err() error {
|
|
|
|
if c.err != nil {
|
|
|
|
return c.err
|
|
|
|
}
|
|
|
|
return c.p.Err()
|
|
|
|
}
|
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
func (c *compactionSeriesSet) At() (labels.Labels, []*ChunkMeta, intervals) {
|
|
|
|
return c.l, c.c, c.intervals
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type compactionMerger struct {
|
2017-01-03 06:43:26 -08:00
|
|
|
a, b compactionSet
|
2017-01-02 01:34:55 -08:00
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
aok, bok bool
|
|
|
|
l labels.Labels
|
|
|
|
c []*ChunkMeta
|
|
|
|
intervals intervals
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type compactionSeries struct {
|
|
|
|
labels labels.Labels
|
2017-03-14 07:40:16 -07:00
|
|
|
chunks []*ChunkMeta
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
|
2017-01-03 06:43:26 -08:00
|
|
|
func newCompactionMerger(a, b compactionSet) (*compactionMerger, error) {
|
2017-01-02 01:34:55 -08:00
|
|
|
c := &compactionMerger{
|
|
|
|
a: a,
|
|
|
|
b: b,
|
|
|
|
}
|
|
|
|
// Initialize first elements of both sets as Next() needs
|
|
|
|
// one element look-ahead.
|
2017-01-04 12:11:15 -08:00
|
|
|
c.aok = c.a.Next()
|
|
|
|
c.bok = c.b.Next()
|
2017-01-02 01:34:55 -08:00
|
|
|
|
|
|
|
return c, c.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactionMerger) compare() int {
|
2017-01-04 12:11:15 -08:00
|
|
|
if !c.aok {
|
2017-01-02 01:34:55 -08:00
|
|
|
return 1
|
|
|
|
}
|
2017-01-04 12:11:15 -08:00
|
|
|
if !c.bok {
|
2017-01-02 01:34:55 -08:00
|
|
|
return -1
|
|
|
|
}
|
2017-05-17 02:19:42 -07:00
|
|
|
a, _, _ := c.a.At()
|
|
|
|
b, _, _ := c.b.At()
|
2017-01-02 01:34:55 -08:00
|
|
|
return labels.Compare(a, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactionMerger) Next() bool {
|
2017-01-04 12:11:15 -08:00
|
|
|
if !c.aok && !c.bok || c.Err() != nil {
|
2017-01-02 01:34:55 -08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
d := c.compare()
|
|
|
|
// Both sets contain the current series. Chain them into a single one.
|
|
|
|
if d > 0 {
|
2017-05-22 04:12:36 -07:00
|
|
|
c.l, c.c, c.intervals = c.b.At()
|
2017-01-04 12:11:15 -08:00
|
|
|
c.bok = c.b.Next()
|
2017-01-02 01:34:55 -08:00
|
|
|
} else if d < 0 {
|
2017-05-22 04:12:36 -07:00
|
|
|
c.l, c.c, c.intervals = c.a.At()
|
2017-01-04 12:11:15 -08:00
|
|
|
c.aok = c.a.Next()
|
2017-01-02 01:34:55 -08:00
|
|
|
} else {
|
2017-05-17 02:19:42 -07:00
|
|
|
l, ca, ra := c.a.At()
|
|
|
|
_, cb, rb := c.b.At()
|
|
|
|
for _, r := range rb {
|
2017-05-22 04:12:36 -07:00
|
|
|
ra = ra.add(r)
|
2017-05-17 02:19:42 -07:00
|
|
|
}
|
2017-01-02 01:34:55 -08:00
|
|
|
|
|
|
|
c.l = l
|
|
|
|
c.c = append(ca, cb...)
|
2017-05-22 04:12:36 -07:00
|
|
|
c.intervals = ra
|
2017-01-02 01:34:55 -08:00
|
|
|
|
2017-01-04 12:11:15 -08:00
|
|
|
c.aok = c.a.Next()
|
|
|
|
c.bok = c.b.Next()
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compactionMerger) Err() error {
|
|
|
|
if c.a.Err() != nil {
|
|
|
|
return c.a.Err()
|
|
|
|
}
|
|
|
|
return c.b.Err()
|
|
|
|
}
|
|
|
|
|
2017-05-22 04:12:36 -07:00
|
|
|
func (c *compactionMerger) At() (labels.Labels, []*ChunkMeta, intervals) {
|
|
|
|
return c.l, c.c, c.intervals
|
2017-01-02 01:34:55 -08:00
|
|
|
}
|
2017-01-02 05:41:13 -08:00
|
|
|
|
2017-03-01 08:19:57 -08:00
|
|
|
func renameFile(from, to string) error {
|
2017-01-02 05:41:13 -08:00
|
|
|
if err := os.RemoveAll(to); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Rename(from, to); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Directory was renamed; sync parent dir to persist rename.
|
|
|
|
pdir, err := fileutil.OpenDir(filepath.Dir(to))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-11 15:05:04 -07:00
|
|
|
defer pdir.Close()
|
|
|
|
|
2017-01-02 05:41:13 -08:00
|
|
|
if err = fileutil.Fsync(pdir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = pdir.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|