-
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
fix tablegc flaky test #10230
fix tablegc flaky test #10230
Changes from 9 commits
539c265
d58999f
0e4cf35
2b91f95
948f2cd
f5f68a7
fde461d
136a8cf
ad7c758
cc05440
09feae2
26a96f8
ea62fde
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 |
---|---|---|
|
@@ -16,6 +16,7 @@ limitations under the License. | |
package tablegc | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"os" | ||
"testing" | ||
|
@@ -90,8 +91,8 @@ func TestMain(m *testing.M) { | |
"--enable_replication_reporter", | ||
"--heartbeat_enable", | ||
"--heartbeat_interval", "250ms", | ||
"--gc_check_interval", "5s", | ||
"--gc_purge_check_interval", "5s", | ||
"--gc_check_interval", "2s", | ||
"--gc_purge_check_interval", "2s", | ||
"--table_gc_lifecycle", "hold,purge,evac,drop", | ||
} | ||
// We do not need semiSync for this test case. | ||
|
@@ -160,12 +161,73 @@ func tableExists(tableExpr string) (exists bool, tableName string, err error) { | |
return true, row.AsString("Name", ""), nil | ||
} | ||
|
||
// tableExists sees that a given table exists in MySQL | ||
func dropTable(tableName string) (err error) { | ||
func validateTableDoesNotExist(t *testing.T, tableExpr string) { | ||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
defer cancel() | ||
|
||
ticker := time.NewTicker(time.Second) | ||
var foundTableName string | ||
var exists bool | ||
var err error | ||
for { | ||
select { | ||
case <-ticker.C: | ||
exists, foundTableName, err = tableExists(tableExpr) | ||
require.NoError(t, err) | ||
if !exists { | ||
return | ||
} | ||
case <-ctx.Done(): | ||
assert.NoError(t, ctx.Err(), "validateTableDoesNotExist timed out, table %v still exists (%v)", tableExpr, foundTableName) | ||
return | ||
} | ||
} | ||
} | ||
|
||
func validateAnyState(t *testing.T, expectNumRows int64, states ...schema.TableGCState) { | ||
for _, state := range states { | ||
expectTableToExist := true | ||
searchExpr := "" | ||
switch state { | ||
case schema.HoldTableGCState: | ||
searchExpr = `\_vt\_HOLD\_%` | ||
case schema.PurgeTableGCState: | ||
searchExpr = `\_vt\_PURGE\_%` | ||
case schema.EvacTableGCState: | ||
searchExpr = `\_vt\_EVAC\_%` | ||
case schema.DropTableGCState: | ||
searchExpr = `\_vt\_DROP\_%` | ||
case schema.TableDroppedGCState: | ||
searchExpr = `\_vt\_%` | ||
expectTableToExist = false | ||
default: | ||
t.Log("Unknown state") | ||
t.Fail() | ||
} | ||
exists, tableName, err := tableExists(searchExpr) | ||
require.NoError(t, err) | ||
|
||
if exists { | ||
if expectNumRows >= 0 { | ||
checkTableRows(t, tableName, expectNumRows) | ||
} | ||
// Now that the table is validated, we can drop it | ||
dropTable(t, tableName) | ||
} | ||
if exists == expectTableToExist { | ||
// condition met | ||
return | ||
} | ||
} | ||
assert.Fail(t, "could not match any of the states: %v", states) | ||
} | ||
|
||
// dropTable drops a table | ||
func dropTable(t *testing.T, tableName string) { | ||
query := `drop table if exists %a` | ||
parsed := sqlparser.BuildParsedQuery(query, tableName) | ||
_, err = primaryTablet.VttabletProcess.QueryTablet(parsed.Query, keyspaceName, true) | ||
return err | ||
_, err := primaryTablet.VttabletProcess.QueryTablet(parsed.Query, keyspaceName, true) | ||
require.NoError(t, err) | ||
} | ||
|
||
func TestPopulateTable(t *testing.T) { | ||
|
@@ -175,11 +237,7 @@ func TestPopulateTable(t *testing.T) { | |
assert.NoError(t, err) | ||
assert.True(t, exists) | ||
} | ||
{ | ||
exists, _, err := tableExists("no_such_table") | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
validateTableDoesNotExist(t, "no_such_table") | ||
} | ||
|
||
func TestHold(t *testing.T) { | ||
|
@@ -190,11 +248,7 @@ func TestHold(t *testing.T) { | |
_, err = primaryTablet.VttabletProcess.QueryTablet(query, keyspaceName, true) | ||
assert.NoError(t, err) | ||
|
||
{ | ||
exists, _, err := tableExists("t1") | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
validateTableDoesNotExist(t, "t1") | ||
{ | ||
exists, _, err := tableExists(tableName) | ||
assert.NoError(t, err) | ||
|
@@ -214,18 +268,9 @@ func TestHold(t *testing.T) { | |
time.Sleep(10 * time.Second) | ||
{ | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
exists, _, err := tableExists(tableName) | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
{ | ||
// Table should be renamed as _vt_PURGE_... | ||
exists, purgeTableName, err := tableExists(`\_vt\_PURGE\_%`) | ||
assert.NoError(t, err) | ||
assert.True(t, exists) | ||
err = dropTable(purgeTableName) | ||
assert.NoError(t, err) | ||
validateTableDoesNotExist(t, tableName) | ||
} | ||
validateAnyState(t, -1, schema.PurgeTableGCState, schema.EvacTableGCState, schema.DropTableGCState, schema.TableDroppedGCState) | ||
} | ||
|
||
func TestEvac(t *testing.T) { | ||
|
@@ -236,11 +281,7 @@ func TestEvac(t *testing.T) { | |
_, err = primaryTablet.VttabletProcess.QueryTablet(query, keyspaceName, true) | ||
assert.NoError(t, err) | ||
|
||
{ | ||
exists, _, err := tableExists("t1") | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
validateTableDoesNotExist(t, "t1") | ||
{ | ||
exists, _, err := tableExists(tableName) | ||
assert.NoError(t, err) | ||
|
@@ -258,19 +299,11 @@ func TestEvac(t *testing.T) { | |
} | ||
|
||
time.Sleep(10 * time.Second) | ||
{ | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
exists, _, err := tableExists(tableName) | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
validateTableDoesNotExist(t, tableName) | ||
time.Sleep(5 * time.Second) | ||
{ | ||
// Table should be renamed as _vt_DROP_... and then dropped! | ||
exists, _, err := tableExists(`\_vt\_DROP\_%`) | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
// Table should be renamed as _vt_DROP_... and then dropped! | ||
validateAnyState(t, 0, schema.DropTableGCState, schema.TableDroppedGCState) | ||
} | ||
|
||
func TestDrop(t *testing.T) { | ||
|
@@ -281,19 +314,11 @@ func TestDrop(t *testing.T) { | |
_, err = primaryTablet.VttabletProcess.QueryTablet(query, keyspaceName, true) | ||
assert.NoError(t, err) | ||
|
||
{ | ||
exists, _, err := tableExists("t1") | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
validateTableDoesNotExist(t, "t1") | ||
|
||
time.Sleep(20 * time.Second) // 10s for timestamp to pass, then 10s for checkTables and drop of table | ||
{ | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
exists, _, err := tableExists(tableName) | ||
assert.NoError(t, err) | ||
assert.False(t, exists) | ||
} | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
validateTableDoesNotExist(t, tableName) | ||
} | ||
|
||
func TestPurge(t *testing.T) { | ||
|
@@ -326,21 +351,7 @@ func TestPurge(t *testing.T) { | |
} | ||
|
||
time.Sleep(15 * time.Second) // purgeReentranceInterval | ||
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. Similar nit here. We could have a variable called 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. done |
||
{ | ||
// We're now both beyond table's timestamp as well as a tableGC interval | ||
exists, _, err := tableExists(tableName) | ||
require.NoError(t, err) | ||
require.False(t, exists) | ||
} | ||
{ | ||
// Table should be renamed as _vt_EVAC_... | ||
exists, evacTableName, err := tableExists(`\_vt\_EVAC\_%`) | ||
require.NoError(t, err) | ||
require.True(t, exists) | ||
checkTableRows(t, evacTableName, 0) | ||
err = dropTable(evacTableName) | ||
require.NoError(t, err) | ||
} | ||
validateAnyState(t, 0, schema.EvacTableGCState, schema.DropTableGCState, schema.TableDroppedGCState) | ||
} | ||
|
||
func TestPurgeView(t *testing.T) { | ||
|
@@ -392,26 +403,5 @@ func TestPurgeView(t *testing.T) { | |
require.NoError(t, err) | ||
require.True(t, exists) | ||
} | ||
{ | ||
// View should be renamed as _vt_EVAC_ or _vt_DROP: views only spend a fraction of a second in "EVAC" | ||
// because evacuation is irrelevant to views. They are immediately renamed to DROP. | ||
// Because there might be a race condition, we allow both cases | ||
evacTableExists, evacTableName, err := tableExists(`\_vt\_EVAC\_%`) | ||
require.NoError(t, err) | ||
|
||
dropTableExists, dropTableName, err := tableExists(`\_vt\_DROP\_%`) | ||
require.NoError(t, err) | ||
|
||
require.True(t, evacTableExists || dropTableExists) | ||
switch { | ||
case evacTableExists: | ||
checkTableRows(t, evacTableName, 1024) // the renamed view still points to t1's data | ||
err = dropTable(evacTableName) | ||
require.NoError(t, err) | ||
case dropTableExists: | ||
checkTableRows(t, dropTableName, 1024) // the renamed view still points to t1's data | ||
err = dropTable(dropTableName) | ||
require.NoError(t, err) | ||
} | ||
} | ||
validateAnyState(t, 1024, schema.EvacTableGCState, schema.DropTableGCState, schema.TableDroppedGCState) | ||
} |
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.
Nit, but can we make this a variable ~
createTableTimeStampOffsetSecs
and then use that everywhere we are doingtime.Sleep
in relation to that variable? We can then use this throughout:This way it's easier to tie all the timing behavior together and adjust it holistically as needed by increasing that one variable.
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.
All time values now parameterized