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

dumpling: improve error hint when fail to set snapshot in the incorrect consistency #40977

Merged
merged 14 commits into from
Feb 9, 2023
8 changes: 6 additions & 2 deletions dumpling/export/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,10 @@ func setSessionParam(d *Dumper) error {
if si.ServerType == version.ServerTypeTiDB && conf.TiDBMemQuotaQuery != UnspecifiedSize {
sessionParam[TiDBMemQuotaQueryName] = conf.TiDBMemQuotaQuery
}
var err error
var (
err error
ignorableSnapshot = false
)
if snapshot != "" {
if si.ServerType != version.ServerTypeTiDB {
return errors.New("snapshot consistency is not supported for this server")
Expand All @@ -1573,13 +1576,14 @@ func setSessionParam(d *Dumper) error {
conf.ServerInfo.HasTiKV, err = CheckTiDBWithTiKV(pool)
if err != nil {
d.L().Info("cannot check whether TiDB has TiKV, will apply tidb_snapshot by default. This won't affect dump process", log.ShortError(err))
ignorableSnapshot = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can't distinguish the snapshot is set by user or dumpling?

Copy link
Contributor Author

@lichunzhu lichunzhu Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No we can't. But we can do this by check conf.Snapshot before adjusting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for user specified snapshot, we can report the error that this TiDB can't set snapshot of that time. For dumpling's automatic snapshot we can fallback to no snapshot and also change the rebuild connection strategy to non-retryable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it a second time. I think changing retry strategy is too complex. If we fail to set snapshot for ConsistencySnapshot, we can continue to ConsistencyNone or ConsistencyLock and notify the users in log. Or, we can directly return an error and give a proper prompt to users to notify them to manually switch consistency to none/lock instead of auto/snapshot. Changing retry strategy may confuse our users and is more difficult to maintain to me. WDYT? @lance6716

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes a simple fix like automatically fallback to consistency none, or output a message is ok. Let's ask another reviewer @gozssky

}
if conf.ServerInfo.HasTiKV {
sessionParam["tidb_snapshot"] = snapshot
}
}
}
if d.dbHandle, err = resetDBWithSessionParams(d.tctx, pool, conf.GetDriverConfig(""), conf.SessionParams); err != nil {
if d.dbHandle, err = resetDBWithSessionParams(d.tctx, pool, conf.GetDriverConfig(""), conf.SessionParams, ignorableSnapshot); err != nil {
return errors.Trace(err)
}
return nil
Expand Down
57 changes: 57 additions & 0 deletions dumpling/export/dump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (

"github.com/DATA-DOG/go-sqlmock"
"github.com/coreos/go-semver/semver"
"github.com/go-sql-driver/mysql"
"github.com/pingcap/errors"
"github.com/pingcap/failpoint"
"github.com/pingcap/tidb/br/pkg/version"
tcontext "github.com/pingcap/tidb/dumpling/context"
"github.com/pingcap/tidb/parser"
Expand Down Expand Up @@ -289,3 +291,58 @@ func TestSetDefaultSessionParams(t *testing.T) {
require.Equal(t, testCase.expectedParams, testCase.sessionParams)
}
}

func TestSetSessionParams(t *testing.T) {
// case 1: fail to set tidb_snapshot when fail to checkTiDBHasKV, shouldn't return an error
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer func() {
require.NoError(t, db.Close())
}()

mock.ExpectQuery("SELECT @@tidb_config").
WillReturnError(errors.New("mock error"))
mock.ExpectQuery("SELECT COUNT\\(1\\) as c FROM MYSQL.TiDB WHERE VARIABLE_NAME='tikv_gc_safe_point'").
WillReturnError(errors.New("mock error"))
tikvErr := &mysql.MySQLError{
Number: 1105,
Message: "can not get 'tikv_gc_safe_point'",
}
mock.ExpectExec("SET SESSION tidb_snapshot").
WillReturnError(tikvErr)

require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/dumpling/export/SkipResetDB", "return(true)"))
defer failpoint.Disable("github.com/pingcap/tidb/dumpling/export/SkipResetDB=return(true)")

tctx, cancel := tcontext.Background().WithLogger(appLogger).WithCancel()
defer cancel()

conf := DefaultConfig()
conf.ServerInfo = version.ServerInfo{
ServerType: version.ServerTypeTiDB,
HasTiKV: false,
}
conf.Snapshot = "439153276059648000"
conf.Consistency = ConsistencyTypeSnapshot
d := &Dumper{
tctx: tctx,
conf: conf,
cancelCtx: cancel,
dbHandle: db,
}
err = setSessionParam(d)
require.NoError(t, err)

// case 2: fail to set tidb_snapshot when succeed to checkTiDBHasKV, should return an error
mock.ExpectQuery("SELECT @@tidb_config").
WillReturnError(errors.New("mock error"))
mock.ExpectQuery("SELECT COUNT\\(1\\) as c FROM MYSQL.TiDB WHERE VARIABLE_NAME='tikv_gc_safe_point'").
WillReturnRows(sqlmock.NewRows([]string{"COUNT(1)"}).AddRow(1))
mock.ExpectExec("SET SESSION tidb_snapshot").
WillReturnError(tikvErr)
mock.ExpectClose()
mock.ExpectClose()

err = setSessionParam(d)
require.True(t, errors.Cause(err) == tikvErr)
}
14 changes: 10 additions & 4 deletions dumpling/export/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -827,13 +827,16 @@ func getSnapshot(db *sql.Conn) (string, error) {
return str[snapshotFieldIndex], nil
}

func isUnknownSystemVariableErr(err error) bool {
return strings.Contains(err.Error(), "Unknown system variable")
func isUnknownSystemVariableErr(err error, ignorableSnapshot bool) bool {
errStr := err.Error()
return strings.Contains(err.Error(), "Unknown system variable") ||
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possible solution is to ignore all set session tidb_snapshot error when ignorableSnapshot is true. Because I'm not so sure whether tidb's error will change in the future or not.

(ignorableSnapshot && strings.Contains(errStr, "can not get 'tikv_gc_safe_point'"))
}

// resetDBWithSessionParams will return a new sql.DB as a replacement for input `db` with new session parameters.
// If returned error is nil, the input `db` will be closed.
func resetDBWithSessionParams(tctx *tcontext.Context, db *sql.DB, cfg *mysql.Config, params map[string]interface{}) (*sql.DB, error) {
func resetDBWithSessionParams(tctx *tcontext.Context, db *sql.DB, cfg *mysql.Config,
params map[string]interface{}, ignorableSnapshot bool) (*sql.DB, error) {
support := make(map[string]interface{})
for k, v := range params {
var pv interface{}
Expand All @@ -851,7 +854,7 @@ func resetDBWithSessionParams(tctx *tcontext.Context, db *sql.DB, cfg *mysql.Con
s := fmt.Sprintf("SET SESSION %s = ?", k)
_, err := db.ExecContext(tctx, s, pv)
if err != nil {
if isUnknownSystemVariableErr(err) {
if isUnknownSystemVariableErr(err, ignorableSnapshot) {
tctx.L().Info("session variable is not supported by db", zap.String("variable", k), zap.Reflect("value", v))
continue
}
Expand All @@ -876,6 +879,9 @@ func resetDBWithSessionParams(tctx *tcontext.Context, db *sql.DB, cfg *mysql.Con
}
cfg.Params[k] = s
}
failpoint.Inject("SkipResetDB", func(_ failpoint.Value) {
failpoint.Return(db, nil)
})

db.Close()
c, err := mysql.NewConnector(cfg)
Expand Down