14
14
package main
15
15
16
16
import (
17
+ "bufio"
17
18
"context"
18
19
"database/sql"
19
- "encoding/json"
20
20
"fmt"
21
- "io"
22
21
"math/rand"
23
- "net/http"
22
+ "os"
23
+ "path/filepath"
24
+ "regexp"
25
+ "strconv"
24
26
"strings"
25
27
"sync/atomic"
26
28
"time"
27
29
28
30
_ "github.com/go-sql-driver/mysql" // MySQL driver
29
31
"github.com/pingcap/errors"
30
32
"github.com/pingcap/log"
31
- cerror "github.com/pingcap/ticdc/pkg/errors"
32
- "github.com/pingcap/ticdc/pkg/retry"
33
+ cerror "github.com/pingcap/tiflow/pkg/errors"
34
+ "github.com/pingcap/tiflow/pkg/retry"
35
+ "github.com/tikv/client-go/v2/oracle"
33
36
"go.uber.org/zap"
34
37
"golang.org/x/sync/errgroup"
35
38
)
@@ -260,11 +263,15 @@ func (s *bankTest) prepare(ctx context.Context, db *sql.DB, accounts, tableID, c
260
263
}
261
264
262
265
func (* bankTest ) verify (ctx context.Context , db * sql.DB , accounts , tableID int , tag string , endTs uint64 ) error {
266
+ retryInterval := 1000
263
267
return retry .Do (ctx ,
264
268
func () (err error ) {
265
269
defer func () {
266
270
if err != nil {
267
- log .Error ("bank test verify failed" , zap .Error (err ))
271
+ physical := oracle .GetTimeFromTS (endTs )
272
+ physical = physical .Add (time .Duration (retryInterval ) * time .Millisecond )
273
+ endTs = oracle .GoTimeToTS (physical )
274
+ log .Error ("bank test verify failed" , zap .Error (err ), zap .Time ("physical" , physical ), zap .Uint64 ("nextRetrySnapshotTs" , endTs ))
268
275
}
269
276
}()
270
277
// use a single connection to keep the same database session.
@@ -281,11 +288,16 @@ func (*bankTest) verify(ctx context.Context, db *sql.DB, accounts, tableID int,
281
288
var obtained , expect int
282
289
283
290
query := fmt .Sprintf ("SELECT SUM(balance) as total FROM accounts%d" , tableID )
284
- if err := conn .QueryRowContext (ctx , query ).Scan (& obtained ); err != nil {
285
- log .Warn ("query failed" , zap .String ("query" , query ), zap .Error (err ), zap .String ("tag" , tag ))
291
+ var nullableResult sql.NullInt64
292
+ if err := conn .QueryRowContext (ctx , query ).Scan (& nullableResult ); err != nil {
293
+ log .Error ("query failed" , zap .String ("query" , query ), zap .Error (err ), zap .String ("tag" , tag ))
286
294
return errors .Trace (err )
287
295
}
288
296
297
+ if nullableResult .Valid {
298
+ obtained = int (nullableResult .Int64 )
299
+ }
300
+
289
301
expect = accounts * initBalance
290
302
if obtained != expect {
291
303
return errors .Errorf ("verify balance failed, accounts%d expect %d, but got %d" , tableID , expect , obtained )
@@ -307,7 +319,7 @@ func (*bankTest) verify(ctx context.Context, db *sql.DB, accounts, tableID int,
307
319
}
308
320
309
321
return nil
310
- }, retry .WithBackoffMaxDelay ( 500 ) , retry .WithBackoffMaxDelay (120 * 1000 ), retry .WithMaxTries (10 ), retry .WithIsRetryableErr (cerror .IsRetryableError ))
322
+ }, retry .WithBackoffBaseDelay ( int64 ( retryInterval )) , retry .WithBackoffMaxDelay (120 * 1000 ), retry .WithMaxTries (20 ), retry .WithIsRetryableErr (cerror .IsRetryableError ))
311
323
}
312
324
313
325
// tryDropDB will drop table if data incorrect and panic error likes bad connect.
@@ -486,8 +498,9 @@ func run(
486
498
}
487
499
}
488
500
489
- // DDL is a strong sync point in TiCDC. Once finishmark table is replicated to downstream
490
- // all previous DDL and DML are replicated too.
501
+ // After we implement concurrent DDL of different tables, DDL is no longer a strong sync point in TiCDC.
502
+ // We need to use changefeed's checkpoint > ddl.FinishedTS to ensure all previous DDL and DML before
503
+ // the DDL are synced.
491
504
mustExec (ctx , upstreamDB , `CREATE TABLE IF NOT EXISTS finishmark (foo BIGINT PRIMARY KEY)` )
492
505
waitCtx , waitCancel := context .WithTimeout (ctx , 15 * time .Minute )
493
506
endTs , err := getDownStreamSyncedEndTs (waitCtx , downstreamDB , downstreamAPIEndpoint , "finishmark" )
@@ -623,55 +636,69 @@ func getDownStreamSyncedEndTs(ctx context.Context, db *sql.DB, tidbAPIEndpoint,
623
636
log .Error ("get downstream sync end ts failed due to timeout" , zap .String ("table" , tableName ), zap .Error (ctx .Err ()))
624
637
return 0 , ctx .Err ()
625
638
case <- time .After (2 * time .Second ):
626
- result , ok := tryGetEndTs (db , tidbAPIEndpoint , tableName )
639
+ result , ok := tryGetEndTsFromLog (db , tableName )
627
640
if ok {
628
641
return result , nil
629
642
}
630
643
}
631
644
}
632
645
}
633
646
634
- func tryGetEndTs (db * sql.DB , tidbAPIEndpoint , tableName string ) (result uint64 , ok bool ) {
635
- // Note: We should not use `END_TS` in the table, because it is encoded in
636
- // the format `2023-03-16 18:12:51`, it's not precise enough.
637
- query := "SELECT JOB_ID FROM information_schema.ddl_jobs WHERE table_name = ?"
638
- log .Info ("try get end ts" , zap .String ("query" , query ), zap .String ("tableName" , tableName ))
639
- var jobID uint64
640
- row := db .QueryRow (query , tableName )
641
- if err := row .Scan (& jobID ); err != nil {
642
- if err != sql .ErrNoRows {
643
- log .Info ("rows scan failed" , zap .Error (err ))
647
+ func tryGetEndTsFromLog (db * sql.DB , tableName string ) (result uint64 , ok bool ) {
648
+ log .Info ("try parse finishedTs from ticdc log" , zap .String ("tableName" , tableName ))
649
+
650
+ logFilePath := "/tmp/tidb_cdc_test/bank"
651
+ cdcLogFiles := make ([]string , 0 )
652
+ // walk all file with cdc prefix
653
+ err := filepath .WalkDir (logFilePath , func (path string , d os.DirEntry , err error ) error {
654
+ if err != nil {
655
+ return err
644
656
}
645
- return 0 , false
646
- }
647
- ddlJobURL := fmt .Sprintf (
648
- "http://%s/ddl/history?start_job_id=%d&limit=1" , tidbAPIEndpoint , jobID )
649
- ddlJobResp , err := http .Get (ddlJobURL )
650
- if err != nil {
651
- log .Warn ("fail to get DDL history" ,
652
- zap .String ("URL" , ddlJobURL ), zap .Error (err ))
653
- return 0 , false
654
- }
655
- defer ddlJobResp .Body .Close ()
656
- ddlJobJSON , err := io .ReadAll (ddlJobResp .Body )
657
+ if ! d .IsDir () {
658
+ if strings .Contains (d .Name (), "down" ) && strings .Contains (d .Name (), "cdc" ) && strings .Contains (d .Name (), "log" ) {
659
+ cdcLogFiles = append (cdcLogFiles , path )
660
+ fmt .Println (path )
661
+ }
662
+ }
663
+ return nil
664
+ })
657
665
if err != nil {
658
- log .Warn ("fail to read DDL history" ,
659
- zap .String ("URL" , ddlJobURL ), zap .Error (err ))
660
- return 0 , false
666
+ log .Error ("Failed to walk dir: %v" , zap .Error (err ))
661
667
}
662
- ddlJob := []struct {
663
- Binlog struct {
664
- FinishedTS uint64 `json:"FinishedTS"`
665
- } `json:"binlog"`
666
- }{{}}
667
- err = json .Unmarshal (ddlJobJSON , & ddlJob )
668
- if err != nil {
669
- log .Warn ("fail to unmarshal DDL history" ,
670
- zap .String ("URL" , ddlJobURL ), zap .String ("resp" , string (ddlJobJSON )), zap .Error (err ))
671
- return 0 , false
668
+ log .Info ("total files" , zap .Any ("file" , cdcLogFiles ))
669
+
670
+ logRegex := regexp .MustCompile (`handle a ddl job` )
671
+ tableNameRegex := regexp .MustCompile (tableName + "`" )
672
+ timeStampRegex := regexp .MustCompile (`jobFinishTs=([0-9]+)` )
673
+ for _ , f := range cdcLogFiles {
674
+ file , err := os .Open (f )
675
+ if err != nil {
676
+ log .Error ("Failed to open file: %v" , zap .Error (err ))
677
+ }
678
+ defer file .Close ()
679
+
680
+ scanner := bufio .NewScanner (file )
681
+ for scanner .Scan () {
682
+ line := scanner .Text ()
683
+ if ! logRegex .MatchString (line ) || ! tableNameRegex .MatchString (line ) {
684
+ continue
685
+ }
686
+
687
+ matches := timeStampRegex .FindStringSubmatch (line )
688
+ if len (matches ) > 1 {
689
+ fmt .Println ("found first match line: " , matches [1 ], ": " , line )
690
+ // convert to uint64
691
+ result , err := strconv .ParseUint (matches [1 ], 10 , 64 )
692
+ if err != nil {
693
+ log .Error ("Failed to parse uint64: %v" , zap .Error (err ))
694
+ }
695
+ return result , true
696
+ }
697
+ }
698
+
699
+ if err := scanner .Err (); err != nil {
700
+ log .Error ("Error scanning file: %v" , zap .Error (err ))
701
+ }
672
702
}
673
- log .Info ("get end ts" ,
674
- zap .String ("tableName" , tableName ),
675
- zap .Uint64 ("ts" , ddlJob [0 ].Binlog .FinishedTS ))
676
- return ddlJob [0 ].Binlog .FinishedTS , true
703
+ return 0 , false
677
704
}
0 commit comments