vitess-gh/go/vt/tabletserver/query_engine.go

295 строки
8.8 KiB
Go

// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tabletserver
import (
"sync"
"time"
log "github.com/golang/glog"
"github.com/youtube/vitess/go/stats"
"github.com/youtube/vitess/go/sync2"
"github.com/youtube/vitess/go/vt/dbconfigs"
"github.com/youtube/vitess/go/vt/dbconnpool"
"github.com/youtube/vitess/go/vt/logutil"
"github.com/youtube/vitess/go/vt/mysqlctl"
)
// spotCheckMultiplier determines the precision of the
// spot check ratio: 1e6 == 6 digits
const spotCheckMultiplier = 1e6
// QueryEngine implements the core functionality of tabletserver.
// It assumes that no requests will be sent to it before Open is
// called and succeeds.
// Shutdown is done in the following order:
//
// WaitForTxEmpty: There should be no more new calls to Begin
// once this function is called. This will return when there
// are no more pending transactions.
//
// Close: There should be no more pending queries when this
// function is called.
//
// Functions of QueryEngine do not return errors. They instead
// panic with NewTabletError as the error type.
// TODO(sougou): Switch to error return scheme.
type QueryEngine struct {
schemaInfo *SchemaInfo
dbconfigs *dbconfigs.DBConfigs
// Pools
cachePool *CachePool
connPool *dbconnpool.ConnectionPool
streamConnPool *dbconnpool.ConnectionPool
// Services
txPool *TxPool
consolidator *Consolidator
invalidator *RowcacheInvalidator
streamQList *QueryList
connKiller *ConnectionKiller
tasks sync.WaitGroup
// Vars
queryTimeout sync2.AtomicDuration
spotCheckFreq sync2.AtomicInt64
strictMode sync2.AtomicInt64
maxResultSize sync2.AtomicInt64
maxDMLRows sync2.AtomicInt64
streamBufferSize sync2.AtomicInt64
strictTableAcl bool
// loggers
accessCheckerLogger *logutil.ThrottledLogger
}
type compiledPlan struct {
Query string
*ExecPlan
BindVars map[string]interface{}
TransactionID int64
}
var (
// stats are globals to allow anybody to set them
mysqlStats *stats.Timings
queryStats *stats.Timings
waitStats *stats.Timings
killStats *stats.Counters
infoErrors *stats.Counters
errorStats *stats.Counters
internalErrors *stats.Counters
resultStats *stats.Histogram
spotCheckCount *stats.Int
qpsRates *stats.Rates
resultBuckets = []int64{0, 1, 5, 10, 50, 100, 500, 1000, 5000, 10000}
connPoolClosedErr = NewTabletError(ErrFatal, "connection pool is closed")
)
// CacheInvalidator provides the abstraction needed for an instant invalidation
// vs. delayed invalidation in the case of in-transaction dmls
type CacheInvalidator interface {
Delete(key string)
}
// Helper method for conn pools to convert errors
func getOrPanic(pool *dbconnpool.ConnectionPool) dbconnpool.PoolConnection {
conn, err := pool.Get(0)
if err == nil {
return conn
}
if err == dbconnpool.ErrConnPoolClosed {
panic(connPoolClosedErr)
}
panic(NewTabletErrorSql(ErrFatal, err))
}
// NewQueryEngine creates a new QueryEngine.
// This is a singleton class.
// You must call this only once.
func NewQueryEngine(config Config) *QueryEngine {
qe := &QueryEngine{}
qe.schemaInfo = NewSchemaInfo(
config.QueryCacheSize,
time.Duration(config.SchemaReloadTime*1e9),
time.Duration(config.IdleTimeout*1e9),
)
mysqlStats = stats.NewTimings("Mysql")
// Pools
qe.cachePool = NewCachePool(
"Rowcache",
config.RowCache,
time.Duration(config.QueryTimeout*1e9),
time.Duration(config.IdleTimeout*1e9),
)
qe.connPool = dbconnpool.NewConnectionPool(
"ConnPool",
config.PoolSize,
time.Duration(config.IdleTimeout*1e9),
)
qe.streamConnPool = dbconnpool.NewConnectionPool(
"StreamConnPool",
config.StreamPoolSize,
time.Duration(config.IdleTimeout*1e9),
)
// Services
qe.txPool = NewTxPool(
"TransactionPool",
config.TransactionCap,
time.Duration(config.TransactionTimeout*1e9),
time.Duration(config.TxPoolTimeout*1e9),
time.Duration(config.IdleTimeout*1e9),
)
qe.connKiller = NewConnectionKiller(1, time.Duration(config.IdleTimeout*1e9))
qe.consolidator = NewConsolidator()
qe.invalidator = NewRowcacheInvalidator(qe)
qe.streamQList = NewQueryList(qe.connKiller)
// Vars
qe.queryTimeout.Set(time.Duration(config.QueryTimeout * 1e9))
qe.spotCheckFreq = sync2.AtomicInt64(config.SpotCheckRatio * spotCheckMultiplier)
if config.StrictMode {
qe.strictMode.Set(1)
}
qe.strictTableAcl = config.StrictTableAcl
qe.maxResultSize = sync2.AtomicInt64(config.MaxResultSize)
qe.maxDMLRows = sync2.AtomicInt64(config.MaxDMLRows)
qe.streamBufferSize = sync2.AtomicInt64(config.StreamBufferSize)
// loggers
qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second)
// Stats
stats.Publish("MaxResultSize", stats.IntFunc(qe.maxResultSize.Get))
stats.Publish("MaxDMLRows", stats.IntFunc(qe.maxDMLRows.Get))
stats.Publish("StreamBufferSize", stats.IntFunc(qe.streamBufferSize.Get))
stats.Publish("QueryTimeout", stats.DurationFunc(qe.queryTimeout.Get))
queryStats = stats.NewTimings("Queries")
qpsRates = stats.NewRates("QPS", queryStats, 15, 60*time.Second)
waitStats = stats.NewTimings("Waits")
killStats = stats.NewCounters("Kills")
infoErrors = stats.NewCounters("InfoErrors")
errorStats = stats.NewCounters("Errors")
internalErrors = stats.NewCounters("InternalErrors")
resultStats = stats.NewHistogram("Results", resultBuckets)
stats.Publish("RowcacheSpotCheckRatio", stats.FloatFunc(func() float64 {
return float64(qe.spotCheckFreq.Get()) / spotCheckMultiplier
}))
spotCheckCount = stats.NewInt("RowcacheSpotCheckCount")
return qe
}
// Open must be called before sending requests to QueryEngine.
func (qe *QueryEngine) Open(dbconfigs *dbconfigs.DBConfigs, schemaOverrides []SchemaOverride, mysqld *mysqlctl.Mysqld) {
qe.dbconfigs = dbconfigs
connFactory := dbconnpool.DBConnectionCreator(&dbconfigs.App.ConnectionParams, mysqlStats)
// Create dba params based on App connection params
// and Dba credentials.
dba := dbconfigs.App.ConnectionParams
if dbconfigs.Dba.Uname != "" {
dba.Uname = dbconfigs.Dba.Uname
dba.Pass = dbconfigs.Dba.Pass
}
dbaConnFactory := dbconnpool.DBConnectionCreator(&dba, mysqlStats)
strictMode := false
if qe.strictMode.Get() != 0 {
strictMode = true
}
if !strictMode && dbconfigs.App.EnableRowcache {
panic(NewTabletError(ErrFatal, "Rowcache cannot be enabled when queryserver-config-strict-mode is false"))
}
if dbconfigs.App.EnableRowcache {
qe.cachePool.Open()
log.Infof("rowcache is enabled")
} else {
// Invalidator should not be enabled if rowcache is not enabled.
dbconfigs.App.EnableInvalidator = false
log.Infof("rowcache is not enabled")
}
start := time.Now()
// schemaInfo depends on cachePool. Every table that has a rowcache
// points to the cachePool.
qe.schemaInfo.Open(dbaConnFactory, schemaOverrides, qe.cachePool, strictMode)
log.Infof("Time taken to load the schema: %v", time.Now().Sub(start))
// Start the invalidator only after schema is loaded.
// This will allow qe to find the table info
// for the invalidation events that will start coming
// immediately.
if dbconfigs.App.EnableInvalidator {
qe.invalidator.Open(dbconfigs.App.DbName, mysqld)
}
qe.connPool.Open(connFactory)
qe.streamConnPool.Open(connFactory)
qe.txPool.Open(connFactory)
qe.connKiller.Open(dbaConnFactory)
}
// Launch launches the specified function inside a goroutine.
// If Close or WaitForTxEmpty is called while a goroutine is running,
// QueryEngine will not return until the existing functions have completed.
// This functionality allows us to launch tasks with the assurance that
// the QueryEngine will not be closed underneath us.
func (qe *QueryEngine) Launch(f func()) {
qe.tasks.Add(1)
go func() {
defer func() {
qe.tasks.Done()
if x := recover(); x != nil {
internalErrors.Add("Task", 1)
log.Errorf("task error: %v", x)
}
}()
f()
}()
}
// CheckMySQL returns true if we can connect to MySQL.
func (qe *QueryEngine) CheckMySQL() bool {
conn, err := dbconnpool.NewDBConnection(&qe.dbconfigs.App.ConnectionParams, mysqlStats)
if err != nil {
if IsConnErr(err) {
return false
}
log.Warningf("checking MySQL, unexpected error: %v", err)
return true
}
conn.Close()
return true
}
// WaitForTxEmpty must be called before calling Close.
// Before calling WaitForTxEmpty, you must ensure that there
// will be no more calls to Begin.
func (qe *QueryEngine) WaitForTxEmpty() {
qe.txPool.WaitForEmpty()
}
// Close must be called to shut down QueryEngine.
// You must ensure that no more queries will be sent
// before calling Close.
func (qe *QueryEngine) Close() {
qe.tasks.Wait()
// Close in reverse order of Open.
qe.connKiller.Close()
qe.txPool.Close()
qe.streamConnPool.Close()
qe.connPool.Close()
qe.invalidator.Close()
qe.schemaInfo.Close()
qe.cachePool.Close()
qe.dbconfigs = nil
}