-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing backup_pitr
flaky tests via wait-for loop on topo reads
#13781
Changes from all commits
4fca8f7
5063139
d71b826
e42b930
f64abbf
5bffaca
b46e33d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -328,7 +328,7 @@ func (be *BuiltinBackupEngine) executeIncrementalBackup(ctx context.Context, par | |
// everything that's ever been applied, and a subset of that is gtid_purged, which are the event no longer available in binary logs. | ||
// When we consider Vitess incremental backups, what's important for us is "what's the GTIDSet that's true when this backup was taken, | ||
// and which will be true when we restore this backup". The answer to this is the GTIDSet that includes the purged GTIDs. | ||
// It's also nice for icnremental backups that are taken on _other_ tablets, so that they don't need to understand what exactly was purged | ||
// It's also nice for incremental backups that are taken on _other_ tablets, so that they don't need to understand what exactly was purged | ||
// on _this_ tablet. They don't care, all they want to know is "what GTIDSet can we get from this". | ||
incrementalBackupToPosition.GTIDSet = incrementalBackupToPosition.GTIDSet.Union(gtidPurged.GTIDSet) | ||
req := &mysqlctl.ReadBinlogFilesTimestampsRequest{} | ||
|
@@ -345,7 +345,7 @@ func (be *BuiltinBackupEngine) executeIncrementalBackup(ctx context.Context, par | |
return false, vterrors.Wrapf(err, "reading timestamps from binlog files %v", binaryLogsToBackup) | ||
} | ||
if resp.FirstTimestampBinlog == "" || resp.LastTimestampBinlog == "" { | ||
return false, vterrors.Wrapf(err, "empty binlog name in response. Request=%v, Response=%v", req, resp) | ||
return false, vterrors.Errorf(vtrpc.Code_ABORTED, "empty binlog name in response. Request=%v, Response=%v", req, resp) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a logical error. The previous |
||
} | ||
incrDetails := &IncrementalBackupDetails{ | ||
FirstTimestamp: FormatRFC3339(logutil.ProtoToTime(resp.FirstTimestamp)), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1321,6 +1321,60 @@ func parseBinlogEntryTimestamp(logEntry string) (found bool, t time.Time, err er | |
return false, t, nil | ||
} | ||
|
||
// scanBinlogTimestamp invokes a `mysqlbinlog` binary to look for a timestamp in the given binary. The function | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mostly just refactored this function outside of |
||
// either looks for the first such timestamp or the last. | ||
func (mysqld *Mysqld) scanBinlogTimestamp(mysqlbinlogDir string, mysqlbinlogEnv []string, mysqlbinlogName string, binlogFile string, stopAtFirst bool) (matchedTime time.Time, matchFound bool, err error) { | ||
args := []string{binlogFile} | ||
mysqlbinlogCmd := exec.Command(mysqlbinlogName, args...) | ||
mysqlbinlogCmd.Dir = mysqlbinlogDir | ||
mysqlbinlogCmd.Env = mysqlbinlogEnv | ||
log.Infof("ApplyBinlogFile: running mysqlbinlog command: %#v", mysqlbinlogCmd) | ||
pipe, err := mysqlbinlogCmd.StdoutPipe() // to be piped into mysql | ||
if err != nil { | ||
return matchedTime, false, err | ||
} | ||
scanComplete := make(chan error) | ||
intentionalKill := false | ||
scan := func() { | ||
defer close(scanComplete) | ||
defer func() { | ||
intentionalKill = true | ||
mysqlbinlogCmd.Process.Kill() // ensures the binlog file is released | ||
}() | ||
// Read line by line and process it | ||
scanner := bufio.NewScanner(pipe) | ||
for scanner.Scan() { | ||
logEntry := scanner.Text() | ||
|
||
found, t, err := parseBinlogEntryTimestamp(logEntry) | ||
if err != nil { | ||
scanComplete <- err | ||
return | ||
} | ||
if found { | ||
matchedTime = t | ||
matchFound = true | ||
} | ||
if found && stopAtFirst { | ||
// Found the first timestamp and it's all we need. We won't scan any further and so we should also | ||
// kill mysqlbinlog (otherwise it keeps waiting until we've read the entire pipe). | ||
return | ||
} | ||
} | ||
} | ||
if err := mysqlbinlogCmd.Start(); err != nil { | ||
return matchedTime, false, err | ||
} | ||
go scan() | ||
if err := mysqlbinlogCmd.Wait(); err != nil && !intentionalKill { | ||
return matchedTime, false, vterrors.Wrapf(err, "waiting on mysqlbinlog command in ReadBinlogFilesTimestamps") | ||
} | ||
if err := <-scanComplete; err != nil { | ||
return matchedTime, false, vterrors.Wrapf(err, "scanning mysqlbinlog output in ReadBinlogFilesTimestamps ") | ||
} | ||
return matchedTime, matchFound, nil | ||
} | ||
|
||
// ReadBinlogFilesTimestamps reads all given binlog files via `mysqlbinlog` command and returns the first and last found transaction timestamps | ||
func (mysqld *Mysqld) ReadBinlogFilesTimestamps(ctx context.Context, req *mysqlctlpb.ReadBinlogFilesTimestampsRequest) (*mysqlctlpb.ReadBinlogFilesTimestampsResponse, error) { | ||
if len(req.BinlogFileNames) == 0 { | ||
|
@@ -1335,8 +1389,6 @@ func (mysqld *Mysqld) ReadBinlogFilesTimestamps(ctx context.Context, req *mysqlc | |
defer client.Close() | ||
return client.ReadBinlogFilesTimestamps(ctx, req) | ||
} | ||
var mysqlbinlogCmd *exec.Cmd | ||
|
||
dir, err := vtenv.VtMysqlRoot() | ||
if err != nil { | ||
return nil, err | ||
|
@@ -1350,59 +1402,10 @@ func (mysqld *Mysqld) ReadBinlogFilesTimestamps(ctx context.Context, req *mysqlc | |
return nil, err | ||
} | ||
|
||
scanTimestamp := func(binlogFile string, stopAtFirst bool) (matchedTime time.Time, matchFound bool, err error) { | ||
args := []string{binlogFile} | ||
mysqlbinlogCmd = exec.Command(mysqlbinlogName, args...) | ||
mysqlbinlogCmd.Dir = dir | ||
mysqlbinlogCmd.Env = env | ||
log.Infof("ApplyBinlogFile: running mysqlbinlog command: %#v", mysqlbinlogCmd) | ||
pipe, err := mysqlbinlogCmd.StdoutPipe() // to be piped into mysql | ||
if err != nil { | ||
return matchedTime, false, err | ||
} | ||
scanner := bufio.NewScanner(pipe) | ||
scanComplete := make(chan error) | ||
intentionalKill := false | ||
scan := func() { | ||
defer close(scanComplete) | ||
// Read line by line and process it | ||
for scanner.Scan() { | ||
logEntry := scanner.Text() | ||
|
||
found, t, err := parseBinlogEntryTimestamp(logEntry) | ||
if err != nil { | ||
scanComplete <- err | ||
return | ||
} | ||
if found { | ||
matchedTime = t | ||
matchFound = true | ||
} | ||
if found && stopAtFirst { | ||
// Found the first timestamp and it's all we need. We won't scan any further and so we should also | ||
// kill mysqlbinlog (otherwise it keeps waiting until we've read the entire pipe). | ||
intentionalKill = true | ||
mysqlbinlogCmd.Process.Kill() | ||
return | ||
} | ||
} | ||
} | ||
if err := mysqlbinlogCmd.Start(); err != nil { | ||
return matchedTime, false, err | ||
} | ||
go scan() | ||
if err := mysqlbinlogCmd.Wait(); err != nil && !intentionalKill { | ||
return matchedTime, false, vterrors.Wrapf(err, "waiting on mysqlbinlog command in ReadBinlogFilesTimestamps") | ||
} | ||
if err := <-scanComplete; err != nil { | ||
return matchedTime, false, vterrors.Wrapf(err, "scanning mysqlbinlog output in ReadBinlogFilesTimestamps ") | ||
} | ||
return matchedTime, matchFound, nil | ||
} | ||
resp := &mysqlctlpb.ReadBinlogFilesTimestampsResponse{} | ||
// Find first timestamp | ||
for _, binlogFile := range req.BinlogFileNames { | ||
t, found, err := scanTimestamp(binlogFile, true) | ||
t, found, err := mysqld.scanBinlogTimestamp(dir, env, mysqlbinlogName, binlogFile, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -1415,7 +1418,7 @@ func (mysqld *Mysqld) ReadBinlogFilesTimestamps(ctx context.Context, req *mysqlc | |
// Find last timestamp | ||
for i := len(req.BinlogFileNames) - 1; i >= 0; i-- { | ||
binlogFile := req.BinlogFileNames[i] | ||
t, found, err := scanTimestamp(binlogFile, false) | ||
t, found, err := mysqld.scanBinlogTimestamp(dir, env, mysqlbinlogName, binlogFile, false) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really want the backup process to print out the name of the backup. It's so useful. I'd also look into formalizing it into something that is machine readable rather than human readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shlomi-noach Would this have potential to create (a lot) of logging noise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dbussink it's a single additional line per backup. A full backup usually has hundreds of log lines. An incremental backup usually has a 5-6 log lines. One extra line is nothing.