Skip to content

Commit 75a346b

Browse files
committed
Add tests, incorporate feedback
1 parent 05f32eb commit 75a346b

File tree

7 files changed

+70
-18
lines changed

7 files changed

+70
-18
lines changed

doc/command-line-flags.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ If you happen to _know_ your servers use RBR (Row Based Replication, i.e. `binlo
4545
Skipping this step means `gh-ost` would not need the `SUPER` privilege in order to operate.
4646
You may want to use this on Amazon RDS.
4747

48+
### attempt-instant-ddl
49+
50+
MySQL 8.0 support "instant DDL" for some operations. If an alter statement can be completed with instant DDL, only a metadata change is required internally, so MySQL will return _instantly_ (only requiring a metadata lock to complete). Instant operations include:
51+
52+
- Adding a column
53+
- Dropping a column
54+
- Dropping an index
55+
- Extending a varchar column
56+
- Adding a virtual generated column
57+
58+
It is not reliable to parse the `ALTER` statement to determine if it is instant or not. This is because the table might be in an older row format, or have some other incompatibility that is difficult to identify.
59+
60+
The risks of attempting to instant DDL are relatively minor: Gh-ost may need to acquire a metadata lock at the start of the operation. This is not a problem for most scenarios, but it could be a problem for users that start the DDL during a period with long running transactions.
61+
62+
gh-ost will automatically fallback to the normal DDL process if the attempt to use instant DDL is unsuccessful.
63+
4864
### conf
4965

5066
`--conf=/path/to/my.cnf`: file where credentials are specified. Should be in (or contain) the following format:

go/cmd/gh-ost/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func main() {
6767
flag.StringVar(&migrationContext.DatabaseName, "database", "", "database name (mandatory)")
6868
flag.StringVar(&migrationContext.OriginalTableName, "table", "", "table name (mandatory)")
6969
flag.StringVar(&migrationContext.AlterStatement, "alter", "", "alter statement (mandatory)")
70-
flag.BoolVar(&migrationContext.AttemptInstantDDL, "attempt-instant-ddl", true, "Attempt to use instant DDL for this migration first.")
70+
flag.BoolVar(&migrationContext.AttemptInstantDDL, "attempt-instant-ddl", false, "Attempt to use instant DDL for this migration first")
7171

7272
flag.BoolVar(&migrationContext.CountTableRows, "exact-rowcount", false, "actually count table rows as opposed to estimate them (results in more accurate progress estimation)")
7373
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")

go/logic/applier.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ func (this *Applier) generateSqlModeQuery() string {
135135
return fmt.Sprintf("sql_mode = %s", sqlModeQuery)
136136
}
137137

138+
// generateInstantDDLQuery returns the SQL for this ALTER operation
139+
// with an INSTANT assertion (requires MySQL 8.0+)
140+
func (this *Applier) generateInstantDDLQuery() string {
141+
return fmt.Sprintf(`ALTER /* gh-ost */ TABLE %s.%s %s, ALGORITHM=INSTANT`,
142+
sql.EscapeName(this.migrationContext.DatabaseName),
143+
sql.EscapeName(this.migrationContext.OriginalTableName),
144+
this.migrationContext.AlterStatementOptions,
145+
)
146+
}
147+
138148
// readTableColumns reads table columns on applier
139149
func (this *Applier) readTableColumns() (err error) {
140150
this.migrationContext.Log.Infof("Examining table structure on applier")
@@ -188,22 +198,21 @@ func (this *Applier) ValidateOrDropExistingTables() error {
188198
return nil
189199
}
190200

191-
// AttemptInstantDDL attempts to use instant DDL (from MySQL 8.0, and earlier in Aurora and some others.)
192-
// to apply the ALTER statement immediately. If it errors, the original
193-
// gh-ost algorithm can be used. However, if it's successful -- a lot
194-
// of time can potentially be saved. Instant operations include:
201+
// AttemptInstantDDL attempts to use instant DDL (from MySQL 8.0, and earlier in Aurora and some others).
202+
// If successful, the operation is only a meta-data change so a lot of time is saved!
203+
// The risk of attempting to instant DDL when not supported is that a metadata lock may be acquired.
204+
// This is minor, since gh-ost will eventually require a metadata lock anyway, but at the cut-over stage.
205+
// Instant operations include:
195206
// - Adding a column
196207
// - Dropping a column
197208
// - Dropping an index
198-
// - Extending a varchar column
199-
// It is safer to attempt the change than try and parse the DDL, since
200-
// there might be specifics about the table which make it not possible to apply instantly.
209+
// - Extending a VARCHAR column
210+
// - Adding a virtual generated column
211+
// It is not reliable to parse the `alter` statement to determine if it is instant or not.
212+
// This is because the table might be in an older row format, or have some other incompatibility
213+
// that is difficult to identify.
201214
func (this *Applier) AttemptInstantDDL() error {
202-
query := fmt.Sprintf(`ALTER /* gh-ost */ TABLE %s.%s %s, ALGORITHM=INSTANT`,
203-
sql.EscapeName(this.migrationContext.DatabaseName),
204-
sql.EscapeName(this.migrationContext.OriginalTableName),
205-
this.migrationContext.AlterStatementOptions,
206-
)
215+
query := this.generateInstantDDLQuery()
207216
this.migrationContext.Log.Infof("INSTANT DDL query is: %s", query)
208217
// We don't need a trx, because for instant DDL the SQL mode doesn't matter.
209218
_, err := this.db.Exec(query)

go/logic/applier_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,17 @@ func TestApplierBuildDMLEventQuery(t *testing.T) {
170170
test.S(t).ExpectEquals(res[0].args[3], 42)
171171
})
172172
}
173+
174+
func TestApplierInstantDDL(t *testing.T) {
175+
migrationContext := base.NewMigrationContext()
176+
migrationContext.DatabaseName = "test"
177+
migrationContext.OriginalTableName = "mytable"
178+
migrationContext.AlterStatementOptions = "ADD INDEX (foo)"
179+
applier := NewApplier(migrationContext)
180+
181+
t.Run("instantDDLstmt", func(t *testing.T) {
182+
stmt := applier.generateInstantDDLQuery()
183+
test.S(t).ExpectEquals(stmt, "ALTER /* gh-ost */ TABLE `test`.`mytable` ADD INDEX (foo), ALGORITHM=INSTANT")
184+
})
185+
186+
}

go/logic/migrator.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,15 +352,14 @@ func (this *Migrator) Migrate() (err error) {
352352
return err
353353
}
354354
// In MySQL 8.0 (and possibly earlier) some DDL statements can be applied instantly.
355-
// As just a metadata change. We can't detect this unless we attempt the statement
356-
// (i.e. there is no explain for DDL).
355+
// Attempt to do this if AttemptInstantDDL is set.
357356
if this.migrationContext.AttemptInstantDDL {
358-
this.migrationContext.Log.Infof("Attempting to execute ALTER TABLE as INSTANT DDL")
357+
this.migrationContext.Log.Infof("Attempting to execute alter with ALGORITHM=INSTANT")
359358
if err := this.attemptInstantDDL(); err == nil {
360-
this.migrationContext.Log.Infof("Success! Table %s.%s migrated instantly", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
359+
this.migrationContext.Log.Infof("Success! table %s.%s migrated instantly", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
361360
return nil
362361
} else {
363-
this.migrationContext.Log.Infof("INSTANT DDL failed, will proceed with original algorithm: %s", err)
362+
this.migrationContext.Log.Infof("ALGORITHM=INSTANT failed, proceeding with original algorithm: %s", err)
364363
}
365364
}
366365

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
drop table if exists gh_ost_test;
2+
create table gh_ost_test (
3+
id int auto_increment,
4+
i int not null,
5+
color varchar(32),
6+
primary key(id)
7+
) auto_increment=1;
8+
9+
drop event if exists gh_ost_test;
10+
11+
insert into gh_ost_test values (null, 11, 'red');
12+
insert into gh_ost_test values (null, 13, 'green');
13+
insert into gh_ost_test values (null, 17, 'blue');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--attempt-instant-ddl

0 commit comments

Comments
 (0)