mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
5.1 KiB
211 lines
5.1 KiB
|
5 years ago
|
package ingester
|
||
|
|
|
||
|
|
import (
|
||
|
|
"flag"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/cortexproject/cortex/pkg/util"
|
||
|
|
"github.com/go-kit/kit/log/level"
|
||
|
|
"github.com/pkg/errors"
|
||
|
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
|
"github.com/prometheus/prometheus/tsdb/wal"
|
||
|
|
|
||
|
|
"github.com/grafana/loki/pkg/logproto"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
// shared pool for WALRecords and []logproto.Entries
|
||
|
|
recordPool = newRecordPool()
|
||
|
|
)
|
||
|
|
|
||
|
|
const walSegmentSize = wal.DefaultSegmentSize * 4
|
||
|
|
|
||
|
|
type WALConfig struct {
|
||
|
|
Enabled bool `yaml:"enabled"`
|
||
|
|
Dir string `yaml:"dir"`
|
||
|
|
Recover bool `yaml:"recover"`
|
||
|
|
CheckpointDuration time.Duration `yaml:"checkpoint_duration"`
|
||
|
|
FlushOnShutdown bool `yaml:"flush_on_shutdown"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (cfg *WALConfig) Validate() error {
|
||
|
|
if cfg.Enabled && cfg.CheckpointDuration < 1 {
|
||
|
|
return errors.Errorf("invalid checkpoint duration: %v", cfg.CheckpointDuration)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// RegisterFlags adds the flags required to config this to the given FlagSet
|
||
|
|
func (cfg *WALConfig) RegisterFlags(f *flag.FlagSet) {
|
||
|
|
f.StringVar(&cfg.Dir, "ingester.wal-dir", "wal", "Directory to store the WAL and/or recover from WAL.")
|
||
|
|
f.BoolVar(&cfg.Enabled, "ingester.wal-enabled", false, "Enable writing of ingested data into WAL.")
|
||
|
|
f.BoolVar(&cfg.Recover, "ingester.recover-from-wal", false, "Recover data from existing WAL irrespective of WAL enabled/disabled.")
|
||
|
|
f.DurationVar(&cfg.CheckpointDuration, "ingester.checkpoint-duration", 5*time.Minute, "Interval at which checkpoints should be created.")
|
||
|
|
f.BoolVar(&cfg.FlushOnShutdown, "ingester.flush-on-shutdown", false, "When WAL is enabled, should chunks be flushed to long-term storage on shutdown.")
|
||
|
|
}
|
||
|
|
|
||
|
|
// WAL interface allows us to have a no-op WAL when the WAL is disabled.
|
||
|
|
type WAL interface {
|
||
|
|
// Log marshalls the records and writes it into the WAL.
|
||
|
|
Log(*WALRecord) error
|
||
|
|
// Stop stops all the WAL operations.
|
||
|
|
Stop() error
|
||
|
|
}
|
||
|
|
|
||
|
|
type noopWAL struct{}
|
||
|
|
|
||
|
|
func (noopWAL) Log(*WALRecord) error { return nil }
|
||
|
|
func (noopWAL) Stop() error { return nil }
|
||
|
|
|
||
|
|
type walWrapper struct {
|
||
|
|
cfg WALConfig
|
||
|
|
wal *wal.WAL
|
||
|
|
metrics *ingesterMetrics
|
||
|
|
seriesIter SeriesIter
|
||
|
|
|
||
|
|
wait sync.WaitGroup
|
||
|
|
quit chan struct{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// newWAL creates a WAL object. If the WAL is disabled, then the returned WAL is a no-op WAL.
|
||
|
|
func newWAL(cfg WALConfig, registerer prometheus.Registerer, metrics *ingesterMetrics, seriesIter SeriesIter) (WAL, error) {
|
||
|
|
if !cfg.Enabled {
|
||
|
|
return noopWAL{}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
tsdbWAL, err := wal.NewSize(util.Logger, registerer, cfg.Dir, walSegmentSize, false)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
w := &walWrapper{
|
||
|
|
cfg: cfg,
|
||
|
|
quit: make(chan struct{}),
|
||
|
|
wal: tsdbWAL,
|
||
|
|
metrics: metrics,
|
||
|
|
seriesIter: seriesIter,
|
||
|
|
}
|
||
|
|
|
||
|
|
w.wait.Add(1)
|
||
|
|
go w.run()
|
||
|
|
return w, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *walWrapper) Log(record *WALRecord) error {
|
||
|
|
if record == nil || (len(record.Series) == 0 && len(record.RefEntries) == 0) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-w.quit:
|
||
|
|
return nil
|
||
|
|
default:
|
||
|
|
buf := recordPool.GetBytes()[:0]
|
||
|
|
defer func() {
|
||
|
|
recordPool.PutBytes(buf)
|
||
|
|
}()
|
||
|
|
|
||
|
|
// Always write series then entries.
|
||
|
|
if len(record.Series) > 0 {
|
||
|
|
buf = record.encodeSeries(buf)
|
||
|
|
if err := w.wal.Log(buf); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
w.metrics.walRecordsLogged.Inc()
|
||
|
|
w.metrics.walLoggedBytesTotal.Add(float64(len(buf)))
|
||
|
|
buf = buf[:0]
|
||
|
|
}
|
||
|
|
if len(record.RefEntries) > 0 {
|
||
|
|
buf = record.encodeEntries(buf)
|
||
|
|
if err := w.wal.Log(buf); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
w.metrics.walRecordsLogged.Inc()
|
||
|
|
w.metrics.walLoggedBytesTotal.Add(float64(len(buf)))
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *walWrapper) Stop() error {
|
||
|
|
close(w.quit)
|
||
|
|
w.wait.Wait()
|
||
|
|
err := w.wal.Close()
|
||
|
|
level.Info(util.Logger).Log("msg", "stopped", "component", "wal")
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *walWrapper) checkpointWriter() *WALCheckpointWriter {
|
||
|
|
return &WALCheckpointWriter{
|
||
|
|
metrics: w.metrics,
|
||
|
|
segmentWAL: w.wal,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *walWrapper) run() {
|
||
|
|
level.Info(util.Logger).Log("msg", "started", "component", "wal")
|
||
|
|
defer w.wait.Done()
|
||
|
|
|
||
|
|
checkpointer := NewCheckpointer(
|
||
|
|
w.cfg.CheckpointDuration,
|
||
|
|
w.seriesIter,
|
||
|
|
w.checkpointWriter(),
|
||
|
|
w.metrics,
|
||
|
|
w.quit,
|
||
|
|
)
|
||
|
|
checkpointer.Run()
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
type resettingPool struct {
|
||
|
|
rPool *sync.Pool // records
|
||
|
|
ePool *sync.Pool // entries
|
||
|
|
bPool *sync.Pool // bytes
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) GetRecord() *WALRecord {
|
||
|
|
rec := p.rPool.Get().(*WALRecord)
|
||
|
|
rec.Reset()
|
||
|
|
return rec
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) PutRecord(r *WALRecord) {
|
||
|
|
p.rPool.Put(r)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) GetEntries() []logproto.Entry {
|
||
|
|
return p.ePool.Get().([]logproto.Entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) PutEntries(es []logproto.Entry) {
|
||
|
|
p.ePool.Put(es[:0]) // nolint:staticcheck
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) GetBytes() []byte {
|
||
|
|
return p.bPool.Get().([]byte)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *resettingPool) PutBytes(b []byte) {
|
||
|
|
p.bPool.Put(b[:0]) // nolint:staticcheck
|
||
|
|
}
|
||
|
|
|
||
|
|
func newRecordPool() *resettingPool {
|
||
|
|
return &resettingPool{
|
||
|
|
rPool: &sync.Pool{
|
||
|
|
New: func() interface{} {
|
||
|
|
return &WALRecord{}
|
||
|
|
},
|
||
|
|
},
|
||
|
|
ePool: &sync.Pool{
|
||
|
|
New: func() interface{} {
|
||
|
|
return make([]logproto.Entry, 0, 512)
|
||
|
|
},
|
||
|
|
},
|
||
|
|
bPool: &sync.Pool{
|
||
|
|
New: func() interface{} {
|
||
|
|
return make([]byte, 0, 1<<10) // 1kb
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|