github.com/zorawar87/trillian@v1.2.1/quota/mysqlqm/mysql_quota.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 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 mysqlqm defines a MySQL-based quota.Manager implementation. 16 package mysqlqm 17 18 import ( 19 "context" 20 "database/sql" 21 "errors" 22 23 "github.com/google/trillian/quota" 24 ) 25 26 const ( 27 // DefaultMaxUnsequenced is a suggested value for MaxUnsequencedRows. 28 // Note that this is a Global/Write quota suggestion, so it applies across trees. 29 DefaultMaxUnsequenced = 500000 // About 2h of non-stop signing at 70QPS. 30 31 countFromInformationSchemaQuery = ` 32 SELECT table_rows 33 FROM information_schema.tables 34 WHERE table_schema = schema() 35 AND table_name = ? 36 AND table_type = ?` 37 countFromUnsequencedQuery = "SELECT COUNT(*) FROM Unsequenced" 38 ) 39 40 var ( 41 // ErrTooManyUnsequencedRows is returned when tokens are requested but Unsequenced has grown 42 // beyond the configured limit. 43 ErrTooManyUnsequencedRows = errors.New("too many unsequenced rows") 44 ) 45 46 // QuotaManager is a MySQL-based quota.Manager implementation. 47 // 48 // It has two working modes: one queries the information schema for the number of Unsequenced rows, 49 // the other does a select count(*) on the Unsequenced table. Information schema queries are 50 // default, even though they are approximate, as they're constant time (select count(*) on InnoDB 51 // based MySQL needs to traverse the index and may take quite a while to complete). 52 // 53 // QuotaManager only implements Global/Write quotas, which is based on the number of Unsequenced 54 // rows (to be exact, tokens = MaxUnsequencedRows - actualUnsequencedRows). 55 // Other quotas are considered infinite. 56 type QuotaManager struct { 57 DB *sql.DB 58 MaxUnsequencedRows int 59 UseSelectCount bool 60 } 61 62 // GetTokens implements quota.Manager.GetTokens. 63 // It doesn't actually reserve or retrieve tokens, instead it allows access based on the number of 64 // rows in the Unsequenced table. 65 func (m *QuotaManager) GetTokens(ctx context.Context, numTokens int, specs []quota.Spec) error { 66 for _, spec := range specs { 67 if spec.Group != quota.Global || spec.Kind != quota.Write { 68 continue 69 } 70 // Only allow global writes if Unsequenced is under the expected limit 71 count, err := m.countUnsequenced(ctx) 72 if err != nil { 73 return err 74 } 75 if count+numTokens > m.MaxUnsequencedRows { 76 return ErrTooManyUnsequencedRows 77 } 78 } 79 return nil 80 } 81 82 // PeekTokens implements quota.Manager.PeekTokens. 83 // Global/Write tokens reflect the number of rows in the Unsequenced tables, other specs are 84 // considered infinite. 85 func (m *QuotaManager) PeekTokens(ctx context.Context, specs []quota.Spec) (map[quota.Spec]int, error) { 86 tokens := make(map[quota.Spec]int) 87 for _, spec := range specs { 88 var num int 89 if spec.Group == quota.Global && spec.Kind == quota.Write { 90 count, err := m.countUnsequenced(ctx) 91 if err != nil { 92 return nil, err 93 } 94 num = m.MaxUnsequencedRows - count 95 } else { 96 num = quota.MaxTokens 97 } 98 tokens[spec] = num 99 } 100 return tokens, nil 101 } 102 103 // PutTokens implements quota.Manager.PutTokens. 104 // It's a noop for QuotaManager. 105 func (m *QuotaManager) PutTokens(ctx context.Context, numTokens int, specs []quota.Spec) error { 106 return nil 107 } 108 109 // ResetQuota implements quota.Manager.ResetQuota. 110 // It's a noop for QuotaManager. 111 func (m *QuotaManager) ResetQuota(ctx context.Context, specs []quota.Spec) error { 112 return nil 113 } 114 115 func (m *QuotaManager) countUnsequenced(ctx context.Context) (int, error) { 116 if m.UseSelectCount { 117 return countFromTable(ctx, m.DB) 118 } 119 return countFromInformationSchema(ctx, m.DB) 120 } 121 122 func countFromInformationSchema(ctx context.Context, db *sql.DB) (int, error) { 123 // information_schema.tables doesn't have an explicit PK, so let's play it safe and ensure 124 // the cursor returns a single row. 125 rows, err := db.QueryContext(ctx, countFromInformationSchemaQuery, "Unsequenced", "BASE TABLE") 126 if err != nil { 127 return 0, err 128 } 129 defer rows.Close() 130 if !rows.Next() { 131 return 0, errors.New("cursor has no rows after information_schema query") 132 } 133 var count int 134 if err := rows.Scan(&count); err != nil { 135 return 0, err 136 } 137 if rows.Next() { 138 return 0, errors.New("too many rows returned from information_schema query") 139 } 140 return count, nil 141 } 142 143 func countFromTable(ctx context.Context, db *sql.DB) (int, error) { 144 var count int 145 if err := db.QueryRowContext(ctx, countFromUnsequencedQuery).Scan(&count); err != nil { 146 return 0, err 147 } 148 return count, nil 149 }