mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-04 22:17:59 +08:00

* Upgrade InfluxDB from 1.x to 2.x Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influx DB configuration updated Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connection to InfluxDBv2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Token cannot be created Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connected to InfluxDB2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connected to InfluxDB2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB v2 Consumer Implementation Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * quickfix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb-Writer Unit Tests Update Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Consumer Update Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB Writer Tests Implemented Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB Connection Check Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolving Remarks Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved consumer-test remark Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * consumer-test slow working version Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * reader changes Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Consumer tests time issue fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Eof warning fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Makefile Fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Readers Initial Setup Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * consumer json fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Bugfix and resolves comments. Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * one test fails Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * solved last page read Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * writers future time problem fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * weird Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * weird Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Tests Passes Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolve Semaphore Issues Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * UUID comment on consumer tests resolved Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Inclusive from and Exclusive to Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Inclusive from and Exclusive to Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * 1 second limits Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * 1 second limits Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fixed json time Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fixed CI error Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved request Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved request Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved requests Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * removed blank line Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved comment Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * deleted unnecessary string builder Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * initial commit Signed-off-by: aryan <aryangodara03@gmail.com> * change influxdb docker image version. Signed-off-by: aryan <aryangodara03@gmail.com> * go mod and vendor fixing Signed-off-by: aryan <aryangodara03@gmail.com> * Upgrade InfluxDB from 1.x to 2.x Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influx DB configuration updated Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connection to InfluxDBv2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Token cannot be created Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connected to InfluxDB2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Connected to InfluxDB2 Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB v2 Consumer Implementation Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * quickfix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb-Writer Unit Tests Update Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Consumer Update Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB Writer Tests Implemented Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * InfluxDB Connection Check Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolving Remarks Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved consumer-test remark Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * consumer-test slow working version Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * reader changes Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Consumer tests time issue fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolved Reviews Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Readers Initial Setup Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * consumer json fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * readers simple version fix Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Influxdb Reader Parsers Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Bugfix and resolves comments. Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * one test fails Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * solved last page read Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * writers future time problem fixed Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * weird Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * weird Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Tests Passes Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Resolve Semaphore Issues Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * UUID comment on consumer tests resolved Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Inclusive from and Exclusive to Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * Inclusive from and Exclusive to Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * 1 second limits Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * 1 second limits Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved review Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fixed json time Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * fixed CI error Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved request Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved request Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved requests Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * removed blank line Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * resolved comment Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * deleted unnecessary string builder Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> * initial commit Signed-off-by: aryan <aryangodara03@gmail.com> * change influxdb docker image version. Signed-off-by: aryan <aryangodara03@gmail.com> * go mod and vendor fixing Signed-off-by: aryan <aryangodara03@gmail.com> * go mod fixing Signed-off-by: aryan <aryangodara03@gmail.com> * make tests pass locally, fix errors Signed-off-by: aryan <aryangodara03@gmail.com> * rem unsused variables/consts Signed-off-by: aryan <aryangodara03@gmail.com> * go mod tidy vendor Signed-off-by: aryan <aryangodara03@gmail.com> * add env var and data types Signed-off-by: aryan <aryangodara03@gmail.com> * update influxdb version to latest (2.3 to 2.12) Signed-off-by: aryan <aryangodara03@gmail.com> * change time precision for message tests Signed-off-by: aryan <aryangodara03@gmail.com> * renamed influxdb2 to influxdata Signed-off-by: aryan <aryangodara03@gmail.com> * address remarks on PR Signed-off-by: aryan <aryangodara03@gmail.com> * update influxbd according to latest mf commit Signed-off-by: aryan <aryangodara03@gmail.com> * temp commit, rem before pushing Signed-off-by: aryan <aryangodara03@gmail.com> * update main files and cassandra-reader messages_tests Signed-off-by: aryan <aryangodara03@gmail.com> * fix name of logger while importing Signed-off-by: aryan <aryangodara03@gmail.com> * remove unnecessary print lines Signed-off-by: aryan <aryangodara03@gmail.com> * correct env var name Signed-off-by: aryan <aryangodara03@gmail.com> * change to async consume Signed-off-by: aryan <aryangodara03@gmail.com> * add option to switch bw sync and async Signed-off-by: aryan <aryangodara03@gmail.com> * test for both async and sync Signed-off-by: aryan <aryangodara03@gmail.com> * update consumer and add writeAPIs to config Signed-off-by: aryan <aryangodara03@gmail.com> * revert back to sync consuming Signed-off-by: aryan <aryangodara03@gmail.com> * temp fix for default timeout value Signed-off-by: aryan <aryangodara03@gmail.com> * set default timeout in config. Signed-off-by: aryan <aryangodara03@gmail.com> * remove unwanted env vars, add required ones. Signed-off-by: aryan <aryangodara03@gmail.com> * rem unused username password from config Signed-off-by: aryan <aryangodara03@gmail.com> * update readme, env vars, and remove grafana Signed-off-by: aryan <aryangodara03@gmail.com> * update readme Signed-off-by: aryan <aryangodara03@gmail.com> * fix typo Signed-off-by: aryan <aryangodara03@gmail.com> * update readme description. Signed-off-by: aryan <aryangodara03@gmail.com> * fix more typos. Signed-off-by: aryan <aryangodara03@gmail.com> * add link to official docs to readme. Signed-off-by: aryan <aryangodara03@gmail.com> --------- Signed-off-by: fatih <fatihdurmaz@sabanciuniv.edu> Signed-off-by: aryan <aryangodara03@gmail.com> Co-authored-by: fatih <fatihdurmaz@sabanciuniv.edu> Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
418 lines
12 KiB
Go
418 lines
12 KiB
Go
// Copyright 2020-2021 InfluxData, Inc. All rights reserved.
|
|
// Use of this source code is governed by MIT
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package write provides service and its stuff
|
|
package write
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
http2 "github.com/influxdata/influxdb-client-go/v2/api/http"
|
|
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
|
"github.com/influxdata/influxdb-client-go/v2/internal/gzip"
|
|
"github.com/influxdata/influxdb-client-go/v2/internal/log"
|
|
ilog "github.com/influxdata/influxdb-client-go/v2/log"
|
|
lp "github.com/influxdata/line-protocol"
|
|
)
|
|
|
|
// Batch holds information for sending points batch
|
|
type Batch struct {
|
|
// lines to send
|
|
Batch string
|
|
// retry attempts so far
|
|
RetryAttempts uint
|
|
// true if it was removed from queue
|
|
Evicted bool
|
|
// time when this batch expires
|
|
Expires time.Time
|
|
}
|
|
|
|
// NewBatch creates new batch
|
|
func NewBatch(data string, expireDelayMs uint) *Batch {
|
|
return &Batch{
|
|
Batch: data,
|
|
Expires: time.Now().Add(time.Duration(expireDelayMs) * time.Millisecond),
|
|
}
|
|
}
|
|
|
|
// BatchErrorCallback is synchronously notified in case non-blocking write fails.
|
|
// It returns true if WriteAPI should continue with retrying, false will discard the batch.
|
|
type BatchErrorCallback func(batch *Batch, error2 http2.Error) bool
|
|
|
|
// Service is responsible for reliable writing of batches
|
|
type Service struct {
|
|
org string
|
|
bucket string
|
|
httpService http2.Service
|
|
url string
|
|
lastWriteAttempt time.Time
|
|
retryQueue *queue
|
|
lock sync.Mutex
|
|
writeOptions *write.Options
|
|
retryExponentialBase uint
|
|
errorCb BatchErrorCallback
|
|
retryDelay uint
|
|
retryAttempts uint
|
|
}
|
|
|
|
// NewService creates new write service
|
|
func NewService(org string, bucket string, httpService http2.Service, options *write.Options) *Service {
|
|
|
|
retryBufferLimit := options.RetryBufferLimit() / options.BatchSize()
|
|
if retryBufferLimit == 0 {
|
|
retryBufferLimit = 1
|
|
}
|
|
u, _ := url.Parse(httpService.ServerAPIURL())
|
|
u, _ = u.Parse("write")
|
|
params := u.Query()
|
|
params.Set("org", org)
|
|
params.Set("bucket", bucket)
|
|
params.Set("precision", precisionToString(options.Precision()))
|
|
if options.Consistency() != "" {
|
|
params.Set("consistency", string(options.Consistency()))
|
|
}
|
|
u.RawQuery = params.Encode()
|
|
writeURL := u.String()
|
|
return &Service{
|
|
org: org,
|
|
bucket: bucket,
|
|
httpService: httpService,
|
|
url: writeURL,
|
|
writeOptions: options,
|
|
retryQueue: newQueue(int(retryBufferLimit)),
|
|
retryExponentialBase: 2,
|
|
retryDelay: options.RetryInterval(),
|
|
retryAttempts: 0,
|
|
}
|
|
}
|
|
|
|
// SetBatchErrorCallback sets callback allowing custom handling of failed writes.
|
|
// If callback returns true, failed batch will be retried, otherwise discarded.
|
|
func (w *Service) SetBatchErrorCallback(cb BatchErrorCallback) {
|
|
w.errorCb = cb
|
|
}
|
|
|
|
// HandleWrite handles writes of batches and handles retrying.
|
|
// Retrying is triggered by new writes, there is no scheduler.
|
|
// It first checks retry queue, because it has the highest priority.
|
|
// If there are some batches in retry queue, those are written and incoming batch is added to end of retry queue.
|
|
// Immediate write is allowed only in case there was success or not retryable error.
|
|
// Otherwise, delay is checked based on recent batch.
|
|
// If write of batch fails with retryable error (connection errors and HTTP code >= 429),
|
|
// Batch retry time is calculated based on #of attempts.
|
|
// If writes continues failing and # of attempts reaches maximum or total retry time reaches maxRetryTime,
|
|
// batch is discarded.
|
|
func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
|
|
log.Debug("Write proc: received write request")
|
|
batchToWrite := batch
|
|
retrying := false
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Debug("Write proc: ctx cancelled req")
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
if !w.retryQueue.isEmpty() {
|
|
log.Debug("Write proc: taking batch from retry queue")
|
|
if !retrying {
|
|
b := w.retryQueue.first()
|
|
|
|
// Discard batches at beginning of retryQueue that have already expired
|
|
if time.Now().After(b.Expires) {
|
|
log.Error("Write proc: oldest batch in retry queue expired, discarding")
|
|
if !b.Evicted {
|
|
w.retryQueue.pop()
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Can we write? In case of retryable error we must wait a bit
|
|
if w.lastWriteAttempt.IsZero() || time.Now().After(w.lastWriteAttempt.Add(time.Millisecond*time.Duration(w.retryDelay))) {
|
|
retrying = true
|
|
} else {
|
|
log.Warn("Write proc: cannot write yet, storing batch to queue")
|
|
if w.retryQueue.push(batch) {
|
|
log.Error("Write proc: Retry buffer full, discarding oldest batch")
|
|
}
|
|
batchToWrite = nil
|
|
}
|
|
}
|
|
if retrying {
|
|
batchToWrite = w.retryQueue.first()
|
|
if batch != nil { //store actual batch to retry queue
|
|
if w.retryQueue.push(batch) {
|
|
log.Error("Write proc: Retry buffer full, discarding oldest batch")
|
|
}
|
|
batch = nil
|
|
}
|
|
}
|
|
}
|
|
// write batch
|
|
if batchToWrite != nil {
|
|
perror := w.WriteBatch(ctx, batchToWrite)
|
|
if perror != nil {
|
|
if isIgnorableError(perror) {
|
|
log.Warnf("Write error: %s", perror.Error())
|
|
} else {
|
|
if w.writeOptions.MaxRetries() != 0 && (perror.StatusCode == 0 || perror.StatusCode >= http.StatusTooManyRequests) {
|
|
log.Errorf("Write error: %s, batch kept for retrying\n", perror.Error())
|
|
if perror.RetryAfter > 0 {
|
|
w.retryDelay = perror.RetryAfter * 1000
|
|
} else {
|
|
w.retryDelay = w.computeRetryDelay(w.retryAttempts)
|
|
}
|
|
if w.errorCb != nil && !w.errorCb(batchToWrite, *perror) {
|
|
log.Error("Callback rejected batch, discarding")
|
|
if !batchToWrite.Evicted {
|
|
w.retryQueue.pop()
|
|
}
|
|
return perror
|
|
}
|
|
// store new batch (not taken from queue)
|
|
if !batchToWrite.Evicted && batchToWrite != w.retryQueue.first() {
|
|
if w.retryQueue.push(batch) {
|
|
log.Error("Retry buffer full, discarding oldest batch")
|
|
}
|
|
} else if batchToWrite.RetryAttempts == w.writeOptions.MaxRetries() {
|
|
log.Error("Reached maximum number of retries, discarding batch")
|
|
if !batchToWrite.Evicted {
|
|
w.retryQueue.pop()
|
|
}
|
|
}
|
|
batchToWrite.RetryAttempts++
|
|
w.retryAttempts++
|
|
log.Debugf("Write proc: next wait for write is %dms\n", w.retryDelay)
|
|
} else {
|
|
log.Errorf("Write error: %s\n", perror.Error())
|
|
}
|
|
return fmt.Errorf("write failed (attempts %d): %w", batchToWrite.RetryAttempts, perror)
|
|
}
|
|
}
|
|
|
|
w.retryDelay = w.writeOptions.RetryInterval()
|
|
w.retryAttempts = 0
|
|
if retrying && !batchToWrite.Evicted {
|
|
w.retryQueue.pop()
|
|
}
|
|
batchToWrite = nil
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Non-retryable errors
|
|
const (
|
|
errStringHintedHandoffNotEmpty = "hinted handoff queue not empty"
|
|
errStringPartialWrite = "partial write"
|
|
errStringPointsBeyondRP = "points beyond retention policy"
|
|
errStringUnableToParse = "unable to parse"
|
|
)
|
|
|
|
func isIgnorableError(error *http2.Error) bool {
|
|
// This "error" is an informational message about the state of the
|
|
// InfluxDB cluster.
|
|
if strings.Contains(error.Message, errStringHintedHandoffNotEmpty) {
|
|
return true
|
|
}
|
|
// Points beyond retention policy is returned when points are immediately
|
|
// discarded for being older than the retention policy. Usually this not
|
|
// a cause for concern, and we don't want to retry.
|
|
if strings.Contains(error.Message, errStringPointsBeyondRP) {
|
|
return true
|
|
}
|
|
// Other partial write errors, such as "field type conflict", are not
|
|
// correctable at this point and so the point is dropped instead of
|
|
// retrying.
|
|
if strings.Contains(error.Message, errStringPartialWrite) {
|
|
return true
|
|
}
|
|
// This error indicates an error in line protocol
|
|
// serialization, retries would not be successful.
|
|
if strings.Contains(error.Message, errStringUnableToParse) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// computeRetryDelay calculates retry delay.
|
|
// Retry delay is calculated as random value within the interval
|
|
// [retry_interval * exponential_base^(attempts) and retry_interval * exponential_base^(attempts+1)]
|
|
func (w *Service) computeRetryDelay(attempts uint) uint {
|
|
minDelay := int(w.writeOptions.RetryInterval() * pow(w.writeOptions.ExponentialBase(), attempts))
|
|
maxDelay := int(w.writeOptions.RetryInterval() * pow(w.writeOptions.ExponentialBase(), attempts+1))
|
|
diff := maxDelay - minDelay
|
|
if diff <= 0 { //check overflows
|
|
return w.writeOptions.MaxRetryInterval()
|
|
}
|
|
retryDelay := uint(rand.Intn(diff) + minDelay)
|
|
if retryDelay > w.writeOptions.MaxRetryInterval() {
|
|
retryDelay = w.writeOptions.MaxRetryInterval()
|
|
}
|
|
return retryDelay
|
|
}
|
|
|
|
// pow computes x**y
|
|
func pow(x, y uint) uint {
|
|
p := uint(1)
|
|
if y == 0 {
|
|
return 1
|
|
}
|
|
for i := uint(1); i <= y; i++ {
|
|
p = p * x
|
|
}
|
|
return p
|
|
}
|
|
|
|
// WriteBatch performs actual writing via HTTP service
|
|
func (w *Service) WriteBatch(ctx context.Context, batch *Batch) *http2.Error {
|
|
var body io.Reader
|
|
var err error
|
|
body = strings.NewReader(batch.Batch)
|
|
|
|
if log.Level() >= ilog.DebugLevel {
|
|
log.Debugf("Writing batch: %s", batch.Batch)
|
|
}
|
|
if w.writeOptions.UseGZip() {
|
|
body, err = gzip.CompressWithGzip(body)
|
|
if err != nil {
|
|
return http2.NewError(err)
|
|
}
|
|
}
|
|
w.lock.Lock()
|
|
w.lastWriteAttempt = time.Now()
|
|
w.lock.Unlock()
|
|
perror := w.httpService.DoPostRequest(ctx, w.url, body, func(req *http.Request) {
|
|
if w.writeOptions.UseGZip() {
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
}
|
|
}, func(r *http.Response) error {
|
|
return r.Body.Close()
|
|
})
|
|
return perror
|
|
}
|
|
|
|
// Flush sends batches from retry queue immediately, without retrying
|
|
func (w *Service) Flush() {
|
|
for !w.retryQueue.isEmpty() {
|
|
b := w.retryQueue.pop()
|
|
if time.Now().After(b.Expires) {
|
|
log.Error("Oldest batch in retry queue expired, discarding")
|
|
continue
|
|
}
|
|
if err := w.WriteBatch(context.Background(), b); err != nil {
|
|
log.Errorf("Error flushing batch from retry queue: %w", err.Unwrap())
|
|
}
|
|
}
|
|
}
|
|
|
|
// pointWithDefaultTags encapsulates Point with default tags
|
|
type pointWithDefaultTags struct {
|
|
point *write.Point
|
|
defaultTags map[string]string
|
|
}
|
|
|
|
// Name returns the name of measurement of a point.
|
|
func (p *pointWithDefaultTags) Name() string {
|
|
return p.point.Name()
|
|
}
|
|
|
|
// Time is the timestamp of a Point.
|
|
func (p *pointWithDefaultTags) Time() time.Time {
|
|
return p.point.Time()
|
|
}
|
|
|
|
// FieldList returns a slice containing the fields of a Point.
|
|
func (p *pointWithDefaultTags) FieldList() []*lp.Field {
|
|
return p.point.FieldList()
|
|
}
|
|
|
|
// TagList returns tags from point along with default tags
|
|
// If point of tag can override default tag
|
|
func (p *pointWithDefaultTags) TagList() []*lp.Tag {
|
|
tags := make([]*lp.Tag, 0, len(p.point.TagList())+len(p.defaultTags))
|
|
tags = append(tags, p.point.TagList()...)
|
|
for k, v := range p.defaultTags {
|
|
if !existTag(p.point.TagList(), k) {
|
|
tags = append(tags, &lp.Tag{
|
|
Key: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
}
|
|
sort.Slice(tags, func(i, j int) bool { return tags[i].Key < tags[j].Key })
|
|
return tags
|
|
}
|
|
|
|
func existTag(tags []*lp.Tag, key string) bool {
|
|
for _, tag := range tags {
|
|
if key == tag.Key {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// EncodePoints creates line protocol string from points
|
|
func (w *Service) EncodePoints(points ...*write.Point) (string, error) {
|
|
var buffer bytes.Buffer
|
|
e := lp.NewEncoder(&buffer)
|
|
e.SetFieldTypeSupport(lp.UintSupport)
|
|
e.FailOnFieldErr(true)
|
|
e.SetPrecision(w.writeOptions.Precision())
|
|
for _, point := range points {
|
|
_, err := e.Encode(w.pointToEncode(point))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return buffer.String(), nil
|
|
}
|
|
|
|
// pointToEncode determines whether default tags should be applied
|
|
// and returns point with default tags instead of point
|
|
func (w *Service) pointToEncode(point *write.Point) lp.Metric {
|
|
var m lp.Metric
|
|
if len(w.writeOptions.DefaultTags()) > 0 {
|
|
m = &pointWithDefaultTags{
|
|
point: point,
|
|
defaultTags: w.writeOptions.DefaultTags(),
|
|
}
|
|
} else {
|
|
m = point
|
|
}
|
|
return m
|
|
}
|
|
|
|
// WriteURL returns current write URL
|
|
func (w *Service) WriteURL() string {
|
|
return w.url
|
|
}
|
|
|
|
func precisionToString(precision time.Duration) string {
|
|
prec := "ns"
|
|
switch precision {
|
|
case time.Microsecond:
|
|
prec = "us"
|
|
case time.Millisecond:
|
|
prec = "ms"
|
|
case time.Second:
|
|
prec = "s"
|
|
}
|
|
return prec
|
|
}
|