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 }