...
1
2
3
4
5
6
7
8
9
10
11
12
13
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
28
29 DefaultQuotaBytes = int64(2 * 1024 * 1024 * 1024)
30
31
32 MaxQuotaBytes = int64(8 * 1024 * 1024 * 1024)
33 )
34
35
36
37
38 type Quota interface {
39
40 Available(req interface{}) bool
41
42 Cost(req interface{}) int
43
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
60 leaseOverhead = 64
61
62 kvOverhead = 256
63 )
64
65 var (
66
67 quotaLogOnce sync.Once
68
69 DefaultQuotaSize = humanize.Bytes(uint64(DefaultQuotaBytes))
70 maxQuotaSize = humanize.Bytes(uint64(MaxQuotaBytes))
71 )
72
73
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
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
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
130 if cost == 0 {
131 return true
132 }
133
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