vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/snapshot_conn.go (about) 1 /* 2 Copyright 2020 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 vstreamer 18 19 import ( 20 "context" 21 "fmt" 22 "sync/atomic" 23 24 "github.com/spf13/pflag" 25 26 "vitess.io/vitess/go/mysql" 27 "vitess.io/vitess/go/vt/dbconfigs" 28 "vitess.io/vitess/go/vt/log" 29 "vitess.io/vitess/go/vt/servenv" 30 "vitess.io/vitess/go/vt/sqlparser" 31 ) 32 33 // If the current binary log is greater than this byte size, we 34 // will attempt to rotate it before starting a GTID snapshot 35 // based stream. 36 // Default is 64MiB. 37 var binlogRotationThreshold = int64(64 * 1024 * 1024) // 64MiB 38 39 // snapshotConn is wrapper on mysql.Conn capable of 40 // reading a table along with a GTID snapshot. 41 type snapshotConn struct { 42 *mysql.Conn 43 cp dbconfigs.Connector 44 } 45 46 func init() { 47 servenv.OnParseFor("vtcombo", registerSnapshotConnFlags) 48 servenv.OnParseFor("vttablet", registerSnapshotConnFlags) 49 } 50 51 func registerSnapshotConnFlags(fs *pflag.FlagSet) { 52 fs.Int64Var(&binlogRotationThreshold, "vstream-binlog-rotation-threshold", binlogRotationThreshold, "Byte size at which a VStreamer will attempt to rotate the source's open binary log before starting a GTID snapshot based stream (e.g. a ResultStreamer or RowStreamer)") 53 } 54 55 func snapshotConnect(ctx context.Context, cp dbconfigs.Connector) (*snapshotConn, error) { 56 mconn, err := mysqlConnect(ctx, cp) 57 if err != nil { 58 return nil, err 59 } 60 return &snapshotConn{ 61 Conn: mconn, 62 cp: cp, 63 }, nil 64 } 65 66 // startSnapshot starts a streaming query with a snapshot view of the specified table. 67 // It returns the GTID set from the time when the snapshot was taken. 68 func (conn *snapshotConn) streamWithSnapshot(ctx context.Context, table, query string) (gtid string, rotatedLog bool, err error) { 69 // Rotate the binary log if needed to limit the GTID auto positioning overhead. 70 // This may be needed as the currently open binary log (which can be up to 1G in 71 // size by default) will need to be scanned and empty events will be streamed for 72 // those GTIDs in the log that we are skipping. In total, this can add a lot of 73 // overhead on both the mysqld instance and the tablet. 74 // Rotating the log when it's above a certain size ensures that we are processing 75 // a relatively small binary log that will be minimal in size and GTID events. 76 // We only attempt to rotate it if the current log is of any significant size to 77 // avoid too many unecessary rotations. 78 if rotatedLog, err = conn.limitOpenBinlogSize(); err != nil { 79 // This is a best effort operation meant to lower overhead and improve performance. 80 // Thus it should not be required, nor cause the operation to fail. 81 log.Warningf("Failed in attempt to potentially flush binary logs in order to lessen overhead and improve performance of a VStream using query %q: %v", 82 query, err) 83 } 84 85 _, err = conn.ExecuteFetch("set session session_track_gtids = START_GTID", 1, false) 86 if err != nil { 87 // session_track_gtids = START_GTID unsupported or cannot execute. Resort to LOCK-based snapshot 88 gtid, err = conn.startSnapshot(ctx, table) 89 } else { 90 // session_track_gtids = START_GTID supported. Get a transaction with consistent GTID without LOCKing tables. 91 gtid, err = conn.startSnapshotWithConsistentGTID(ctx) 92 } 93 if err != nil { 94 return "", rotatedLog, err 95 } 96 if err := conn.ExecuteStreamFetch(query); err != nil { 97 return "", rotatedLog, err 98 } 99 return gtid, rotatedLog, nil 100 } 101 102 // snapshot performs the snapshotting. 103 func (conn *snapshotConn) startSnapshot(ctx context.Context, table string) (gtid string, err error) { 104 lockConn, err := mysqlConnect(ctx, conn.cp) 105 if err != nil { 106 return "", err 107 } 108 // To be safe, always unlock tables, even if lock tables might fail. 109 defer func() { 110 _, err := lockConn.ExecuteFetch("unlock tables", 0, false) 111 if err != nil { 112 log.Warning("Unlock tables failed: %v", err) 113 } else { 114 log.Infof("Tables unlocked: %v", table) 115 } 116 lockConn.Close() 117 }() 118 119 tableName := sqlparser.String(sqlparser.NewIdentifierCS(table)) 120 121 log.Infof("Locking table %s for copying", table) 122 if _, err := lockConn.ExecuteFetch(fmt.Sprintf("lock tables %s read", tableName), 1, false); err != nil { 123 log.Infof("Error locking table %s to read", tableName) 124 return "", err 125 } 126 mpos, err := lockConn.PrimaryPosition() 127 if err != nil { 128 return "", err 129 } 130 131 // Starting a transaction now will allow us to start the read later, 132 // which will happen after we release the lock on the table. 133 if _, err := conn.ExecuteFetch("set transaction isolation level repeatable read", 1, false); err != nil { 134 return "", err 135 } 136 if _, err := conn.ExecuteFetch("start transaction with consistent snapshot", 1, false); err != nil { 137 return "", err 138 } 139 if _, err := conn.ExecuteFetch("set @@session.time_zone = '+00:00'", 1, false); err != nil { 140 return "", err 141 } 142 return mysql.EncodePosition(mpos), nil 143 } 144 145 // startSnapshotWithConsistentGTID performs the snapshotting without locking tables. This assumes 146 // session_track_gtids = START_GTID, which is a contribution to MySQL and is not in vanilla MySQL at the 147 // time of this writing. 148 func (conn *snapshotConn) startSnapshotWithConsistentGTID(ctx context.Context) (gtid string, err error) { 149 if _, err := conn.ExecuteFetch("set transaction isolation level repeatable read", 1, false); err != nil { 150 return "", err 151 } 152 result, err := conn.ExecuteFetch("start transaction with consistent snapshot", 1, false) 153 if err != nil { 154 return "", err 155 } 156 // The "session_track_gtids = START_GTID" patch is only applicable to MySQL56 GTID, which is 157 // why we hardcode the position as mysql.Mysql56FlavorID 158 mpos, err := mysql.ParsePosition(mysql.Mysql56FlavorID, result.SessionStateChanges) 159 if err != nil { 160 return "", err 161 } 162 if _, err := conn.ExecuteFetch("set @@session.time_zone = '+00:00'", 1, false); err != nil { 163 return "", err 164 } 165 return mysql.EncodePosition(mpos), nil 166 } 167 168 // Close rollsback any open transactions and closes the connection. 169 func (conn *snapshotConn) Close() { 170 _, _ = conn.ExecuteFetch("rollback", 1, false) 171 conn.Conn.Close() 172 } 173 174 func mysqlConnect(ctx context.Context, cp dbconfigs.Connector) (*mysql.Conn, error) { 175 return cp.Connect(ctx) 176 } 177 178 // limitOpenBinlogSize will rotate the binary log if the current binary 179 // log is greater than binlogRotationThreshold. 180 func (conn *snapshotConn) limitOpenBinlogSize() (bool, error) { 181 rotatedLog := false 182 // Output: https://dev.mysql.com/doc/refman/en/show-binary-logs.html 183 res, err := conn.ExecuteFetch("SHOW BINARY LOGS", -1, false) 184 if err != nil { 185 return rotatedLog, err 186 } 187 if res == nil || len(res.Rows) == 0 { 188 return rotatedLog, fmt.Errorf("SHOW BINARY LOGS returned no rows") 189 } 190 // the current log will be the last one in the results 191 curLogIdx := len(res.Rows) - 1 192 curLogSize, err := res.Rows[curLogIdx][1].ToInt64() 193 if err != nil { 194 return rotatedLog, err 195 } 196 if curLogSize > atomic.LoadInt64(&binlogRotationThreshold) { 197 if _, err = conn.ExecuteFetch("FLUSH BINARY LOGS", 0, false); err != nil { 198 return rotatedLog, err 199 } 200 rotatedLog = true 201 } 202 return rotatedLog, nil 203 } 204 205 // GetBinlogRotationThreshold returns the current byte size at which a VStreamer 206 // will attempt to rotate the binary log before starting a GTID snapshot based 207 // stream (e.g. a ResultStreamer or RowStreamer). 208 func GetBinlogRotationThreshold() int64 { 209 return atomic.LoadInt64(&binlogRotationThreshold) 210 } 211 212 // SetBinlogRotationThreshold sets the byte size at which a VStreamer will 213 // attempt to rotate the binary log before starting a GTID snapshot based 214 // stream (e.g. a ResultStreamer or RowStreamer). 215 func SetBinlogRotationThreshold(threshold int64) { 216 atomic.StoreInt64(&binlogRotationThreshold, threshold) 217 }