github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/lokifrontend/frontend/v1/frontend.go (about)

     1  package v1
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/go-kit/log"
    11  	"github.com/go-kit/log/level"
    12  	"github.com/grafana/dskit/services"
    13  	"github.com/opentracing/opentracing-go"
    14  	"github.com/pkg/errors"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/prometheus/client_golang/prometheus/promauto"
    17  	"github.com/weaveworks/common/httpgrpc"
    18  
    19  	"github.com/grafana/dskit/tenant"
    20  
    21  	"github.com/grafana/loki/pkg/lokifrontend/frontend/v1/frontendv1pb"
    22  	"github.com/grafana/loki/pkg/querier/stats"
    23  	"github.com/grafana/loki/pkg/scheduler/queue"
    24  	"github.com/grafana/loki/pkg/util"
    25  	lokigrpc "github.com/grafana/loki/pkg/util/httpgrpc"
    26  	"github.com/grafana/loki/pkg/util/validation"
    27  )
    28  
    29  var errTooManyRequest = httpgrpc.Errorf(http.StatusTooManyRequests, "too many outstanding requests")
    30  
    31  // Config for a Frontend.
    32  type Config struct {
    33  	MaxOutstandingPerTenant int           `yaml:"max_outstanding_per_tenant"`
    34  	QuerierForgetDelay      time.Duration `yaml:"querier_forget_delay"`
    35  }
    36  
    37  // RegisterFlags adds the flags required to config this to the given FlagSet.
    38  func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
    39  	f.IntVar(&cfg.MaxOutstandingPerTenant, "querier.max-outstanding-requests-per-tenant", 2048, "Maximum number of outstanding requests per tenant per frontend; requests beyond this error with HTTP 429.")
    40  	f.DurationVar(&cfg.QuerierForgetDelay, "query-frontend.querier-forget-delay", 0, "If a querier disconnects without sending notification about graceful shutdown, the query-frontend will keep the querier in the tenant's shard until the forget delay has passed. This feature is useful to reduce the blast radius when shuffle-sharding is enabled.")
    41  }
    42  
    43  type Limits interface {
    44  	// Returns max queriers to use per tenant, or 0 if shuffle sharding is disabled.
    45  	MaxQueriersPerUser(user string) int
    46  }
    47  
    48  // Frontend queues HTTP requests, dispatches them to backends, and handles retries
    49  // for requests which failed.
    50  type Frontend struct {
    51  	services.Service
    52  
    53  	cfg    Config
    54  	log    log.Logger
    55  	limits Limits
    56  
    57  	requestQueue *queue.RequestQueue
    58  	activeUsers  *util.ActiveUsersCleanupService
    59  
    60  	// Subservices manager.
    61  	subservices        *services.Manager
    62  	subservicesWatcher *services.FailureWatcher
    63  
    64  	// Metrics.
    65  	queueLength       *prometheus.GaugeVec
    66  	discardedRequests *prometheus.CounterVec
    67  	numClients        prometheus.GaugeFunc
    68  	queueDuration     prometheus.Histogram
    69  }
    70  
    71  type request struct {
    72  	enqueueTime time.Time
    73  	queueSpan   opentracing.Span
    74  	originalCtx context.Context
    75  
    76  	request  *httpgrpc.HTTPRequest
    77  	err      chan error
    78  	response chan *httpgrpc.HTTPResponse
    79  }
    80  
    81  // New creates a new frontend. Frontend implements service, and must be started and stopped.
    82  func New(cfg Config, limits Limits, log log.Logger, registerer prometheus.Registerer) (*Frontend, error) {
    83  	f := &Frontend{
    84  		cfg:    cfg,
    85  		log:    log,
    86  		limits: limits,
    87  		queueLength: promauto.With(registerer).NewGaugeVec(prometheus.GaugeOpts{
    88  			Name: "cortex_query_frontend_queue_length",
    89  			Help: "Number of queries in the queue.",
    90  		}, []string{"user"}),
    91  		discardedRequests: promauto.With(registerer).NewCounterVec(prometheus.CounterOpts{
    92  			Name: "cortex_query_frontend_discarded_requests_total",
    93  			Help: "Total number of query requests discarded.",
    94  		}, []string{"user"}),
    95  		queueDuration: promauto.With(registerer).NewHistogram(prometheus.HistogramOpts{
    96  			Name:    "cortex_query_frontend_queue_duration_seconds",
    97  			Help:    "Time spend by requests queued.",
    98  			Buckets: prometheus.DefBuckets,
    99  		}),
   100  	}
   101  
   102  	f.requestQueue = queue.NewRequestQueue(cfg.MaxOutstandingPerTenant, cfg.QuerierForgetDelay, f.queueLength, f.discardedRequests)
   103  	f.activeUsers = util.NewActiveUsersCleanupWithDefaultValues(f.cleanupInactiveUserMetrics)
   104  
   105  	var err error
   106  	f.subservices, err = services.NewManager(f.requestQueue, f.activeUsers)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	f.numClients = promauto.With(registerer).NewGaugeFunc(prometheus.GaugeOpts{
   112  		Name: "cortex_query_frontend_connected_clients",
   113  		Help: "Number of worker clients currently connected to the frontend.",
   114  	}, f.requestQueue.GetConnectedQuerierWorkersMetric)
   115  
   116  	f.Service = services.NewBasicService(f.starting, f.running, f.stopping)
   117  	return f, nil
   118  }
   119  
   120  func (f *Frontend) starting(ctx context.Context) error {
   121  	f.subservicesWatcher.WatchManager(f.subservices)
   122  
   123  	if err := services.StartManagerAndAwaitHealthy(ctx, f.subservices); err != nil {
   124  		return errors.Wrap(err, "unable to start frontend subservices")
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  func (f *Frontend) running(ctx context.Context) error {
   131  	for {
   132  		select {
   133  		case <-ctx.Done():
   134  			return nil
   135  		case err := <-f.subservicesWatcher.Chan():
   136  			return errors.Wrap(err, "frontend subservice failed")
   137  		}
   138  	}
   139  }
   140  
   141  func (f *Frontend) stopping(_ error) error {
   142  	// This will also stop the requests queue, which stop accepting new requests and errors out any pending requests.
   143  	return services.StopManagerAndAwaitStopped(context.Background(), f.subservices)
   144  }
   145  
   146  func (f *Frontend) cleanupInactiveUserMetrics(user string) {
   147  	f.queueLength.DeleteLabelValues(user)
   148  	f.discardedRequests.DeleteLabelValues(user)
   149  }
   150  
   151  // RoundTripGRPC round trips a proto (instead of a HTTP request).
   152  func (f *Frontend) RoundTripGRPC(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) {
   153  	// Propagate trace context in gRPC too - this will be ignored if using HTTP.
   154  	tracer, span := opentracing.GlobalTracer(), opentracing.SpanFromContext(ctx)
   155  	if tracer != nil && span != nil {
   156  		carrier := (*lokigrpc.HeadersCarrier)(req)
   157  		err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  	}
   162  
   163  	request := request{
   164  		request:     req,
   165  		originalCtx: ctx,
   166  
   167  		// Buffer of 1 to ensure response can be written by the server side
   168  		// of the Process stream, even if this goroutine goes away due to
   169  		// client context cancellation.
   170  		err:      make(chan error, 1),
   171  		response: make(chan *httpgrpc.HTTPResponse, 1),
   172  	}
   173  
   174  	if err := f.queueRequest(ctx, &request); err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	select {
   179  	case <-ctx.Done():
   180  		return nil, ctx.Err()
   181  
   182  	case resp := <-request.response:
   183  		return resp, nil
   184  
   185  	case err := <-request.err:
   186  		return nil, err
   187  	}
   188  }
   189  
   190  // Process allows backends to pull requests from the frontend.
   191  func (f *Frontend) Process(server frontendv1pb.Frontend_ProcessServer) error {
   192  	querierID, err := getQuerierID(server)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	f.requestQueue.RegisterQuerierConnection(querierID)
   198  	defer f.requestQueue.UnregisterQuerierConnection(querierID)
   199  
   200  	lastUserIndex := queue.FirstUser()
   201  
   202  	for {
   203  		reqWrapper, idx, err := f.requestQueue.GetNextRequestForQuerier(server.Context(), lastUserIndex, querierID)
   204  		if err != nil {
   205  			return err
   206  		}
   207  		lastUserIndex = idx
   208  
   209  		req := reqWrapper.(*request)
   210  
   211  		f.queueDuration.Observe(time.Since(req.enqueueTime).Seconds())
   212  		req.queueSpan.Finish()
   213  
   214  		/*
   215  		  We want to dequeue the next unexpired request from the chosen tenant queue.
   216  		  The chance of choosing a particular tenant for dequeueing is (1/active_tenants).
   217  		  This is problematic under load, especially with other middleware enabled such as
   218  		  querier.split-by-interval, where one request may fan out into many.
   219  		  If expired requests aren't exhausted before checking another tenant, it would take
   220  		  n_active_tenants * n_expired_requests_at_front_of_queue requests being processed
   221  		  before an active request was handled for the tenant in question.
   222  		  If this tenant meanwhile continued to queue requests,
   223  		  it's possible that it's own queue would perpetually contain only expired requests.
   224  		*/
   225  		if req.originalCtx.Err() != nil {
   226  			lastUserIndex = lastUserIndex.ReuseLastUser()
   227  			continue
   228  		}
   229  
   230  		// Handle the stream sending & receiving on a goroutine so we can
   231  		// monitoring the contexts in a select and cancel things appropriately.
   232  		resps := make(chan *frontendv1pb.ClientToFrontend, 1)
   233  		errs := make(chan error, 1)
   234  		go func() {
   235  			err = server.Send(&frontendv1pb.FrontendToClient{
   236  				Type:         frontendv1pb.HTTP_REQUEST,
   237  				HttpRequest:  req.request,
   238  				StatsEnabled: stats.IsEnabled(req.originalCtx),
   239  			})
   240  			if err != nil {
   241  				errs <- err
   242  				return
   243  			}
   244  
   245  			resp, err := server.Recv()
   246  			if err != nil {
   247  				errs <- err
   248  				return
   249  			}
   250  
   251  			resps <- resp
   252  		}()
   253  
   254  		select {
   255  		// If the upstream request is cancelled, we need to cancel the
   256  		// downstream req.  Only way we can do that is to close the stream.
   257  		// The worker client is expecting this semantics.
   258  		case <-req.originalCtx.Done():
   259  			return req.originalCtx.Err()
   260  
   261  		// Is there was an error handling this request due to network IO,
   262  		// then error out this upstream request _and_ stream.
   263  		case err := <-errs:
   264  			req.err <- err
   265  			return err
   266  
   267  		// Happy path: merge the stats and propagate the response.
   268  		case resp := <-resps:
   269  			if stats.ShouldTrackHTTPGRPCResponse(resp.HttpResponse) {
   270  				stats := stats.FromContext(req.originalCtx)
   271  				stats.Merge(resp.Stats) // Safe if stats is nil.
   272  			}
   273  
   274  			req.response <- resp.HttpResponse
   275  		}
   276  	}
   277  }
   278  
   279  func (f *Frontend) NotifyClientShutdown(_ context.Context, req *frontendv1pb.NotifyClientShutdownRequest) (*frontendv1pb.NotifyClientShutdownResponse, error) {
   280  	level.Info(f.log).Log("msg", "received shutdown notification from querier", "querier", req.GetClientID())
   281  	f.requestQueue.NotifyQuerierShutdown(req.GetClientID())
   282  
   283  	return &frontendv1pb.NotifyClientShutdownResponse{}, nil
   284  }
   285  
   286  func getQuerierID(server frontendv1pb.Frontend_ProcessServer) (string, error) {
   287  	err := server.Send(&frontendv1pb.FrontendToClient{
   288  		Type: frontendv1pb.GET_ID,
   289  		// Old queriers don't support GET_ID, and will try to use the request.
   290  		// To avoid confusing them, include dummy request.
   291  		HttpRequest: &httpgrpc.HTTPRequest{
   292  			Method: "GET",
   293  			Url:    "/invalid_request_sent_by_frontend",
   294  		},
   295  	})
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  
   300  	resp, err := server.Recv()
   301  
   302  	// Old queriers will return empty string, which is fine. All old queriers will be
   303  	// treated as single querier with lot of connections.
   304  	// (Note: if resp is nil, GetClientID() returns "")
   305  	return resp.GetClientID(), err
   306  }
   307  
   308  func (f *Frontend) queueRequest(ctx context.Context, req *request) error {
   309  	tenantIDs, err := tenant.TenantIDs(ctx)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	now := time.Now()
   315  	req.enqueueTime = now
   316  	req.queueSpan, _ = opentracing.StartSpanFromContext(ctx, "queued")
   317  
   318  	// aggregate the max queriers limit in the case of a multi tenant query
   319  	maxQueriers := validation.SmallestPositiveNonZeroIntPerTenant(tenantIDs, f.limits.MaxQueriersPerUser)
   320  
   321  	joinedTenantID := tenant.JoinTenantIDs(tenantIDs)
   322  	f.activeUsers.UpdateUserTimestamp(joinedTenantID, now)
   323  
   324  	err = f.requestQueue.EnqueueRequest(joinedTenantID, req, maxQueriers, nil)
   325  	if err == queue.ErrTooManyRequests {
   326  		return errTooManyRequest
   327  	}
   328  	return err
   329  }
   330  
   331  // CheckReady determines if the query frontend is ready.  Function parameters/return
   332  // chosen to match the same method in the ingester
   333  func (f *Frontend) CheckReady(_ context.Context) error {
   334  	// if we have more than one querier connected we will consider ourselves ready
   335  	connectedClients := f.requestQueue.GetConnectedQuerierWorkersMetric()
   336  	if connectedClients > 0 {
   337  		return nil
   338  	}
   339  
   340  	msg := fmt.Sprintf("not ready: number of queriers connected to query-frontend is %d", int64(connectedClients))
   341  	level.Info(f.log).Log("msg", msg)
   342  	return errors.New(msg)
   343  }