Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/replica-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
version: [mysql-5.7.25,mysql-8.0.16]
version: [mysql-5.7.25,mysql-8.0.16,PerconaServer-8.0.21]

steps:
- uses: actions/checkout@v2
Expand Down
12 changes: 12 additions & 0 deletions doc/command-line-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ Allows `gh-ost` to connect to the MySQL servers using encrypted connections, but

`--ssl-key=/path/to/ssl-key.key`: SSL private key file (in PEM format).

### storage-engine
Default is `innodb`, and `rocksdb` support is currently experimental. InnoDB and RocksDB are both transactional engines, supporting both shared and exclusive row locks.

But RocksDB currently lacks a few features support compared to InnoDB:
- Gap Locks
- Foreign Key
- Generated Columns
- Spatial
- Geometry

When `--storage-engine=rocksdb`, `gh-ost` will make some changes necessary (e.g. sets isolation level to `READ_COMMITTED`) to support RocksDB.

### test-on-replica

Issue the migration on a replica; do not modify data on master. Useful for validating, testing and benchmarking. See [`testing-on-replica`](testing-on-replica.md)
Expand Down
17 changes: 17 additions & 0 deletions go/base/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,19 @@ func NewMigrationContext() *MigrationContext {
}
}

func (this *MigrationContext) SetConnectionConfig(storageEngine string) error {
var transactionIsolation string
switch storageEngine {
case "rocksdb":
transactionIsolation = "READ-COMMITTED"
default:
transactionIsolation = "REPEATABLE-READ"
}
this.InspectorConnectionConfig.TransactionIsolation = transactionIsolation
this.ApplierConnectionConfig.TransactionIsolation = transactionIsolation
return nil
}

func getSafeTableName(baseName string, suffix string) string {
name := fmt.Sprintf("_%s_%s", baseName, suffix)
if len(name) <= mysql.MaxTableNameLength {
Expand Down Expand Up @@ -428,6 +441,10 @@ func (this *MigrationContext) IsTransactionalTable() bool {
{
return true
}
case "rocksdb":
{
return true
}
}
return false
}
Expand Down
8 changes: 8 additions & 0 deletions go/cmd/gh-ost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func main() {
flag.StringVar(&migrationContext.OriginalTableName, "table", "", "table name (mandatory)")
flag.StringVar(&migrationContext.AlterStatement, "alter", "", "alter statement (mandatory)")
flag.BoolVar(&migrationContext.AttemptInstantDDL, "attempt-instant-ddl", false, "Attempt to use instant DDL for this migration first")
storageEngine := flag.String("storage-engine", "innodb", "Specify table storage engine (default: 'innodb'). When 'rocksdb': the session transaction isolation level is changed from REPEATABLE_READ to READ_COMMITTED.")

flag.BoolVar(&migrationContext.CountTableRows, "exact-rowcount", false, "actually count table rows as opposed to estimate them (results in more accurate progress estimation)")
flag.BoolVar(&migrationContext.ConcurrentCountTableRows, "concurrent-rowcount", true, "(with --exact-rowcount), when true (default): count rows after row-copy begins, concurrently, and adjust row estimate later on; when false: first count rows, then start row copy")
Expand Down Expand Up @@ -182,6 +183,10 @@ func main() {
migrationContext.Log.SetLevel(log.ERROR)
}

if err := migrationContext.SetConnectionConfig(*storageEngine); err != nil {
migrationContext.Log.Fatale(err)
}

if migrationContext.AlterStatement == "" {
log.Fatal("--alter must be provided and statement must not be empty")
}
Expand Down Expand Up @@ -247,6 +252,9 @@ func main() {
if *replicationLagQuery != "" {
migrationContext.Log.Warning("--replication-lag-query is deprecated")
}
if *storageEngine == "rocksdb" {
migrationContext.Log.Warning("RocksDB storage engine support is experimental")
}

switch *cutOver {
case "atomic", "default", "":
Expand Down
29 changes: 15 additions & 14 deletions go/mysql/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ import (
)

const (
transactionIsolation = "REPEATABLE-READ"
TLS_CONFIG_KEY = "ghost"
TLS_CONFIG_KEY = "ghost"
)

// ConnectionConfig is the minimal configuration required to connect to a MySQL server
type ConnectionConfig struct {
Key InstanceKey
User string
Password string
ImpliedKey *InstanceKey
tlsConfig *tls.Config
Timeout float64
Key InstanceKey
User string
Password string
ImpliedKey *InstanceKey
tlsConfig *tls.Config
Timeout float64
TransactionIsolation string
}

func NewConnectionConfig() *ConnectionConfig {
Expand All @@ -43,11 +43,12 @@ func NewConnectionConfig() *ConnectionConfig {
// DuplicateCredentials creates a new connection config with given key and with same credentials as this config
func (this *ConnectionConfig) DuplicateCredentials(key InstanceKey) *ConnectionConfig {
config := &ConnectionConfig{
Key: key,
User: this.User,
Password: this.Password,
tlsConfig: this.tlsConfig,
Timeout: this.Timeout,
Key: key,
User: this.User,
Password: this.Password,
tlsConfig: this.tlsConfig,
Timeout: this.Timeout,
TransactionIsolation: this.TransactionIsolation,
}
config.ImpliedKey = &config.Key
return config
Expand Down Expand Up @@ -126,7 +127,7 @@ func (this *ConnectionConfig) GetDBUri(databaseName string) string {
"charset=utf8mb4,utf8,latin1",
"interpolateParams=true",
fmt.Sprintf("tls=%s", tlsOption),
fmt.Sprintf("transaction_isolation=%q", transactionIsolation),
fmt.Sprintf("transaction_isolation=%q", this.TransactionIsolation),
fmt.Sprintf("timeout=%fs", this.Timeout),
fmt.Sprintf("readTimeout=%fs", this.Timeout),
fmt.Sprintf("writeTimeout=%fs", this.Timeout),
Expand Down
11 changes: 11 additions & 0 deletions go/mysql/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import (
test "github.com/openark/golib/tests"
)

const (
transactionIsolation = "REPEATABLE-READ"
)

func init() {
log.SetLevel(log.ERROR)
}
Expand All @@ -25,6 +29,7 @@ func TestNewConnectionConfig(t *testing.T) {
test.S(t).ExpectEquals(c.ImpliedKey.Port, 0)
test.S(t).ExpectEquals(c.User, "")
test.S(t).ExpectEquals(c.Password, "")
test.S(t).ExpectEquals(c.TransactionIsolation, "")
}

func TestDuplicateCredentials(t *testing.T) {
Expand All @@ -36,6 +41,7 @@ func TestDuplicateCredentials(t *testing.T) {
InsecureSkipVerify: true,
ServerName: "feathers",
}
c.TransactionIsolation = transactionIsolation

dup := c.DuplicateCredentials(InstanceKey{Hostname: "otherhost", Port: 3310})
test.S(t).ExpectEquals(dup.Key.Hostname, "otherhost")
Expand All @@ -45,13 +51,15 @@ func TestDuplicateCredentials(t *testing.T) {
test.S(t).ExpectEquals(dup.User, "gromit")
test.S(t).ExpectEquals(dup.Password, "penguin")
test.S(t).ExpectEquals(dup.tlsConfig, c.tlsConfig)
test.S(t).ExpectEquals(dup.TransactionIsolation, c.TransactionIsolation)
}

func TestDuplicate(t *testing.T) {
c := NewConnectionConfig()
c.Key = InstanceKey{Hostname: "myhost", Port: 3306}
c.User = "gromit"
c.Password = "penguin"
c.TransactionIsolation = transactionIsolation

dup := c.Duplicate()
test.S(t).ExpectEquals(dup.Key.Hostname, "myhost")
Expand All @@ -60,6 +68,7 @@ func TestDuplicate(t *testing.T) {
test.S(t).ExpectEquals(dup.ImpliedKey.Port, 3306)
test.S(t).ExpectEquals(dup.User, "gromit")
test.S(t).ExpectEquals(dup.Password, "penguin")
test.S(t).ExpectEquals(dup.TransactionIsolation, transactionIsolation)
}

func TestGetDBUri(t *testing.T) {
Expand All @@ -68,6 +77,7 @@ func TestGetDBUri(t *testing.T) {
c.User = "gromit"
c.Password = "penguin"
c.Timeout = 1.2345
c.TransactionIsolation = transactionIsolation

uri := c.GetDBUri("test")
test.S(t).ExpectEquals(uri, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&charset=utf8mb4,utf8,latin1&interpolateParams=true&tls=false&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`)
Expand All @@ -80,6 +90,7 @@ func TestGetDBUriWithTLSSetup(t *testing.T) {
c.Password = "penguin"
c.Timeout = 1.2345
c.tlsConfig = &tls.Config{}
c.TransactionIsolation = transactionIsolation

uri := c.GetDBUri("test")
test.S(t).ExpectEquals(uri, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&charset=utf8mb4,utf8,latin1&interpolateParams=true&tls=ghost&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`)
Expand Down
1 change: 1 addition & 0 deletions localtests/discard-fk/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/fail-fk-parent/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/fail-fk/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/generated-columns-add/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/generated-columns-rename/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/generated-columns-unique/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/generated-columns/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/geometry/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
1 change: 1 addition & 0 deletions localtests/spatial/ignore_versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Percona
15 changes: 11 additions & 4 deletions localtests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tests_path=$(dirname $0)
test_logfile=/tmp/gh-ost-test.log
default_ghost_binary=/tmp/gh-ost-test
ghost_binary=""
storage_engine=innodb
exec_command_file=/tmp/gh-ost-test.bash
ghost_structure_output_file=/tmp/gh-ost-test.ghost.structure.sql
orig_content_output_file=/tmp/gh-ost-test.orig.content.csv
Expand All @@ -24,12 +25,13 @@ replica_port=
original_sql_mode=

OPTIND=1
while getopts "b:" OPTION
while getopts "b:s:" OPTION
do
case $OPTION in
b)
ghost_binary="$OPTARG"
;;
ghost_binary="$OPTARG";;
s)
storage_engine="$OPTARG";;
esac
done
shift $((OPTIND-1))
Expand Down Expand Up @@ -99,9 +101,13 @@ test_single() {
if [ -f $tests_path/$test_name/ignore_versions ] ; then
ignore_versions=$(cat $tests_path/$test_name/ignore_versions)
mysql_version=$(gh-ost-test-mysql-master -s -s -e "select @@version")
mysql_version_comment=$(gh-ost-test-mysql-master -s -s -e "select @@version_comment")
if echo "$mysql_version" | egrep -q "^${ignore_versions}" ; then
echo -n "Skipping: $test_name"
return 0
elif echo "$mysql_version_comment" | egrep -i -q "^${ignore_versions}" ; then
echo -n "Skipping: $test_name"
return 0
fi
fi

Expand Down Expand Up @@ -154,7 +160,8 @@ test_single() {
--assume-master-host=${master_host}:${master_port}
--database=test \
--table=gh_ost_test \
--alter='engine=innodb' \
--storage-engine=${storage_engine} \
--alter='engine=${storage_engine}' \
--exact-rowcount \
--assume-rbr \
--initially-drop-old-table \
Expand Down
30 changes: 25 additions & 5 deletions script/cibuild-gh-ost-replica-tests
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ test_mysql_version() {

mkdir -p sandbox/binary
rm -rf sandbox/binary/*
gh-ost-ci-env/bin/linux/dbdeployer unpack gh-ost-ci-env/mysql-tarballs/"$mysql_version".tar.xz --sandbox-binary ${PWD}/sandbox/binary

local mysql_server=${mysql_version%-*}
if echo "$mysql_server" | egrep -i "percona" ; then
tarball_name=Percona-Server-${mysql_version#*-}-12-Linux.x86_64.glibc2.12-minimal.tar.gz
rm -f gh-ost-ci-env/mysql-tarballs/${tarball_name}
ln -s "$mysql_version".tar.xz gh-ost-ci-env/mysql-tarballs/${tarball_name}
gh-ost-ci-env/bin/linux/dbdeployer unpack gh-ost-ci-env/mysql-tarballs/${tarball_name} --sandbox-binary ${PWD}/sandbox/binary
rm -f gh-ost-ci-env/mysql-tarballs/${tarball_name}
else
gh-ost-ci-env/bin/linux/dbdeployer unpack gh-ost-ci-env/mysql-tarballs/"$mysql_version".tar.xz --sandbox-binary ${PWD}/sandbox/binary
fi
mkdir -p sandboxes
rm -rf sandboxes/*

Expand All @@ -60,9 +68,21 @@ test_mysql_version() {
gh-ost-test-mysql-master -uroot -e "create user 'gh-ost'@'%' identified by 'gh-ost'"
gh-ost-test-mysql-master -uroot -e "grant all on *.* to 'gh-ost'@'%'"

echo "### Running gh-ost tests for $mysql_version"
./localtests/test.sh -b bin/gh-ost

if echo "$mysql_server" | egrep -i "percona" ; then
echo "### Preparing for rocksdb in PerconaServer"
gh-ost-test-mysql-master -uroot -e 'INSTALL PLUGIN ROCKSDB SONAME "ha_rocksdb.so"'
gh-ost-test-mysql-master -uroot -e 'set global default_storage_engine="ROCKSDB"'
gh-ost-test-mysql-master -uroot -e 'set global transaction_isolation="READ-COMMITTED"'
gh-ost-test-mysql-replica -uroot -e 'INSTALL PLUGIN ROCKSDB SONAME "ha_rocksdb.so"'
gh-ost-test-mysql-replica -uroot -e 'set global default_storage_engine="ROCKSDB"'
gh-ost-test-mysql-replica -uroot -e 'set global transaction_isolation="READ-COMMITTED"'

echo "### Running gh-ost tests for $mysql_version"
./localtests/test.sh -b bin/gh-ost -s rocksdb
else
echo "### Running gh-ost tests for $mysql_version"
./localtests/test.sh -b bin/gh-ost -s innodb
fi
find sandboxes -name "stop_all" | bash
}

Expand Down