github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/internal/client/requestbatcher/batcher.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package requestbatcher is a library to enable easy batching of roachpb
    12  // requests.
    13  //
    14  // Batching in general represents a tradeoff between throughput and latency. The
    15  // underlying assumption being that batched operations are cheaper than an
    16  // individual operation. If this is not the case for your workload, don't use
    17  // this library.
    18  //
    19  // Batching assumes that data with the same key can be sent in a single batch.
    20  // The initial implementation uses rangeID as the key explicitly to avoid
    21  // creating an overly general solution without motivation but interested readers
    22  // should recognize that it would be easy to extend this package to accept an
    23  // arbitrary comparable key.
    24  package requestbatcher
    25  
    26  import (
    27  	"container/heap"
    28  	"context"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/cockroachdb/cockroach/pkg/kv"
    33  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    34  	"github.com/cockroachdb/cockroach/pkg/util/contextutil"
    35  	"github.com/cockroachdb/cockroach/pkg/util/log"
    36  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    37  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    38  )
    39  
    40  // The motivating use case for this package are opportunities to perform cleanup
    41  // operations in a single raft transaction rather than several. Three main
    42  // opportunities are known:
    43  //
    44  //   1) Intent resolution
    45  //   2) Txn heartbeating
    46  //   3) Txn record garbage collection
    47  //
    48  // The first two have a relatively tight time bound expectations. In other words
    49  // it would be surprising and negative for a client if operations were not sent
    50  // soon after they were queued. The transaction record GC workload can be rather
    51  // asynchronous. This motivates the need for some knobs to control the maximum
    52  // acceptable amount of time to buffer operations before sending.
    53  // Another wrinkle is dealing with the different ways in which sending a batch
    54  // may fail. A batch may fail in an ambiguous way (RPC/network errors), it may
    55  // fail completely (which is likely indistinguishable from the ambiguous
    56  // failure) and lastly it may fail partially. Today's Sender contract is fairly
    57  // ambiguous about the contract between BatchResponse inner responses and errors
    58  // returned from a batch request.
    59  
    60  // TODO(ajwerner): Do we need to consider ordering dependencies between
    61  // operations? For the initial motivating use cases for this library there are
    62  // no data dependencies between operations and the only key will be the guess
    63  // for where an operation should go.
    64  
    65  // TODO(ajwerner): Consider a more sophisticated mechanism to limit on maximum
    66  // number of requests in flight at a time. This may ultimately lead to a need
    67  // for queuing. Furthermore consider using batch time to dynamically tune the
    68  // amount of time we wait.
    69  
    70  // TODO(ajwerner): Consider filtering requests which might have been canceled
    71  // before sending a batch.
    72  
    73  // TODO(ajwerner): Consider more dynamic policies with regards to deadlines.
    74  // Perhaps we want to wait no more than some percentile of the duration of
    75  // historical operations and stay idle only some other percentile. For example
    76  // imagine if the max delay was the 50th and the max idle was the 10th? This
    77  // has a problem when much of the prior workload was say local operations and
    78  // happened very rapidly. Perhaps we need to provide some bounding envelope?
    79  
    80  // TODO(ajwerner): Consider a more general purpose interface for this package.
    81  // While several interface-oriented interfaces have been explored they all felt
    82  // heavy and allocation intensive.
    83  
    84  // TODO(ajwerner): Consider providing an interface which enables a single
    85  // goroutine to dispatch a number of requests destined for different ranges to
    86  // the RequestBatcher which may then wait for completion of all of the requests.
    87  // What is the right contract for error handling? Imagine a situation where a
    88  // client has dispatched N requests and one has been sent and returns with an
    89  // error while others are queued. What should happen? Should the client receive
    90  // the error rapidly? Should the other requests be sent at all? Should they be
    91  // filtered before sending?
    92  
    93  // Config contains the dependencies and configuration for a Batcher.
    94  type Config struct {
    95  
    96  	// Name of the batcher, used for logging, timeout errors, and the stopper.
    97  	Name string
    98  
    99  	// Sender can round-trip a batch. Sender must not be nil.
   100  	Sender kv.Sender
   101  
   102  	// Stopper controls the lifecycle of the Batcher. Stopper must not be nil.
   103  	Stopper *stop.Stopper
   104  
   105  	// MaxSizePerBatch is the maximum number of bytes in individual requests in a
   106  	// batch. If MaxSizePerBatch <= 0 then no limit is enforced.
   107  	MaxSizePerBatch int
   108  
   109  	// MaxMsgsPerBatch is the maximum number of messages.
   110  	// If MaxMsgsPerBatch <= 0 then no limit is enforced.
   111  	MaxMsgsPerBatch int
   112  
   113  	// MaxKeysPerBatchReq is the maximum number of keys that each batch is
   114  	// allowed to touch during one of its requests. If the limit is exceeded,
   115  	// the batch is paginated over a series of individual requests. This limit
   116  	// corresponds to the MaxSpanRequestKeys assigned to the Header of each
   117  	// request. If MaxKeysPerBatchReq <= 0 then no limit is enforced.
   118  	MaxKeysPerBatchReq int
   119  
   120  	// MaxWait is the maximum amount of time a message should wait in a batch
   121  	// before being sent. If MaxWait is <= 0 then no wait timeout is enforced.
   122  	// It is inadvisable to disable both MaxIdle and MaxWait.
   123  	MaxWait time.Duration
   124  
   125  	// MaxIdle is the amount of time a batch should wait between message additions
   126  	// before being sent. The idle timer allows clients to observe low latencies
   127  	// when throughput is low. If MaxWait is <= 0 then no wait timeout is
   128  	// enforced. It is inadvisable to disable both MaxIdle and MaxWait.
   129  	MaxIdle time.Duration
   130  
   131  	// InFlightBackpressureLimit is the number of batches in flight above which
   132  	// sending clients should experience backpressure. If the batcher has more
   133  	// requests than this in flight it will not accept new requests until the
   134  	// number of in flight batches is again below this threshold. This value does
   135  	// not limit the number of batches which may ultimately be in flight as
   136  	// batches which are queued to send but not yet in flight will still send.
   137  	// Note that values	less than or equal to zero will result in the use of
   138  	// DefaultInFlightBackpressureLimit.
   139  	InFlightBackpressureLimit int
   140  
   141  	// NowFunc is used to determine the current time. It defaults to timeutil.Now.
   142  	NowFunc func() time.Time
   143  }
   144  
   145  const (
   146  	// DefaultInFlightBackpressureLimit is the InFlightBackpressureLimit used if
   147  	// a zero value for that setting is passed in a Config to New.
   148  	// TODO(ajwerner): Justify this number.
   149  	DefaultInFlightBackpressureLimit = 1000
   150  
   151  	// BackpressureRecoveryFraction is the fraction of InFlightBackpressureLimit
   152  	// used to detect when enough in flight requests have completed such that more
   153  	// requests should now be accepted. A value less than 1 is chosen in order to
   154  	// avoid thrashing on backpressure which might ultimately defeat the purpose
   155  	// of the RequestBatcher.
   156  	backpressureRecoveryFraction = .8
   157  )
   158  
   159  func backpressureRecoveryThreshold(limit int) int {
   160  	if l := int(float64(limit) * backpressureRecoveryFraction); l > 0 {
   161  		return l
   162  	}
   163  	return 1 // don't allow the recovery threshold to be 0
   164  }
   165  
   166  // RequestBatcher batches requests destined for a single range based on
   167  // a configured batching policy.
   168  type RequestBatcher struct {
   169  	pool pool
   170  	cfg  Config
   171  
   172  	// sendBatchOpName is the string passed to contextutil.RunWithTimeout when
   173  	// sending a batch.
   174  	sendBatchOpName string
   175  
   176  	batches batchQueue
   177  
   178  	requestChan  chan *request
   179  	sendDoneChan chan struct{}
   180  }
   181  
   182  // Response is exported for use with the channel-oriented SendWithChan method.
   183  // At least one of Resp or Err will be populated for every sent Response.
   184  type Response struct {
   185  	Resp roachpb.Response
   186  	Err  error
   187  }
   188  
   189  // New creates a new RequestBatcher.
   190  func New(cfg Config) *RequestBatcher {
   191  	validateConfig(&cfg)
   192  	b := &RequestBatcher{
   193  		cfg:          cfg,
   194  		pool:         makePool(),
   195  		batches:      makeBatchQueue(),
   196  		requestChan:  make(chan *request),
   197  		sendDoneChan: make(chan struct{}),
   198  	}
   199  	b.sendBatchOpName = b.cfg.Name + ".sendBatch"
   200  	if err := cfg.Stopper.RunAsyncTask(context.Background(), b.cfg.Name, b.run); err != nil {
   201  		panic(err)
   202  	}
   203  	return b
   204  }
   205  
   206  func validateConfig(cfg *Config) {
   207  	if cfg.Stopper == nil {
   208  		panic("cannot construct a Batcher with a nil Stopper")
   209  	} else if cfg.Sender == nil {
   210  		panic("cannot construct a Batcher with a nil Sender")
   211  	}
   212  	if cfg.InFlightBackpressureLimit <= 0 {
   213  		cfg.InFlightBackpressureLimit = DefaultInFlightBackpressureLimit
   214  	}
   215  	if cfg.NowFunc == nil {
   216  		cfg.NowFunc = timeutil.Now
   217  	}
   218  }
   219  
   220  // SendWithChan sends a request with a client provided response channel. The
   221  // client is responsible for ensuring that the passed respChan has a buffer at
   222  // least as large as the number of responses it expects to receive. Using an
   223  // insufficiently buffered channel can lead to deadlocks and unintended delays
   224  // processing requests inside the RequestBatcher.
   225  func (b *RequestBatcher) SendWithChan(
   226  	ctx context.Context, respChan chan<- Response, rangeID roachpb.RangeID, req roachpb.Request,
   227  ) error {
   228  	select {
   229  	case b.requestChan <- b.pool.newRequest(ctx, rangeID, req, respChan):
   230  		return nil
   231  	case <-b.cfg.Stopper.ShouldQuiesce():
   232  		return stop.ErrUnavailable
   233  	case <-ctx.Done():
   234  		return ctx.Err()
   235  	}
   236  }
   237  
   238  // Send sends req as a part of a batch. An error is returned if the context
   239  // is canceled before the sending of the request completes. The context with
   240  // the latest deadline for a batch is used to send the underlying batch request.
   241  func (b *RequestBatcher) Send(
   242  	ctx context.Context, rangeID roachpb.RangeID, req roachpb.Request,
   243  ) (roachpb.Response, error) {
   244  	responseChan := b.pool.getResponseChan()
   245  	if err := b.SendWithChan(ctx, responseChan, rangeID, req); err != nil {
   246  		return nil, err
   247  	}
   248  	select {
   249  	case resp := <-responseChan:
   250  		// It's only safe to put responseChan back in the pool if it has been
   251  		// received from.
   252  		b.pool.putResponseChan(responseChan)
   253  		return resp.Resp, resp.Err
   254  	case <-b.cfg.Stopper.ShouldQuiesce():
   255  		return nil, stop.ErrUnavailable
   256  	case <-ctx.Done():
   257  		return nil, ctx.Err()
   258  	}
   259  }
   260  
   261  func (b *RequestBatcher) sendDone(ba *batch) {
   262  	b.pool.putBatch(ba)
   263  	select {
   264  	case b.sendDoneChan <- struct{}{}:
   265  	case <-b.cfg.Stopper.ShouldQuiesce():
   266  	}
   267  }
   268  
   269  func (b *RequestBatcher) sendBatch(ctx context.Context, ba *batch) {
   270  	b.cfg.Stopper.RunWorker(ctx, func(ctx context.Context) {
   271  		defer b.sendDone(ba)
   272  		var br *roachpb.BatchResponse
   273  		send := func(ctx context.Context) error {
   274  			var pErr *roachpb.Error
   275  			if br, pErr = b.cfg.Sender.Send(ctx, ba.batchRequest(&b.cfg)); pErr != nil {
   276  				return pErr.GoError()
   277  			}
   278  			return nil
   279  		}
   280  		if !ba.sendDeadline.IsZero() {
   281  			actualSend := send
   282  			send = func(context.Context) error {
   283  				return contextutil.RunWithTimeout(
   284  					ctx, b.sendBatchOpName, timeutil.Until(ba.sendDeadline), actualSend)
   285  			}
   286  		}
   287  		// Send requests in a loop to support pagination, which may be necessary
   288  		// if MaxKeysPerBatchReq is set. If so, partial responses with resume
   289  		// spans may be returned for requests, indicating that the limit was hit
   290  		// before they could complete and that they should be resumed over the
   291  		// specified key span. Requests in the batch are neither guaranteed to
   292  		// be ordered nor guaranteed to be non-overlapping, so we can make no
   293  		// assumptions about the requests that will result in full responses
   294  		// (with no resume spans) vs. partial responses vs. empty responses (see
   295  		// the comment on roachpb.Header.MaxSpanRequestKeys).
   296  		//
   297  		// To accommodate this, we keep track of all partial responses from
   298  		// previous iterations. After receiving a batch of responses during an
   299  		// iteration, the responses are each combined with the previous response
   300  		// for their corresponding requests. From there, responses that have no
   301  		// resume spans are removed. Responses that have resume spans are
   302  		// updated appropriately and sent again in the next iteration. The loop
   303  		// proceeds until all requests have been run to completion.
   304  		var prevResps []roachpb.Response
   305  		for len(ba.reqs) > 0 {
   306  			err := send(ctx)
   307  			nextReqs, nextPrevResps := ba.reqs[:0], prevResps[:0]
   308  			for i, r := range ba.reqs {
   309  				var res Response
   310  				if br != nil {
   311  					resp := br.Responses[i].GetInner()
   312  					if prevResps != nil {
   313  						prevResp := prevResps[i]
   314  						if cErr := roachpb.CombineResponses(prevResp, resp); cErr != nil {
   315  							log.Fatalf(ctx, "%v", cErr)
   316  						}
   317  						resp = prevResp
   318  					}
   319  					if resume := resp.Header().ResumeSpan; resume != nil {
   320  						// Add a trimmed request to the next batch.
   321  						h := r.req.Header()
   322  						h.SetSpan(*resume)
   323  						r.req = r.req.ShallowCopy()
   324  						r.req.SetHeader(h)
   325  						nextReqs = append(nextReqs, r)
   326  						// Strip resume span from previous response and record.
   327  						prevH := resp.Header()
   328  						prevH.ResumeSpan = nil
   329  						prevResp := resp
   330  						prevResp.SetHeader(prevH)
   331  						nextPrevResps = append(nextPrevResps, prevResp)
   332  						continue
   333  					}
   334  					res.Resp = resp
   335  				}
   336  				if err != nil {
   337  					res.Err = err
   338  				}
   339  				b.sendResponse(r, res)
   340  			}
   341  			ba.reqs, prevResps = nextReqs, nextPrevResps
   342  		}
   343  	})
   344  }
   345  
   346  func (b *RequestBatcher) sendResponse(req *request, resp Response) {
   347  	// This send should never block because responseChan is buffered.
   348  	req.responseChan <- resp
   349  	b.pool.putRequest(req)
   350  }
   351  
   352  func addRequestToBatch(cfg *Config, now time.Time, ba *batch, r *request) (shouldSend bool) {
   353  	// Update the deadline for the batch if this requests's deadline is later
   354  	// than the current latest.
   355  	rDeadline, rHasDeadline := r.ctx.Deadline()
   356  	// If this is the first request or
   357  	if len(ba.reqs) == 0 ||
   358  		// there are already requests and there is a deadline and
   359  		(len(ba.reqs) > 0 && !ba.sendDeadline.IsZero() &&
   360  			// this request either doesn't have a deadline or has a later deadline,
   361  			(!rHasDeadline || rDeadline.After(ba.sendDeadline))) {
   362  		// set the deadline to this request's deadline.
   363  		ba.sendDeadline = rDeadline
   364  	}
   365  
   366  	ba.reqs = append(ba.reqs, r)
   367  	ba.size += r.req.Size()
   368  	ba.lastUpdated = now
   369  
   370  	if cfg.MaxIdle > 0 {
   371  		ba.deadline = ba.lastUpdated.Add(cfg.MaxIdle)
   372  	}
   373  	if cfg.MaxWait > 0 {
   374  		waitDeadline := ba.startTime.Add(cfg.MaxWait)
   375  		if cfg.MaxIdle <= 0 || waitDeadline.Before(ba.deadline) {
   376  			ba.deadline = waitDeadline
   377  		}
   378  	}
   379  	return (cfg.MaxMsgsPerBatch > 0 && len(ba.reqs) >= cfg.MaxMsgsPerBatch) ||
   380  		(cfg.MaxSizePerBatch > 0 && ba.size >= cfg.MaxSizePerBatch)
   381  }
   382  
   383  func (b *RequestBatcher) cleanup(err error) {
   384  	for ba := b.batches.popFront(); ba != nil; ba = b.batches.popFront() {
   385  		for _, r := range ba.reqs {
   386  			b.sendResponse(r, Response{Err: err})
   387  		}
   388  	}
   389  }
   390  
   391  func (b *RequestBatcher) run(ctx context.Context) {
   392  	// Create a context to be used in sendBatch to cancel in-flight batches when
   393  	// this function exits. If we did not cancel in-flight requests then the
   394  	// Stopper might get stuck waiting for those requests to complete.
   395  	sendCtx, cancel := context.WithCancel(ctx)
   396  	defer cancel()
   397  	var (
   398  		// inFlight tracks the number of batches currently being sent.
   399  		// true.
   400  		inFlight = 0
   401  		// inBackPressure indicates whether the reqChan is enabled.
   402  		// It becomes true when inFlight exceeds b.cfg.InFlightBackpressureLimit.
   403  		inBackPressure = false
   404  		// recoveryThreshold is the number of in flight requests below which the
   405  		// the inBackPressure state should exit.
   406  		recoveryThreshold = backpressureRecoveryThreshold(b.cfg.InFlightBackpressureLimit)
   407  		// reqChan consults inBackPressure to determine whether the goroutine is
   408  		// accepting new requests.
   409  		reqChan = func() <-chan *request {
   410  			if inBackPressure {
   411  				return nil
   412  			}
   413  			return b.requestChan
   414  		}
   415  		sendBatch = func(ba *batch) {
   416  			inFlight++
   417  			if inFlight >= b.cfg.InFlightBackpressureLimit {
   418  				inBackPressure = true
   419  			}
   420  			b.sendBatch(sendCtx, ba)
   421  		}
   422  		handleSendDone = func() {
   423  			inFlight--
   424  			if inFlight < recoveryThreshold {
   425  				inBackPressure = false
   426  			}
   427  		}
   428  		handleRequest = func(req *request) {
   429  			now := b.cfg.NowFunc()
   430  			ba, existsInQueue := b.batches.get(req.rangeID)
   431  			if !existsInQueue {
   432  				ba = b.pool.newBatch(now)
   433  			}
   434  			if shouldSend := addRequestToBatch(&b.cfg, now, ba, req); shouldSend {
   435  				if existsInQueue {
   436  					b.batches.remove(ba)
   437  				}
   438  				sendBatch(ba)
   439  			} else {
   440  				b.batches.upsert(ba)
   441  			}
   442  		}
   443  		deadline      time.Time
   444  		timer         = timeutil.NewTimer()
   445  		maybeSetTimer = func() {
   446  			var nextDeadline time.Time
   447  			if next := b.batches.peekFront(); next != nil {
   448  				nextDeadline = next.deadline
   449  			}
   450  			if !deadline.Equal(nextDeadline) || timer.Read {
   451  				deadline = nextDeadline
   452  				if !deadline.IsZero() {
   453  					timer.Reset(timeutil.Until(deadline))
   454  				} else {
   455  					// Clear the current timer due to a sole batch already sent before
   456  					// the timer fired.
   457  					timer.Stop()
   458  					timer = timeutil.NewTimer()
   459  				}
   460  			}
   461  		}
   462  	)
   463  	for {
   464  		select {
   465  		case req := <-reqChan():
   466  			handleRequest(req)
   467  			maybeSetTimer()
   468  		case <-timer.C:
   469  			timer.Read = true
   470  			sendBatch(b.batches.popFront())
   471  			maybeSetTimer()
   472  		case <-b.sendDoneChan:
   473  			handleSendDone()
   474  		case <-b.cfg.Stopper.ShouldQuiesce():
   475  			b.cleanup(stop.ErrUnavailable)
   476  			return
   477  		case <-ctx.Done():
   478  			b.cleanup(ctx.Err())
   479  			return
   480  		}
   481  	}
   482  }
   483  
   484  type request struct {
   485  	ctx          context.Context
   486  	req          roachpb.Request
   487  	rangeID      roachpb.RangeID
   488  	responseChan chan<- Response
   489  }
   490  
   491  type batch struct {
   492  	reqs []*request
   493  	size int // bytes
   494  
   495  	// sendDeadline is the latest deadline reported by a request's context.
   496  	// It will be zero valued if any request does not contain a deadline.
   497  	sendDeadline time.Time
   498  
   499  	// idx is the batch's index in the batchQueue.
   500  	idx int
   501  
   502  	// deadline is the time at which this batch should be sent according to the
   503  	// Batcher's configuration.
   504  	deadline time.Time
   505  	// startTime is the time at which the first request was added to the batch.
   506  	startTime time.Time
   507  	// lastUpdated is the latest time when a request was added to the batch.
   508  	lastUpdated time.Time
   509  }
   510  
   511  func (b *batch) rangeID() roachpb.RangeID {
   512  	if len(b.reqs) == 0 {
   513  		panic("rangeID cannot be called on an empty batch")
   514  	}
   515  	return b.reqs[0].rangeID
   516  }
   517  
   518  func (b *batch) batchRequest(cfg *Config) roachpb.BatchRequest {
   519  	req := roachpb.BatchRequest{
   520  		// Preallocate the Requests slice.
   521  		Requests: make([]roachpb.RequestUnion, 0, len(b.reqs)),
   522  	}
   523  	for _, r := range b.reqs {
   524  		req.Add(r.req)
   525  	}
   526  	if cfg.MaxKeysPerBatchReq > 0 {
   527  		req.MaxSpanRequestKeys = int64(cfg.MaxKeysPerBatchReq)
   528  	}
   529  	return req
   530  }
   531  
   532  // pool stores object pools for the various commonly reused objects of the
   533  // batcher
   534  type pool struct {
   535  	responseChanPool sync.Pool
   536  	batchPool        sync.Pool
   537  	requestPool      sync.Pool
   538  }
   539  
   540  func makePool() pool {
   541  	return pool{
   542  		responseChanPool: sync.Pool{
   543  			New: func() interface{} { return make(chan Response, 1) },
   544  		},
   545  		batchPool: sync.Pool{
   546  			New: func() interface{} { return &batch{} },
   547  		},
   548  		requestPool: sync.Pool{
   549  			New: func() interface{} { return &request{} },
   550  		},
   551  	}
   552  }
   553  
   554  func (p *pool) getResponseChan() chan Response {
   555  	return p.responseChanPool.Get().(chan Response)
   556  }
   557  
   558  func (p *pool) putResponseChan(r chan Response) {
   559  	p.responseChanPool.Put(r)
   560  }
   561  
   562  func (p *pool) newRequest(
   563  	ctx context.Context, rangeID roachpb.RangeID, req roachpb.Request, responseChan chan<- Response,
   564  ) *request {
   565  	r := p.requestPool.Get().(*request)
   566  	*r = request{
   567  		ctx:          ctx,
   568  		rangeID:      rangeID,
   569  		req:          req,
   570  		responseChan: responseChan,
   571  	}
   572  	return r
   573  }
   574  
   575  func (p *pool) putRequest(r *request) {
   576  	*r = request{}
   577  	p.requestPool.Put(r)
   578  }
   579  
   580  func (p *pool) newBatch(now time.Time) *batch {
   581  	ba := p.batchPool.Get().(*batch)
   582  	*ba = batch{
   583  		startTime: now,
   584  		idx:       -1,
   585  	}
   586  	return ba
   587  }
   588  
   589  func (p *pool) putBatch(b *batch) {
   590  	*b = batch{}
   591  	p.batchPool.Put(b)
   592  }
   593  
   594  // batchQueue is a container for batch objects which offers O(1) get based on
   595  // rangeID and peekFront as well as O(log(n)) upsert, removal, popFront.
   596  // Batch structs are heap ordered inside of the batches slice based on their
   597  // deadline with the earliest deadline at the front.
   598  //
   599  // Note that the batch struct stores its index in the batches slice and is -1
   600  // when not part of the queue. The heap methods update the batch indices when
   601  // updating the heap. Take care not to ever put a batch in to multiple
   602  // batchQueues. At time of writing this package only ever used one batchQueue
   603  // per RequestBatcher.
   604  type batchQueue struct {
   605  	batches []*batch
   606  	byRange map[roachpb.RangeID]*batch
   607  }
   608  
   609  var _ heap.Interface = (*batchQueue)(nil)
   610  
   611  func makeBatchQueue() batchQueue {
   612  	return batchQueue{
   613  		byRange: map[roachpb.RangeID]*batch{},
   614  	}
   615  }
   616  
   617  func (q *batchQueue) peekFront() *batch {
   618  	if q.Len() == 0 {
   619  		return nil
   620  	}
   621  	return q.batches[0]
   622  }
   623  
   624  func (q *batchQueue) popFront() *batch {
   625  	if q.Len() == 0 {
   626  		return nil
   627  	}
   628  	return heap.Pop(q).(*batch)
   629  }
   630  
   631  func (q *batchQueue) get(id roachpb.RangeID) (*batch, bool) {
   632  	b, exists := q.byRange[id]
   633  	return b, exists
   634  }
   635  
   636  func (q *batchQueue) remove(ba *batch) {
   637  	delete(q.byRange, ba.rangeID())
   638  	heap.Remove(q, ba.idx)
   639  }
   640  
   641  func (q *batchQueue) upsert(ba *batch) {
   642  	if ba.idx >= 0 {
   643  		heap.Fix(q, ba.idx)
   644  	} else {
   645  		heap.Push(q, ba)
   646  	}
   647  }
   648  
   649  func (q *batchQueue) Len() int {
   650  	return len(q.batches)
   651  }
   652  
   653  func (q *batchQueue) Swap(i, j int) {
   654  	q.batches[i], q.batches[j] = q.batches[j], q.batches[i]
   655  	q.batches[i].idx = i
   656  	q.batches[j].idx = j
   657  }
   658  
   659  func (q *batchQueue) Less(i, j int) bool {
   660  	idl, jdl := q.batches[i].deadline, q.batches[j].deadline
   661  	if before := idl.Before(jdl); before || !idl.Equal(jdl) {
   662  		return before
   663  	}
   664  	return q.batches[i].rangeID() < q.batches[j].rangeID()
   665  }
   666  
   667  func (q *batchQueue) Push(v interface{}) {
   668  	ba := v.(*batch)
   669  	ba.idx = len(q.batches)
   670  	q.byRange[ba.rangeID()] = ba
   671  	q.batches = append(q.batches, ba)
   672  }
   673  
   674  func (q *batchQueue) Pop() interface{} {
   675  	ba := q.batches[len(q.batches)-1]
   676  	q.batches = q.batches[:len(q.batches)-1]
   677  	delete(q.byRange, ba.rangeID())
   678  	ba.idx = -1
   679  	return ba
   680  }