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 }