vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/txlimiter/tx_limiter.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package txlimiter
    18  
    19  import (
    20  	"strings"
    21  	"sync"
    22  
    23  	"vitess.io/vitess/go/stats"
    24  	"vitess.io/vitess/go/vt/callerid"
    25  	"vitess.io/vitess/go/vt/log"
    26  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    27  
    28  	querypb "vitess.io/vitess/go/vt/proto/query"
    29  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    30  )
    31  
    32  const unknown string = "unknown"
    33  
    34  // TxLimiter is the transaction limiter interface.
    35  type TxLimiter interface {
    36  	Get(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) bool
    37  	Release(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID)
    38  }
    39  
    40  // New creates a new TxLimiter.
    41  // slotCount: total slot count in transaction pool
    42  // maxPerUser: fraction of the pool that may be taken by single user
    43  // enabled: should the feature be enabled. If false, will return
    44  // "allow-all" limiter
    45  // dryRun: if true, does no limiting, but records stats of the decisions made
    46  // byXXX: whether given field from immediate/effective caller id should be taken
    47  // into account when deciding "user" identity for purposes of transaction
    48  // limiting.
    49  func New(env tabletenv.Env) TxLimiter {
    50  	config := env.Config()
    51  	if !config.EnableTransactionLimit && !config.EnableTransactionLimitDryRun {
    52  		return &TxAllowAll{}
    53  	}
    54  
    55  	return &Impl{
    56  		maxPerUser:       int64(float64(config.TxPool.Size) * config.TransactionLimitPerUser),
    57  		dryRun:           config.EnableTransactionLimitDryRun,
    58  		byUsername:       config.TransactionLimitByUsername,
    59  		byPrincipal:      config.TransactionLimitByPrincipal,
    60  		byComponent:      config.TransactionLimitByComponent,
    61  		bySubcomponent:   config.TransactionLimitBySubcomponent,
    62  		byEffectiveUser:  config.TransactionLimitByPrincipal || config.TransactionLimitByComponent || config.TransactionLimitBySubcomponent,
    63  		usageMap:         make(map[string]int64),
    64  		rejections:       env.Exporter().NewCountersWithSingleLabel("TxLimiterRejections", "rejections from TxLimiter", "user"),
    65  		rejectionsDryRun: env.Exporter().NewCountersWithSingleLabel("TxLimiterRejectionsDryRun", "rejections from TxLimiter in dry run", "user"),
    66  	}
    67  }
    68  
    69  // TxAllowAll is a TxLimiter that allows all Get requests and does no tracking.
    70  // Implements Txlimiter.
    71  type TxAllowAll struct{}
    72  
    73  // Get always returns true (allows all requests).
    74  // Implements TxLimiter.Get
    75  func (txa *TxAllowAll) Get(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) bool {
    76  	return true
    77  }
    78  
    79  // Release is noop, because TxAllowAll does no tracking.
    80  // Implements TxLimiter.Release
    81  func (txa *TxAllowAll) Release(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) {
    82  	// NOOP
    83  }
    84  
    85  // Impl limits the total number of transactions a single user may use
    86  // concurrently.
    87  // Implements TxLimiter.
    88  type Impl struct {
    89  	maxPerUser int64
    90  	usageMap   map[string]int64
    91  	mu         sync.Mutex
    92  
    93  	dryRun          bool
    94  	byUsername      bool
    95  	byPrincipal     bool
    96  	byComponent     bool
    97  	bySubcomponent  bool
    98  	byEffectiveUser bool
    99  
   100  	rejections, rejectionsDryRun *stats.CountersWithSingleLabel
   101  }
   102  
   103  // Get tells whether given user (identified by context.Context) is allowed
   104  // to use another transaction slot. If this method returns true, it's
   105  // necessary to call Release once transaction is returned to the pool.
   106  // Implements TxLimiter.Get
   107  func (txl *Impl) Get(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) bool {
   108  	key := txl.extractKey(immediate, effective)
   109  
   110  	txl.mu.Lock()
   111  	defer txl.mu.Unlock()
   112  
   113  	usage := txl.usageMap[key]
   114  	if usage < txl.maxPerUser {
   115  		txl.usageMap[key] = usage + 1
   116  		return true
   117  	}
   118  
   119  	if txl.dryRun {
   120  		log.Infof("TxLimiter: DRY RUN: user over limit: %s", key)
   121  		txl.rejectionsDryRun.Add(key, 1)
   122  		return true
   123  	}
   124  
   125  	log.Infof("TxLimiter: Over limit, rejecting transaction request for user: %s", key)
   126  	txl.rejections.Add(key, 1)
   127  	return false
   128  }
   129  
   130  // Release marks that given user (identified by caller ID) is no longer using
   131  // a transaction slot.
   132  // Implements TxLimiter.Release
   133  func (txl *Impl) Release(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) {
   134  	key := txl.extractKey(immediate, effective)
   135  
   136  	txl.mu.Lock()
   137  	defer txl.mu.Unlock()
   138  
   139  	usage, ok := txl.usageMap[key]
   140  	if !ok {
   141  		return
   142  	}
   143  	if usage == 1 {
   144  		delete(txl.usageMap, key)
   145  		return
   146  	}
   147  
   148  	txl.usageMap[key] = usage - 1
   149  }
   150  
   151  // extractKey builds a string key used to differentiate users, based
   152  // on fields specified in configuration and their values from caller ID.
   153  func (txl *Impl) extractKey(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) string {
   154  	var parts []string
   155  	if txl.byUsername {
   156  		if immediate != nil {
   157  			parts = append(parts, callerid.GetUsername(immediate))
   158  		} else {
   159  			parts = append(parts, unknown)
   160  		}
   161  	}
   162  	if txl.byEffectiveUser {
   163  		if effective != nil {
   164  			if txl.byPrincipal {
   165  				parts = append(parts, callerid.GetPrincipal(effective))
   166  			}
   167  			if txl.byComponent {
   168  				parts = append(parts, callerid.GetComponent(effective))
   169  			}
   170  			if txl.bySubcomponent {
   171  				parts = append(parts, callerid.GetSubcomponent(effective))
   172  			}
   173  		} else {
   174  			parts = append(parts, unknown)
   175  		}
   176  	}
   177  
   178  	return strings.Join(parts, "/")
   179  }