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

     1  package v2
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"math/rand"
     8  	"net/http"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/go-kit/log"
    13  	"github.com/go-kit/log/level"
    14  	"github.com/grafana/dskit/flagext"
    15  	"github.com/grafana/dskit/grpcclient"
    16  	"github.com/grafana/dskit/netutil"
    17  	"github.com/grafana/dskit/ring"
    18  	"github.com/grafana/dskit/services"
    19  	"github.com/opentracing/opentracing-go"
    20  	"github.com/pkg/errors"
    21  	"github.com/prometheus/client_golang/prometheus"
    22  	"github.com/prometheus/client_golang/prometheus/promauto"
    23  	"github.com/weaveworks/common/httpgrpc"
    24  	"go.uber.org/atomic"
    25  
    26  	"github.com/grafana/dskit/tenant"
    27  
    28  	"github.com/grafana/loki/pkg/lokifrontend/frontend/v2/frontendv2pb"
    29  	"github.com/grafana/loki/pkg/querier/stats"
    30  	lokigrpc "github.com/grafana/loki/pkg/util/httpgrpc"
    31  	util_log "github.com/grafana/loki/pkg/util/log"
    32  )
    33  
    34  // Config for a Frontend.
    35  type Config struct {
    36  	SchedulerAddress  string            `yaml:"scheduler_address"`
    37  	DNSLookupPeriod   time.Duration     `yaml:"scheduler_dns_lookup_period"`
    38  	WorkerConcurrency int               `yaml:"scheduler_worker_concurrency"`
    39  	GRPCClientConfig  grpcclient.Config `yaml:"grpc_client_config"`
    40  
    41  	// Used to find local IP address, that is sent to scheduler and querier-worker.
    42  	InfNames []string `yaml:"instance_interface_names"`
    43  
    44  	// If set, address is not computed from interfaces.
    45  	Addr string `yaml:"address" doc:"hidden"`
    46  	Port int    `doc:"hidden"`
    47  }
    48  
    49  func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
    50  	f.StringVar(&cfg.SchedulerAddress, "frontend.scheduler-address", "", "DNS hostname used for finding query-schedulers.")
    51  	f.DurationVar(&cfg.DNSLookupPeriod, "frontend.scheduler-dns-lookup-period", 10*time.Second, "How often to resolve the scheduler-address, in order to look for new query-scheduler instances. Also used to determine how often to poll the scheduler-ring for addresses if the scheduler-ring is configured.")
    52  	f.IntVar(&cfg.WorkerConcurrency, "frontend.scheduler-worker-concurrency", 5, "Number of concurrent workers forwarding queries to single query-scheduler.")
    53  
    54  	cfg.InfNames = netutil.PrivateNetworkInterfacesWithFallback([]string{"eth0", "en0"}, util_log.Logger)
    55  	f.Var((*flagext.StringSlice)(&cfg.InfNames), "frontend.instance-interface-names", "Name of network interface to read address from. This address is sent to query-scheduler and querier, which uses it to send the query response back to query-frontend.")
    56  	f.StringVar(&cfg.Addr, "frontend.instance-addr", "", "IP address to advertise to querier (via scheduler) (resolved via interfaces by default).")
    57  	f.IntVar(&cfg.Port, "frontend.instance-port", 0, "Port to advertise to querier (via scheduler) (defaults to server.grpc-listen-port).")
    58  
    59  	cfg.GRPCClientConfig.RegisterFlagsWithPrefix("frontend.grpc-client-config", f)
    60  }
    61  
    62  // Frontend implements GrpcRoundTripper. It queues HTTP requests,
    63  // dispatches them to backends via gRPC, and handles retries for requests which failed.
    64  type Frontend struct {
    65  	services.Service
    66  
    67  	cfg Config
    68  	log log.Logger
    69  
    70  	lastQueryID atomic.Uint64
    71  
    72  	// frontend workers will read from this channel, and send request to scheduler.
    73  	requestsCh chan *frontendRequest
    74  
    75  	schedulerWorkers *frontendSchedulerWorkers
    76  	requests         *requestsInProgress
    77  }
    78  
    79  type frontendRequest struct {
    80  	queryID      uint64
    81  	request      *httpgrpc.HTTPRequest
    82  	userID       string
    83  	statsEnabled bool
    84  
    85  	cancel context.CancelFunc
    86  
    87  	enqueue  chan enqueueResult
    88  	response chan *frontendv2pb.QueryResultRequest
    89  }
    90  
    91  type enqueueStatus int
    92  
    93  const (
    94  	// Sent to scheduler successfully, and frontend should wait for response now.
    95  	waitForResponse enqueueStatus = iota
    96  
    97  	// Failed to forward request to scheduler, frontend will try again.
    98  	failed
    99  )
   100  
   101  type enqueueResult struct {
   102  	status enqueueStatus
   103  
   104  	cancelCh chan<- uint64 // Channel that can be used for request cancellation. If nil, cancellation is not possible.
   105  }
   106  
   107  // NewFrontend creates a new frontend.
   108  func NewFrontend(cfg Config, ring ring.ReadRing, log log.Logger, reg prometheus.Registerer) (*Frontend, error) {
   109  	requestsCh := make(chan *frontendRequest)
   110  
   111  	schedulerWorkers, err := newFrontendSchedulerWorkers(cfg, fmt.Sprintf("%s:%d", cfg.Addr, cfg.Port), ring, requestsCh, log)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	f := &Frontend{
   117  		cfg:              cfg,
   118  		log:              log,
   119  		requestsCh:       requestsCh,
   120  		schedulerWorkers: schedulerWorkers,
   121  		requests:         newRequestsInProgress(),
   122  	}
   123  	// Randomize to avoid getting responses from queries sent before restart, which could lead to mixing results
   124  	// between different queries. Note that frontend verifies the user, so it cannot leak results between tenants.
   125  	// This isn't perfect, but better than nothing.
   126  	f.lastQueryID.Store(rand.Uint64())
   127  
   128  	promauto.With(reg).NewGaugeFunc(prometheus.GaugeOpts{
   129  		Name: "cortex_query_frontend_queries_in_progress",
   130  		Help: "Number of queries in progress handled by this frontend.",
   131  	}, func() float64 {
   132  		return float64(f.requests.count())
   133  	})
   134  
   135  	promauto.With(reg).NewGaugeFunc(prometheus.GaugeOpts{
   136  		Name: "cortex_query_frontend_connected_schedulers",
   137  		Help: "Number of schedulers this frontend is connected to.",
   138  	}, func() float64 {
   139  		return float64(f.schedulerWorkers.getWorkersCount())
   140  	})
   141  
   142  	f.Service = services.NewIdleService(f.starting, f.stopping)
   143  	return f, nil
   144  }
   145  
   146  func (f *Frontend) starting(ctx context.Context) error {
   147  	return errors.Wrap(services.StartAndAwaitRunning(ctx, f.schedulerWorkers), "failed to start frontend scheduler workers")
   148  }
   149  
   150  func (f *Frontend) stopping(_ error) error {
   151  	return errors.Wrap(services.StopAndAwaitTerminated(context.Background(), f.schedulerWorkers), "failed to stop frontend scheduler workers")
   152  }
   153  
   154  // RoundTripGRPC round trips a proto (instead of a HTTP request).
   155  func (f *Frontend) RoundTripGRPC(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) {
   156  	if s := f.State(); s != services.Running {
   157  		return nil, fmt.Errorf("frontend not running: %v", s)
   158  	}
   159  
   160  	tenantIDs, err := tenant.TenantIDs(ctx)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	userID := tenant.JoinTenantIDs(tenantIDs)
   165  
   166  	// Propagate trace context in gRPC too - this will be ignored if using HTTP.
   167  	tracer, span := opentracing.GlobalTracer(), opentracing.SpanFromContext(ctx)
   168  	if tracer != nil && span != nil {
   169  		carrier := (*lokigrpc.HeadersCarrier)(req)
   170  		if err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier); err != nil {
   171  			return nil, err
   172  		}
   173  	}
   174  
   175  	ctx, cancel := context.WithCancel(ctx)
   176  	defer cancel()
   177  
   178  	freq := &frontendRequest{
   179  		queryID:      f.lastQueryID.Inc(),
   180  		request:      req,
   181  		userID:       userID,
   182  		statsEnabled: stats.IsEnabled(ctx),
   183  
   184  		cancel: cancel,
   185  
   186  		// Buffer of 1 to ensure response or error can be written to the channel
   187  		// even if this goroutine goes away due to client context cancellation.
   188  		enqueue:  make(chan enqueueResult, 1),
   189  		response: make(chan *frontendv2pb.QueryResultRequest, 1),
   190  	}
   191  
   192  	f.requests.put(freq)
   193  	defer f.requests.delete(freq.queryID)
   194  
   195  	retries := f.cfg.WorkerConcurrency + 1 // To make sure we hit at least two different schedulers.
   196  
   197  enqueueAgain:
   198  	var cancelCh chan<- uint64
   199  	select {
   200  	case <-ctx.Done():
   201  		return nil, ctx.Err()
   202  
   203  	case f.requestsCh <- freq:
   204  		// Enqueued, let's wait for response.
   205  		enqRes := <-freq.enqueue
   206  
   207  		if enqRes.status == waitForResponse {
   208  			cancelCh = enqRes.cancelCh
   209  			break // go wait for response.
   210  		} else if enqRes.status == failed {
   211  			retries--
   212  			if retries > 0 {
   213  				goto enqueueAgain
   214  			}
   215  		}
   216  
   217  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "failed to enqueue request")
   218  	}
   219  
   220  	select {
   221  	case <-ctx.Done():
   222  		if cancelCh != nil {
   223  			select {
   224  			case cancelCh <- freq.queryID:
   225  				// cancellation sent.
   226  			default:
   227  				// failed to cancel, ignore.
   228  				level.Warn(f.log).Log("msg", "failed to send cancellation request to scheduler, queue full")
   229  			}
   230  		}
   231  		return nil, ctx.Err()
   232  
   233  	case resp := <-freq.response:
   234  		if stats.ShouldTrackHTTPGRPCResponse(resp.HttpResponse) {
   235  			stats := stats.FromContext(ctx)
   236  			stats.Merge(resp.Stats) // Safe if stats is nil.
   237  		}
   238  
   239  		return resp.HttpResponse, nil
   240  	}
   241  }
   242  
   243  func (f *Frontend) QueryResult(ctx context.Context, qrReq *frontendv2pb.QueryResultRequest) (*frontendv2pb.QueryResultResponse, error) {
   244  	tenantIDs, err := tenant.TenantIDs(ctx)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	userID := tenant.JoinTenantIDs(tenantIDs)
   249  
   250  	req := f.requests.get(qrReq.QueryID)
   251  	// It is possible that some old response belonging to different user was received, if frontend has restarted.
   252  	// To avoid leaking query results between users, we verify the user here.
   253  	// To avoid mixing results from different queries, we randomize queryID counter on start.
   254  	if req != nil && req.userID == userID {
   255  		select {
   256  		case req.response <- qrReq:
   257  			// Should always be possible, unless QueryResult is called multiple times with the same queryID.
   258  		default:
   259  			level.Warn(f.log).Log("msg", "failed to write query result to the response channel", "queryID", qrReq.QueryID, "user", userID)
   260  		}
   261  	}
   262  
   263  	return &frontendv2pb.QueryResultResponse{}, nil
   264  }
   265  
   266  // CheckReady determines if the query frontend is ready.  Function parameters/return
   267  // chosen to match the same method in the ingester
   268  func (f *Frontend) CheckReady(_ context.Context) error {
   269  	workers := f.schedulerWorkers.getWorkersCount()
   270  
   271  	// If frontend is connected to at least one scheduler, we are ready.
   272  	if workers > 0 {
   273  		return nil
   274  	}
   275  
   276  	msg := fmt.Sprintf("not ready: number of schedulers this worker is connected to is %d", workers)
   277  	level.Info(f.log).Log("msg", msg)
   278  	return errors.New(msg)
   279  }
   280  
   281  const stripeSize = 1 << 6
   282  
   283  type requestsInProgress struct {
   284  	locks    []sync.Mutex
   285  	requests []map[uint64]*frontendRequest
   286  }
   287  
   288  func newRequestsInProgress() *requestsInProgress {
   289  	x := &requestsInProgress{
   290  		requests: make([]map[uint64]*frontendRequest, stripeSize),
   291  		locks:    make([]sync.Mutex, stripeSize),
   292  	}
   293  
   294  	for i := range x.requests {
   295  		x.requests[i] = map[uint64]*frontendRequest{}
   296  	}
   297  
   298  	return x
   299  }
   300  
   301  func (r *requestsInProgress) count() (res int) {
   302  	for i := range r.requests {
   303  		r.locks[i].Lock()
   304  		res += len(r.requests[i])
   305  		r.locks[i].Unlock()
   306  	}
   307  	return
   308  }
   309  
   310  func (r *requestsInProgress) put(req *frontendRequest) {
   311  	i := req.queryID & uint64(stripeSize-1)
   312  	r.locks[i].Lock()
   313  	r.requests[i][req.queryID] = req
   314  	r.locks[i].Unlock()
   315  }
   316  
   317  func (r *requestsInProgress) delete(queryID uint64) {
   318  	i := queryID & uint64(stripeSize-1)
   319  	r.locks[i].Lock()
   320  	delete(r.requests[i], queryID)
   321  	r.locks[i].Unlock()
   322  
   323  }
   324  
   325  func (r *requestsInProgress) get(queryID uint64) *frontendRequest {
   326  	i := queryID & uint64(stripeSize-1)
   327  	r.locks[i].Lock()
   328  	req := r.requests[i][queryID]
   329  	r.locks[i].Unlock()
   330  	return req
   331  }