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 15package etcdserver 16 17import pb "github.com/coreos/etcd/etcdserver/etcdserverpb" 18 19const ( 20 // DefaultQuotaBytes is the number of bytes the backend Size may 21 // consume before exceeding the space quota. 22 DefaultQuotaBytes = int64(2 * 1024 * 1024 * 1024) // 2GB 23 // MaxQuotaBytes is the maximum number of bytes suggested for a backend 24 // quota. A larger quota may lead to degraded performance. 25 MaxQuotaBytes = int64(8 * 1024 * 1024 * 1024) // 8GB 26) 27 28// Quota represents an arbitrary quota against arbitrary requests. Each request 29// costs some charge; if there is not enough remaining charge, then there are 30// too few resources available within the quota to apply the request. 31type Quota interface { 32 // Available judges whether the given request fits within the quota. 33 Available(req interface{}) bool 34 // Cost computes the charge against the quota for a given request. 35 Cost(req interface{}) int 36 // Remaining is the amount of charge left for the quota. 37 Remaining() int64 38} 39 40type passthroughQuota struct{} 41 42func (*passthroughQuota) Available(interface{}) bool { return true } 43func (*passthroughQuota) Cost(interface{}) int { return 0 } 44func (*passthroughQuota) Remaining() int64 { return 1 } 45 46type backendQuota struct { 47 s *EtcdServer 48 maxBackendBytes int64 49} 50 51const ( 52 // leaseOverhead is an estimate for the cost of storing a lease 53 leaseOverhead = 64 54 // kvOverhead is an estimate for the cost of storing a key's metadata 55 kvOverhead = 256 56) 57 58func NewBackendQuota(s *EtcdServer) Quota { 59 quotaBackendBytes.Set(float64(s.Cfg.QuotaBackendBytes)) 60 61 if s.Cfg.QuotaBackendBytes < 0 { 62 // disable quotas if negative 63 plog.Warningf("disabling backend quota") 64 return &passthroughQuota{} 65 } 66 67 if s.Cfg.QuotaBackendBytes == 0 { 68 // use default size if no quota size given 69 quotaBackendBytes.Set(float64(DefaultQuotaBytes)) 70 return &backendQuota{s, DefaultQuotaBytes} 71 } 72 73 if s.Cfg.QuotaBackendBytes > MaxQuotaBytes { 74 plog.Warningf("backend quota %v exceeds maximum recommended quota %v", s.Cfg.QuotaBackendBytes, MaxQuotaBytes) 75 } 76 return &backendQuota{s, s.Cfg.QuotaBackendBytes} 77} 78 79func (b *backendQuota) Available(v interface{}) bool { 80 // TODO: maybe optimize backend.Size() 81 return b.s.Backend().Size()+int64(b.Cost(v)) < b.maxBackendBytes 82} 83 84func (b *backendQuota) Cost(v interface{}) int { 85 switch r := v.(type) { 86 case *pb.PutRequest: 87 return costPut(r) 88 case *pb.TxnRequest: 89 return costTxn(r) 90 case *pb.LeaseGrantRequest: 91 return leaseOverhead 92 default: 93 panic("unexpected cost") 94 } 95} 96 97func costPut(r *pb.PutRequest) int { return kvOverhead + len(r.Key) + len(r.Value) } 98 99func costTxnReq(u *pb.RequestOp) int { 100 r := u.GetRequestPut() 101 if r == nil { 102 return 0 103 } 104 return costPut(r) 105} 106 107func costTxn(r *pb.TxnRequest) int { 108 sizeSuccess := 0 109 for _, u := range r.Success { 110 sizeSuccess += costTxnReq(u) 111 } 112 sizeFailure := 0 113 for _, u := range r.Failure { 114 sizeFailure += costTxnReq(u) 115 } 116 if sizeFailure > sizeSuccess { 117 return sizeFailure 118 } 119 return sizeSuccess 120} 121 122func (b *backendQuota) Remaining() int64 { 123 return b.maxBackendBytes - b.s.Backend().Size() 124} 125