...

Source file src/go.etcd.io/etcd/server/v3/etcdserver/quota.go

Documentation: go.etcd.io/etcd/server/v3/etcdserver

     1  // Copyright 2016 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package etcdserver
    16  
    17  import (
    18  	"sync"
    19  
    20  	pb "go.etcd.io/etcd/api/v3/etcdserverpb"
    21  
    22  	humanize "github.com/dustin/go-humanize"
    23  	"go.uber.org/zap"
    24  )
    25  
    26  const (
    27  	// DefaultQuotaBytes is the number of bytes the backend Size may
    28  	// consume before exceeding the space quota.
    29  	DefaultQuotaBytes = int64(2 * 1024 * 1024 * 1024) // 2GB
    30  	// MaxQuotaBytes is the maximum number of bytes suggested for a backend
    31  	// quota. A larger quota may lead to degraded performance.
    32  	MaxQuotaBytes = int64(8 * 1024 * 1024 * 1024) // 8GB
    33  )
    34  
    35  // Quota represents an arbitrary quota against arbitrary requests. Each request
    36  // costs some charge; if there is not enough remaining charge, then there are
    37  // too few resources available within the quota to apply the request.
    38  type Quota interface {
    39  	// Available judges whether the given request fits within the quota.
    40  	Available(req interface{}) bool
    41  	// Cost computes the charge against the quota for a given request.
    42  	Cost(req interface{}) int
    43  	// Remaining is the amount of charge left for the quota.
    44  	Remaining() int64
    45  }
    46  
    47  type passthroughQuota struct{}
    48  
    49  func (*passthroughQuota) Available(interface{}) bool { return true }
    50  func (*passthroughQuota) Cost(interface{}) int       { return 0 }
    51  func (*passthroughQuota) Remaining() int64           { return 1 }
    52  
    53  type backendQuota struct {
    54  	s               *EtcdServer
    55  	maxBackendBytes int64
    56  }
    57  
    58  const (
    59  	// leaseOverhead is an estimate for the cost of storing a lease
    60  	leaseOverhead = 64
    61  	// kvOverhead is an estimate for the cost of storing a key's metadata
    62  	kvOverhead = 256
    63  )
    64  
    65  var (
    66  	// only log once
    67  	quotaLogOnce sync.Once
    68  
    69  	DefaultQuotaSize = humanize.Bytes(uint64(DefaultQuotaBytes))
    70  	maxQuotaSize     = humanize.Bytes(uint64(MaxQuotaBytes))
    71  )
    72  
    73  // NewBackendQuota creates a quota layer with the given storage limit.
    74  func NewBackendQuota(s *EtcdServer, name string) Quota {
    75  	lg := s.Logger()
    76  	quotaBackendBytes.Set(float64(s.Cfg.QuotaBackendBytes))
    77  
    78  	if s.Cfg.QuotaBackendBytes < 0 {
    79  		// disable quotas if negative
    80  		quotaLogOnce.Do(func() {
    81  			lg.Info(
    82  				"disabled backend quota",
    83  				zap.String("quota-name", name),
    84  				zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
    85  			)
    86  		})
    87  		return &passthroughQuota{}
    88  	}
    89  
    90  	if s.Cfg.QuotaBackendBytes == 0 {
    91  		// use default size if no quota size given
    92  		quotaLogOnce.Do(func() {
    93  			if lg != nil {
    94  				lg.Info(
    95  					"enabled backend quota with default value",
    96  					zap.String("quota-name", name),
    97  					zap.Int64("quota-size-bytes", DefaultQuotaBytes),
    98  					zap.String("quota-size", DefaultQuotaSize),
    99  				)
   100  			}
   101  		})
   102  		quotaBackendBytes.Set(float64(DefaultQuotaBytes))
   103  		return &backendQuota{s, DefaultQuotaBytes}
   104  	}
   105  
   106  	quotaLogOnce.Do(func() {
   107  		if s.Cfg.QuotaBackendBytes > MaxQuotaBytes {
   108  			lg.Warn(
   109  				"quota exceeds the maximum value",
   110  				zap.String("quota-name", name),
   111  				zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
   112  				zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
   113  				zap.Int64("quota-maximum-size-bytes", MaxQuotaBytes),
   114  				zap.String("quota-maximum-size", maxQuotaSize),
   115  			)
   116  		}
   117  		lg.Info(
   118  			"enabled backend quota",
   119  			zap.String("quota-name", name),
   120  			zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
   121  			zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
   122  		)
   123  	})
   124  	return &backendQuota{s, s.Cfg.QuotaBackendBytes}
   125  }
   126  
   127  func (b *backendQuota) Available(v interface{}) bool {
   128  	cost := b.Cost(v)
   129  	// if there are no mutating requests, it's safe to pass through
   130  	if cost == 0 {
   131  		return true
   132  	}
   133  	// TODO: maybe optimize backend.Size()
   134  	return b.s.Backend().Size()+int64(cost) < b.maxBackendBytes
   135  }
   136  
   137  func (b *backendQuota) Cost(v interface{}) int {
   138  	switch r := v.(type) {
   139  	case *pb.PutRequest:
   140  		return costPut(r)
   141  	case *pb.TxnRequest:
   142  		return costTxn(r)
   143  	case *pb.LeaseGrantRequest:
   144  		return leaseOverhead
   145  	default:
   146  		panic("unexpected cost")
   147  	}
   148  }
   149  
   150  func costPut(r *pb.PutRequest) int { return kvOverhead + len(r.Key) + len(r.Value) }
   151  
   152  func costTxnReq(u *pb.RequestOp) int {
   153  	r := u.GetRequestPut()
   154  	if r == nil {
   155  		return 0
   156  	}
   157  	return costPut(r)
   158  }
   159  
   160  func costTxn(r *pb.TxnRequest) int {
   161  	sizeSuccess := 0
   162  	for _, u := range r.Success {
   163  		sizeSuccess += costTxnReq(u)
   164  	}
   165  	sizeFailure := 0
   166  	for _, u := range r.Failure {
   167  		sizeFailure += costTxnReq(u)
   168  	}
   169  	if sizeFailure > sizeSuccess {
   170  		return sizeFailure
   171  	}
   172  	return sizeSuccess
   173  }
   174  
   175  func (b *backendQuota) Remaining() int64 {
   176  	return b.maxBackendBytes - b.s.Backend().Size()
   177  }
   178  

View as plain text