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 )