github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/domain/domain.go (about)

     1  // Copyright 2015 PingCAP, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package domain
    15  
    16  import (
    17  	"sync"
    18  	"sync/atomic"
    19  	"time"
    20  
    21  	"github.com/insionng/yougam/libraries/juju/errors"
    22  	"github.com/insionng/yougam/libraries/ngaut/log"
    23  	"github.com/insionng/yougam/libraries/pingcap/tidb/ddl"
    24  	"github.com/insionng/yougam/libraries/pingcap/tidb/infoschema"
    25  	"github.com/insionng/yougam/libraries/pingcap/tidb/kv"
    26  	"github.com/insionng/yougam/libraries/pingcap/tidb/meta"
    27  	"github.com/insionng/yougam/libraries/pingcap/tidb/model"
    28  	"github.com/insionng/yougam/libraries/pingcap/tidb/perfschema"
    29  	"github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/variable"
    30  	"github.com/insionng/yougam/libraries/pingcap/tidb/store/localstore"
    31  	"github.com/insionng/yougam/libraries/pingcap/tidb/terror"
    32  )
    33  
    34  var ddlLastReloadSchemaTS = "ddl_last_reload_schema_ts"
    35  
    36  // Domain represents a storage space. Different domains can use the same database name.
    37  // Multiple domains can be used in parallel without synchronization.
    38  type Domain struct {
    39  	store       kv.Storage
    40  	infoHandle  *infoschema.Handle
    41  	ddl         ddl.DDL
    42  	leaseCh     chan time.Duration
    43  	lastLeaseTS int64 // nano seconds
    44  	m           sync.Mutex
    45  }
    46  
    47  func (do *Domain) loadInfoSchema(txn kv.Transaction) (err error) {
    48  	m := meta.NewMeta(txn)
    49  	schemaMetaVersion, err := m.GetSchemaVersion()
    50  	if err != nil {
    51  		return errors.Trace(err)
    52  	}
    53  
    54  	info := do.infoHandle.Get()
    55  	if info != nil && schemaMetaVersion <= info.SchemaMetaVersion() {
    56  		// info may be changed by other txn, so here its version may be bigger than schema version,
    57  		// so we don't need to reload.
    58  		log.Debugf("[ddl] schema version is still %d, no need reload", schemaMetaVersion)
    59  		return nil
    60  	}
    61  
    62  	schemas, err := m.ListDatabases()
    63  	if err != nil {
    64  		return errors.Trace(err)
    65  	}
    66  
    67  	for _, di := range schemas {
    68  		if di.State != model.StatePublic {
    69  			// schema is not public, can't be used outside.
    70  			continue
    71  		}
    72  
    73  		tables, err1 := m.ListTables(di.ID)
    74  		if err1 != nil {
    75  			return errors.Trace(err1)
    76  		}
    77  
    78  		di.Tables = make([]*model.TableInfo, 0, len(tables))
    79  		for _, tbl := range tables {
    80  			if tbl.State != model.StatePublic {
    81  				// schema is not public, can't be used outsiee.
    82  				continue
    83  			}
    84  			di.Tables = append(di.Tables, tbl)
    85  		}
    86  	}
    87  
    88  	log.Infof("[ddl] loadInfoSchema %d", schemaMetaVersion)
    89  	err = do.infoHandle.Set(schemas, schemaMetaVersion)
    90  	return errors.Trace(err)
    91  }
    92  
    93  // InfoSchema gets information schema from domain.
    94  func (do *Domain) InfoSchema() infoschema.InfoSchema {
    95  	// try reload if possible.
    96  	do.tryReload()
    97  	return do.infoHandle.Get()
    98  }
    99  
   100  // PerfSchema gets performance schema from domain.
   101  func (do *Domain) PerfSchema() perfschema.PerfSchema {
   102  	return do.infoHandle.GetPerfHandle()
   103  }
   104  
   105  // DDL gets DDL from domain.
   106  func (do *Domain) DDL() ddl.DDL {
   107  	return do.ddl
   108  }
   109  
   110  // Store gets KV store from domain.
   111  func (do *Domain) Store() kv.Storage {
   112  	return do.store
   113  }
   114  
   115  // SetLease will reset the lease time for online DDL change.
   116  func (do *Domain) SetLease(lease time.Duration) {
   117  	if lease <= 0 {
   118  		log.Warnf("[ddl] set the current lease:%v into a new lease:%v failed, so do nothing",
   119  			do.ddl.GetLease(), lease)
   120  		return
   121  	}
   122  
   123  	if do.leaseCh == nil {
   124  		log.Errorf("[ddl] set the current lease:%v into a new lease:%v failed, so do nothing",
   125  			do.ddl.GetLease(), lease)
   126  		return
   127  	}
   128  
   129  	do.leaseCh <- lease
   130  	// let ddl to reset lease too.
   131  	do.ddl.SetLease(lease)
   132  }
   133  
   134  // Stats returns the domain statistic.
   135  func (do *Domain) Stats() (map[string]interface{}, error) {
   136  	m := make(map[string]interface{})
   137  	m[ddlLastReloadSchemaTS] = atomic.LoadInt64(&do.lastLeaseTS) / 1e9
   138  
   139  	return m, nil
   140  }
   141  
   142  // GetScope gets the status variables scope.
   143  func (do *Domain) GetScope(status string) variable.ScopeFlag {
   144  	// Now domain status variables scope are all default scope.
   145  	return variable.DefaultScopeFlag
   146  }
   147  
   148  func (do *Domain) tryReload() {
   149  	// if we don't have update the schema for a long time > lease, we must force reloading it.
   150  	// Although we try to reload schema every lease time in a goroutine, sometimes it may not
   151  	// run accurately, e.g, the machine has a very high load, running the ticker is delayed.
   152  	last := atomic.LoadInt64(&do.lastLeaseTS)
   153  	lease := do.ddl.GetLease()
   154  
   155  	// if lease is 0, we use the local store, so no need to reload.
   156  	if lease > 0 && time.Now().UnixNano()-last > lease.Nanoseconds() {
   157  		do.mustReload()
   158  	}
   159  }
   160  
   161  const minReloadTimeout = 20 * time.Second
   162  
   163  func (do *Domain) reload() error {
   164  	// lock here for only once at same time.
   165  	do.m.Lock()
   166  	defer do.m.Unlock()
   167  
   168  	timeout := do.ddl.GetLease() / 2
   169  	if timeout < minReloadTimeout {
   170  		timeout = minReloadTimeout
   171  	}
   172  
   173  	done := make(chan error, 1)
   174  	go func() {
   175  		var err error
   176  
   177  		for {
   178  			err = kv.RunInNewTxn(do.store, false, do.loadInfoSchema)
   179  			// if err is db closed, we will return it directly, otherwise, we will
   180  			// check reloading again.
   181  			if terror.ErrorEqual(err, localstore.ErrDBClosed) {
   182  				break
   183  			}
   184  
   185  			if err != nil {
   186  				log.Errorf("[ddl] load schema err %v, retry again", errors.ErrorStack(err))
   187  				// TODO: use a backoff algorithm.
   188  				time.Sleep(500 * time.Millisecond)
   189  				continue
   190  			}
   191  
   192  			atomic.StoreInt64(&do.lastLeaseTS, time.Now().UnixNano())
   193  			break
   194  		}
   195  
   196  		done <- err
   197  	}()
   198  
   199  	select {
   200  	case err := <-done:
   201  		return errors.Trace(err)
   202  	case <-time.After(timeout):
   203  		return errLoadSchemaTimeOut
   204  	}
   205  }
   206  
   207  func (do *Domain) mustReload() {
   208  	// if reload error, we will terminate whole program to guarantee data safe.
   209  	err := do.reload()
   210  	if err != nil {
   211  		log.Fatalf("[ddl] reload schema err %v", errors.ErrorStack(err))
   212  	}
   213  }
   214  
   215  // check schema every 300 seconds default.
   216  const defaultLoadTime = 300 * time.Second
   217  
   218  func (do *Domain) loadSchemaInLoop(lease time.Duration) {
   219  	ticker := time.NewTicker(lease)
   220  	defer ticker.Stop()
   221  
   222  	for {
   223  		select {
   224  		case <-ticker.C:
   225  			err := do.reload()
   226  			// we may close store in test, but the domain load schema loop is still checking,
   227  			// so we can't panic for ErrDBClosed and just return here.
   228  			if terror.ErrorEqual(err, localstore.ErrDBClosed) {
   229  				return
   230  			} else if err != nil {
   231  				log.Fatalf("[ddl] reload schema err %v", errors.ErrorStack(err))
   232  			}
   233  		case newLease := <-do.leaseCh:
   234  			if lease == newLease {
   235  				// nothing to do
   236  				continue
   237  			}
   238  
   239  			lease = newLease
   240  			// reset ticker too.
   241  			ticker.Stop()
   242  			ticker = time.NewTicker(lease)
   243  		}
   244  	}
   245  }
   246  
   247  type ddlCallback struct {
   248  	ddl.BaseCallback
   249  	do *Domain
   250  }
   251  
   252  func (c *ddlCallback) OnChanged(err error) error {
   253  	if err != nil {
   254  		return err
   255  	}
   256  	log.Warnf("[ddl] on DDL change")
   257  
   258  	c.do.mustReload()
   259  	return nil
   260  }
   261  
   262  // NewDomain creates a new domain.
   263  func NewDomain(store kv.Storage, lease time.Duration) (d *Domain, err error) {
   264  	d = &Domain{store: store}
   265  
   266  	d.infoHandle, err = infoschema.NewHandle(d.store)
   267  	if err != nil {
   268  		return nil, errors.Trace(err)
   269  	}
   270  	d.ddl = ddl.NewDDL(d.store, d.infoHandle, &ddlCallback{do: d}, lease)
   271  	d.mustReload()
   272  
   273  	variable.RegisterStatistics(d)
   274  
   275  	// Only when the store is local that the lease value is 0.
   276  	// If the store is local, it doesn't need loadSchemaInLoop.
   277  	if lease > 0 {
   278  		d.leaseCh = make(chan time.Duration, 1)
   279  		go d.loadSchemaInLoop(lease)
   280  	}
   281  
   282  	return d, nil
   283  }
   284  
   285  // Domain error codes.
   286  const (
   287  	codeLoadSchemaTimeOut terror.ErrCode = 1
   288  )
   289  
   290  var (
   291  	errLoadSchemaTimeOut = terror.ClassDomain.New(codeLoadSchemaTimeOut, "reload schema timeout")
   292  )