vitess.io/vitess@v0.16.2/go/vt/vtgate/schema/update_controller.go (about)

     1  /*
     2  Copyright 2021 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 schema
    18  
    19  import (
    20  	"sync"
    21  	"time"
    22  
    23  	"vitess.io/vitess/go/mysql"
    24  
    25  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    26  
    27  	"vitess.io/vitess/go/vt/discovery"
    28  )
    29  
    30  type (
    31  	queue struct {
    32  		items []*discovery.TabletHealth
    33  	}
    34  
    35  	updateController struct {
    36  		mu             sync.Mutex
    37  		queue          *queue
    38  		consumeDelay   time.Duration
    39  		update         func(th *discovery.TabletHealth) bool
    40  		reloadKeyspace func(th *discovery.TabletHealth) error
    41  		signal         func()
    42  		loaded         bool
    43  
    44  		// we'll only log a failed keyspace loading once
    45  		ignore bool
    46  	}
    47  )
    48  
    49  func (u *updateController) consume() {
    50  	for {
    51  		time.Sleep(u.consumeDelay)
    52  
    53  		u.mu.Lock()
    54  		if len(u.queue.items) == 0 {
    55  			u.queue = nil
    56  			u.mu.Unlock()
    57  			return
    58  		}
    59  
    60  		// todo: scan queue for multiple update from the same shard, be clever
    61  		item := u.getItemFromQueueLocked()
    62  		loaded := u.loaded
    63  		u.mu.Unlock()
    64  
    65  		var success bool
    66  		if loaded {
    67  			success = u.update(item)
    68  		} else {
    69  			if err := u.reloadKeyspace(item); err == nil {
    70  				success = true
    71  			} else {
    72  				if checkIfWeShouldIgnoreKeyspace(err) {
    73  					u.setIgnore(true)
    74  				}
    75  				success = false
    76  			}
    77  		}
    78  		if success && u.signal != nil {
    79  			u.signal()
    80  		}
    81  	}
    82  }
    83  
    84  // checkIfWeShouldIgnoreKeyspace inspects an error and
    85  // will mark a keyspace as failed and won't try to load more information from it
    86  func checkIfWeShouldIgnoreKeyspace(err error) bool {
    87  	sqlErr := mysql.NewSQLErrorFromError(err).(*mysql.SQLError)
    88  	if sqlErr.Num == mysql.ERBadDb || sqlErr.Num == mysql.ERNoSuchTable {
    89  		// if we are missing the db or table, no point in retrying
    90  		return true
    91  	}
    92  	return false
    93  }
    94  
    95  func (u *updateController) getItemFromQueueLocked() *discovery.TabletHealth {
    96  	item := u.queue.items[0]
    97  	itemsCount := len(u.queue.items)
    98  	// Only when we want to update selected tables.
    99  	if u.loaded {
   100  		// We are trying to minimize the vttablet calls here by merging all the table/view changes received into a single changed item
   101  		// with all the table and view names.
   102  		for i := 1; i < itemsCount; i++ {
   103  			for _, table := range u.queue.items[i].Stats.TableSchemaChanged {
   104  				found := false
   105  				for _, itemTable := range item.Stats.TableSchemaChanged {
   106  					if itemTable == table {
   107  						found = true
   108  						break
   109  					}
   110  				}
   111  				if !found {
   112  					item.Stats.TableSchemaChanged = append(item.Stats.TableSchemaChanged, table)
   113  				}
   114  			}
   115  			for _, view := range u.queue.items[i].Stats.ViewSchemaChanged {
   116  				found := false
   117  				for _, itemView := range item.Stats.ViewSchemaChanged {
   118  					if itemView == view {
   119  						found = true
   120  						break
   121  					}
   122  				}
   123  				if !found {
   124  					item.Stats.ViewSchemaChanged = append(item.Stats.ViewSchemaChanged, view)
   125  				}
   126  			}
   127  		}
   128  	}
   129  	// emptying queue's items as all items from 0 to i (length of the queue) are merged
   130  	u.queue.items = u.queue.items[itemsCount:]
   131  	return item
   132  }
   133  
   134  func (u *updateController) add(th *discovery.TabletHealth) {
   135  	// For non-primary tablet health, there is no schema tracking.
   136  	if th.Target.TabletType != topodatapb.TabletType_PRIMARY {
   137  		return
   138  	}
   139  
   140  	u.mu.Lock()
   141  	defer u.mu.Unlock()
   142  
   143  	// Received a health check from primary tablet that is not reachable from VTGate.
   144  	// The connection will get reset and the tracker needs to reload the schema for the keyspace.
   145  	if !th.Serving {
   146  		u.loaded = false
   147  		return
   148  	}
   149  
   150  	// If the keyspace schema is loaded and there is no schema change detected. Then there is nothing to process.
   151  	if len(th.Stats.TableSchemaChanged) == 0 && len(th.Stats.ViewSchemaChanged) == 0 && u.loaded {
   152  		return
   153  	}
   154  
   155  	if (len(th.Stats.TableSchemaChanged) > 0 || len(th.Stats.ViewSchemaChanged) > 0) && u.ignore {
   156  		// we got an update for this keyspace - we need to stop ignoring it, and reload everything
   157  		u.ignore = false
   158  		u.loaded = false
   159  	}
   160  
   161  	if u.ignore {
   162  		// keyspace marked as not working correctly, so we are ignoring it for now
   163  		return
   164  	}
   165  
   166  	if u.queue == nil {
   167  		u.queue = &queue{}
   168  		go u.consume()
   169  	}
   170  	u.queue.items = append(u.queue.items, th)
   171  }
   172  
   173  func (u *updateController) setLoaded(loaded bool) {
   174  	u.mu.Lock()
   175  	defer u.mu.Unlock()
   176  	u.loaded = loaded
   177  }
   178  
   179  func (u *updateController) setIgnore(i bool) {
   180  	u.mu.Lock()
   181  	defer u.mu.Unlock()
   182  	u.ignore = i
   183  }