mirror of
https://github.com/Oxalide/vsphere-influxdb-go.git
synced 2023-10-10 13:36:51 +02:00
1652 lines
42 KiB
Go
1652 lines
42 KiB
Go
|
package tsdb
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math"
|
||
|
"path/filepath"
|
||
|
"regexp"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
|
|
||
|
"github.com/gogo/protobuf/proto"
|
||
|
"github.com/influxdata/influxdb/influxql"
|
||
|
"github.com/influxdata/influxdb/models"
|
||
|
"github.com/influxdata/influxdb/pkg/estimator"
|
||
|
internal "github.com/influxdata/influxdb/tsdb/internal"
|
||
|
"github.com/uber-go/zap"
|
||
|
)
|
||
|
|
||
|
// monitorStatInterval is the interval at which the shard is inspected
|
||
|
// for the purpose of determining certain monitoring statistics.
|
||
|
const monitorStatInterval = 30 * time.Second
|
||
|
|
||
|
const (
|
||
|
statWriteReq = "writeReq"
|
||
|
statWriteReqOK = "writeReqOk"
|
||
|
statWriteReqErr = "writeReqErr"
|
||
|
statSeriesCreate = "seriesCreate"
|
||
|
statFieldsCreate = "fieldsCreate"
|
||
|
statWritePointsErr = "writePointsErr"
|
||
|
statWritePointsDropped = "writePointsDropped"
|
||
|
statWritePointsOK = "writePointsOk"
|
||
|
statWriteBytes = "writeBytes"
|
||
|
statDiskBytes = "diskBytes"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrFieldOverflow is returned when too many fields are created on a measurement.
|
||
|
ErrFieldOverflow = errors.New("field overflow")
|
||
|
|
||
|
// ErrFieldTypeConflict is returned when a new field already exists with a different type.
|
||
|
ErrFieldTypeConflict = errors.New("field type conflict")
|
||
|
|
||
|
// ErrFieldNotFound is returned when a field cannot be found.
|
||
|
ErrFieldNotFound = errors.New("field not found")
|
||
|
|
||
|
// ErrFieldUnmappedID is returned when the system is presented, during decode, with a field ID
|
||
|
// there is no mapping for.
|
||
|
ErrFieldUnmappedID = errors.New("field ID not mapped")
|
||
|
|
||
|
// ErrEngineClosed is returned when a caller attempts indirectly to
|
||
|
// access the shard's underlying engine.
|
||
|
ErrEngineClosed = errors.New("engine is closed")
|
||
|
|
||
|
// ErrShardDisabled is returned when a the shard is not available for
|
||
|
// queries or writes.
|
||
|
ErrShardDisabled = errors.New("shard is disabled")
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Static objects to prevent small allocs.
|
||
|
timeBytes = []byte("time")
|
||
|
)
|
||
|
|
||
|
// A ShardError implements the error interface, and contains extra
|
||
|
// context about the shard that generated the error.
|
||
|
type ShardError struct {
|
||
|
id uint64
|
||
|
Err error
|
||
|
}
|
||
|
|
||
|
// NewShardError returns a new ShardError.
|
||
|
func NewShardError(id uint64, err error) error {
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
return ShardError{id: id, Err: err}
|
||
|
}
|
||
|
|
||
|
// Error returns the string representation of the error, to satisfy the error interface.
|
||
|
func (e ShardError) Error() string {
|
||
|
return fmt.Sprintf("[shard %d] %s", e.id, e.Err)
|
||
|
}
|
||
|
|
||
|
// PartialWriteError indicates a write request could only write a portion of the
|
||
|
// requested values.
|
||
|
type PartialWriteError struct {
|
||
|
Reason string
|
||
|
Dropped int
|
||
|
|
||
|
// The set of series keys that were dropped. Can be nil.
|
||
|
DroppedKeys map[string]struct{}
|
||
|
}
|
||
|
|
||
|
func (e PartialWriteError) Error() string {
|
||
|
return fmt.Sprintf("partial write: %s dropped=%d", e.Reason, e.Dropped)
|
||
|
}
|
||
|
|
||
|
// Shard represents a self-contained time series database. An inverted index of
|
||
|
// the measurement and tag data is kept along with the raw time series data.
|
||
|
// Data can be split across many shards. The query engine in TSDB is responsible
|
||
|
// for combining the output of many shards into a single query result.
|
||
|
type Shard struct {
|
||
|
path string
|
||
|
walPath string
|
||
|
id uint64
|
||
|
|
||
|
database string
|
||
|
retentionPolicy string
|
||
|
|
||
|
options EngineOptions
|
||
|
|
||
|
mu sync.RWMutex
|
||
|
_engine Engine
|
||
|
index Index
|
||
|
|
||
|
closing chan struct{}
|
||
|
enabled bool
|
||
|
|
||
|
// expvar-based stats.
|
||
|
stats *ShardStatistics
|
||
|
defaultTags models.StatisticTags
|
||
|
|
||
|
baseLogger zap.Logger
|
||
|
logger zap.Logger
|
||
|
|
||
|
EnableOnOpen bool
|
||
|
}
|
||
|
|
||
|
// NewShard returns a new initialized Shard. walPath doesn't apply to the b1 type index
|
||
|
func NewShard(id uint64, path string, walPath string, opt EngineOptions) *Shard {
|
||
|
db, rp := decodeStorePath(path)
|
||
|
logger := zap.New(zap.NullEncoder())
|
||
|
|
||
|
s := &Shard{
|
||
|
id: id,
|
||
|
path: path,
|
||
|
walPath: walPath,
|
||
|
options: opt,
|
||
|
closing: make(chan struct{}),
|
||
|
|
||
|
stats: &ShardStatistics{},
|
||
|
defaultTags: models.StatisticTags{
|
||
|
"path": path,
|
||
|
"walPath": walPath,
|
||
|
"id": fmt.Sprintf("%d", id),
|
||
|
"database": db,
|
||
|
"retentionPolicy": rp,
|
||
|
"engine": opt.EngineVersion,
|
||
|
},
|
||
|
|
||
|
database: db,
|
||
|
retentionPolicy: rp,
|
||
|
|
||
|
logger: logger,
|
||
|
baseLogger: logger,
|
||
|
EnableOnOpen: true,
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// WithLogger sets the logger on the shard.
|
||
|
func (s *Shard) WithLogger(log zap.Logger) {
|
||
|
s.baseLogger = log
|
||
|
engine, err := s.engine()
|
||
|
if err == nil {
|
||
|
engine.WithLogger(s.baseLogger)
|
||
|
s.index.WithLogger(s.baseLogger)
|
||
|
}
|
||
|
s.logger = s.baseLogger.With(zap.String("service", "shard"))
|
||
|
}
|
||
|
|
||
|
// SetEnabled enables the shard for queries and write. When disabled, all
|
||
|
// writes and queries return an error and compactions are stopped for the shard.
|
||
|
func (s *Shard) SetEnabled(enabled bool) {
|
||
|
s.mu.Lock()
|
||
|
// Prevent writes and queries
|
||
|
s.enabled = enabled
|
||
|
if s._engine != nil {
|
||
|
// Disable background compactions and snapshotting
|
||
|
s._engine.SetEnabled(enabled)
|
||
|
}
|
||
|
s.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
// ID returns the shards ID.
|
||
|
func (s *Shard) ID() uint64 {
|
||
|
return s.id
|
||
|
}
|
||
|
|
||
|
// Database returns the database of the shard.
|
||
|
func (s *Shard) Database() string {
|
||
|
return s.database
|
||
|
}
|
||
|
|
||
|
// RetentionPolicy returns the retention policy of the shard.
|
||
|
func (s *Shard) RetentionPolicy() string {
|
||
|
return s.retentionPolicy
|
||
|
}
|
||
|
|
||
|
// ShardStatistics maintains statistics for a shard.
|
||
|
type ShardStatistics struct {
|
||
|
WriteReq int64
|
||
|
WriteReqOK int64
|
||
|
WriteReqErr int64
|
||
|
FieldsCreated int64
|
||
|
WritePointsErr int64
|
||
|
WritePointsDropped int64
|
||
|
WritePointsOK int64
|
||
|
BytesWritten int64
|
||
|
DiskBytes int64
|
||
|
}
|
||
|
|
||
|
// Statistics returns statistics for periodic monitoring.
|
||
|
func (s *Shard) Statistics(tags map[string]string) []models.Statistic {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Refresh our disk size stat
|
||
|
if _, err := s.DiskSize(); err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
seriesN := engine.SeriesN()
|
||
|
|
||
|
tags = s.defaultTags.Merge(tags)
|
||
|
statistics := []models.Statistic{{
|
||
|
Name: "shard",
|
||
|
Tags: tags,
|
||
|
Values: map[string]interface{}{
|
||
|
statWriteReq: atomic.LoadInt64(&s.stats.WriteReq),
|
||
|
statWriteReqOK: atomic.LoadInt64(&s.stats.WriteReqOK),
|
||
|
statWriteReqErr: atomic.LoadInt64(&s.stats.WriteReqErr),
|
||
|
statSeriesCreate: seriesN,
|
||
|
statFieldsCreate: atomic.LoadInt64(&s.stats.FieldsCreated),
|
||
|
statWritePointsErr: atomic.LoadInt64(&s.stats.WritePointsErr),
|
||
|
statWritePointsDropped: atomic.LoadInt64(&s.stats.WritePointsDropped),
|
||
|
statWritePointsOK: atomic.LoadInt64(&s.stats.WritePointsOK),
|
||
|
statWriteBytes: atomic.LoadInt64(&s.stats.BytesWritten),
|
||
|
statDiskBytes: atomic.LoadInt64(&s.stats.DiskBytes),
|
||
|
},
|
||
|
}}
|
||
|
|
||
|
// Add the index and engine statistics.
|
||
|
statistics = append(statistics, engine.Statistics(tags)...)
|
||
|
return statistics
|
||
|
}
|
||
|
|
||
|
// Path returns the path set on the shard when it was created.
|
||
|
func (s *Shard) Path() string { return s.path }
|
||
|
|
||
|
// Open initializes and opens the shard's store.
|
||
|
func (s *Shard) Open() error {
|
||
|
if err := func() error {
|
||
|
s.mu.Lock()
|
||
|
defer s.mu.Unlock()
|
||
|
|
||
|
// Return if the shard is already open
|
||
|
if s._engine != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Initialize underlying index.
|
||
|
ipath := filepath.Join(s.path, "index")
|
||
|
idx, err := NewIndex(s.id, s.database, ipath, s.options)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Open index.
|
||
|
if err := idx.Open(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
s.index = idx
|
||
|
idx.WithLogger(s.baseLogger)
|
||
|
|
||
|
// Initialize underlying engine.
|
||
|
e, err := NewEngine(s.id, idx, s.database, s.path, s.walPath, s.options)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Set log output on the engine.
|
||
|
e.WithLogger(s.baseLogger)
|
||
|
|
||
|
// Disable compactions while loading the index
|
||
|
e.SetEnabled(false)
|
||
|
|
||
|
// Open engine.
|
||
|
if err := e.Open(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Load metadata index for the inmem index only.
|
||
|
if err := e.LoadMetadataIndex(s.id, s.index); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
s._engine = e
|
||
|
|
||
|
return nil
|
||
|
}(); err != nil {
|
||
|
s.close(true)
|
||
|
return NewShardError(s.id, err)
|
||
|
}
|
||
|
|
||
|
if s.EnableOnOpen {
|
||
|
// enable writes, queries and compactions
|
||
|
s.SetEnabled(true)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close shuts down the shard's store.
|
||
|
func (s *Shard) Close() error {
|
||
|
s.mu.Lock()
|
||
|
defer s.mu.Unlock()
|
||
|
return s.close(true)
|
||
|
}
|
||
|
|
||
|
// CloseFast closes the shard without cleaning up the shard ID or any of the
|
||
|
// shard's series keys from the index it belongs to.
|
||
|
//
|
||
|
// CloseFast can be called when the entire index is being removed, e.g., when
|
||
|
// the database the shard belongs to is being dropped.
|
||
|
func (s *Shard) CloseFast() error {
|
||
|
s.mu.Lock()
|
||
|
defer s.mu.Unlock()
|
||
|
return s.close(false)
|
||
|
}
|
||
|
|
||
|
// close closes the shard an removes reference to the shard from associated
|
||
|
// indexes, unless clean is false.
|
||
|
func (s *Shard) close(clean bool) error {
|
||
|
if s._engine == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close the closing channel at most once.
|
||
|
select {
|
||
|
case <-s.closing:
|
||
|
default:
|
||
|
close(s.closing)
|
||
|
}
|
||
|
|
||
|
if clean {
|
||
|
// Don't leak our shard ID and series keys in the index
|
||
|
s.index.RemoveShard(s.id)
|
||
|
}
|
||
|
|
||
|
err := s._engine.Close()
|
||
|
if err == nil {
|
||
|
s._engine = nil
|
||
|
}
|
||
|
|
||
|
if e := s.index.Close(); e == nil {
|
||
|
s.index = nil
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (s *Shard) IndexType() string {
|
||
|
s.mu.RLock()
|
||
|
defer s.mu.RUnlock()
|
||
|
if err := s.ready(); err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
return s.index.Type()
|
||
|
}
|
||
|
|
||
|
// ready determines if the Shard is ready for queries or writes.
|
||
|
// It returns nil if ready, otherwise ErrShardClosed or ErrShardDiabled
|
||
|
func (s *Shard) ready() error {
|
||
|
var err error
|
||
|
if s._engine == nil {
|
||
|
err = ErrEngineClosed
|
||
|
} else if !s.enabled {
|
||
|
err = ErrShardDisabled
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// LastModified returns the time when this shard was last modified.
|
||
|
func (s *Shard) LastModified() time.Time {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return time.Time{}
|
||
|
}
|
||
|
return engine.LastModified()
|
||
|
}
|
||
|
|
||
|
// UnloadIndex removes all references to this shard from the DatabaseIndex
|
||
|
func (s *Shard) UnloadIndex() {
|
||
|
s.mu.RLock()
|
||
|
defer s.mu.RUnlock()
|
||
|
if err := s.ready(); err != nil {
|
||
|
return
|
||
|
}
|
||
|
s.index.RemoveShard(s.id)
|
||
|
}
|
||
|
|
||
|
// IsIdle return true if the shard is not receiving writes and is fully compacted.
|
||
|
func (s *Shard) IsIdle() bool {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return true
|
||
|
}
|
||
|
return engine.IsIdle()
|
||
|
}
|
||
|
|
||
|
// SetCompactionsEnabled enables or disable shard background compactions.
|
||
|
func (s *Shard) SetCompactionsEnabled(enabled bool) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
engine.SetCompactionsEnabled(enabled)
|
||
|
}
|
||
|
|
||
|
// DiskSize returns the size on disk of this shard.
|
||
|
func (s *Shard) DiskSize() (int64, error) {
|
||
|
s.mu.RLock()
|
||
|
defer s.mu.RUnlock()
|
||
|
// We don't use engine() becuase we still want to report the shard's disk
|
||
|
// size even if the shard has been disabled.
|
||
|
if s._engine == nil {
|
||
|
return 0, ErrEngineClosed
|
||
|
}
|
||
|
size := s._engine.DiskSize()
|
||
|
atomic.StoreInt64(&s.stats.DiskBytes, size)
|
||
|
return size, nil
|
||
|
}
|
||
|
|
||
|
// FieldCreate holds information for a field to create on a measurement.
|
||
|
type FieldCreate struct {
|
||
|
Measurement []byte
|
||
|
Field *Field
|
||
|
}
|
||
|
|
||
|
// WritePoints will write the raw data points and any new metadata to the index in the shard.
|
||
|
func (s *Shard) WritePoints(points []models.Point) error {
|
||
|
s.mu.RLock()
|
||
|
defer s.mu.RUnlock()
|
||
|
|
||
|
engine, err := s.engineNoLock()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var writeError error
|
||
|
atomic.AddInt64(&s.stats.WriteReq, 1)
|
||
|
|
||
|
points, fieldsToCreate, err := s.validateSeriesAndFields(points)
|
||
|
if err != nil {
|
||
|
if _, ok := err.(PartialWriteError); !ok {
|
||
|
return err
|
||
|
}
|
||
|
// There was a partial write (points dropped), hold onto the error to return
|
||
|
// to the caller, but continue on writing the remaining points.
|
||
|
writeError = err
|
||
|
}
|
||
|
atomic.AddInt64(&s.stats.FieldsCreated, int64(len(fieldsToCreate)))
|
||
|
|
||
|
// add any new fields and keep track of what needs to be saved
|
||
|
if err := s.createFieldsAndMeasurements(fieldsToCreate); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Write to the engine.
|
||
|
if err := engine.WritePoints(points); err != nil {
|
||
|
atomic.AddInt64(&s.stats.WritePointsErr, int64(len(points)))
|
||
|
atomic.AddInt64(&s.stats.WriteReqErr, 1)
|
||
|
return fmt.Errorf("engine: %s", err)
|
||
|
}
|
||
|
atomic.AddInt64(&s.stats.WritePointsOK, int64(len(points)))
|
||
|
atomic.AddInt64(&s.stats.WriteReqOK, 1)
|
||
|
|
||
|
return writeError
|
||
|
}
|
||
|
|
||
|
// validateSeriesAndFields checks which series and fields are new and whose metadata should be saved and indexed.
|
||
|
func (s *Shard) validateSeriesAndFields(points []models.Point) ([]models.Point, []*FieldCreate, error) {
|
||
|
var (
|
||
|
fieldsToCreate []*FieldCreate
|
||
|
err error
|
||
|
dropped int
|
||
|
reason string // only first error reason is set unless returned from CreateSeriesListIfNotExists
|
||
|
)
|
||
|
|
||
|
// Create all series against the index in bulk.
|
||
|
keys := make([][]byte, len(points))
|
||
|
names := make([][]byte, len(points))
|
||
|
tagsSlice := make([]models.Tags, len(points))
|
||
|
|
||
|
// Drop any series w/ a "time" tag, these are illegal
|
||
|
var j int
|
||
|
for i, p := range points {
|
||
|
tags := p.Tags()
|
||
|
if v := tags.Get(timeBytes); v != nil {
|
||
|
dropped++
|
||
|
if reason == "" {
|
||
|
reason = fmt.Sprintf("invalid tag key: input tag \"%s\" on measurement \"%s\" is invalid", "time", string(p.Name()))
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
keys[j] = p.Key()
|
||
|
names[j] = p.Name()
|
||
|
tagsSlice[j] = tags
|
||
|
points[j] = points[i]
|
||
|
j++
|
||
|
}
|
||
|
points, keys, names, tagsSlice = points[:j], keys[:j], names[:j], tagsSlice[:j]
|
||
|
|
||
|
engine, err := s.engineNoLock()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
// Add new series. Check for partial writes.
|
||
|
var droppedKeys map[string]struct{}
|
||
|
if err := engine.CreateSeriesListIfNotExists(keys, names, tagsSlice); err != nil {
|
||
|
switch err := err.(type) {
|
||
|
case *PartialWriteError:
|
||
|
reason = err.Reason
|
||
|
dropped += err.Dropped
|
||
|
droppedKeys = err.DroppedKeys
|
||
|
atomic.AddInt64(&s.stats.WritePointsDropped, int64(err.Dropped))
|
||
|
default:
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get the shard mutex for locally defined fields
|
||
|
n := 0
|
||
|
|
||
|
// mfCache is a local cache of MeasurementFields to reduce lock contention when validating
|
||
|
// field types.
|
||
|
mfCache := make(map[string]*MeasurementFields, 16)
|
||
|
for i, p := range points {
|
||
|
var skip bool
|
||
|
var validField bool
|
||
|
iter := p.FieldIterator()
|
||
|
for iter.Next() {
|
||
|
if bytes.Equal(iter.FieldKey(), timeBytes) {
|
||
|
continue
|
||
|
}
|
||
|
validField = true
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if !validField {
|
||
|
dropped++
|
||
|
if reason == "" {
|
||
|
reason = fmt.Sprintf("invalid field name: input field \"%s\" on measurement \"%s\" is invalid", "time", string(p.Name()))
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
iter.Reset()
|
||
|
|
||
|
// Skip points if keys have been dropped.
|
||
|
// The drop count has already been incremented during series creation.
|
||
|
if droppedKeys != nil {
|
||
|
if _, ok := droppedKeys[string(keys[i])]; ok {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
name := p.Name()
|
||
|
// see if the field definitions need to be saved to the shard
|
||
|
mf := mfCache[string(name)]
|
||
|
if mf == nil {
|
||
|
mf = engine.MeasurementFields(name).Clone()
|
||
|
mfCache[string(name)] = mf
|
||
|
}
|
||
|
iter.Reset()
|
||
|
|
||
|
// validate field types and encode data
|
||
|
for iter.Next() {
|
||
|
|
||
|
// Skip fields name "time", they are illegal
|
||
|
if bytes.Equal(iter.FieldKey(), timeBytes) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var fieldType influxql.DataType
|
||
|
switch iter.Type() {
|
||
|
case models.Float:
|
||
|
fieldType = influxql.Float
|
||
|
case models.Integer:
|
||
|
fieldType = influxql.Integer
|
||
|
case models.Boolean:
|
||
|
fieldType = influxql.Boolean
|
||
|
case models.String:
|
||
|
fieldType = influxql.String
|
||
|
default:
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if f := mf.FieldBytes(iter.FieldKey()); f != nil {
|
||
|
// Field present in shard metadata, make sure there is no type conflict.
|
||
|
if f.Type != fieldType {
|
||
|
atomic.AddInt64(&s.stats.WritePointsDropped, 1)
|
||
|
dropped++
|
||
|
if reason == "" {
|
||
|
reason = fmt.Sprintf("%s: input field \"%s\" on measurement \"%s\" is type %s, already exists as type %s", ErrFieldTypeConflict, iter.FieldKey(), name, fieldType, f.Type)
|
||
|
}
|
||
|
skip = true
|
||
|
} else {
|
||
|
continue // Field is present, and it's of the same type. Nothing more to do.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !skip {
|
||
|
fieldsToCreate = append(fieldsToCreate, &FieldCreate{p.Name(), &Field{Name: string(iter.FieldKey()), Type: fieldType}})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !skip {
|
||
|
points[n] = points[i]
|
||
|
n++
|
||
|
}
|
||
|
}
|
||
|
points = points[:n]
|
||
|
|
||
|
if dropped > 0 {
|
||
|
err = PartialWriteError{Reason: reason, Dropped: dropped}
|
||
|
}
|
||
|
|
||
|
return points, fieldsToCreate, err
|
||
|
}
|
||
|
|
||
|
func (s *Shard) createFieldsAndMeasurements(fieldsToCreate []*FieldCreate) error {
|
||
|
if len(fieldsToCreate) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
engine, err := s.engineNoLock()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// add fields
|
||
|
for _, f := range fieldsToCreate {
|
||
|
mf := engine.MeasurementFields(f.Measurement)
|
||
|
if err := mf.CreateFieldIfNotExists([]byte(f.Field.Name), f.Field.Type, false); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
s.index.SetFieldName(f.Measurement, f.Field.Name)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// DeleteSeries deletes a list of series.
|
||
|
func (s *Shard) DeleteSeries(seriesKeys [][]byte) error {
|
||
|
return s.DeleteSeriesRange(seriesKeys, math.MinInt64, math.MaxInt64)
|
||
|
}
|
||
|
|
||
|
// DeleteSeriesRange deletes all values from for seriesKeys between min and max (inclusive)
|
||
|
func (s *Shard) DeleteSeriesRange(seriesKeys [][]byte, min, max int64) error {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return engine.DeleteSeriesRange(seriesKeys, min, max)
|
||
|
}
|
||
|
|
||
|
// DeleteMeasurement deletes a measurement and all underlying series.
|
||
|
func (s *Shard) DeleteMeasurement(name []byte) error {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return engine.DeleteMeasurement(name)
|
||
|
}
|
||
|
|
||
|
// SeriesN returns the unique number of series in the shard.
|
||
|
func (s *Shard) SeriesN() int64 {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
return engine.SeriesN()
|
||
|
}
|
||
|
|
||
|
// SeriesSketches returns the series sketches for the shard.
|
||
|
func (s *Shard) SeriesSketches() (estimator.Sketch, estimator.Sketch, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return engine.SeriesSketches()
|
||
|
}
|
||
|
|
||
|
// MeasurementsSketches returns the measurement sketches for the shard.
|
||
|
func (s *Shard) MeasurementsSketches() (estimator.Sketch, estimator.Sketch, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return engine.MeasurementsSketches()
|
||
|
}
|
||
|
|
||
|
// MeasurementNamesByExpr returns names of measurements matching the condition.
|
||
|
// If cond is nil then all measurement names are returned.
|
||
|
func (s *Shard) MeasurementNamesByExpr(cond influxql.Expr) ([][]byte, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return engine.MeasurementNamesByExpr(cond)
|
||
|
}
|
||
|
|
||
|
// MeasurementNamesByRegex returns names of measurements matching the regular expression.
|
||
|
func (s *Shard) MeasurementNamesByRegex(re *regexp.Regexp) ([][]byte, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return engine.MeasurementNamesByRegex(re)
|
||
|
}
|
||
|
|
||
|
// MeasurementSeriesKeysByExpr returns a list of series keys from the shard
|
||
|
// matching expr.
|
||
|
func (s *Shard) MeasurementSeriesKeysByExpr(name []byte, expr influxql.Expr) ([][]byte, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return engine.MeasurementSeriesKeysByExpr(name, expr)
|
||
|
}
|
||
|
|
||
|
// MeasurementTagKeysByExpr returns all the tag keys for the provided expression.
|
||
|
func (s *Shard) MeasurementTagKeysByExpr(name []byte, expr influxql.Expr) (map[string]struct{}, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return engine.MeasurementTagKeysByExpr(name, expr)
|
||
|
}
|
||
|
|
||
|
// MeasurementTagKeyValuesByExpr returns all the tag keys values for the
|
||
|
// provided expression.
|
||
|
func (s *Shard) MeasurementTagKeyValuesByExpr(name []byte, key []string, expr influxql.Expr, keysSorted bool) ([][]string, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return engine.MeasurementTagKeyValuesByExpr(name, key, expr, keysSorted)
|
||
|
}
|
||
|
|
||
|
// MeasurementFields returns fields for a measurement.
|
||
|
// TODO(edd): This method is currently only being called from tests; do we
|
||
|
// really need it?
|
||
|
func (s *Shard) MeasurementFields(name []byte) *MeasurementFields {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
return engine.MeasurementFields(name)
|
||
|
}
|
||
|
|
||
|
// MeasurementExists returns true if the shard contains name.
|
||
|
// TODO(edd): This method is currently only being called from tests; do we
|
||
|
// really need it?
|
||
|
func (s *Shard) MeasurementExists(name []byte) (bool, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
return engine.MeasurementExists(name)
|
||
|
}
|
||
|
|
||
|
// WriteTo writes the shard's data to w.
|
||
|
func (s *Shard) WriteTo(w io.Writer) (int64, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
n, err := engine.WriteTo(w)
|
||
|
atomic.AddInt64(&s.stats.BytesWritten, int64(n))
|
||
|
return n, err
|
||
|
}
|
||
|
|
||
|
// CreateIterator returns an iterator for the data in the shard.
|
||
|
func (s *Shard) CreateIterator(measurement string, opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if strings.HasPrefix(measurement, "_") {
|
||
|
if itr, ok, err := s.createSystemIterator(engine, measurement, opt); ok {
|
||
|
return itr, err
|
||
|
}
|
||
|
// Unknown system source so pass this to the engine.
|
||
|
}
|
||
|
return engine.CreateIterator(measurement, opt)
|
||
|
}
|
||
|
|
||
|
// createSystemIterator returns an iterator for a system source.
|
||
|
func (s *Shard) createSystemIterator(engine Engine, measurement string, opt influxql.IteratorOptions) (influxql.Iterator, bool, error) {
|
||
|
switch measurement {
|
||
|
case "_fieldKeys":
|
||
|
itr, err := NewFieldKeysIterator(engine, opt)
|
||
|
return itr, true, err
|
||
|
case "_series":
|
||
|
itr, err := s.createSeriesIterator(opt)
|
||
|
return itr, true, err
|
||
|
case "_tagKeys":
|
||
|
itr, err := NewTagKeysIterator(engine, opt)
|
||
|
return itr, true, err
|
||
|
}
|
||
|
return nil, false, nil
|
||
|
}
|
||
|
|
||
|
// createSeriesIterator returns a new instance of SeriesIterator.
|
||
|
func (s *Shard) createSeriesIterator(opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Only equality operators are allowed.
|
||
|
influxql.WalkFunc(opt.Condition, func(n influxql.Node) {
|
||
|
switch n := n.(type) {
|
||
|
case *influxql.BinaryExpr:
|
||
|
switch n.Op {
|
||
|
case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX,
|
||
|
influxql.OR, influxql.AND:
|
||
|
default:
|
||
|
err = errors.New("invalid tag comparison operator")
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return engine.SeriesPointIterator(opt)
|
||
|
}
|
||
|
|
||
|
// FieldDimensions returns unique sets of fields and dimensions across a list of sources.
|
||
|
func (s *Shard) FieldDimensions(measurements []string) (fields map[string]influxql.DataType, dimensions map[string]struct{}, err error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
fields = make(map[string]influxql.DataType)
|
||
|
dimensions = make(map[string]struct{})
|
||
|
|
||
|
for _, name := range measurements {
|
||
|
// Handle system sources.
|
||
|
if strings.HasPrefix(name, "_") {
|
||
|
var keys []string
|
||
|
switch name {
|
||
|
case "_fieldKeys":
|
||
|
keys = []string{"fieldKey", "fieldType"}
|
||
|
case "_series":
|
||
|
keys = []string{"key"}
|
||
|
case "_tagKeys":
|
||
|
keys = []string{"tagKey"}
|
||
|
}
|
||
|
|
||
|
if len(keys) > 0 {
|
||
|
for _, k := range keys {
|
||
|
if _, ok := fields[k]; !ok || influxql.String < fields[k] {
|
||
|
fields[k] = influxql.String
|
||
|
}
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
// Unknown system source so default to looking for a measurement.
|
||
|
}
|
||
|
|
||
|
// Retrieve measurement.
|
||
|
if exists, err := engine.MeasurementExists([]byte(name)); err != nil {
|
||
|
return nil, nil, err
|
||
|
} else if !exists {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Append fields and dimensions.
|
||
|
mf := engine.MeasurementFields([]byte(name))
|
||
|
if mf != nil {
|
||
|
for k, typ := range mf.FieldSet() {
|
||
|
if _, ok := fields[k]; !ok || typ < fields[k] {
|
||
|
fields[k] = typ
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := engine.ForEachMeasurementTagKey([]byte(name), func(key []byte) error {
|
||
|
dimensions[string(key)] = struct{}{}
|
||
|
return nil
|
||
|
}); err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fields, dimensions, nil
|
||
|
}
|
||
|
|
||
|
// mapType returns the data type for the field within the measurement.
|
||
|
func (s *Shard) mapType(measurement, field string) (influxql.DataType, error) {
|
||
|
engine, err := s.engineNoLock()
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
// Process system measurements.
|
||
|
if strings.HasPrefix(measurement, "_") {
|
||
|
switch measurement {
|
||
|
case "_fieldKeys":
|
||
|
if field == "fieldKey" || field == "fieldType" {
|
||
|
return influxql.String, nil
|
||
|
}
|
||
|
return influxql.Unknown, nil
|
||
|
case "_series":
|
||
|
if field == "key" {
|
||
|
return influxql.String, nil
|
||
|
}
|
||
|
return influxql.Unknown, nil
|
||
|
case "_tagKeys":
|
||
|
if field == "tagKey" {
|
||
|
return influxql.String, nil
|
||
|
}
|
||
|
return influxql.Unknown, nil
|
||
|
}
|
||
|
// Unknown system source so default to looking for a measurement.
|
||
|
}
|
||
|
|
||
|
if exists, _ := engine.MeasurementExists([]byte(measurement)); !exists {
|
||
|
return influxql.Unknown, nil
|
||
|
}
|
||
|
|
||
|
mf := engine.MeasurementFields([]byte(measurement))
|
||
|
if mf != nil {
|
||
|
f := mf.Field(field)
|
||
|
if f != nil {
|
||
|
return f.Type, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if exists, _ := engine.HasTagKey([]byte(measurement), []byte(field)); exists {
|
||
|
return influxql.Tag, nil
|
||
|
}
|
||
|
|
||
|
return influxql.Unknown, nil
|
||
|
}
|
||
|
|
||
|
// expandSources expands regex sources and removes duplicates.
|
||
|
// NOTE: sources must be normalized (db and rp set) before calling this function.
|
||
|
func (s *Shard) expandSources(sources influxql.Sources) (influxql.Sources, error) {
|
||
|
engine, err := s.engineNoLock()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Use a map as a set to prevent duplicates.
|
||
|
set := map[string]influxql.Source{}
|
||
|
|
||
|
// Iterate all sources, expanding regexes when they're found.
|
||
|
for _, source := range sources {
|
||
|
switch src := source.(type) {
|
||
|
case *influxql.Measurement:
|
||
|
// Add non-regex measurements directly to the set.
|
||
|
if src.Regex == nil {
|
||
|
set[src.String()] = src
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Loop over matching measurements.
|
||
|
names, err := engine.MeasurementNamesByRegex(src.Regex.Val)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, name := range names {
|
||
|
other := &influxql.Measurement{
|
||
|
Database: src.Database,
|
||
|
RetentionPolicy: src.RetentionPolicy,
|
||
|
Name: string(name),
|
||
|
}
|
||
|
set[other.String()] = other
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return nil, fmt.Errorf("expandSources: unsupported source type: %T", source)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert set to sorted slice.
|
||
|
names := make([]string, 0, len(set))
|
||
|
for name := range set {
|
||
|
names = append(names, name)
|
||
|
}
|
||
|
sort.Strings(names)
|
||
|
|
||
|
// Convert set to a list of Sources.
|
||
|
expanded := make(influxql.Sources, 0, len(set))
|
||
|
for _, name := range names {
|
||
|
expanded = append(expanded, set[name])
|
||
|
}
|
||
|
|
||
|
return expanded, nil
|
||
|
}
|
||
|
|
||
|
// Backup backs up the shard by creating a tar archive of all TSM files that
|
||
|
// have been modified since the provided time. See Engine.Backup for more details.
|
||
|
func (s *Shard) Backup(w io.Writer, basePath string, since time.Time) error {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return engine.Backup(w, basePath, since)
|
||
|
}
|
||
|
|
||
|
// Restore restores data to the underlying engine for the shard.
|
||
|
// The shard is reopened after restore.
|
||
|
func (s *Shard) Restore(r io.Reader, basePath string) error {
|
||
|
if err := func() error {
|
||
|
s.mu.Lock()
|
||
|
defer s.mu.Unlock()
|
||
|
|
||
|
// Special case - we can still restore to a disabled shard, so we should
|
||
|
// only check if the engine is closed and not care if the shard is
|
||
|
// disabled.
|
||
|
if s._engine == nil {
|
||
|
return ErrEngineClosed
|
||
|
}
|
||
|
|
||
|
// Restore to engine.
|
||
|
return s._engine.Restore(r, basePath)
|
||
|
}(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Close shard.
|
||
|
if err := s.Close(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Reopen engine.
|
||
|
return s.Open()
|
||
|
}
|
||
|
|
||
|
// Import imports data to the underlying engine for the shard. r should
|
||
|
// be a reader from a backup created by Backup.
|
||
|
func (s *Shard) Import(r io.Reader, basePath string) error {
|
||
|
// Special case - we can still import to a disabled shard, so we should
|
||
|
// only check if the engine is closed and not care if the shard is
|
||
|
// disabled.
|
||
|
s.mu.Lock()
|
||
|
defer s.mu.Unlock()
|
||
|
if s._engine == nil {
|
||
|
return ErrEngineClosed
|
||
|
}
|
||
|
|
||
|
// Import to engine.
|
||
|
return s._engine.Import(r, basePath)
|
||
|
}
|
||
|
|
||
|
// CreateSnapshot will return a path to a temp directory
|
||
|
// containing hard links to the underlying shard files.
|
||
|
func (s *Shard) CreateSnapshot() (string, error) {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return engine.CreateSnapshot()
|
||
|
}
|
||
|
|
||
|
// ForEachMeasurementName iterates over each measurement in the shard.
|
||
|
func (s *Shard) ForEachMeasurementName(fn func(name []byte) error) error {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return engine.ForEachMeasurementName(fn)
|
||
|
}
|
||
|
|
||
|
func (s *Shard) ForEachMeasurementTagKey(name []byte, fn func(key []byte) error) error {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return engine.ForEachMeasurementTagKey(name, fn)
|
||
|
}
|
||
|
|
||
|
func (s *Shard) TagKeyCardinality(name, key []byte) int {
|
||
|
engine, err := s.engine()
|
||
|
if err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
return engine.TagKeyCardinality(name, key)
|
||
|
}
|
||
|
|
||
|
// engine safely (under an RLock) returns a reference to the shard's Engine, or
|
||
|
// an error if the Engine is closed, or the shard is currently disabled.
|
||
|
//
|
||
|
// The shard's Engine should always be accessed via a call to engine(), rather
|
||
|
// than directly referencing Shard.engine.
|
||
|
//
|
||
|
// If a caller needs an Engine reference but is already under a lock, then they
|
||
|
// should use engineNoLock().
|
||
|
func (s *Shard) engine() (Engine, error) {
|
||
|
s.mu.RLock()
|
||
|
defer s.mu.RUnlock()
|
||
|
return s.engineNoLock()
|
||
|
}
|
||
|
|
||
|
// engineNoLock is similar to calling engine(), but the caller must guarantee
|
||
|
// that they already hold an appropriate lock.
|
||
|
func (s *Shard) engineNoLock() (Engine, error) {
|
||
|
if err := s.ready(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return s._engine, nil
|
||
|
}
|
||
|
|
||
|
type ShardGroup interface {
|
||
|
MeasurementsByRegex(re *regexp.Regexp) []string
|
||
|
FieldDimensions(measurements []string) (fields map[string]influxql.DataType, dimensions map[string]struct{}, err error)
|
||
|
MapType(measurement, field string) influxql.DataType
|
||
|
CreateIterator(measurement string, opt influxql.IteratorOptions) (influxql.Iterator, error)
|
||
|
ExpandSources(sources influxql.Sources) (influxql.Sources, error)
|
||
|
}
|
||
|
|
||
|
// Shards represents a sortable list of shards.
|
||
|
type Shards []*Shard
|
||
|
|
||
|
// Len implements sort.Interface.
|
||
|
func (a Shards) Len() int { return len(a) }
|
||
|
|
||
|
// Less implements sort.Interface.
|
||
|
func (a Shards) Less(i, j int) bool { return a[i].id < a[j].id }
|
||
|
|
||
|
// Swap implements sort.Interface.
|
||
|
func (a Shards) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||
|
|
||
|
// MeasurementsByRegex returns the unique set of measurements matching the
|
||
|
// provided regex, for all the shards.
|
||
|
func (a Shards) MeasurementsByRegex(re *regexp.Regexp) []string {
|
||
|
var m map[string]struct{}
|
||
|
for _, sh := range a {
|
||
|
names, err := sh.MeasurementNamesByRegex(re)
|
||
|
if err != nil {
|
||
|
continue // Skip this shard's results—previous behaviour.
|
||
|
}
|
||
|
|
||
|
if m == nil {
|
||
|
m = make(map[string]struct{}, len(names))
|
||
|
}
|
||
|
|
||
|
for _, name := range names {
|
||
|
m[string(name)] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(m) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
names := make([]string, 0, len(m))
|
||
|
for key := range m {
|
||
|
names = append(names, key)
|
||
|
}
|
||
|
sort.Strings(names)
|
||
|
return names
|
||
|
}
|
||
|
|
||
|
func (a Shards) FieldDimensions(measurements []string) (fields map[string]influxql.DataType, dimensions map[string]struct{}, err error) {
|
||
|
fields = make(map[string]influxql.DataType)
|
||
|
dimensions = make(map[string]struct{})
|
||
|
|
||
|
for _, sh := range a {
|
||
|
f, d, err := sh.FieldDimensions(measurements)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
for k, typ := range f {
|
||
|
if _, ok := fields[k]; typ != influxql.Unknown && (!ok || typ < fields[k]) {
|
||
|
fields[k] = typ
|
||
|
}
|
||
|
}
|
||
|
for k := range d {
|
||
|
dimensions[k] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (a Shards) MapType(measurement, field string) influxql.DataType {
|
||
|
var typ influxql.DataType
|
||
|
for _, sh := range a {
|
||
|
sh.mu.RLock()
|
||
|
if t, err := sh.mapType(measurement, field); err == nil && typ.LessThan(t) {
|
||
|
typ = t
|
||
|
}
|
||
|
sh.mu.RUnlock()
|
||
|
}
|
||
|
return typ
|
||
|
}
|
||
|
|
||
|
func (a Shards) CreateIterator(measurement string, opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
|
itrs := make([]influxql.Iterator, 0, len(a))
|
||
|
for _, sh := range a {
|
||
|
itr, err := sh.CreateIterator(measurement, opt)
|
||
|
if err != nil {
|
||
|
influxql.Iterators(itrs).Close()
|
||
|
return nil, err
|
||
|
} else if itr == nil {
|
||
|
continue
|
||
|
}
|
||
|
itrs = append(itrs, itr)
|
||
|
|
||
|
select {
|
||
|
case <-opt.InterruptCh:
|
||
|
influxql.Iterators(itrs).Close()
|
||
|
return nil, err
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
// Enforce series limit at creation time.
|
||
|
if opt.MaxSeriesN > 0 {
|
||
|
stats := itr.Stats()
|
||
|
if stats.SeriesN > opt.MaxSeriesN {
|
||
|
influxql.Iterators(itrs).Close()
|
||
|
return nil, fmt.Errorf("max-select-series limit exceeded: (%d/%d)", stats.SeriesN, opt.MaxSeriesN)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return influxql.Iterators(itrs).Merge(opt)
|
||
|
}
|
||
|
|
||
|
func (a Shards) ExpandSources(sources influxql.Sources) (influxql.Sources, error) {
|
||
|
// Use a map as a set to prevent duplicates.
|
||
|
set := map[string]influxql.Source{}
|
||
|
|
||
|
// Iterate through every shard and expand the sources.
|
||
|
for _, sh := range a {
|
||
|
sh.mu.RLock()
|
||
|
expanded, err := sh.expandSources(sources)
|
||
|
sh.mu.RUnlock()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, src := range expanded {
|
||
|
switch src := src.(type) {
|
||
|
case *influxql.Measurement:
|
||
|
set[src.String()] = src
|
||
|
default:
|
||
|
return nil, fmt.Errorf("Store.ExpandSources: unsupported source type: %T", src)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert set to sorted slice.
|
||
|
names := make([]string, 0, len(set))
|
||
|
for name := range set {
|
||
|
names = append(names, name)
|
||
|
}
|
||
|
sort.Strings(names)
|
||
|
|
||
|
// Convert set to a list of Sources.
|
||
|
sorted := make([]influxql.Source, 0, len(set))
|
||
|
for _, name := range names {
|
||
|
sorted = append(sorted, set[name])
|
||
|
}
|
||
|
return sorted, nil
|
||
|
}
|
||
|
|
||
|
// MeasurementFields holds the fields of a measurement and their codec.
|
||
|
type MeasurementFields struct {
|
||
|
mu sync.RWMutex
|
||
|
|
||
|
fields map[string]*Field
|
||
|
}
|
||
|
|
||
|
// NewMeasurementFields returns an initialised *MeasurementFields value.
|
||
|
func NewMeasurementFields() *MeasurementFields {
|
||
|
return &MeasurementFields{fields: make(map[string]*Field)}
|
||
|
}
|
||
|
|
||
|
// MarshalBinary encodes the object to a binary format.
|
||
|
func (m *MeasurementFields) MarshalBinary() ([]byte, error) {
|
||
|
m.mu.RLock()
|
||
|
defer m.mu.RUnlock()
|
||
|
|
||
|
var pb internal.MeasurementFields
|
||
|
for _, f := range m.fields {
|
||
|
id := int32(f.ID)
|
||
|
name := f.Name
|
||
|
t := int32(f.Type)
|
||
|
pb.Fields = append(pb.Fields, &internal.Field{ID: &id, Name: &name, Type: &t})
|
||
|
}
|
||
|
return proto.Marshal(&pb)
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary decodes the object from a binary format.
|
||
|
func (m *MeasurementFields) UnmarshalBinary(buf []byte) error {
|
||
|
m.mu.Lock()
|
||
|
defer m.mu.Unlock()
|
||
|
|
||
|
var pb internal.MeasurementFields
|
||
|
if err := proto.Unmarshal(buf, &pb); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
m.fields = make(map[string]*Field, len(pb.Fields))
|
||
|
for _, f := range pb.Fields {
|
||
|
m.fields[f.GetName()] = &Field{ID: uint8(f.GetID()), Name: f.GetName(), Type: influxql.DataType(f.GetType())}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CreateFieldIfNotExists creates a new field with an autoincrementing ID.
|
||
|
// Returns an error if 255 fields have already been created on the measurement or
|
||
|
// the fields already exists with a different type.
|
||
|
func (m *MeasurementFields) CreateFieldIfNotExists(name []byte, typ influxql.DataType, limitCount bool) error {
|
||
|
m.mu.RLock()
|
||
|
|
||
|
// Ignore if the field already exists.
|
||
|
if f := m.fields[string(name)]; f != nil {
|
||
|
if f.Type != typ {
|
||
|
m.mu.RUnlock()
|
||
|
return ErrFieldTypeConflict
|
||
|
}
|
||
|
m.mu.RUnlock()
|
||
|
return nil
|
||
|
}
|
||
|
m.mu.RUnlock()
|
||
|
|
||
|
m.mu.Lock()
|
||
|
defer m.mu.Unlock()
|
||
|
|
||
|
// Re-check field and type under write lock.
|
||
|
if f := m.fields[string(name)]; f != nil {
|
||
|
if f.Type != typ {
|
||
|
return ErrFieldTypeConflict
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Create and append a new field.
|
||
|
f := &Field{
|
||
|
ID: uint8(len(m.fields) + 1),
|
||
|
Name: string(name),
|
||
|
Type: typ,
|
||
|
}
|
||
|
m.fields[string(name)] = f
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (m *MeasurementFields) FieldN() int {
|
||
|
m.mu.RLock()
|
||
|
n := len(m.fields)
|
||
|
m.mu.RUnlock()
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// Field returns the field for name, or nil if there is no field for name.
|
||
|
func (m *MeasurementFields) Field(name string) *Field {
|
||
|
m.mu.RLock()
|
||
|
f := m.fields[name]
|
||
|
m.mu.RUnlock()
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
func (m *MeasurementFields) HasField(name string) bool {
|
||
|
m.mu.RLock()
|
||
|
f := m.fields[name]
|
||
|
m.mu.RUnlock()
|
||
|
return f != nil
|
||
|
}
|
||
|
|
||
|
// FieldBytes returns the field for name, or nil if there is no field for name.
|
||
|
// FieldBytes should be preferred to Field when the caller has a []byte, because
|
||
|
// it avoids a string allocation, which can't be avoided if the caller converts
|
||
|
// the []byte to a string and calls Field.
|
||
|
func (m *MeasurementFields) FieldBytes(name []byte) *Field {
|
||
|
m.mu.RLock()
|
||
|
f := m.fields[string(name)]
|
||
|
m.mu.RUnlock()
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
// FieldSet returns the set of fields and their types for the measurement.
|
||
|
func (m *MeasurementFields) FieldSet() map[string]influxql.DataType {
|
||
|
m.mu.RLock()
|
||
|
defer m.mu.RUnlock()
|
||
|
|
||
|
fields := make(map[string]influxql.DataType)
|
||
|
for name, f := range m.fields {
|
||
|
fields[name] = f.Type
|
||
|
}
|
||
|
return fields
|
||
|
}
|
||
|
|
||
|
// Clone returns copy of the MeasurementFields
|
||
|
func (m *MeasurementFields) Clone() *MeasurementFields {
|
||
|
m.mu.RLock()
|
||
|
defer m.mu.RUnlock()
|
||
|
fields := make(map[string]*Field, len(m.fields))
|
||
|
for key, field := range m.fields {
|
||
|
fields[key] = field
|
||
|
}
|
||
|
return &MeasurementFields{
|
||
|
fields: fields,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MeasurementFieldSet represents a collection of fields by measurement.
|
||
|
// This safe for concurrent use.
|
||
|
type MeasurementFieldSet struct {
|
||
|
mu sync.RWMutex
|
||
|
fields map[string]*MeasurementFields
|
||
|
}
|
||
|
|
||
|
// NewMeasurementFieldSet returns a new instance of MeasurementFieldSet.
|
||
|
func NewMeasurementFieldSet() *MeasurementFieldSet {
|
||
|
return &MeasurementFieldSet{
|
||
|
fields: make(map[string]*MeasurementFields),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fields returns fields for a measurement by name.
|
||
|
func (fs *MeasurementFieldSet) Fields(name string) *MeasurementFields {
|
||
|
fs.mu.RLock()
|
||
|
mf := fs.fields[name]
|
||
|
fs.mu.RUnlock()
|
||
|
return mf
|
||
|
}
|
||
|
|
||
|
// CreateFieldsIfNotExists returns fields for a measurement by name.
|
||
|
func (fs *MeasurementFieldSet) CreateFieldsIfNotExists(name []byte) *MeasurementFields {
|
||
|
fs.mu.RLock()
|
||
|
mf := fs.fields[string(name)]
|
||
|
fs.mu.RUnlock()
|
||
|
|
||
|
if mf != nil {
|
||
|
return mf
|
||
|
}
|
||
|
|
||
|
fs.mu.Lock()
|
||
|
mf = fs.fields[string(name)]
|
||
|
if mf == nil {
|
||
|
mf = NewMeasurementFields()
|
||
|
fs.fields[string(name)] = mf
|
||
|
}
|
||
|
fs.mu.Unlock()
|
||
|
return mf
|
||
|
}
|
||
|
|
||
|
// Delete removes a field set for a measurement.
|
||
|
func (fs *MeasurementFieldSet) Delete(name string) {
|
||
|
fs.mu.Lock()
|
||
|
delete(fs.fields, name)
|
||
|
fs.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
// DeleteWithLock executes fn and removes a field set from a measurement under lock.
|
||
|
func (fs *MeasurementFieldSet) DeleteWithLock(name string, fn func() error) error {
|
||
|
fs.mu.Lock()
|
||
|
defer fs.mu.Unlock()
|
||
|
|
||
|
if err := fn(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
delete(fs.fields, name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Field represents a series field.
|
||
|
type Field struct {
|
||
|
ID uint8 `json:"id,omitempty"`
|
||
|
Name string `json:"name,omitempty"`
|
||
|
Type influxql.DataType `json:"type,omitempty"`
|
||
|
}
|
||
|
|
||
|
// NewFieldKeysIterator returns an iterator that can be iterated over to
|
||
|
// retrieve field keys.
|
||
|
func NewFieldKeysIterator(engine Engine, opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
|
itr := &fieldKeysIterator{engine: engine}
|
||
|
|
||
|
// Retrieve measurements from shard. Filter if condition specified.
|
||
|
names, err := engine.MeasurementNamesByExpr(opt.Condition)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
itr.names = names
|
||
|
|
||
|
return itr, nil
|
||
|
}
|
||
|
|
||
|
// fieldKeysIterator iterates over measurements and gets field keys from each measurement.
|
||
|
type fieldKeysIterator struct {
|
||
|
engine Engine
|
||
|
names [][]byte // remaining measurement names
|
||
|
buf struct {
|
||
|
name []byte // current measurement name
|
||
|
fields []Field // current measurement's fields
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Stats returns stats about the points processed.
|
||
|
func (itr *fieldKeysIterator) Stats() influxql.IteratorStats { return influxql.IteratorStats{} }
|
||
|
|
||
|
// Close closes the iterator.
|
||
|
func (itr *fieldKeysIterator) Close() error { return nil }
|
||
|
|
||
|
// Next emits the next tag key name.
|
||
|
func (itr *fieldKeysIterator) Next() (*influxql.FloatPoint, error) {
|
||
|
for {
|
||
|
// If there are no more keys then move to the next measurements.
|
||
|
if len(itr.buf.fields) == 0 {
|
||
|
if len(itr.names) == 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
itr.buf.name = itr.names[0]
|
||
|
mf := itr.engine.MeasurementFields(itr.buf.name)
|
||
|
if mf != nil {
|
||
|
fset := mf.FieldSet()
|
||
|
if len(fset) == 0 {
|
||
|
itr.names = itr.names[1:]
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
keys := make([]string, 0, len(fset))
|
||
|
for k := range fset {
|
||
|
keys = append(keys, k)
|
||
|
}
|
||
|
sort.Strings(keys)
|
||
|
|
||
|
itr.buf.fields = make([]Field, len(keys))
|
||
|
for i, name := range keys {
|
||
|
itr.buf.fields[i] = Field{Name: name, Type: fset[name]}
|
||
|
}
|
||
|
}
|
||
|
itr.names = itr.names[1:]
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Return next key.
|
||
|
field := itr.buf.fields[0]
|
||
|
p := &influxql.FloatPoint{
|
||
|
Name: string(itr.buf.name),
|
||
|
Aux: []interface{}{field.Name, field.Type.String()},
|
||
|
}
|
||
|
itr.buf.fields = itr.buf.fields[1:]
|
||
|
|
||
|
return p, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NewTagKeysIterator returns a new instance of TagKeysIterator.
|
||
|
func NewTagKeysIterator(engine Engine, opt influxql.IteratorOptions) (influxql.Iterator, error) {
|
||
|
fn := func(name []byte) ([][]byte, error) {
|
||
|
var keys [][]byte
|
||
|
if err := engine.ForEachMeasurementTagKey(name, func(key []byte) error {
|
||
|
keys = append(keys, key)
|
||
|
return nil
|
||
|
}); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return keys, nil
|
||
|
}
|
||
|
return newMeasurementKeysIterator(engine, fn, opt)
|
||
|
}
|
||
|
|
||
|
// measurementKeyFunc is the function called by measurementKeysIterator.
|
||
|
type measurementKeyFunc func(name []byte) ([][]byte, error)
|
||
|
|
||
|
func newMeasurementKeysIterator(engine Engine, fn measurementKeyFunc, opt influxql.IteratorOptions) (*measurementKeysIterator, error) {
|
||
|
itr := &measurementKeysIterator{fn: fn}
|
||
|
|
||
|
names, err := engine.MeasurementNamesByExpr(opt.Condition)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
itr.names = names
|
||
|
|
||
|
return itr, nil
|
||
|
}
|
||
|
|
||
|
// measurementKeysIterator iterates over measurements and gets keys from each measurement.
|
||
|
type measurementKeysIterator struct {
|
||
|
names [][]byte // remaining measurement names
|
||
|
buf struct {
|
||
|
name []byte // current measurement name
|
||
|
keys [][]byte // current measurement's keys
|
||
|
}
|
||
|
fn measurementKeyFunc
|
||
|
}
|
||
|
|
||
|
// Stats returns stats about the points processed.
|
||
|
func (itr *measurementKeysIterator) Stats() influxql.IteratorStats { return influxql.IteratorStats{} }
|
||
|
|
||
|
// Close closes the iterator.
|
||
|
func (itr *measurementKeysIterator) Close() error { return nil }
|
||
|
|
||
|
// Next emits the next tag key name.
|
||
|
func (itr *measurementKeysIterator) Next() (*influxql.FloatPoint, error) {
|
||
|
for {
|
||
|
// If there are no more keys then move to the next measurements.
|
||
|
if len(itr.buf.keys) == 0 {
|
||
|
if len(itr.names) == 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
itr.buf.name, itr.names = itr.names[0], itr.names[1:]
|
||
|
|
||
|
keys, err := itr.fn(itr.buf.name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
itr.buf.keys = keys
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Return next key.
|
||
|
p := &influxql.FloatPoint{
|
||
|
Name: string(itr.buf.name),
|
||
|
Aux: []interface{}{string(itr.buf.keys[0])},
|
||
|
}
|
||
|
itr.buf.keys = itr.buf.keys[1:]
|
||
|
|
||
|
return p, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// LimitError represents an error caused by a configurable limit.
|
||
|
type LimitError struct {
|
||
|
Reason string
|
||
|
}
|
||
|
|
||
|
func (e *LimitError) Error() string { return e.Reason }
|