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  }