|
|
|
@ -15,6 +15,7 @@ package tsdb
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"math" |
|
|
|
|
"runtime" |
|
|
|
|
"sort" |
|
|
|
|
"sync" |
|
|
|
|
"sync/atomic" |
|
|
|
@ -186,6 +187,37 @@ func NewHead(r prometheus.Registerer, l log.Logger, wal WAL, chunkRange int64) (
|
|
|
|
|
return h, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// processWALSamples adds a partition of samples it receives to the head and passes
|
|
|
|
|
// them on to other workers.
|
|
|
|
|
// Samples before the mint timestamp are discarded.
|
|
|
|
|
func (h *Head) processWALSamples( |
|
|
|
|
mint int64, |
|
|
|
|
partition, total uint64, |
|
|
|
|
input <-chan []RefSample, output chan<- []RefSample, |
|
|
|
|
) (unknownRefs uint64) { |
|
|
|
|
defer close(output) |
|
|
|
|
|
|
|
|
|
for samples := range input { |
|
|
|
|
for _, s := range samples { |
|
|
|
|
if s.T < mint || s.Ref%total != partition { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
ms := h.series.getByID(s.Ref) |
|
|
|
|
if ms == nil { |
|
|
|
|
unknownRefs++ |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
_, chunkCreated := ms.append(s.T, s.V) |
|
|
|
|
if chunkCreated { |
|
|
|
|
h.metrics.chunksCreated.Inc() |
|
|
|
|
h.metrics.chunks.Inc() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
output <- samples |
|
|
|
|
} |
|
|
|
|
return unknownRefs |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ReadWAL initializes the head by consuming the write ahead log.
|
|
|
|
|
func (h *Head) ReadWAL() error { |
|
|
|
|
defer h.postings.ensureOrder() |
|
|
|
@ -195,8 +227,32 @@ func (h *Head) ReadWAL() error {
|
|
|
|
|
|
|
|
|
|
// Track number of samples that referenced a series we don't know about
|
|
|
|
|
// for error reporting.
|
|
|
|
|
var unknownRefs int |
|
|
|
|
var unknownRefs uint64 |
|
|
|
|
|
|
|
|
|
// Start workers that each process samples for a partition of the series ID space.
|
|
|
|
|
// They are connected through a ring of channels which ensures that all sample batches
|
|
|
|
|
// read from the WAL are processed in order.
|
|
|
|
|
var ( |
|
|
|
|
n = runtime.GOMAXPROCS(0) |
|
|
|
|
firstInput = make(chan []RefSample, 300) |
|
|
|
|
input = firstInput |
|
|
|
|
) |
|
|
|
|
for i := 0; i < n; i++ { |
|
|
|
|
output := make(chan []RefSample, 300) |
|
|
|
|
|
|
|
|
|
go func(i int, input <-chan []RefSample, output chan<- []RefSample) { |
|
|
|
|
unknown := h.processWALSamples(mint, uint64(i), uint64(n), input, output) |
|
|
|
|
atomic.AddUint64(&unknownRefs, unknown) |
|
|
|
|
}(i, input, output) |
|
|
|
|
|
|
|
|
|
// The output feeds the next worker goroutine. For the last worker,
|
|
|
|
|
// it feeds the initial input again to reuse the RefSample slices.
|
|
|
|
|
input = output |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO(fabxc): series entries spread between samples can starve the sample workers.
|
|
|
|
|
// Even with bufferd channels, this can impact startup time with lots of series churn.
|
|
|
|
|
// We must not pralellize series creation itself but could make the indexing asynchronous.
|
|
|
|
|
seriesFunc := func(series []RefSeries) { |
|
|
|
|
for _, s := range series { |
|
|
|
|
h.getOrCreateWithID(s.Ref, s.Labels.Hash(), s.Labels) |
|
|
|
@ -207,21 +263,13 @@ func (h *Head) ReadWAL() error {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
samplesFunc := func(samples []RefSample) { |
|
|
|
|
for _, s := range samples { |
|
|
|
|
if s.T < mint { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
ms := h.series.getByID(s.Ref) |
|
|
|
|
if ms == nil { |
|
|
|
|
unknownRefs++ |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
_, chunkCreated := ms.append(s.T, s.V) |
|
|
|
|
if chunkCreated { |
|
|
|
|
h.metrics.chunksCreated.Inc() |
|
|
|
|
h.metrics.chunks.Inc() |
|
|
|
|
} |
|
|
|
|
var buf []RefSample |
|
|
|
|
select { |
|
|
|
|
case buf = <-input: |
|
|
|
|
default: |
|
|
|
|
buf = make([]RefSample, 0, len(samples)*11/10) |
|
|
|
|
} |
|
|
|
|
firstInput <- append(buf[:0], samples...) |
|
|
|
|
} |
|
|
|
|
deletesFunc := func(stones []Stone) { |
|
|
|
|
for _, s := range stones { |
|
|
|
@ -234,13 +282,18 @@ func (h *Head) ReadWAL() error {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if unknownRefs > 0 { |
|
|
|
|
level.Warn(h.logger).Log("msg", "unknown series references in WAL samples", "count", unknownRefs) |
|
|
|
|
} |
|
|
|
|
err := r.Read(seriesFunc, samplesFunc, deletesFunc) |
|
|
|
|
|
|
|
|
|
if err := r.Read(seriesFunc, samplesFunc, deletesFunc); err != nil { |
|
|
|
|
// Signal termination to first worker and wait for last one to close its output channel.
|
|
|
|
|
close(firstInput) |
|
|
|
|
for range input { |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return errors.Wrap(err, "consume WAL") |
|
|
|
|
} |
|
|
|
|
if unknownRefs > 0 { |
|
|
|
|
level.Warn(h.logger).Log("msg", "unknown series references in WAL samples", "count", unknownRefs) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1168,10 +1221,12 @@ func (s *memSeries) append(t int64, v float64) (success, chunkCreated bool) {
|
|
|
|
|
c = s.cut(t) |
|
|
|
|
chunkCreated = true |
|
|
|
|
} |
|
|
|
|
numSamples := c.chunk.NumSamples() |
|
|
|
|
|
|
|
|
|
if c.maxTime >= t { |
|
|
|
|
return false, chunkCreated |
|
|
|
|
} |
|
|
|
|
if c.chunk.NumSamples() > samplesPerChunk/4 && t >= s.nextAt { |
|
|
|
|
if numSamples > samplesPerChunk/4 && t >= s.nextAt { |
|
|
|
|
c = s.cut(t) |
|
|
|
|
chunkCreated = true |
|
|
|
|
} |
|
|
|
@ -1179,7 +1234,7 @@ func (s *memSeries) append(t int64, v float64) (success, chunkCreated bool) {
|
|
|
|
|
|
|
|
|
|
c.maxTime = t |
|
|
|
|
|
|
|
|
|
if c.chunk.NumSamples() == samplesPerChunk/4 { |
|
|
|
|
if numSamples == samplesPerChunk/4 { |
|
|
|
|
_, maxt := rangeForTimestamp(c.minTime, s.chunkRange) |
|
|
|
|
s.nextAt = computeChunkEndTime(c.minTime, c.maxTime, maxt) |
|
|
|
|
} |
|
|
|
|