mirror of
https://github.com/Oxalide/vsphere-influxdb-go.git
synced 2023-10-10 13:36:51 +02:00
989 lines
26 KiB
Go
989 lines
26 KiB
Go
/*
|
||
Package inmem implements a shared, in-memory index for each database.
|
||
|
||
The in-memory index is the original index implementation and provides fast
|
||
access to index data. However, it also forces high memory usage for large
|
||
datasets and can cause OOM errors.
|
||
|
||
Index is the shared index structure that provides most of the functionality.
|
||
However, ShardIndex is a light per-shard wrapper that adapts this original
|
||
shared index format to the new per-shard format.
|
||
*/
|
||
package inmem
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
"sort"
|
||
"sync"
|
||
// "sync/atomic"
|
||
|
||
"github.com/influxdata/influxdb/influxql"
|
||
"github.com/influxdata/influxdb/models"
|
||
"github.com/influxdata/influxdb/pkg/bytesutil"
|
||
"github.com/influxdata/influxdb/pkg/escape"
|
||
"github.com/influxdata/influxdb/pkg/estimator"
|
||
"github.com/influxdata/influxdb/pkg/estimator/hll"
|
||
"github.com/influxdata/influxdb/tsdb"
|
||
"github.com/uber-go/zap"
|
||
)
|
||
|
||
// IndexName is the name of this index.
|
||
const IndexName = "inmem"
|
||
|
||
func init() {
|
||
tsdb.NewInmemIndex = func(name string) (interface{}, error) { return NewIndex(name), nil }
|
||
|
||
tsdb.RegisterIndex(IndexName, func(id uint64, database, path string, opt tsdb.EngineOptions) tsdb.Index {
|
||
return NewShardIndex(id, database, path, opt)
|
||
})
|
||
}
|
||
|
||
// Index is the in memory index of a collection of measurements, time
|
||
// series, and their tags. Exported functions are goroutine safe while
|
||
// un-exported functions assume the caller will use the appropriate locks.
|
||
type Index struct {
|
||
mu sync.RWMutex
|
||
|
||
database string
|
||
|
||
// In-memory metadata index, built on load and updated when new series come in
|
||
measurements map[string]*Measurement // measurement name to object and index
|
||
series map[string]*Series // map series key to the Series object
|
||
lastID uint64 // last used series ID. They're in memory only for this shard
|
||
|
||
seriesSketch, seriesTSSketch *hll.Plus
|
||
measurementsSketch, measurementsTSSketch *hll.Plus
|
||
}
|
||
|
||
// NewIndex returns a new initialized Index.
|
||
func NewIndex(database string) *Index {
|
||
index := &Index{
|
||
database: database,
|
||
measurements: make(map[string]*Measurement),
|
||
series: make(map[string]*Series),
|
||
}
|
||
|
||
index.seriesSketch = hll.NewDefaultPlus()
|
||
index.seriesTSSketch = hll.NewDefaultPlus()
|
||
index.measurementsSketch = hll.NewDefaultPlus()
|
||
index.measurementsTSSketch = hll.NewDefaultPlus()
|
||
|
||
return index
|
||
}
|
||
|
||
func (i *Index) Type() string { return IndexName }
|
||
func (i *Index) Open() (err error) { return nil }
|
||
func (i *Index) Close() error { return nil }
|
||
|
||
func (i *Index) WithLogger(zap.Logger) {}
|
||
|
||
// Series returns a series by key.
|
||
func (i *Index) Series(key []byte) (*Series, error) {
|
||
i.mu.RLock()
|
||
s := i.series[string(key)]
|
||
i.mu.RUnlock()
|
||
return s, nil
|
||
}
|
||
|
||
// SeriesSketches returns the sketches for the series.
|
||
func (i *Index) SeriesSketches() (estimator.Sketch, estimator.Sketch, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
return i.seriesSketch.Clone(), i.seriesTSSketch.Clone(), nil
|
||
}
|
||
|
||
// SeriesN returns the number of unique non-tombstoned series in the index.
|
||
// Since indexes are not shared across shards, the count returned by SeriesN
|
||
// cannot be combined with other shards' counts.
|
||
func (i *Index) SeriesN() int64 {
|
||
i.mu.RLock()
|
||
n := int64(len(i.series))
|
||
i.mu.RUnlock()
|
||
return n
|
||
}
|
||
|
||
// Measurement returns the measurement object from the index by the name
|
||
func (i *Index) Measurement(name []byte) (*Measurement, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
return i.measurements[string(name)], nil
|
||
}
|
||
|
||
// MeasurementExists returns true if the measurement exists.
|
||
func (i *Index) MeasurementExists(name []byte) (bool, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
return i.measurements[string(name)] != nil, nil
|
||
}
|
||
|
||
// MeasurementsSketches returns the sketches for the measurements.
|
||
func (i *Index) MeasurementsSketches() (estimator.Sketch, estimator.Sketch, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
return i.measurementsSketch.Clone(), i.measurementsTSSketch.Clone(), nil
|
||
}
|
||
|
||
// MeasurementsByName returns a list of measurements.
|
||
func (i *Index) MeasurementsByName(names [][]byte) ([]*Measurement, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
a := make([]*Measurement, 0, len(names))
|
||
for _, name := range names {
|
||
if m := i.measurements[string(name)]; m != nil {
|
||
a = append(a, m)
|
||
}
|
||
}
|
||
return a, nil
|
||
}
|
||
|
||
// CreateSeriesIfNotExists adds the series for the given measurement to the
|
||
// index and sets its ID or returns the existing series object
|
||
func (i *Index) CreateSeriesIfNotExists(shardID uint64, key, name []byte, tags models.Tags, opt *tsdb.EngineOptions, ignoreLimits bool) error {
|
||
i.mu.RLock()
|
||
// if there is a series for this id, it's already been added
|
||
ss := i.series[string(key)]
|
||
i.mu.RUnlock()
|
||
|
||
if ss != nil {
|
||
ss.AssignShard(shardID)
|
||
return nil
|
||
}
|
||
|
||
// get or create the measurement index
|
||
m := i.CreateMeasurementIndexIfNotExists(name)
|
||
|
||
i.mu.Lock()
|
||
// Check for the series again under a write lock
|
||
ss = i.series[string(key)]
|
||
if ss != nil {
|
||
i.mu.Unlock()
|
||
ss.AssignShard(shardID)
|
||
return nil
|
||
}
|
||
|
||
// Verify that the series will not exceed limit.
|
||
if !ignoreLimits {
|
||
if max := opt.Config.MaxSeriesPerDatabase; max > 0 && len(i.series)+1 > max {
|
||
i.mu.Unlock()
|
||
return errMaxSeriesPerDatabaseExceeded
|
||
}
|
||
}
|
||
|
||
// set the in memory ID for query processing on this shard
|
||
// The series key and tags are clone to prevent a memory leak
|
||
series := NewSeries([]byte(string(key)), tags.Clone())
|
||
series.ID = i.lastID + 1
|
||
i.lastID++
|
||
|
||
series.SetMeasurement(m)
|
||
i.series[string(key)] = series
|
||
|
||
m.AddSeries(series)
|
||
series.AssignShard(shardID)
|
||
|
||
// Add the series to the series sketch.
|
||
i.seriesSketch.Add(key)
|
||
i.mu.Unlock()
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateMeasurementIndexIfNotExists creates or retrieves an in memory index
|
||
// object for the measurement
|
||
func (i *Index) CreateMeasurementIndexIfNotExists(name []byte) *Measurement {
|
||
name = escape.Unescape(name)
|
||
|
||
// See if the measurement exists using a read-lock
|
||
i.mu.RLock()
|
||
m := i.measurements[string(name)]
|
||
if m != nil {
|
||
i.mu.RUnlock()
|
||
return m
|
||
}
|
||
i.mu.RUnlock()
|
||
|
||
// Doesn't exist, so lock the index to create it
|
||
i.mu.Lock()
|
||
defer i.mu.Unlock()
|
||
|
||
// Make sure it was created in between the time we released our read-lock
|
||
// and acquire the write lock
|
||
m = i.measurements[string(name)]
|
||
if m == nil {
|
||
m = NewMeasurement(i.database, string(name))
|
||
i.measurements[string(name)] = m
|
||
|
||
// Add the measurement to the measurements sketch.
|
||
i.measurementsSketch.Add([]byte(name))
|
||
}
|
||
return m
|
||
}
|
||
|
||
// HasTagKey returns true if tag key exists.
|
||
func (i *Index) HasTagKey(name, key []byte) (bool, error) {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return false, nil
|
||
}
|
||
return mm.HasTagKey(string(key)), nil
|
||
}
|
||
|
||
// HasTagValue returns true if tag value exists.
|
||
func (i *Index) HasTagValue(name, key, value []byte) bool {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return false
|
||
}
|
||
return mm.HasTagKeyValue(key, value)
|
||
}
|
||
|
||
// TagValueN returns the cardinality of a tag value.
|
||
func (i *Index) TagValueN(name, key []byte) int {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return 0
|
||
}
|
||
return mm.CardinalityBytes(key)
|
||
}
|
||
|
||
// MeasurementTagKeysByExpr returns an ordered set of tag keys filtered by an expression.
|
||
func (i *Index) MeasurementTagKeysByExpr(name []byte, expr influxql.Expr) (map[string]struct{}, error) {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return nil, nil
|
||
}
|
||
return mm.TagKeysByExpr(expr)
|
||
}
|
||
|
||
// MeasurementTagKeyValuesByExpr returns a set of tag values filtered by an expression.
|
||
//
|
||
// See tsm1.Engine.MeasurementTagKeyValuesByExpr for a fuller description of this
|
||
// method.
|
||
func (i *Index) MeasurementTagKeyValuesByExpr(name []byte, keys []string, expr influxql.Expr, keysSorted bool) ([][]string, error) {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil || len(keys) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
results := make([][]string, len(keys))
|
||
|
||
// If we haven't been provided sorted keys, then we need to sort them.
|
||
if !keysSorted {
|
||
sort.Sort(sort.StringSlice(keys))
|
||
}
|
||
|
||
ids, _, _ := mm.WalkWhereForSeriesIds(expr)
|
||
if ids.Len() == 0 && expr == nil {
|
||
for ki, key := range keys {
|
||
values := mm.TagValues(key)
|
||
sort.Sort(sort.StringSlice(values))
|
||
results[ki] = values
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
// This is the case where we have filtered series by some WHERE condition.
|
||
// We only care about the tag values for the keys given the
|
||
// filtered set of series ids.
|
||
|
||
keyIdxs := make(map[string]int, len(keys))
|
||
for ki, key := range keys {
|
||
keyIdxs[key] = ki
|
||
}
|
||
|
||
resultSet := make([]stringSet, len(keys))
|
||
for i := 0; i < len(resultSet); i++ {
|
||
resultSet[i] = newStringSet()
|
||
}
|
||
|
||
// Iterate all series to collect tag values.
|
||
for _, id := range ids {
|
||
s := mm.SeriesByID(id)
|
||
if s == nil {
|
||
continue
|
||
}
|
||
|
||
// Iterate the tag keys we're interested in and collect values
|
||
// from this series, if they exist.
|
||
for _, t := range s.Tags() {
|
||
if idx, ok := keyIdxs[string(t.Key)]; ok {
|
||
resultSet[idx].add(string(t.Value))
|
||
} else if string(t.Key) > keys[len(keys)-1] {
|
||
// The tag key is > the largest key we're interested in.
|
||
break
|
||
}
|
||
}
|
||
}
|
||
for i, s := range resultSet {
|
||
results[i] = s.list()
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
// ForEachMeasurementTagKey iterates over all tag keys for a measurement.
|
||
func (i *Index) ForEachMeasurementTagKey(name []byte, fn func(key []byte) error) error {
|
||
// Ensure we do not hold a lock on the index while fn executes in case fn tries
|
||
// to acquire a lock on the index again. If another goroutine has Lock, this will
|
||
// deadlock.
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return nil
|
||
}
|
||
|
||
for _, key := range mm.TagKeys() {
|
||
if err := fn([]byte(key)); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// TagKeyCardinality returns the number of values for a measurement/tag key.
|
||
func (i *Index) TagKeyCardinality(name, key []byte) int {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return 0
|
||
}
|
||
return mm.CardinalityBytes(key)
|
||
}
|
||
|
||
// TagsForSeries returns the tag map for the passed in series
|
||
func (i *Index) TagsForSeries(key string) (models.Tags, error) {
|
||
i.mu.RLock()
|
||
ss := i.series[key]
|
||
i.mu.RUnlock()
|
||
|
||
if ss == nil {
|
||
return nil, nil
|
||
}
|
||
return ss.Tags(), nil
|
||
}
|
||
|
||
// MeasurementNamesByExpr takes an expression containing only tags and returns a
|
||
// list of matching meaurement names.
|
||
func (i *Index) MeasurementNamesByExpr(expr influxql.Expr) ([][]byte, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
// Return all measurement names if no expression is provided.
|
||
if expr == nil {
|
||
a := make([][]byte, 0, len(i.measurements))
|
||
for name := range i.measurements {
|
||
a = append(a, []byte(name))
|
||
}
|
||
bytesutil.Sort(a)
|
||
return a, nil
|
||
}
|
||
|
||
return i.measurementNamesByExpr(expr)
|
||
}
|
||
|
||
func (i *Index) measurementNamesByExpr(expr influxql.Expr) ([][]byte, error) {
|
||
if expr == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
switch e := expr.(type) {
|
||
case *influxql.BinaryExpr:
|
||
switch e.Op {
|
||
case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX:
|
||
tag, ok := e.LHS.(*influxql.VarRef)
|
||
if !ok {
|
||
return nil, fmt.Errorf("left side of '%s' must be a tag key", e.Op.String())
|
||
}
|
||
|
||
tf := &TagFilter{
|
||
Op: e.Op,
|
||
Key: tag.Val,
|
||
}
|
||
|
||
if influxql.IsRegexOp(e.Op) {
|
||
re, ok := e.RHS.(*influxql.RegexLiteral)
|
||
if !ok {
|
||
return nil, fmt.Errorf("right side of '%s' must be a regular expression", e.Op.String())
|
||
}
|
||
tf.Regex = re.Val
|
||
} else {
|
||
s, ok := e.RHS.(*influxql.StringLiteral)
|
||
if !ok {
|
||
return nil, fmt.Errorf("right side of '%s' must be a tag value string", e.Op.String())
|
||
}
|
||
tf.Value = s.Val
|
||
}
|
||
|
||
// Match on name, if specified.
|
||
if tag.Val == "_name" {
|
||
return i.measurementNamesByNameFilter(tf.Op, tf.Value, tf.Regex), nil
|
||
} else if influxql.IsSystemName(tag.Val) {
|
||
return nil, nil
|
||
}
|
||
|
||
return i.measurementNamesByTagFilters(tf), nil
|
||
case influxql.OR, influxql.AND:
|
||
lhs, err := i.measurementNamesByExpr(e.LHS)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
rhs, err := i.measurementNamesByExpr(e.RHS)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if e.Op == influxql.OR {
|
||
return bytesutil.Union(lhs, rhs), nil
|
||
}
|
||
return bytesutil.Intersect(lhs, rhs), nil
|
||
default:
|
||
return nil, fmt.Errorf("invalid tag comparison operator")
|
||
}
|
||
case *influxql.ParenExpr:
|
||
return i.measurementNamesByExpr(e.Expr)
|
||
}
|
||
return nil, fmt.Errorf("%#v", expr)
|
||
}
|
||
|
||
// measurementNamesByNameFilter returns the sorted measurements matching a name.
|
||
func (i *Index) measurementNamesByNameFilter(op influxql.Token, val string, regex *regexp.Regexp) [][]byte {
|
||
var names [][]byte
|
||
for _, m := range i.measurements {
|
||
var matched bool
|
||
switch op {
|
||
case influxql.EQ:
|
||
matched = m.Name == val
|
||
case influxql.NEQ:
|
||
matched = m.Name != val
|
||
case influxql.EQREGEX:
|
||
matched = regex.MatchString(m.Name)
|
||
case influxql.NEQREGEX:
|
||
matched = !regex.MatchString(m.Name)
|
||
}
|
||
|
||
if !matched {
|
||
continue
|
||
}
|
||
names = append(names, []byte(m.Name))
|
||
}
|
||
bytesutil.Sort(names)
|
||
return names
|
||
}
|
||
|
||
// measurementNamesByTagFilters returns the sorted measurements matching the filters on tag values.
|
||
func (i *Index) measurementNamesByTagFilters(filter *TagFilter) [][]byte {
|
||
// Build a list of measurements matching the filters.
|
||
var names [][]byte
|
||
var tagMatch bool
|
||
|
||
// Iterate through all measurements in the database.
|
||
for _, m := range i.measurements {
|
||
tagVals := m.SeriesByTagKeyValue(filter.Key)
|
||
if tagVals == nil {
|
||
continue
|
||
}
|
||
|
||
tagMatch = false
|
||
|
||
// If the operator is non-regex, only check the specified value.
|
||
if filter.Op == influxql.EQ || filter.Op == influxql.NEQ {
|
||
if _, ok := tagVals[filter.Value]; ok {
|
||
tagMatch = true
|
||
}
|
||
} else {
|
||
// Else, the operator is a regex and we have to check all tag
|
||
// values against the regular expression.
|
||
for tagVal := range tagVals {
|
||
if filter.Regex.MatchString(tagVal) {
|
||
tagMatch = true
|
||
continue
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// XNOR gate
|
||
//
|
||
// tags match | operation is EQ | measurement matches
|
||
// --------------------------------------------------
|
||
// True | True | True
|
||
// True | False | False
|
||
// False | True | False
|
||
// False | False | True
|
||
if tagMatch == (filter.Op == influxql.EQ || filter.Op == influxql.EQREGEX) {
|
||
names = append(names, []byte(m.Name))
|
||
continue
|
||
}
|
||
}
|
||
|
||
bytesutil.Sort(names)
|
||
return names
|
||
}
|
||
|
||
// MeasurementNamesByRegex returns the measurements that match the regex.
|
||
func (i *Index) MeasurementNamesByRegex(re *regexp.Regexp) ([][]byte, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
var matches [][]byte
|
||
for _, m := range i.measurements {
|
||
if re.MatchString(m.Name) {
|
||
matches = append(matches, []byte(m.Name))
|
||
}
|
||
}
|
||
return matches, nil
|
||
}
|
||
|
||
// DropMeasurement removes the measurement and all of its underlying
|
||
// series from the database index
|
||
func (i *Index) DropMeasurement(name []byte) error {
|
||
i.mu.Lock()
|
||
defer i.mu.Unlock()
|
||
return i.dropMeasurement(string(name))
|
||
}
|
||
|
||
func (i *Index) dropMeasurement(name string) error {
|
||
// Update the tombstone sketch.
|
||
i.measurementsTSSketch.Add([]byte(name))
|
||
|
||
m := i.measurements[name]
|
||
if m == nil {
|
||
return nil
|
||
}
|
||
|
||
delete(i.measurements, name)
|
||
for _, s := range m.SeriesByIDMap() {
|
||
delete(i.series, s.Key)
|
||
i.seriesTSSketch.Add([]byte(s.Key))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DropSeries removes the series key and its tags from the index.
|
||
func (i *Index) DropSeries(key []byte) error {
|
||
if key == nil {
|
||
return nil
|
||
}
|
||
|
||
i.mu.Lock()
|
||
k := string(key)
|
||
series := i.series[k]
|
||
if series == nil {
|
||
i.mu.Unlock()
|
||
return nil
|
||
}
|
||
|
||
// Update the tombstone sketch.
|
||
i.seriesTSSketch.Add([]byte(k))
|
||
|
||
// Remove from the index.
|
||
delete(i.series, k)
|
||
|
||
// Remove the measurement's reference.
|
||
series.Measurement().DropSeries(series)
|
||
|
||
// If the measurement no longer has any series, remove it as well.
|
||
if !series.Measurement().HasSeries() {
|
||
i.dropMeasurement(series.Measurement().Name)
|
||
}
|
||
i.mu.Unlock()
|
||
|
||
return nil
|
||
}
|
||
|
||
// ForEachMeasurementSeriesByExpr iterates over all series in a measurement filtered by an expression.
|
||
func (i *Index) ForEachMeasurementSeriesByExpr(name []byte, expr influxql.Expr, fn func(tags models.Tags) error) error {
|
||
i.mu.RLock()
|
||
mm := i.measurements[string(name)]
|
||
i.mu.RUnlock()
|
||
|
||
if mm == nil {
|
||
return nil
|
||
}
|
||
|
||
if err := mm.ForEachSeriesByExpr(expr, fn); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// TagSets returns a list of tag sets.
|
||
func (i *Index) TagSets(shardID uint64, name []byte, opt influxql.IteratorOptions) ([]*influxql.TagSet, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
mm := i.measurements[string(name)]
|
||
if mm == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
tagSets, err := mm.TagSets(shardID, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return tagSets, nil
|
||
}
|
||
|
||
func (i *Index) SeriesKeys() []string {
|
||
i.mu.RLock()
|
||
s := make([]string, 0, len(i.series))
|
||
for k := range i.series {
|
||
s = append(s, k)
|
||
}
|
||
i.mu.RUnlock()
|
||
return s
|
||
}
|
||
|
||
// SetFieldSet sets a shared field set from the engine.
|
||
func (i *Index) SetFieldSet(*tsdb.MeasurementFieldSet) {}
|
||
|
||
// SetFieldName adds a field name to a measurement.
|
||
func (i *Index) SetFieldName(measurement []byte, name string) {
|
||
m := i.CreateMeasurementIndexIfNotExists(measurement)
|
||
m.SetFieldName(name)
|
||
}
|
||
|
||
// ForEachMeasurementName iterates over each measurement name.
|
||
func (i *Index) ForEachMeasurementName(fn func(name []byte) error) error {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
mms := make(Measurements, 0, len(i.measurements))
|
||
for _, m := range i.measurements {
|
||
mms = append(mms, m)
|
||
}
|
||
sort.Sort(mms)
|
||
|
||
for _, m := range mms {
|
||
if err := fn([]byte(m.Name)); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (i *Index) MeasurementSeriesKeysByExpr(name []byte, condition influxql.Expr) ([][]byte, error) {
|
||
i.mu.RLock()
|
||
defer i.mu.RUnlock()
|
||
|
||
m := i.measurements[string(name)]
|
||
if m == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
// Return all series if no condition specified.
|
||
if condition == nil {
|
||
return m.SeriesKeys(), nil
|
||
}
|
||
|
||
// Get series IDs that match the WHERE clause.
|
||
ids, filters, err := m.WalkWhereForSeriesIds(condition)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Delete boolean literal true filter expressions.
|
||
// These are returned for `WHERE tagKey = 'tagVal'` type expressions and are okay.
|
||
filters.DeleteBoolLiteralTrues()
|
||
|
||
// Check for unsupported field filters.
|
||
// Any remaining filters means there were fields (e.g., `WHERE value = 1.2`).
|
||
if filters.Len() > 0 {
|
||
return nil, errors.New("fields not supported in WHERE clause during deletion")
|
||
}
|
||
|
||
return m.SeriesKeysByID(ids), nil
|
||
}
|
||
|
||
// SeriesPointIterator returns an influxql iterator over all series.
|
||
func (i *Index) SeriesPointIterator(opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
// Read and sort all measurements.
|
||
mms := make(Measurements, 0, len(i.measurements))
|
||
for _, mm := range i.measurements {
|
||
mms = append(mms, mm)
|
||
}
|
||
sort.Sort(mms)
|
||
|
||
return &seriesPointIterator{
|
||
mms: mms,
|
||
point: influxql.FloatPoint{
|
||
Aux: make([]interface{}, len(opt.Aux)),
|
||
},
|
||
opt: opt,
|
||
}, nil
|
||
}
|
||
|
||
// SnapshotTo is a no-op since this is an in-memory index.
|
||
func (i *Index) SnapshotTo(path string) error { return nil }
|
||
|
||
// AssignShard update the index to indicate that series k exists in the given shardID.
|
||
func (i *Index) AssignShard(k string, shardID uint64) {
|
||
ss, _ := i.Series([]byte(k))
|
||
if ss != nil {
|
||
ss.AssignShard(shardID)
|
||
}
|
||
}
|
||
|
||
// UnassignShard updates the index to indicate that series k does not exist in
|
||
// the given shardID.
|
||
func (i *Index) UnassignShard(k string, shardID uint64) error {
|
||
ss, _ := i.Series([]byte(k))
|
||
if ss != nil {
|
||
if ss.Assigned(shardID) {
|
||
// Remove the shard from any series
|
||
ss.UnassignShard(shardID)
|
||
|
||
// If this series no longer has shards assigned, remove the series
|
||
if ss.ShardN() == 0 {
|
||
// Remove the series key from the index.
|
||
return i.DropSeries([]byte(k))
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// RemoveShard removes all references to shardID from any series or measurements
|
||
// in the index. If the shard was the only owner of data for the series, the series
|
||
// is removed from the index.
|
||
func (i *Index) RemoveShard(shardID uint64) {
|
||
for _, k := range i.SeriesKeys() {
|
||
i.UnassignShard(k, shardID)
|
||
}
|
||
}
|
||
|
||
// assignExistingSeries assigns the existings series to shardID and returns the series, names and tags that
|
||
// do not exists yet.
|
||
func (i *Index) assignExistingSeries(shardID uint64, keys, names [][]byte, tagsSlice []models.Tags) ([][]byte, [][]byte, []models.Tags) {
|
||
i.mu.RLock()
|
||
var n int
|
||
for j, key := range keys {
|
||
if ss, ok := i.series[string(key)]; !ok {
|
||
keys[n] = keys[j]
|
||
names[n] = names[j]
|
||
tagsSlice[n] = tagsSlice[j]
|
||
n++
|
||
} else {
|
||
ss.AssignShard(shardID)
|
||
}
|
||
}
|
||
i.mu.RUnlock()
|
||
return keys[:n], names[:n], tagsSlice[:n]
|
||
}
|
||
|
||
// Ensure index implements interface.
|
||
var _ tsdb.Index = &ShardIndex{}
|
||
|
||
// ShardIndex represents a shim between the TSDB index interface and the shared
|
||
// in-memory index. This is required because per-shard in-memory indexes will
|
||
// grow the heap size too large.
|
||
type ShardIndex struct {
|
||
*Index
|
||
|
||
id uint64 // shard id
|
||
opt tsdb.EngineOptions
|
||
}
|
||
|
||
// CreateSeriesListIfNotExists creates a list of series if they doesn't exist in bulk.
|
||
func (idx *ShardIndex) CreateSeriesListIfNotExists(keys, names [][]byte, tagsSlice []models.Tags) error {
|
||
keys, names, tagsSlice = idx.assignExistingSeries(idx.id, keys, names, tagsSlice)
|
||
if len(keys) == 0 {
|
||
return nil
|
||
}
|
||
|
||
var reason string
|
||
var dropped int
|
||
var droppedKeys map[string]struct{}
|
||
|
||
// Ensure that no tags go over the maximum cardinality.
|
||
if maxValuesPerTag := idx.opt.Config.MaxValuesPerTag; maxValuesPerTag > 0 {
|
||
var n int
|
||
|
||
outer:
|
||
for i, name := range names {
|
||
tags := tagsSlice[i]
|
||
for _, tag := range tags {
|
||
// Skip if the tag value already exists.
|
||
if idx.HasTagValue(name, tag.Key, tag.Value) {
|
||
continue
|
||
}
|
||
|
||
// Read cardinality. Skip if we're below the threshold.
|
||
n := idx.TagValueN(name, tag.Key)
|
||
if n < maxValuesPerTag {
|
||
continue
|
||
}
|
||
|
||
dropped++
|
||
reason = fmt.Sprintf("max-values-per-tag limit exceeded (%d/%d): measurement=%q tag=%q value=%q",
|
||
n, maxValuesPerTag, name, string(tag.Key), string(tag.Value))
|
||
|
||
if droppedKeys == nil {
|
||
droppedKeys = make(map[string]struct{})
|
||
}
|
||
droppedKeys[string(keys[i])] = struct{}{}
|
||
continue outer
|
||
}
|
||
|
||
// Increment success count if all checks complete.
|
||
keys[n], names[n], tagsSlice[n] = keys[i], names[i], tagsSlice[i]
|
||
n++
|
||
}
|
||
|
||
// Slice to only include successful points.
|
||
keys, names, tagsSlice = keys[:n], names[:n], tagsSlice[:n]
|
||
}
|
||
|
||
// Write
|
||
for i := range keys {
|
||
if err := idx.CreateSeriesIfNotExists(keys[i], names[i], tagsSlice[i]); err == errMaxSeriesPerDatabaseExceeded {
|
||
dropped++
|
||
reason = fmt.Sprintf("max-series-per-database limit exceeded: (%d)", idx.opt.Config.MaxSeriesPerDatabase)
|
||
if droppedKeys == nil {
|
||
droppedKeys = make(map[string]struct{})
|
||
}
|
||
droppedKeys[string(keys[i])] = struct{}{}
|
||
continue
|
||
} else if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// Report partial writes back to shard.
|
||
if dropped > 0 {
|
||
return &tsdb.PartialWriteError{
|
||
Reason: reason,
|
||
Dropped: dropped,
|
||
DroppedKeys: droppedKeys,
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// InitializeSeries is called during startup.
|
||
// This works the same as CreateSeriesIfNotExists except it ignore limit errors.
|
||
func (i *ShardIndex) InitializeSeries(key, name []byte, tags models.Tags) error {
|
||
return i.Index.CreateSeriesIfNotExists(i.id, key, name, tags, &i.opt, true)
|
||
}
|
||
|
||
func (i *ShardIndex) CreateSeriesIfNotExists(key, name []byte, tags models.Tags) error {
|
||
return i.Index.CreateSeriesIfNotExists(i.id, key, name, tags, &i.opt, false)
|
||
}
|
||
|
||
// TagSets returns a list of tag sets based on series filtering.
|
||
func (i *ShardIndex) TagSets(name []byte, opt influxql.IteratorOptions) ([]*influxql.TagSet, error) {
|
||
return i.Index.TagSets(i.id, name, opt)
|
||
}
|
||
|
||
// NewShardIndex returns a new index for a shard.
|
||
func NewShardIndex(id uint64, database, path string, opt tsdb.EngineOptions) tsdb.Index {
|
||
return &ShardIndex{
|
||
Index: opt.InmemIndex.(*Index),
|
||
id: id,
|
||
opt: opt,
|
||
}
|
||
}
|
||
|
||
// seriesPointIterator emits series as influxql points.
|
||
type seriesPointIterator struct {
|
||
mms Measurements
|
||
keys struct {
|
||
buf []string
|
||
i int
|
||
}
|
||
|
||
point influxql.FloatPoint // reusable point
|
||
opt influxql.IteratorOptions
|
||
}
|
||
|
||
// Stats returns stats about the points processed.
|
||
func (itr *seriesPointIterator) Stats() influxql.IteratorStats { return influxql.IteratorStats{} }
|
||
|
||
// Close closes the iterator.
|
||
func (itr *seriesPointIterator) Close() error { return nil }
|
||
|
||
// Next emits the next point in the iterator.
|
||
func (itr *seriesPointIterator) Next() (*influxql.FloatPoint, error) {
|
||
for {
|
||
// Load next measurement's keys if there are no more remaining.
|
||
if itr.keys.i >= len(itr.keys.buf) {
|
||
if err := itr.nextKeys(); err != nil {
|
||
return nil, err
|
||
}
|
||
if len(itr.keys.buf) == 0 {
|
||
return nil, nil
|
||
}
|
||
}
|
||
|
||
// Read the next key.
|
||
key := itr.keys.buf[itr.keys.i]
|
||
itr.keys.i++
|
||
|
||
// Write auxiliary fields.
|
||
for i, f := range itr.opt.Aux {
|
||
switch f.Val {
|
||
case "key":
|
||
itr.point.Aux[i] = key
|
||
}
|
||
}
|
||
return &itr.point, nil
|
||
}
|
||
}
|
||
|
||
// nextKeys reads all keys for the next measurement.
|
||
func (itr *seriesPointIterator) nextKeys() error {
|
||
for {
|
||
// Ensure previous keys are cleared out.
|
||
itr.keys.i, itr.keys.buf = 0, itr.keys.buf[:0]
|
||
|
||
// Read next measurement.
|
||
if len(itr.mms) == 0 {
|
||
return nil
|
||
}
|
||
mm := itr.mms[0]
|
||
itr.mms = itr.mms[1:]
|
||
|
||
// Read all series keys.
|
||
ids, err := mm.SeriesIDsAllOrByExpr(itr.opt.Condition)
|
||
if err != nil {
|
||
return err
|
||
} else if len(ids) == 0 {
|
||
continue
|
||
}
|
||
itr.keys.buf = mm.AppendSeriesKeysByID(itr.keys.buf, ids)
|
||
sort.Strings(itr.keys.buf)
|
||
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// errMaxSeriesPerDatabaseExceeded is a marker error returned during series creation
|
||
// to indicate that a new series would exceed the limits of the database.
|
||
var errMaxSeriesPerDatabaseExceeded = errors.New("max series per database exceeded")
|