-
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
adding new mysql shell backup engine #16295
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Renan Rangel <rrangel@slack-corp.com>
Review ChecklistHello reviewers! 👋 Please follow this checklist when reviewing this Pull Request. General
Tests
Documentation
New flags
If a workflow is added or modified:
Backward compatibility
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #16295 +/- ##
=========================================
Coverage 68.61% 68.61%
=========================================
Files 1544 1549 +5
Lines 197993 199256 +1263
=========================================
+ Hits 135848 136728 +880
- Misses 62145 62528 +383 ☔ View full report in Codecov by Sentry. |
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.
Thank you for this submission! Some initial general thoughts, before a deeper code review. PErhaps these questions are more appropriate on #16294 but I did not want to then split the discussion so let's keep it here.
I have not used MySQL Shell backups before. Some questions and notes:
-
This PR adds dependencies on
mysqlsh
andmysqlshell
binaries. This is just an observation, but points for consideration are:- Neither are included in a standard MySQL build. What are version dependencies between mysqlsh/mysqlshell and the MySQL server?
- Neither are included in the MySQL docker images, to the best of my understanding. This means this backup method will not be available on kubernetes deployments via
vitess-operator
.
-
Re: GTID not being available in the manifest file, this means we will not be able to run point in time recoveries with a mysqlshell-based full backup. Point in time recoveries require GTID information. As mentioned in Feature Request: MySQL Shell Logical Backups #16294 (comment), the mysqlshell method is the first and only (thus far) logical backup solution, so it's unfortunate that this solution will not support logical point in time recoveries.
Is it not possible to read thegtidExecuted
field from the@.json
dump file immediately after the backup is complete, and update the manifest file? E.g. if the dump is into a directory, isn't that directory available for us to read?
// This is usually run in a background goroutine, so there's no point | ||
// returning an error. Just log it. | ||
logger.Warningf("error scanning lines from %s: %v", prefix, 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.
I notice we do not have a unit test for this function. Having moved it around, perhaps now is a good opportunity?
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.
yeah, I can probably add it there 👍
These are good questions, thanks Shlomi!
|
Signed-off-by: Renan Rangel <rrangel@slack-corp.com>
Signed-off-by: Renan Rangel <rrangel@slack-corp.com>
Signed-off-by: Renan Rangel <rrangel@slack-corp.com>
ce12fdd
to
c51ee4b
Compare
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.
Hello, thank you for this contribution.
Since we have not fully finished the deletion of mysqld
in the vitess/lite
Docker Images, the mysqlsh
binary will have to be included in the vitess/lite
image regardless if it's included in the official MySQL Docker Images or not. Since we are letting people choose between using an official MySQL image or the vitess/lite
image for their Docker/K8S deployment we must have the binary in both.
Regarding vitess-operator
, a follow-up PR is needed on the vitess-operator to allow this new backup engine. In our CRDs we have an enumeration that restrict what backup engines are allowed, we just need to add a new entry in the enumeration. This can be done here.
FYI, I can handle the vitess-operator changes.
Have you looked at |
Oh, that's nice! The reason I thought it wasn't included is that
I feel like it's OK to have some solution "with limitations". We should strive to support as much functionality as possible though. So IMHO we should strive to include the GTID when the backup goes into a directory. This should be possible to do, which then means the backup should fail if for some reason we can't fetch the GTID or validate it (correct GTID form). i.e. return I'd like @deepthi to weigh in her opinion. Assuming we do decide to move forward, then I'd next expect a CI/endtoend test please, as follows:
When these are all added, a new CI job will run to test If this test passes, then you will have validated the full cycle of backup and resotre, as well as correctness of the captured GTID. Edit: since S3, Azure, GCP can be left without GTID support for now. We'd need a documentation PR that clearly indicates the limitations of this method. |
var ( | ||
// location to store the mysql shell backup | ||
mysqlShellBackupLocation = "" | ||
// flags passed to the mysql shell utility, used both on dump/restore | ||
mysqlShellFlags = "--defaults-file=/dev/null --js -h localhost" | ||
// flags passed to the Dump command, as a JSON string | ||
mysqlShellDumpFlags = `{"threads": 2}` | ||
// flags passed to the Load command, as a JSON string | ||
mysqlShellLoadFlags = `{"threads": 4, "updateGtidSet": "replace", "skipBinlog": true, "progressFile": ""}` | ||
// drain a tablet when taking a backup | ||
mysqlShellBackupShouldDrain = false | ||
// disable redo logging and double write buffer | ||
mysqlShellSpeedUpRestore = false | ||
|
||
MySQLShellPreCheckError = errors.New("MySQLShellPreCheckError") | ||
) |
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.
You have correctly followed the existing design. I'm just taking the opportunity to say at some point we will want to move away from these global variables.
// location to store the mysql shell backup | ||
mysqlShellBackupLocation = "" | ||
// flags passed to the mysql shell utility, used both on dump/restore | ||
mysqlShellFlags = "--defaults-file=/dev/null --js -h localhost" |
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.
// flags passed to the mysql shell utility, used both on dump/restore | ||
mysqlShellFlags = "--defaults-file=/dev/null --js -h localhost" | ||
// flags passed to the Dump command, as a JSON string | ||
mysqlShellDumpFlags = `{"threads": 2}` |
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.
Any particular reason to choose 2
rather than something based on runtime.NumCPU()
?
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.
I would say because MySQL Shell backups can be a bit more intensive - we are requesting a bunch of data off MySQL which needs to be fetched, parsed and compressed - and in our particular use case we were taking backups online so we didn't wan't it to cause that much disruption. It is also part of the reason why I made sure ShouldDrainForBackup()
was configurable in case it is more suitable for the use case.
I am fine with changing the default to runtime.NumCPU()
though since it is configurable and leave this up to the user to decide based on their environment requirements, although I am also conscious that it might cause some issues in Kube where it will show the number of CPUs of the underlying node despite the pod being possibly limited on how much CPU it can use.
fs.StringVar(&mysqlShellFlags, "mysql_shell_flags", mysqlShellFlags, "execution flags to pass to mysqlsh binary to be used during dump/load") | ||
fs.StringVar(&mysqlShellDumpFlags, "mysql_shell_dump_flags", mysqlShellDumpFlags, "flags to pass to mysql shell dump utility. This should be a JSON string and will be saved in the MANIFEST") | ||
fs.StringVar(&mysqlShellLoadFlags, "mysql_shell_load_flags", mysqlShellLoadFlags, "flags to pass to mysql shell load utility. This should be a JSON string") | ||
fs.BoolVar(&mysqlShellBackupShouldDrain, "mysql_shell_should_drain", mysqlShellBackupShouldDrain, "decide if we should drain while taking a backup or continue to serving traffic") |
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.
I'm guessing the choice of draining vs not draining is due to the increased workload on the server?
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.
yeah, exactly. in fact I have been meaning to propose this to be modifiable for the xtrabackup
engine as well, so a tablet won't be service traffic when it is taking a backup
fs.StringVar(&mysqlShellDumpFlags, "mysql_shell_dump_flags", mysqlShellDumpFlags, "flags to pass to mysql shell dump utility. This should be a JSON string and will be saved in the MANIFEST") | ||
fs.StringVar(&mysqlShellLoadFlags, "mysql_shell_load_flags", mysqlShellLoadFlags, "flags to pass to mysql shell load utility. This should be a JSON string") | ||
fs.BoolVar(&mysqlShellBackupShouldDrain, "mysql_shell_should_drain", mysqlShellBackupShouldDrain, "decide if we should drain while taking a backup or continue to serving traffic") | ||
fs.BoolVar(&mysqlShellSpeedUpRestore, "mysql_shell_speedup_restore", mysqlShellSpeedUpRestore, "speed up restore by disabling redo logging and double write buffer during the restore process") |
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.
This feels risky. Please indicate caveats in this flag's description. Otherwise this looks "too good", why wouldn't anyone want to speed up the restore?
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.
unless you need the redo log/double write buffer to be disable once the instance has completed the restore, there shouldn't be much risk in enabling this. for some setups there might be an interest in disabling this (I can see as a possible case, somebody running on zfs
and wanting to keep the double write buffer disabled), so I didn't want to force it if the user has a similar scenario
} | ||
|
||
start := time.Now().UTC() | ||
location := path.Join(mysqlShellBackupLocation, params.Keyspace, params.Shard, start.Format("2006-01-02_15-04-05")) |
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.
This is a non-standard format. It looks to my like you wish to avoid special characters -- which makes sense for S3 etc.
But then, why mix and match -
and _
and not set all to -
, or all to _
?
Alternatively, consider this more condensed format:
Lines 77 to 82 in 485d736
// ToReadableTimestamp returns a timestamp, in seconds resolution, that is human readable | |
// (as opposed to unix timestamp which is just a number) | |
// Example: for Aug 25 2020, 16:04:25 we return "20200825160425" | |
func ToReadableTimestamp(t time.Time) string { | |
return t.Format(readableTimeFormat) | |
} |
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.
ah, simply because we were already using another alternative to taking logical backups outside of vitess and this follows the same format.
but to be fair, vitess already doesn't use a standard format either. backups are created using:
vitess/go/vt/mysqlctl/backup.go
Lines 59 to 60 in c51ee4b
// BackupTimestampFormat is the format in which we save BackupTime and FinishedTime | |
BackupTimestampFormat = "2006-01-02.150405" |
I can switch to using that format if it makes more sense?
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.
Yes, let us use this format, and keep it consistent within the backups code.
args = append(args, strings.Fields(mysqlShellFlags)...) | ||
} | ||
|
||
args = append(args, "-e", fmt.Sprintf("util.dumpSchemas([\"vt_%s\"], %q, %s)", |
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.
should the keyspace/schema names be escaped here?
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.
you mean escape like in MySQL as `vt_keyspace`
? I am not sure MySQL Shell supports or expects it, I will verify that
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.
You can't assume that the database name is vt_keyspace
. It is determined by the --init_db_name_override
flag.
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.
This brings up another point. Typically when we do the regular kind of backup we also backup the sidecar db, which defaults to _vt
. Does the mysqlshell backup specifically backup only the actual keyspace/database? What are the implications of this?
defer func() { // re-enable once we are done with the restore. | ||
err := params.Mysqld.ExecuteSuperQueryList(ctx, []string{"ALTER INSTANCE ENABLE INNODB REDO_LOG"}) | ||
if err != nil { | ||
params.Logger.Errorf("unable to re-enable REDO_LOG: %v", 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.
Should we fail the restore process? I'm not sure if I have a good answer here.
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.
that's a good question. the original intention here is, since we were able to successfully disable Redo logs/double write buffer, it would be better to fail the restore than potentially put it in service with a potentially dangerous configuration without the user realising it.
if the user wishes to run with redo log/double write disabled buffer they can avoid setting --mysql_shell_speedup_restore
and handle it outside of vitess.
@shlomi-noach yeah, we looked into I think the proposal to have the GTID read when backing up to a directory while not when using an object store is fair, let me look into it and make the necessary changes. If all looks good I will proceed with working on the CI/endtoend tests, I just wanted to get some initial feedback before doing so. Also curious what @deepthi thinks about this approach.
Is that something that needs to happen as part of this PR or something separate? |
@frouioui We don't use the docker images, but I am not sure I understand, if regarding the change in the |
@rvrangel absolutely! Let's wait for more input.
In the scope of this PR please. I was merely pointing out that our standard workflow won't install mysql-shell. Oh, and there's a greater discussion about how our workflows are generated, see |
@rvrangel, historically we have always shipped
EDIT: I said I was going to push to this PR, but I ended up not doing this in case you have ongoing work on the branch or else. We need to add
You can ensure that the fix works by doing the following:
Doing this, the version of
Note that if you are on different architecture than For @shlomi-noach, in the output I pasted just above, where I run
Thank you for opening this! 🙇🏻 I will take a look and comment on that PR directly. |
@shlomi-noach and @frouioui have pretty much covered all of the main concerns. My opinion is that in principle this is a good feature addition.
Can you explain what will and will not work with S3, GCP and Azure? |
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.
I think it might be worth writing something about this new backup engine in the release notes for v21.0.0: ./changelog/21.0/21.0.0/summary.md
.
package mysqlctl | ||
|
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.
This file and go/vt/mysqlctl/mysqlshellbackupengine_test.go
need a licence header.
--mysql_shell_backup_location string location where the backup will be stored | ||
--mysql_shell_dump_flags string flags to pass to mysql shell dump utility. This should be a JSON string and will be saved in the MANIFEST (default "{\"threads\": 2}") | ||
--mysql_shell_flags string execution flags to pass to mysqlsh binary to be used during dump/load (default "--defaults-file=/dev/null --js -h localhost") | ||
--mysql_shell_load_flags string flags to pass to mysql shell load utility. This should be a JSON string (default "{\"threads\": 4, \"updateGtidSet\": \"replace\", \"skipBinlog\": true, \"progressFile\": \"\"}") | ||
--mysql_shell_should_drain decide if we should drain while taking a backup or continue to serving traffic | ||
--mysql_shell_speedup_restore speed up restore by disabling redo logging and double write buffer during the restore process |
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.
New flags need to use dashes as separators.
args = append(args, strings.Fields(mysqlShellFlags)...) | ||
} | ||
|
||
args = append(args, "-e", fmt.Sprintf("util.dumpSchemas([\"vt_%s\"], %q, %s)", |
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.
You can't assume that the database name is vt_keyspace
. It is determined by the --init_db_name_override
flag.
args = append(args, strings.Fields(mysqlShellFlags)...) | ||
} | ||
|
||
args = append(args, "-e", fmt.Sprintf("util.dumpSchemas([\"vt_%s\"], %q, %s)", |
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.
This brings up another point. Typically when we do the regular kind of backup we also backup the sidecar db, which defaults to _vt
. Does the mysqlshell backup specifically backup only the actual keyspace/database? What are the implications of this?
@deepthi I will meanwhile explain what my understanding is: that in S3, Azure, and GCP, we won't have GTID information in the backup manifest, which means we won't be able to use such backups for point in time recoveries. |
Description
This is a PR that implements a new backup engine for use with MySQL Shell, which is mentioned in the feature request here: #16294
It works a bit differently than the existing engines in vitess in which it only stores the metadata of how the backup was created (location + parameters used) and during the restore uses the location plus other parameters (mysql shell are different if you are doing a dump vs a restore, so we can't use exactly the same ones)
Related Issue(s)
Fixes #16294
Checklist
Deployment Notes