Skip to content

Commit e5b3f9a

Browse files
mcrauwelclaudemattlord
authored
VDiff: save a sample for every drained extra row so reconciliation can match them (#20855)
Signed-off-by: Matthias Crauwels <matthias.crauwels@planetscale.com> Signed-off-by: Matt Lord <mattalord@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Matt Lord <mattalord@gmail.com>
1 parent 39bcd63 commit e5b3f9a

7 files changed

Lines changed: 808 additions & 54 deletions

File tree

go/vt/vttablet/tabletmanager/vdiff/primitive_executor.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,3 @@ func (pe *primitiveExecutor) next() ([]sqltypes.Value, error) {
8989
pe.rows = pe.rows[1:]
9090
return row, nil
9191
}
92-
93-
// drain fastforward's a shard to process (and ignore) everything from its results stream and return a count of the
94-
// discarded rows.
95-
func (pe *primitiveExecutor) drain(ctx context.Context) (int64, error) {
96-
var count int64
97-
for {
98-
row, err := pe.next()
99-
if err != nil {
100-
return 0, err
101-
}
102-
if row == nil {
103-
return count, nil
104-
}
105-
count++
106-
}
107-
}

go/vt/vttablet/tabletmanager/vdiff/report.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ type DiffMismatch struct {
6363
type RowDiff struct {
6464
Row map[string]string `json:"Row,omitempty"`
6565
Query string `json:"Query,omitempty"`
66+
// LosslessValues is set when the sample contains all of the row's column
67+
// values without truncation, meaning it can be used to prove that two
68+
// rows are identical during extra-row reconciliation. The marker is
69+
// deliberately affirmative: samples that are lossy (only-pks, truncated
70+
// values) -- or that were persisted by an older binary and reloaded on
71+
// resume -- lack it and are excluded from reconciliation.
72+
LosslessValues bool `json:"LosslessValues,omitempty"`
6673
}
6774

6875
func (td *tableDiffer) genRowDiff(queryStmt string, row []sqltypes.Value, opts *tabletmanagerdatapb.VDiffReportOptions) (*RowDiff, error) {
@@ -81,13 +88,15 @@ func (td *tableDiffer) genRowDiff(queryStmt string, row []sqltypes.Value, opts *
8188
rd.Query = td.genDebugQueryDiff(sel, row, opts.GetOnlyPks())
8289
}
8390

91+
truncated := false
8492
addVal := func(index int, truncateAt int) error {
8593
buf := sqlparser.NewTrackedBuffer(nil)
8694
sel.SelectExprs.Exprs[index].Format(buf)
8795
col := buf.String()
8896
// Let's truncate if it's really worth it to avoid losing
8997
// value for a few chars.
9098
if truncateAt > 0 && row[index].Len() >= truncateAt+len(truncatedNotation)+20 {
99+
truncated = true
91100
if row[index].IsBinary() {
92101
rb, err := row[index].ToBytes()
93102
if err != nil { // Should never happen
@@ -126,6 +135,9 @@ func (td *tableDiffer) genRowDiff(queryStmt string, row []sqltypes.Value, opts *
126135
}
127136

128137
if opts.GetOnlyPks() {
138+
// A PK-only sample is still lossless when the PK columns cover the
139+
// entire projection, since PK values are never truncated.
140+
rd.LosslessValues = len(pks) == len(sel.SelectExprs.Exprs)
129141
return rd, nil
130142
}
131143

@@ -138,6 +150,10 @@ func (td *tableDiffer) genRowDiff(queryStmt string, row []sqltypes.Value, opts *
138150
}
139151
}
140152

153+
// The sample contains all of the row's column values (this point is not
154+
// reached with only-pks); it is lossless if none of them were truncated.
155+
rd.LosslessValues = !truncated
156+
141157
return rd, nil
142158
}
143159

go/vt/vttablet/tabletmanager/vdiff/report_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func TestGenRowDiff(t *testing.T) {
6666
},
6767
reportOptions: &tabletmanagerdatapb.VDiffReportOptions{},
6868
want: &RowDiff{
69+
LosslessValues: true,
6970
Row: map[string]string{ // The two PK cols should be first
7071
// mysql> select hex("hi4");
7172
// +------------+
@@ -131,6 +132,7 @@ func TestGenRowDiff(t *testing.T) {
131132
DebugQuery: true,
132133
},
133134
want: &RowDiff{
135+
LosslessValues: true,
134136
Row: map[string]string{
135137
"c1": "1",
136138
"c2": "2",

go/vt/vttablet/tabletmanager/vdiff/table_differ.go

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,14 @@ func (td *tableDiffer) diff(ctx context.Context, coreOpts *tabletmanagerdatapb.V
581581
maxReportSampleRows := reportOpts.GetMaxSampleRows()
582582

583583
for {
584-
lastProcessedRow = sourceRow
584+
// Only advance the persisted position when the previous iteration
585+
// consumed the held source row (advanceSource still holds that
586+
// iteration's decision here). After an extra-target-row iteration the
587+
// held source row has not been processed yet, and recording it as
588+
// lastpk would make a resumed diff skip it permanently.
589+
if advanceSource {
590+
lastProcessedRow = sourceRow
591+
}
585592

586593
select {
587594
case <-ctx.Done():
@@ -629,35 +636,60 @@ func (td *tableDiffer) diff(ctx context.Context, coreOpts *tabletmanagerdatapb.V
629636
advanceSource = true
630637
advanceTarget = true
631638
if sourceRow == nil {
632-
diffRow, err := td.genRowDiff(td.tablePlan.sourceQuery, targetRow, reportOpts)
633-
if err != nil {
634-
return nil, vterrors.Wrap(err, "unexpected error generating diff")
635-
}
636-
dr.ExtraRowsTargetDiffs = append(dr.ExtraRowsTargetDiffs, diffRow)
637-
638-
// Drain target, update count.
639-
count, err := targetExecutor.drain(ctx)
640-
if err != nil {
641-
return nil, err
639+
// No more rows from the source; drain the remaining target rows,
640+
// saving a sample for each one (up to maxExtraRowsToCompare) so that
641+
// reconcileExtraRows can match them against any extra source rows.
642+
// Counting drained rows without saving a sample makes them impossible
643+
// to reconcile, producing false positive extra rows in the report.
644+
// The drained rows are merged into the report only after the full
645+
// drain succeeds: they are beyond the persisted lastpk, so partially
646+
// counted rows would be counted again when a failed diff is resumed.
647+
drainedRows := int64(0)
648+
var drainedDiffs []*RowDiff
649+
for targetRow != nil {
650+
if dr.ExtraRowsTarget+drainedRows < maxExtraRowsToCompare {
651+
diffRow, err := td.genRowDiff(td.tablePlan.targetQuery, targetRow, reportOpts)
652+
if err != nil {
653+
return nil, vterrors.Wrap(err, "unexpected error generating diff")
654+
}
655+
drainedDiffs = append(drainedDiffs, diffRow)
656+
}
657+
drainedRows++
658+
targetRow, err = targetExecutor.next()
659+
if err != nil {
660+
return nil, err
661+
}
642662
}
643-
dr.ExtraRowsTarget += 1 + count
644-
dr.ProcessedRows += 1 + count
663+
dr.ExtraRowsTarget += drainedRows
664+
dr.ProcessedRows += drainedRows
665+
dr.ExtraRowsTargetDiffs = append(dr.ExtraRowsTargetDiffs, drainedDiffs...)
645666
return dr, nil
646667
}
647668
if targetRow == nil {
648-
// No more rows from the target but we know we have more rows from
649-
// source, so drain them and update the counts.
650-
diffRow, err := td.genRowDiff(td.tablePlan.sourceQuery, sourceRow, reportOpts)
651-
if err != nil {
652-
return nil, vterrors.Wrap(err, "unexpected error generating diff")
653-
}
654-
dr.ExtraRowsSourceDiffs = append(dr.ExtraRowsSourceDiffs, diffRow)
655-
count, err := sourceExecutor.drain(ctx)
656-
if err != nil {
657-
return nil, err
669+
// No more rows from the target; drain the remaining source rows,
670+
// saving a sample for each one (up to maxExtraRowsToCompare) so that
671+
// reconcileExtraRows can match them against any extra target rows.
672+
// As above, the drained rows are merged into the report only after
673+
// the full drain succeeds.
674+
drainedRows := int64(0)
675+
var drainedDiffs []*RowDiff
676+
for sourceRow != nil {
677+
if dr.ExtraRowsSource+drainedRows < maxExtraRowsToCompare {
678+
diffRow, err := td.genRowDiff(td.tablePlan.sourceQuery, sourceRow, reportOpts)
679+
if err != nil {
680+
return nil, vterrors.Wrap(err, "unexpected error generating diff")
681+
}
682+
drainedDiffs = append(drainedDiffs, diffRow)
683+
}
684+
drainedRows++
685+
sourceRow, err = sourceExecutor.next()
686+
if err != nil {
687+
return nil, err
688+
}
658689
}
659-
dr.ExtraRowsSource += 1 + count
660-
dr.ProcessedRows += 1 + count
690+
dr.ExtraRowsSource += drainedRows
691+
dr.ProcessedRows += drainedRows
692+
dr.ExtraRowsSourceDiffs = append(dr.ExtraRowsSourceDiffs, drainedDiffs...)
661693
return dr, nil
662694
}
663695

0 commit comments

Comments
 (0)