github.com/go-graphite/carbonapi@v0.17.0/zipper/zipper.go (about)

     1  package zipper
     2  
     3  import (
     4  	"context"
     5  	_ "net/http/pprof"
     6  	"time"
     7  
     8  	"github.com/ansel1/merry"
     9  	protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
    10  	"go.uber.org/zap"
    11  
    12  	utilctx "github.com/go-graphite/carbonapi/util/ctx"
    13  	"github.com/go-graphite/carbonapi/zipper/broadcast"
    14  	"github.com/go-graphite/carbonapi/zipper/config"
    15  	"github.com/go-graphite/carbonapi/zipper/helper"
    16  	"github.com/go-graphite/carbonapi/zipper/metadata"
    17  	"github.com/go-graphite/carbonapi/zipper/types"
    18  
    19  	_ "github.com/go-graphite/carbonapi/zipper/protocols/auto"
    20  	_ "github.com/go-graphite/carbonapi/zipper/protocols/graphite"
    21  	_ "github.com/go-graphite/carbonapi/zipper/protocols/irondb"
    22  	_ "github.com/go-graphite/carbonapi/zipper/protocols/prometheus"
    23  	_ "github.com/go-graphite/carbonapi/zipper/protocols/v2"
    24  	_ "github.com/go-graphite/carbonapi/zipper/protocols/v3"
    25  	_ "github.com/go-graphite/carbonapi/zipper/protocols/victoriametrics"
    26  )
    27  
    28  // Zipper provides interface to Zipper-related functions
    29  type Zipper struct {
    30  	probeTicker *time.Ticker
    31  	ProbeQuit   chan struct{}
    32  	ProbeForce  chan int
    33  
    34  	timeout           time.Duration
    35  	timeoutConnect    time.Duration
    36  	keepAliveInterval time.Duration
    37  
    38  	// Will broadcast to all servers there
    39  	backend                   types.BackendServer
    40  	concurrencyLimitPerServer int
    41  
    42  	ScaleToCommonStep bool
    43  
    44  	sendStats func(*types.Stats)
    45  
    46  	logger *zap.Logger
    47  }
    48  
    49  func createBackendsV2(logger *zap.Logger, backends types.BackendsV2, expireDelaySec int32, tldCacheDisabled, requireSuccessAll bool) ([]types.BackendServer, merry.Error) {
    50  	backendServers := make([]types.BackendServer, 0)
    51  	var e merry.Error
    52  	timeouts := backends.Timeouts
    53  	for _, backend := range backends.Backends {
    54  		concurrencyLimit := backends.ConcurrencyLimitPerServer
    55  		tries := backends.MaxTries
    56  		maxIdleConnsPerHost := backends.MaxIdleConnsPerHost
    57  		keepAliveInterval := backends.KeepAliveInterval
    58  		maxBatchSize := backends.MaxBatchSize
    59  
    60  		if backend.Timeouts == nil {
    61  			backend.Timeouts = &timeouts
    62  		}
    63  		if backend.ConcurrencyLimit == nil {
    64  			backend.ConcurrencyLimit = &concurrencyLimit
    65  		}
    66  		if backend.MaxTries == nil {
    67  			backend.MaxTries = &tries
    68  		}
    69  		if backend.MaxBatchSize == nil {
    70  			backend.MaxBatchSize = maxBatchSize
    71  		}
    72  		if backend.MaxIdleConnsPerHost == nil {
    73  			backend.MaxIdleConnsPerHost = &maxIdleConnsPerHost
    74  		}
    75  		if backend.KeepAliveInterval == nil {
    76  			backend.KeepAliveInterval = &keepAliveInterval
    77  		}
    78  
    79  		var backendServer types.BackendServer
    80  		logger.Debug("creating lb group",
    81  			zap.String("name", backend.GroupName),
    82  			zap.Strings("servers", backend.Servers),
    83  			zap.Any("type", backend.LBMethod),
    84  		)
    85  
    86  		metadata.Metadata.RLock()
    87  		backendInit, ok := metadata.Metadata.ProtocolInits[backend.Protocol]
    88  		metadata.Metadata.RUnlock()
    89  		if !ok {
    90  			var protocols []string
    91  			metadata.Metadata.RLock()
    92  			for p := range metadata.Metadata.SupportedProtocols {
    93  				protocols = append(protocols, p)
    94  			}
    95  			metadata.Metadata.RUnlock()
    96  			logger.Error("unknown backend protocol",
    97  				zap.Any("backend", backend),
    98  				zap.String("requested_protocol", backend.Protocol),
    99  				zap.Strings("supported_backends", protocols),
   100  			)
   101  			return nil, merry.Errorf("unknown backend protocol '%v'", backend.Protocol)
   102  		}
   103  
   104  		var lbMethod types.LBMethod
   105  		err := lbMethod.FromString(backend.LBMethod)
   106  		if err != nil {
   107  			logger.Fatal("failed to parse lbMethod",
   108  				zap.String("lbMethod", backend.LBMethod),
   109  				zap.Error(err),
   110  			)
   111  		}
   112  		if lbMethod == types.RoundRobinLB {
   113  			backendServer, e = backendInit(logger, backend, tldCacheDisabled, requireSuccessAll)
   114  			if e != nil {
   115  				return nil, e
   116  			}
   117  		} else {
   118  			config := backend
   119  
   120  			backendServers := make([]types.BackendServer, 0, len(backend.Servers))
   121  			for _, server := range backend.Servers {
   122  				config.Servers = []string{server}
   123  				config.GroupName = server
   124  				backendServer, e = backendInit(logger, config, tldCacheDisabled, requireSuccessAll)
   125  				if e != nil {
   126  					return nil, e
   127  				}
   128  				backendServers = append(backendServers, backendServer)
   129  			}
   130  
   131  			backendServer, err = broadcast.NewBroadcastGroup(logger, backend.GroupName, backend.DoMultipleRequestsIfSplit, backendServers,
   132  				expireDelaySec, *backend.ConcurrencyLimit, *backend.MaxBatchSize, timeouts, tldCacheDisabled, requireSuccessAll,
   133  			)
   134  			if err != nil {
   135  				return nil, merry.Wrap(err)
   136  			}
   137  		}
   138  		backendServers = append(backendServers, backendServer)
   139  	}
   140  	return backendServers, nil
   141  }
   142  
   143  // NewZipper allows to create new Zipper
   144  func NewZipper(sender func(*types.Stats), cfg *config.Config, logger *zap.Logger) (*Zipper, merry.Error) {
   145  	if !cfg.IsSanitized() {
   146  		cfg = config.SanitizeConfig(logger, *cfg)
   147  	}
   148  
   149  	backends, err := createBackendsV2(logger, cfg.BackendsV2, int32(cfg.InternalRoutingCache.Seconds()), cfg.TLDCacheDisabled, cfg.RequireSuccessAll)
   150  	if err != nil {
   151  		logger.Fatal("errors while initialing zipper store backend",
   152  			zap.Any("error", err),
   153  		)
   154  	}
   155  
   156  	logger.Error("DEBUG ERROR LOGGGGG", zap.Any("cfg", cfg))
   157  	broadcastGroup, err := broadcast.NewBroadcastGroup(logger, "root", cfg.DoMultipleRequestsIfSplit, backends,
   158  		int32(cfg.InternalRoutingCache.Seconds()), cfg.ConcurrencyLimitPerServer, *cfg.MaxBatchSize, cfg.Timeouts, cfg.TLDCacheDisabled, cfg.RequireSuccessAll,
   159  	)
   160  	if err != nil {
   161  		logger.Fatal("error while initialing zipper store backend",
   162  			zap.Any("error", err),
   163  		)
   164  	}
   165  
   166  	z := &Zipper{
   167  		ProbeQuit:  make(chan struct{}),
   168  		ProbeForce: make(chan int),
   169  
   170  		ScaleToCommonStep: cfg.ScaleToCommonStep,
   171  		sendStats:         sender,
   172  
   173  		backend:                   broadcastGroup,
   174  		concurrencyLimitPerServer: cfg.ConcurrencyLimitPerServer,
   175  		keepAliveInterval:         cfg.KeepAliveInterval,
   176  		timeout:                   cfg.Timeouts.Render,
   177  		timeoutConnect:            cfg.Timeouts.Connect,
   178  		logger:                    logger,
   179  	}
   180  
   181  	logger.Debug("zipper config",
   182  		zap.Any("config", cfg),
   183  	)
   184  
   185  	if !cfg.TLDCacheDisabled {
   186  		z.probeTicker = time.NewTicker(cfg.InternalRoutingCache)
   187  
   188  		go z.probeTlds()
   189  
   190  		z.ProbeForce <- 1
   191  	}
   192  	return z, nil
   193  }
   194  
   195  func (z *Zipper) doProbe(logger *zap.Logger) {
   196  	ctx := context.Background()
   197  
   198  	_, err := z.backend.ProbeTLDs(ctx)
   199  	if err != nil {
   200  		logger.Error("failed to probe tlds",
   201  			zap.String("errors", err.Cause().Error()),
   202  		)
   203  		if ce := logger.Check(zap.DebugLevel, "failed to probe tlds (verbose)"); ce != nil {
   204  			ce.Write(
   205  				zap.Any("errorVerbose", err),
   206  			)
   207  		}
   208  	}
   209  }
   210  
   211  func (z *Zipper) probeTlds() {
   212  	logger := z.logger.With(zap.String("type", "probe"))
   213  	for {
   214  		select {
   215  		case <-z.probeTicker.C:
   216  			z.doProbe(logger)
   217  		case <-z.ProbeForce:
   218  			z.doProbe(logger)
   219  		case <-z.ProbeQuit:
   220  			z.probeTicker.Stop()
   221  			return
   222  		}
   223  	}
   224  }
   225  
   226  // GRPC-compatible methods
   227  func (z Zipper) FetchProtoV3(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) {
   228  	logger := z.logger.With(zap.String("function", "FetchProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   229  
   230  	res, stats, e := z.backend.Fetch(ctx, request)
   231  
   232  	if e != nil {
   233  		logger.Debug("had errors while fetching result",
   234  			zap.Any("errors", e),
   235  			zap.Int("httpCode", merry.HTTPCode(e)),
   236  		)
   237  	}
   238  	if res == nil || len(res.Metrics) == 0 {
   239  		logger.Debug("no metrics fetched",
   240  			zap.Any("errors", e),
   241  		)
   242  
   243  		err := helper.HttpErrorByCode(e)
   244  
   245  		return nil, stats, err
   246  	}
   247  
   248  	return res, stats, merry.WithHTTPCode(e, 200)
   249  }
   250  
   251  func (z Zipper) FindProtoV3(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) {
   252  	logger := z.logger.With(zap.String("function", "FindProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   253  
   254  	res, stats, err := z.backend.Find(ctx, request)
   255  
   256  	var errs []merry.Error
   257  	if err != nil {
   258  		errs = []merry.Error{err}
   259  	}
   260  
   261  	findResponse := &types.ServerFindResponse{
   262  		Response: res,
   263  		Stats:    stats,
   264  		Err:      errs,
   265  	}
   266  
   267  	if len(findResponse.Err) > 0 {
   268  		var e merry.Error
   269  		if len(findResponse.Err) == 1 {
   270  			e = helper.HttpErrorByCode(findResponse.Err[0])
   271  		} else {
   272  			e = helper.HttpErrorByCode(findResponse.Err[1].WithCause(findResponse.Err[0]))
   273  		}
   274  		logger.Debug("had errors while fetching result",
   275  			zap.Any("errors", e),
   276  		)
   277  		// TODO(civil): Not Found error cases across all zipper code should be handled in the same way
   278  		// See FetchProtoV3 for more examples
   279  		if findResponse.Response != nil && len(findResponse.Response.Metrics) > 0 {
   280  			return findResponse.Response, findResponse.Stats, merry.WithHTTPCode(e, 200)
   281  		}
   282  		return nil, stats, e
   283  	}
   284  
   285  	return findResponse.Response, findResponse.Stats, nil
   286  }
   287  
   288  func (z Zipper) InfoProtoV3(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) {
   289  	logger := z.logger.With(zap.String("function", "InfoProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   290  	realRequest := &protov3.MultiMetricsInfoRequest{Names: make([]string, 0, len(request.Metrics))}
   291  	res, _, err := z.FindProtoV3(ctx, request)
   292  	if err == nil || merry.Is(err, types.ErrNonFatalErrors) {
   293  		for _, m := range res.Metrics {
   294  			for _, match := range m.Matches {
   295  				if match.IsLeaf {
   296  					realRequest.Names = append(realRequest.Names, match.Path)
   297  				}
   298  			}
   299  		}
   300  	} else {
   301  		realRequest.Names = append(realRequest.Names, request.Metrics...)
   302  	}
   303  
   304  	r, stats, e := z.backend.Info(ctx, realRequest)
   305  	if e != nil {
   306  		if merry.Is(e, types.ErrNotFound) {
   307  			return nil, nil, e
   308  		} else {
   309  			logger.Debug("had errors while fetching result",
   310  				zap.Any("errors", e),
   311  			)
   312  			return nil, stats, e
   313  		}
   314  	}
   315  
   316  	return r, stats, nil
   317  }
   318  
   319  func (z Zipper) ListProtoV3(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) {
   320  	logger := z.logger.With(zap.String("function", "ListProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   321  	r, stats, e := z.backend.List(ctx)
   322  	if e != nil {
   323  		if merry.Is(e, types.ErrNotFound) {
   324  			return nil, nil, e
   325  		} else {
   326  			logger.Debug("had errors while fetching result",
   327  				zap.Any("errors", e),
   328  			)
   329  			return r, stats, e
   330  		}
   331  	}
   332  
   333  	return r, stats, e
   334  }
   335  func (z Zipper) StatsProtoV3(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) {
   336  	logger := z.logger.With(zap.String("function", "StatsProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   337  	r, stats, e := z.backend.Stats(ctx)
   338  	if e != nil {
   339  		if merry.Is(e, types.ErrNotFound) {
   340  			return nil, nil, e
   341  		} else {
   342  			logger.Debug("had errors while fetching result",
   343  				zap.Any("errors", e),
   344  			)
   345  			return r, stats, e
   346  		}
   347  	}
   348  
   349  	return r, stats, nil
   350  }
   351  
   352  // Tags
   353  
   354  func (z Zipper) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   355  	logger := z.logger.With(zap.String("function", "TagNames"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   356  	data, err := z.backend.TagNames(ctx, query, limit)
   357  	if err != nil {
   358  		logger.Debug("had errors while fetching result",
   359  			zap.Any("errors", err),
   360  		)
   361  		return data, err
   362  	}
   363  
   364  	return data, nil
   365  }
   366  
   367  func (z Zipper) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   368  	logger := z.logger.With(zap.String("function", "TagValues"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   369  	data, err := z.backend.TagValues(ctx, query, limit)
   370  	if err != nil {
   371  		logger.Debug("had errors while fetching result",
   372  			zap.Any("errors", err),
   373  		)
   374  		return data, err
   375  	}
   376  
   377  	return data, nil
   378  }