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  }