Skip to content
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

ddl: add restore deleted table #7937

Merged
merged 64 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
e404a01
ddl: add admin restore support
crazycs520 Dec 15, 2018
6991fa1
check gc enable status
crazycs520 Dec 15, 2018
72b921d
check gc safe point
crazycs520 Dec 15, 2018
b0b1107
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Dec 18, 2018
fd70afa
change go.mod temporarily
crazycs520 Dec 18, 2018
c7aaa8a
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Dec 18, 2018
a2169ac
refine err msg
crazycs520 Dec 18, 2018
883cf94
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Dec 24, 2018
b659617
fix txn
crazycs520 Dec 24, 2018
83477fb
fix go.mod
crazycs520 Dec 24, 2018
ee5f7e0
update go.mod parser
crazycs520 Dec 24, 2018
7669086
fix lint
crazycs520 Dec 24, 2018
8278a9d
fix make tidy
crazycs520 Dec 24, 2018
a0945d6
update go.mod parser
crazycs520 Jan 4, 2019
9c6f91d
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 4, 2019
f829fe3
update go.mod parser
crazycs520 Jan 4, 2019
6f7deee
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 4, 2019
7e943f6
fix error check
crazycs520 Jan 4, 2019
1182242
refine code
crazycs520 Jan 4, 2019
d36a7cb
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 7, 2019
864c006
address comment
crazycs520 Jan 7, 2019
ff4c470
add test
crazycs520 Jan 7, 2019
01cb0d8
address comment
crazycs520 Jan 7, 2019
1ba3afb
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 10, 2019
c7ed2dc
address comment
crazycs520 Jan 10, 2019
31082b0
add gofail test
crazycs520 Jan 10, 2019
3f34fb6
update comment
crazycs520 Jan 10, 2019
9af52a7
refine gofail test
crazycs520 Jan 10, 2019
03f0b7a
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 10, 2019
af8ebcb
fix data race in gofail test
crazycs520 Jan 10, 2019
795607c
refine gofail test
crazycs520 Jan 10, 2019
180d0a4
refine check gc logic
crazycs520 Jan 11, 2019
8a25126
fix test
crazycs520 Jan 11, 2019
0723678
remove blank line
crazycs520 Jan 11, 2019
89bda46
move check gc enable to ddl owner
crazycs520 Jan 11, 2019
21d8e1a
add comment
crazycs520 Jan 11, 2019
8cfd779
address comment
crazycs520 Jan 11, 2019
68453bc
update comment
crazycs520 Jan 11, 2019
fbd1e17
clean code
crazycs520 Jan 11, 2019
29ac1a1
add syntax: restore table by job id
crazycs520 Jan 12, 2019
18d4a5f
add syntax: restore table [table_name]
crazycs520 Jan 12, 2019
0ed20c5
check schema name
crazycs520 Jan 12, 2019
538b23a
update comment
crazycs520 Jan 13, 2019
157061d
update parser and address comment
crazycs520 Jan 14, 2019
5580550
add check in preprocesst
crazycs520 Jan 14, 2019
a48119c
add comment
crazycs520 Jan 14, 2019
286c419
update parser go.mod
crazycs520 Jan 14, 2019
49e23dc
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 14, 2019
e5a453e
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 14, 2019
378aef6
address comment
crazycs520 Jan 14, 2019
c2cd61e
address comment
crazycs520 Jan 14, 2019
15ae648
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 14, 2019
58cc85b
Merge branch 'master' into recover-table
crazycs520 Jan 14, 2019
806b7ad
add check before job enqueue
crazycs520 Jan 15, 2019
2b19d98
Merge branch 'master' of https://github.com/pingcap/tidb into recover…
crazycs520 Jan 15, 2019
dde5647
move test to serial execute
crazycs520 Jan 15, 2019
b92ab5e
fix ddl test
crazycs520 Jan 15, 2019
5bab994
Merge branch 'master' into recover-table
crazycs520 Jan 15, 2019
7f02c48
Merge branch 'master' into recover-table
crazycs520 Jan 15, 2019
b3df2bf
move check
crazycs520 Jan 15, 2019
443b684
address comment
crazycs520 Jan 15, 2019
6946c08
address comment
crazycs520 Jan 15, 2019
f72802d
address comment
crazycs520 Jan 16, 2019
5e8ea48
Merge branch 'master' into recover-table
crazycs520 Jan 16, 2019
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
109 changes: 109 additions & 0 deletions ddl/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ import (
"github.com/pingcap/tidb/meta/autoid"
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/store/mockstore"
"github.com/pingcap/tidb/store/mockstore/mocktikv"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/admin"
"github.com/pingcap/tidb/util/gcutil"
"github.com/pingcap/tidb/util/mock"
"github.com/pingcap/tidb/util/schemautil"
"github.com/pingcap/tidb/util/testkit"
Expand Down Expand Up @@ -2128,6 +2130,113 @@ LOOP:
s.mustExec(c, "drop table t1")
}

func (s *testDBSuite) TestRestoreTable(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("create database if not exists test_restore")
tk.MustExec("use test_restore")
tk.MustExec("create table t_recover (a int);")
defer func(originGC bool) {
if originGC {
ddl.EmulatorGCEnable()
} else {
ddl.EmulatorGCDisable()
}
}(ddl.IsEmulatorGCEnable())

// disable emulator GC.
// Otherwise emulator GC will delete table record as soon as possible after execute drop table ddl.
ddl.EmulatorGCDisable()
gcTimeFormat := "20060102-15:04:05 -0700 MST"
timeBeforeDrop := time.Now().Add(0 - time.Duration(48*60*60*time.Second)).Format(gcTimeFormat)
timeAfterDrop := time.Now().Add(time.Duration(48 * 60 * 60 * time.Second)).Format(gcTimeFormat)
safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', '')
ON DUPLICATE KEY
UPDATE variable_value = '%[1]s'`

tk.MustExec("insert into t_recover values (1),(2),(3)")
tk.MustExec("drop table t_recover")

rs, err := tk.Exec("admin show ddl jobs")
c.Assert(err, IsNil)
rows, err := session.GetRows4Test(context.Background(), tk.Se, rs)
c.Assert(err, IsNil)
row := rows[0]
c.Assert(row.GetString(1), Equals, "test_restore")
c.Assert(row.GetString(3), Equals, "drop table")
jobID := row.GetInt64(0)

// if gc safe point is not exists in mysql.tidb
_, err = tk.Exec(fmt.Sprintf("admin restore table by job %d", jobID))
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "can not get 'tikv_gc_safe_point'")
// set gc safe point
tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop))

// if gc enable is not exists in mysql.tidb
_, err = tk.Exec(fmt.Sprintf("admin restore table by job %d", jobID))
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[ddl:-1]can not get 'tikv_gc_enable'")

err = gcutil.EnableGC(tk.Se)
c.Assert(err, IsNil)

// recover job is before gc safe point
tk.MustExec(fmt.Sprintf(safePointSQL, timeAfterDrop))
_, err = tk.Exec(fmt.Sprintf("admin restore table by job %d", jobID))
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, variable.ErrSnapshotTooOld.GenWithStackByArgs(timeAfterDrop).Error())

// set gc safe point
tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop))
// if there is a new table with the same name, should return failed.
tk.MustExec("create table t_recover (a int);")
_, err = tk.Exec(fmt.Sprintf("admin restore table by job %d", jobID))
c.Assert(err.Error(), Equals, infoschema.ErrTableExists.GenWithStackByArgs("t_recover").Error())

// drop the new table with the same name, then restore table.
tk.MustExec("drop table t_recover")

// do restore table.
tk.MustExec(fmt.Sprintf("admin restore table by job %d", jobID))

// check recover table meta and data record.
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1", "2", "3"))
// check recover table autoID.
tk.MustExec("insert into t_recover values (4),(5),(6)")
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1", "2", "3", "4", "5", "6"))

// restore table by none exits job.
_, err = tk.Exec(fmt.Sprintf("admin restore table by job %d", 10000000))
c.Assert(err, NotNil)

// Disable gc by manual first, then after recover table, the gc enable status should also be disabled.
err = gcutil.DisableGC(tk.Se)
c.Assert(err, IsNil)

tk.MustExec("delete from t_recover where a > 1")
tk.MustExec("drop table t_recover")
rs, err = tk.Exec("admin show ddl jobs")
c.Assert(err, IsNil)
rows, err = session.GetRows4Test(context.Background(), tk.Se, rs)
c.Assert(err, IsNil)
row = rows[0]
c.Assert(row.GetString(1), Equals, "test_restore")
c.Assert(row.GetString(3), Equals, "drop table")
jobID = row.GetInt64(0)

tk.MustExec(fmt.Sprintf("admin restore table by job %d", jobID))

// check recover table meta and data record.
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1"))
// check recover table autoID.
tk.MustExec("insert into t_recover values (7),(8),(9)")
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1", "7", "8", "9"))

gcEnable, err := gcutil.CheckGCEnable(tk.Se)
c.Assert(err, IsNil)
c.Assert(gcEnable, Equals, false)
}

func (s *testDBSuite) TestTransactionOnAddDropColumn(c *C) {
s.tk = testkit.NewTestKit(c, s.store)
s.mustExec(c, "use test_db")
Expand Down
1 change: 1 addition & 0 deletions ddl/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type DDL interface {
CreateView(ctx sessionctx.Context, stmt *ast.CreateViewStmt) error
CreateTableWithLike(ctx sessionctx.Context, ident, referIdent ast.Ident, ifNotExists bool) error
DropTable(ctx sessionctx.Context, tableIdent ast.Ident) (err error)
RestoreTable(ctx sessionctx.Context, tbInfo *model.TableInfo, schemaID, autoID, dropJobID int64, snapshotTS uint64) (err error)
DropView(ctx sessionctx.Context, tableIdent ast.Ident) (err error)
CreateIndex(ctx sessionctx.Context, tableIdent ast.Ident, unique bool, indexName model.CIStr,
columnNames []*ast.IndexColName, indexOption *ast.IndexOption) error
Expand Down
13 changes: 13 additions & 0 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,19 @@ func (d *ddl) CreateTable(ctx sessionctx.Context, s *ast.CreateTableStmt) (err e
return errors.Trace(err)
}

func (d *ddl) RestoreTable(ctx sessionctx.Context, tbInfo *model.TableInfo, schemaID, autoID, dropJobID int64, snapshotTS uint64) (err error) {
tbInfo.State = model.StateNone
job := &model.Job{
SchemaID: schemaID,
TableID: tbInfo.ID,
Type: model.ActionRestoreTable,
BinlogInfo: &model.HistoryInfo{},
Args: []interface{}{tbInfo, autoID, dropJobID, snapshotTS, restoreTableCheckFlagNone},
ciscoxll marked this conversation as resolved.
Show resolved Hide resolved
}
err = d.doDDLJob(ctx, job)
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
return errors.Trace(err)
}

func (d *ddl) CreateView(ctx sessionctx.Context, s *ast.CreateViewStmt) (err error) {
ident := ast.Ident{Name: s.ViewName.Name, Schema: s.ViewName.Schema}
is := d.GetInformationSchema(ctx)
Expand Down
21 changes: 21 additions & 0 deletions ddl/ddl_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ func (w *worker) finishDDLJob(t *meta.Meta, job *model.Job) (err error) {
err = w.deleteRange(job)
case model.ActionDropSchema, model.ActionDropTable, model.ActionTruncateTable, model.ActionDropIndex, model.ActionDropTablePartition, model.ActionTruncateTablePartition:
err = w.deleteRange(job)
case model.ActionRestoreTable:
err = finishRestoreTable(w, t, job)
}
if err != nil {
return errors.Trace(err)
Expand All @@ -293,6 +295,23 @@ func (w *worker) finishDDLJob(t *meta.Meta, job *model.Job) (err error) {
return errors.Trace(err)
}

func finishRestoreTable(w *worker, t *meta.Meta, job *model.Job) error {
tbInfo := &model.TableInfo{}
var autoID, dropJobID, restoreTableCheckFlag int64
var snapshotTS uint64
err := job.DecodeArgs(tbInfo, &autoID, &dropJobID, &snapshotTS, &restoreTableCheckFlag)
if err != nil {
return errors.Trace(err)
}
if restoreTableCheckFlag == restoreTableCheckFlagEnableGC {
err = enableGC(w)
if err != nil {
return errors.Trace(err)
}
}
return nil
}

func isDependencyJobDone(t *meta.Meta, job *model.Job) (bool, error) {
if job.DependencyID == noneDependencyJob {
return true, nil
Expand Down Expand Up @@ -497,6 +516,8 @@ func (w *worker) runDDLJob(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64,
ver, err = onAddTablePartition(t, job)
case model.ActionModifyTableCharsetAndCollate:
ver, err = onModifyTableCharsetAndCollate(t, job)
case model.ActionRestoreTable:
ver, err = w.onRestoreTable(d, t, job)
default:
// Invalid job, cancel it.
job.State = model.JobStateCancelled
Expand Down
40 changes: 38 additions & 2 deletions ddl/delete_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"math"
"sync"
"sync/atomic"

"github.com/pingcap/errors"
"github.com/pingcap/parser/model"
Expand All @@ -38,9 +39,16 @@ const (
delBackLog = 128
)

// enableEmulatorGC means whether to enable emulator GC. The default is enable.
// In some unit test, we want to stop emulator GC, then wen can set enableEmulatorGC to 0.
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
var emulatorGCEnable = int32(1)

type delRangeManager interface {
// addDelRangeJob add a DDL job into gc_delete_range table.
addDelRangeJob(job *model.Job) error
// removeFromGCDeleteRange remove delete table job from gc_delete_range table by jobID and tableID.
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
// It's use for recover the table that was mistakenly deleted.
removeFromGCDeleteRange(jobID, tableID int64) error
start()
clear()
}
Expand Down Expand Up @@ -90,6 +98,17 @@ func (dr *delRange) addDelRangeJob(job *model.Job) error {
return nil
}

// removeFromGCDeleteRange implements delRangeManager interface.
func (dr *delRange) removeFromGCDeleteRange(jobID, tableID int64) error {
ctx, err := dr.sessPool.get()
if err != nil {
return errors.Trace(err)
}
defer dr.sessPool.put(ctx)
err = util.RemoveFromGCDeleteRange(ctx, jobID, tableID)
return errors.Trace(err)
}

// start implements delRangeManager interface.
func (dr *delRange) start() {
if !dr.storeSupport {
Expand Down Expand Up @@ -117,11 +136,28 @@ func (dr *delRange) startEmulator() {
case <-dr.quitCh:
return
}
err := dr.doDelRangeWork()
terror.Log(errors.Trace(err))
if IsEmulatorGCEnable() {
err := dr.doDelRangeWork()
terror.Log(errors.Trace(err))
}
}
}

// EmulatorGCEnable enables emulator gc. It exports for testing.
func EmulatorGCEnable() {
atomic.StoreInt32(&emulatorGCEnable, 1)
}

// EmulatorGCDisable disables emulator gc. It exports for testing.
func EmulatorGCDisable() {
atomic.StoreInt32(&emulatorGCEnable, 0)
}

// IsEmulatorGCEnable indicates whether emulator gc enabled. It exports for testing.
func IsEmulatorGCEnable() bool {
return atomic.LoadInt32(&emulatorGCEnable) == 1
}

func (dr *delRange) doDelRangeWork() error {
ctx, err := dr.sessPool.get()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions ddl/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ func (dr *mockDelRange) addDelRangeJob(job *model.Job) error {
return nil
}

// removeFromGCDeleteRange implements delRangeManager interface.
func (dr *mockDelRange) removeFromGCDeleteRange(jobID, tableID int64) error {
return nil
}

// start implements delRangeManager interface.
func (dr *mockDelRange) start() {
return
Expand Down
70 changes: 70 additions & 0 deletions ddl/serial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package ddl_test

import (
"context"
"fmt"
"time"

. "github.com/pingcap/check"
Expand All @@ -27,6 +28,7 @@ import (
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/store/mockstore"
"github.com/pingcap/tidb/util/admin"
"github.com/pingcap/tidb/util/gcutil"
"github.com/pingcap/tidb/util/mock"
"github.com/pingcap/tidb/util/testkit"
"github.com/pingcap/tidb/util/testleak"
Expand Down Expand Up @@ -123,3 +125,71 @@ func (s *testSerialSuite) TestCancelAddIndexPanic(c *C) {
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[ddl:12]cancelled DDL job")
}

func (s *testSerialSuite) TestRestoreTableFail(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("create database if not exists test_restore")
tk.MustExec("use test_restore")
tk.MustExec("create table t_recover (a int);")
defer func(originGC bool) {
if originGC {
ddl.EmulatorGCEnable()
} else {
ddl.EmulatorGCDisable()
}
}(ddl.IsEmulatorGCEnable())

// disable emulator GC.
// Otherwise emulator GC will delete table record as soon as possible after execute drop table ddl.
ddl.EmulatorGCDisable()
gcTimeFormat := "20060102-15:04:05 -0700 MST"
timeBeforeDrop := time.Now().Add(0 - time.Duration(48*60*60*time.Second)).Format(gcTimeFormat)
safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', '')
ON DUPLICATE KEY
UPDATE variable_value = '%[1]s'`

tk.MustExec("insert into t_recover values (1),(2),(3)")
tk.MustExec("drop table t_recover")

rs, err := tk.Exec("admin show ddl jobs")
c.Assert(err, IsNil)
rows, err := session.GetRows4Test(context.Background(), tk.Se, rs)
c.Assert(err, IsNil)
row := rows[0]
c.Assert(row.GetString(1), Equals, "test_restore")
c.Assert(row.GetString(3), Equals, "drop table")
jobID := row.GetInt64(0)

// enableGC first
err = gcutil.EnableGC(tk.Se)
c.Assert(err, IsNil)
tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop))

// set hook
hook := &ddl.TestDDLCallback{}
hook.OnJobRunBeforeExported = func(job *model.Job) {
if job.Type == model.ActionRestoreTable {
gofail.Enable("github.com/pingcap/tidb/store/tikv/mockCommitError", `return(true)`)
gofail.Enable("github.com/pingcap/tidb/ddl/mockRestoreTableCommitErr", `return(true)`)
}
}
origHook := s.dom.DDL().GetHook()
defer s.dom.DDL().(ddl.DDLForTest).SetHook(origHook)
s.dom.DDL().(ddl.DDLForTest).SetHook(hook)

// do restore table.
tk.MustExec(fmt.Sprintf("admin restore table by job %d", jobID))
gofail.Disable("github.com/pingcap/tidb/store/tikv/mockCommitError")
gofail.Disable("github.com/pingcap/tidb/ddl/mockRestoreTableCommitErr")

// make sure enable gc after restore table.
enable, err := gcutil.CheckGCEnable(tk.Se)
c.Assert(err, IsNil)
c.Assert(enable, Equals, true)

// check recover table meta and data record.
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1", "2", "3"))
// check recover table autoID.
tk.MustExec("insert into t_recover values (4),(5),(6)")
tk.MustQuery("select * from t_recover;").Check(testkit.Rows("1", "2", "3", "4", "5", "6"))
}
Loading