vitess.io/vitess@v0.16.2/go/vt/vtgate/vschema_manager.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vtgate 18 19 import ( 20 "context" 21 "sync" 22 23 "vitess.io/vitess/go/vt/sqlparser" 24 "vitess.io/vitess/go/vt/srvtopo" 25 26 "google.golang.org/protobuf/proto" 27 28 "vitess.io/vitess/go/vt/log" 29 "vitess.io/vitess/go/vt/topo" 30 "vitess.io/vitess/go/vt/vtgate/vindexes" 31 32 vschemapb "vitess.io/vitess/go/vt/proto/vschema" 33 ) 34 35 var _ VSchemaOperator = (*VSchemaManager)(nil) 36 37 // VSchemaManager is used to watch for updates to the vschema and to implement 38 // the DDL commands to add / remove vindexes 39 type VSchemaManager struct { 40 mu sync.Mutex 41 currentSrvVschema *vschemapb.SrvVSchema 42 currentVschema *vindexes.VSchema 43 serv srvtopo.Server 44 cell string 45 subscriber func(vschema *vindexes.VSchema, stats *VSchemaStats) 46 schema SchemaInfo 47 } 48 49 // SchemaInfo is an interface to schema tracker. 50 type SchemaInfo interface { 51 Tables(ks string) map[string][]vindexes.Column 52 Views(ks string) map[string]sqlparser.SelectStatement 53 } 54 55 // GetCurrentSrvVschema returns a copy of the latest SrvVschema from the 56 // topo watch 57 func (vm *VSchemaManager) GetCurrentSrvVschema() *vschemapb.SrvVSchema { 58 vm.mu.Lock() 59 defer vm.mu.Unlock() 60 return proto.Clone(vm.currentSrvVschema).(*vschemapb.SrvVSchema) 61 } 62 63 // UpdateVSchema propagates the updated vschema to the topo. The entry for 64 // the given keyspace is updated in the global topo, and the full SrvVSchema 65 // is updated in all known cells. 66 func (vm *VSchemaManager) UpdateVSchema(ctx context.Context, ksName string, vschema *vschemapb.SrvVSchema) error { 67 topoServer, err := vm.serv.GetTopoServer() 68 if err != nil { 69 return err 70 } 71 72 ks := vschema.Keyspaces[ksName] 73 err = topoServer.SaveVSchema(ctx, ksName, ks) 74 if err != nil { 75 return err 76 } 77 78 cells, err := topoServer.GetKnownCells(ctx) 79 if err != nil { 80 return err 81 } 82 83 // even if one cell fails, continue to try the others 84 for _, cell := range cells { 85 cellErr := topoServer.UpdateSrvVSchema(ctx, cell, vschema) 86 if cellErr != nil { 87 err = cellErr 88 log.Errorf("error updating vschema in cell %s: %v", cell, cellErr) 89 } 90 } 91 if err != nil { 92 return err 93 } 94 95 // Update all the local copy of VSchema if the topo update is successful. 96 vm.VSchemaUpdate(vschema, err) 97 98 return nil 99 } 100 101 // VSchemaUpdate builds the VSchema from SrvVschema and call subscribers. 102 func (vm *VSchemaManager) VSchemaUpdate(v *vschemapb.SrvVSchema, err error) bool { 103 log.Infof("Received vschema update") 104 switch { 105 case err == nil: 106 // Good case, we can try to save that value. 107 case topo.IsErrType(err, topo.NoNode): 108 // If the SrvVschema disappears, we need to clear our record. 109 // Otherwise, keep what we already had before. 110 v = nil 111 default: 112 log.Errorf("SrvVschema watch error: %v", err) 113 // Watch error, increment our counters. 114 if vschemaCounters != nil { 115 vschemaCounters.Add("WatchError", 1) 116 } 117 } 118 119 vm.mu.Lock() 120 defer vm.mu.Unlock() 121 122 // keep a copy of the latest SrvVschema and Vschema 123 vm.currentSrvVschema = v // TODO: should we do this locking? 124 vschema := vm.currentVschema 125 126 if v == nil { 127 // We encountered an error, build an empty vschema. 128 if vm.currentVschema == nil { 129 vschema = vindexes.BuildVSchema(&vschemapb.SrvVSchema{}) 130 } 131 } else { 132 vschema = vm.buildAndEnhanceVSchema(v) 133 vm.currentVschema = vschema 134 } 135 136 if vm.subscriber != nil { 137 vm.subscriber(vschema, vSchemaStats(err, vschema)) 138 } 139 return true 140 } 141 142 func vSchemaStats(err error, vschema *vindexes.VSchema) *VSchemaStats { 143 // Build the display version. At this point, three cases: 144 // - v is nil, vschema is empty, and err is set: 145 // 1. when the watch returned an error. 146 // 2. when BuildVSchema failed. 147 // - v is set, vschema is full, and err is nil: 148 // 3. when everything worked. 149 errorMessage := "" 150 if err != nil { 151 errorMessage = err.Error() 152 } 153 154 stats := NewVSchemaStats(vschema, errorMessage) 155 return stats 156 } 157 158 // Rebuild will rebuild and publish the new vschema. 159 // This method should be called when the underlying schema has changed. 160 func (vm *VSchemaManager) Rebuild() { 161 vm.mu.Lock() 162 v := vm.currentSrvVschema 163 vm.mu.Unlock() 164 165 log.Infof("Received schema update") 166 if v == nil { 167 log.Infof("No vschema to enhance") 168 return 169 } 170 171 vschema := vm.buildAndEnhanceVSchema(v) 172 vm.mu.Lock() 173 vm.currentVschema = vschema 174 vm.mu.Unlock() 175 176 if vm.subscriber != nil { 177 vm.subscriber(vschema, vSchemaStats(nil, vschema)) 178 log.Infof("Sent vschema to subscriber") 179 } 180 } 181 182 // buildAndEnhanceVSchema builds a new VSchema and uses information from the schema tracker to update it 183 func (vm *VSchemaManager) buildAndEnhanceVSchema(v *vschemapb.SrvVSchema) *vindexes.VSchema { 184 vschema := vindexes.BuildVSchema(v) 185 if vm.schema != nil { 186 vm.updateFromSchema(vschema) 187 } 188 return vschema 189 } 190 191 func (vm *VSchemaManager) updateFromSchema(vschema *vindexes.VSchema) { 192 for ksName, ks := range vschema.Keyspaces { 193 m := vm.schema.Tables(ksName) 194 195 for tblName, columns := range m { 196 vTbl := ks.Tables[tblName] 197 if vTbl == nil { 198 // a table that is unknown by the vschema. we add it as a normal table 199 ks.Tables[tblName] = &vindexes.Table{ 200 Name: sqlparser.NewIdentifierCS(tblName), 201 Keyspace: ks.Keyspace, 202 Columns: columns, 203 ColumnListAuthoritative: true, 204 } 205 continue 206 } 207 if !vTbl.ColumnListAuthoritative { 208 // if we found the matching table and the vschema view of it is not authoritative, then we just update the columns of the table 209 vTbl.Columns = columns 210 vTbl.ColumnListAuthoritative = true 211 } 212 } 213 214 views := vm.schema.Views(ksName) 215 if views != nil { 216 ks.Views = make(map[string]sqlparser.SelectStatement, len(views)) 217 for name, def := range views { 218 ks.Views[name] = sqlparser.CloneSelectStatement(def) 219 } 220 } 221 } 222 }