Skip to content

Commit

Permalink
vtbackup: disable redo log before starting replication (vitessio#11330)
Browse files Browse the repository at this point in the history
* vtbackup: disable redo log before starting replication

According to MySQL docs:

> As of MySQL 8.0.21, you can disable redo logging using the ALTER
> INSTANCE DISABLE INNODB REDO_LOG statement. This functionality is
> intended for loading data into a new MySQL instance. Disabling redo
> logging speeds up data loading by avoiding redo log writes and
> doublewrite buffering.

See: https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html#innodb-disable-redo-logging

We can take advantage of this in vtbackup. This change disables the redo
log on MySQL >= 8.0.21 before starting replication, and re-enables the
redo log after stopping replication.

Signed-off-by: Max Englander <max@planetscale.com>

* non-fatal error, robus status check, copyright current year

Signed-off-by: Max Englander <max@planetscale.com>

* try checking redo log status in vtbackup e2e test

Signed-off-by: Max Englander <max@planetscale.com>

* retain vtbackup temporary files so we can verify them in e2e test

Signed-off-by: Max Englander <max@planetscale.com>

* make e2e test work with upgrade/downgrade tests

Signed-off-by: Max Englander <max@planetscale.com>

* check for mysql 8.0.21 in e2e test redo log assertions

Signed-off-by: Max Englander <max@planetscale.com>

* fmt.Println debug

Signed-off-by: Max Englander <max@planetscale.com>

* perconnnaaaaaa

Signed-off-by: Max Englander <max@planetscale.com>

* fix e2e flags

Signed-off-by: Max Englander <max@planetscale.com>

* simplify e2e test, revert flags and cnf cruft

Signed-off-by: Max Englander <max@planetscale.com>

* capitalize log msgs

Signed-off-by: Max Englander <max@planetscale.com>

* dont verify redo log for --initial_backup

Signed-off-by: Max Englander <max@planetscale.com>

* address pr comments: fix typo and remove package alias

Signed-off-by: Max Englander <max@planetscale.com>

* address pr comments: report redo log enabled when we can't disable

Signed-off-by: Max Englander <max@planetscale.com>

* rely on mysql server variable to determine if can disable/enable redo log

Signed-off-by: Max Englander <max@planetscale.com>

* split up binary vs. process redo log capability

Signed-off-by: Max Englander <max@planetscale.com>

* simplify further

Signed-off-by: Max Englander <max@planetscale.com>

* Address my own pedantic nits

Signed-off-by: Matt Lord <mattalord@gmail.com>

* Sorry, can't help myself

Signed-off-by: Matt Lord <mattalord@gmail.com>

Signed-off-by: Max Englander <max@planetscale.com>
Signed-off-by: Matt Lord <mattalord@gmail.com>
Co-authored-by: Matt Lord <mattalord@gmail.com>
  • Loading branch information
2 people authored and tanjinx committed Oct 10, 2024
1 parent 37b690c commit 94cb53c
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 11 deletions.
15 changes: 15 additions & 0 deletions go/cmd/vtbackup/vtbackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,14 @@ func takeBackup(ctx context.Context, topoServer *topo.Server, backupStorage back
return fmt.Errorf("can't restore from backup: %v", err)
}

// Disable redo logging (if we can) before we start replication.
disabledRedoLog := false
if err := mysqld.DisableRedoLog(ctx); err != nil {
log.Warningf("Error disabling redo logging: %v", err)
} else {
disabledRedoLog = true
}

// We have restored a backup. Now start replication.
if err := resetReplication(ctx, restorePos, mysqld); err != nil {
return fmt.Errorf("error resetting replication: %v", err)
Expand Down Expand Up @@ -431,6 +439,13 @@ func takeBackup(ctx context.Context, topoServer *topo.Server, backupStorage back
return fmt.Errorf("not taking backup: replication did not make any progress from restore point: %v", restorePos)
}

// Re-enable redo logging.
if disabledRedoLog {
if err := mysqld.EnableRedoLog(ctx); err != nil {
return fmt.Errorf("failed to re-enable redo log: %v", err)
}
}

if restartBeforeBackup {
log.Info("Proceeding with clean MySQL shutdown and startup to flush all buffers.")
// Prep for full/clean shutdown (not typically the default)
Expand Down
1 change: 1 addition & 0 deletions go/mysql/flavor.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
MySQLJSONFlavorCapability
MySQLUpgradeInServerFlavorCapability
DynamicRedoLogCapacityFlavorCapability // supported in MySQL 8.0.30 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-30.html
DisableRedoLogFlavorCapability // supported in MySQL 8.0.21 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html
)

const (
Expand Down
2 changes: 2 additions & 0 deletions go/mysql/flavor_mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,8 @@ func (mysqlFlavor80) supportsCapability(serverVersion string, capability FlavorC
return ServerVersionAtLeast(serverVersion, 8, 0, 16)
case DynamicRedoLogCapacityFlavorCapability:
return ServerVersionAtLeast(serverVersion, 8, 0, 30)
case DisableRedoLogFlavorCapability:
return ServerVersionAtLeast(serverVersion, 8, 0, 21)
default:
return false, nil
}
Expand Down
10 changes: 10 additions & 0 deletions go/mysql/flavor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ func TestGetFlavor(t *testing.T) {
capability: DynamicRedoLogCapacityFlavorCapability,
isCapable: false,
},
{
version: "8.0.21",
capability: DisableRedoLogFlavorCapability,
isCapable: true,
},
{
version: "8.0.20",
capability: DisableRedoLogFlavorCapability,
isCapable: false,
},
}
for _, tc := range testcases {
name := fmt.Sprintf("%s %v", tc.version, tc.capability)
Expand Down
79 changes: 70 additions & 9 deletions go/test/endtoend/backup/vtbackup/backup_only_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@ limitations under the License.
package vtbackup

import (
"context"
"fmt"
"os"
"path"
"strings"
"testing"
"time"

"vitess.io/vitess/go/vt/mysqlctl"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/test/endtoend/cluster"

"github.com/stretchr/testify/assert"

"vitess.io/vitess/go/vt/log"
"vitess.io/vitess/go/vt/mysqlctl"
)

var (
Expand Down Expand Up @@ -78,6 +77,7 @@ func TestTabletInitialBackup(t *testing.T) {

tearDown(t, true)
}

func TestTabletBackupOnly(t *testing.T) {
// Test Backup Flow
// TestTabletBackupOnly will:
Expand Down Expand Up @@ -175,13 +175,29 @@ func firstBackupTest(t *testing.T, tabletType string) {
}

func vtBackup(t *testing.T, initialBackup bool, restartBeforeBackup bool) {
mysqlSocket, err := os.CreateTemp("", "vtbackup_test_mysql.sock")
require.Nil(t, err)
defer os.Remove(mysqlSocket.Name())

// Take the back using vtbackup executable
extraArgs := []string{"--allow_first_backup", "--db-credentials-file", dbCredentialFile}
extraArgs := []string{
"--allow_first_backup",
"--db-credentials-file", dbCredentialFile,
"--mysql_socket", mysqlSocket.Name(),
}
if restartBeforeBackup {
extraArgs = append(extraArgs, "--restart_before_backup")
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

if !initialBackup {
go verifyDisableEnableRedoLogs(ctx, t, mysqlSocket.Name())
}

log.Infof("starting backup tablet %s", time.Now())
err := localCluster.StartVtbackup(newInitDBFile, initialBackup, keyspaceName, shardName, cell, extraArgs...)
err = localCluster.StartVtbackup(newInitDBFile, initialBackup, keyspaceName, shardName, cell, extraArgs...)
require.Nil(t, err)
}

Expand Down Expand Up @@ -265,7 +281,6 @@ func restore(t *testing.T, tablet *cluster.Vttablet, tabletType string, waitForS
}

func resetTabletDirectory(t *testing.T, tablet cluster.Vttablet, initMysql bool) {

extraArgs := []string{"--db-credentials-file", dbCredentialFile}
tablet.MysqlctlProcess.ExtraArgs = extraArgs

Expand All @@ -285,7 +300,6 @@ func resetTabletDirectory(t *testing.T, tablet cluster.Vttablet, initMysql bool)
err = tablet.MysqlctlProcess.Start()
require.Nil(t, err)
}

}

func tearDown(t *testing.T, initMysql bool) {
Expand Down Expand Up @@ -316,3 +330,50 @@ func tearDown(t *testing.T, initMysql bool) {
require.Nil(t, err)
}
}

func verifyDisableEnableRedoLogs(ctx context.Context, t *testing.T, mysqlSocket string) {
params := cluster.NewConnParams(0, dbPassword, mysqlSocket, keyspaceName)

for {
select {
case <-time.After(100 * time.Millisecond):
// Connect to vtbackup mysqld.
conn, err := mysql.Connect(ctx, &params)
if err != nil {
// Keep trying, vtbackup mysqld may not be ready yet.
continue
}

// Check if server supports disable/enable redo log.
qr, err := conn.ExecuteFetch("SELECT 1 FROM performance_schema.global_status WHERE variable_name = 'innodb_redo_log_enabled'", 1, false)
require.Nil(t, err)
// If not, there's nothing to test.
if len(qr.Rows) == 0 {
return
}

// MY-013600
// https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_ib_wrn_redo_disabled
qr, err = conn.ExecuteFetch("SELECT 1 FROM performance_schema.error_log WHERE error_code = 'MY-013600'", 1, false)
require.Nil(t, err)
if len(qr.Rows) != 1 {
// Keep trying, possible we haven't disabled yet.
continue
}

// MY-013601
// https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_ib_wrn_redo_enabled
qr, err = conn.ExecuteFetch("SELECT 1 FROM performance_schema.error_log WHERE error_code = 'MY-013601'", 1, false)
require.Nil(t, err)
if len(qr.Rows) != 1 {
// Keep trying, possible we haven't disabled yet.
continue
}

// Success
return
case <-ctx.Done():
require.Fail(t, "Failed to verify disable/enable redo log.")
}
}
}
4 changes: 2 additions & 2 deletions go/test/endtoend/cluster/cluster_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ func (cluster *LocalProcessCluster) waitForMySQLProcessToExit(mysqlctlProcessLis
}

// StartVtbackup starts a vtbackup
func (cluster *LocalProcessCluster) StartVtbackup(newInitDBFile string, initalBackup bool,
func (cluster *LocalProcessCluster) StartVtbackup(newInitDBFile string, initialBackup bool,
keyspace string, shard string, cell string, extraArgs ...string) error {
log.Info("Starting vtbackup")
cluster.VtbackupProcess = *VtbackupProcessInstance(
Expand All @@ -1011,7 +1011,7 @@ func (cluster *LocalProcessCluster) StartVtbackup(newInitDBFile string, initalBa
cluster.Hostname,
cluster.TmpDirectory,
cluster.TopoPort,
initalBackup)
initialBackup)
cluster.VtbackupProcess.ExtraArgs = extraArgs
return cluster.VtbackupProcess.Setup()

Expand Down
12 changes: 12 additions & 0 deletions go/vt/mysqlctl/capabilityset.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ func (c *capabilitySet) hasMaria104InstallDb() bool {
return c.isMariaDB() && c.version.atLeast(ServerVersion{Major: 10, Minor: 4, Patch: 0})
}

// hasDisableRedoLog tells you if the version of MySQL in use can disable redo logging.
//
// As of MySQL 8.0.21, you can disable redo logging using the ALTER INSTANCE
// DISABLE INNODB REDO_LOG statement. This functionality is intended for
// loading data into a new MySQL instance. Disabling redo logging speeds up
// data loading by avoiding redo log writes and doublewrite buffering.
//
// https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html#innodb-disable-redo-logging
func (c *capabilitySet) hasDisableRedoLog() bool {
return c.isMySQLLike() && c.version.atLeast(ServerVersion{Major: 8, Minor: 0, Patch: 21})
}

// IsMySQLLike tests if the server is either MySQL
// or Percona Server. At least currently, Vitess doesn't
// make use of any specific Percona Server features.
Expand Down
48 changes: 48 additions & 0 deletions go/vt/mysqlctl/redo_log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2022 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mysqlctl

import (
"context"
"fmt"
)

func (mysqld *Mysqld) BinaryHasDisableRedoLog() bool {
return mysqld.capabilities.hasDisableRedoLog()
}

func (mysqld *Mysqld) DisableRedoLog(ctx context.Context) error {
return mysqld.ExecuteSuperQuery(ctx, "ALTER INSTANCE DISABLE INNODB REDO_LOG")
}

func (mysqld *Mysqld) EnableRedoLog(ctx context.Context) error {
return mysqld.ExecuteSuperQuery(ctx, "ALTER INSTANCE ENABLE INNODB REDO_LOG")
}

func (mysqld *Mysqld) ProcessCanDisableRedoLog(ctx context.Context) (bool, error) {
qr, err := mysqld.FetchSuperQuery(ctx, "SELECT variable_value FROM performance_schema.global_status WHERE variable_name = 'innodb_redo_log_enabled'")
if err != nil {
// It's possible that the MySQL process can disable redo logging, but
// we were unable to connect in order to verify. Let's assume not and
// let the caller decide if they want to retry.
return false, err
}
if len(qr.Rows) == 0 {
return false, fmt.Errorf("mysqld >= 8.0.21 required to disable the redo log")
}
return true, nil
}

0 comments on commit 94cb53c

Please sign in to comment.