github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/kvfeed/kv_feed_test.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package kvfeed
    10  
    11  import (
    12  	"context"
    13  	"math"
    14  	"sort"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/changefeedbase"
    19  	"github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/schemafeed"
    20  	"github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/schemafeed/schematestutils"
    21  	"github.com/cockroachdb/cockroach/pkg/keys"
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    26  	"github.com/cockroachdb/cockroach/pkg/util/ctxgroup"
    27  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    28  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    29  	"github.com/cockroachdb/cockroach/pkg/util/mon"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestKVFeed(t *testing.T) {
    35  	// We want to inject fake table events and data into the buffer
    36  	// and use that to assert that there are proper calls to the kvScanner and
    37  	// what not.
    38  	ts := func(seconds int) hlc.Timestamp {
    39  		return hlc.Timestamp{WallTime: (time.Duration(seconds) * time.Second).Nanoseconds()}
    40  	}
    41  	kv := func(tableID uint32, k, v string, ts hlc.Timestamp) roachpb.KeyValue {
    42  		vDatum := tree.DString(k)
    43  		key, err := sqlbase.EncodeTableKey(keys.SystemSQLCodec.TablePrefix(tableID), &vDatum, encoding.Ascending)
    44  		if err != nil {
    45  			panic(err)
    46  		}
    47  		return roachpb.KeyValue{
    48  			Key: key,
    49  			Value: roachpb.Value{
    50  				RawBytes:  []byte(v),
    51  				Timestamp: ts,
    52  			},
    53  		}
    54  	}
    55  	kvEvent := func(tableID uint32, k, v string, ts hlc.Timestamp) roachpb.RangeFeedEvent {
    56  		keyVal := kv(tableID, k, v, ts)
    57  		return roachpb.RangeFeedEvent{
    58  			Val: &roachpb.RangeFeedValue{
    59  				Key:   keyVal.Key,
    60  				Value: keyVal.Value,
    61  			},
    62  			Checkpoint: nil,
    63  			Error:      nil,
    64  		}
    65  	}
    66  	checkpointEvent := func(span roachpb.Span, ts hlc.Timestamp) roachpb.RangeFeedEvent {
    67  		return roachpb.RangeFeedEvent{
    68  			Checkpoint: &roachpb.RangeFeedCheckpoint{
    69  				Span:       span,
    70  				ResolvedTS: ts,
    71  			},
    72  		}
    73  	}
    74  
    75  	type testCase struct {
    76  		name               string
    77  		needsInitialScan   bool
    78  		withDiff           bool
    79  		schemaChangeEvents changefeedbase.SchemaChangeEventClass
    80  		schemaChangePolicy changefeedbase.SchemaChangePolicy
    81  		initialHighWater   hlc.Timestamp
    82  		spans              []roachpb.Span
    83  		events             []roachpb.RangeFeedEvent
    84  
    85  		descs []*sqlbase.TableDescriptor
    86  
    87  		expScans  []hlc.Timestamp
    88  		expEvents int
    89  		expErrRE  string
    90  	}
    91  	runTest := func(t *testing.T, tc testCase) {
    92  		settings := cluster.MakeTestingClusterSettings()
    93  		buf := MakeChanBuffer()
    94  		mm := mon.MakeUnlimitedMonitor(
    95  			context.Background(), "test", mon.MemoryResource,
    96  			nil /* curCount */, nil /* maxHist */, math.MaxInt64, settings,
    97  		)
    98  		metrics := MakeMetrics(time.Minute)
    99  		bufferFactory := func() EventBuffer {
   100  			return makeMemBuffer(mm.MakeBoundAccount(), &metrics)
   101  		}
   102  		scans := make(chan physicalConfig)
   103  		sf := scannerFunc(func(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error {
   104  			select {
   105  			case scans <- cfg:
   106  				return nil
   107  			case <-ctx.Done():
   108  				return ctx.Err()
   109  			}
   110  		})
   111  		ref := rawEventFeed(tc.events)
   112  		tf := newRawTableFeed(tc.descs, tc.initialHighWater)
   113  		f := newKVFeed(buf, tc.spans,
   114  			tc.schemaChangeEvents, tc.schemaChangePolicy,
   115  			tc.needsInitialScan, tc.withDiff,
   116  			tc.initialHighWater,
   117  			&tf, sf, rangefeedFactory(ref.run), bufferFactory)
   118  		ctx, cancel := context.WithCancel(context.Background())
   119  		g := ctxgroup.WithContext(ctx)
   120  		g.GoCtx(func(ctx context.Context) error {
   121  			return f.run(ctx)
   122  		})
   123  		testG := ctxgroup.WithContext(ctx)
   124  		testG.GoCtx(func(ctx context.Context) error {
   125  			for expScans := tc.expScans; len(expScans) > 0; expScans = expScans[1:] {
   126  				scan := <-scans
   127  				assert.Equal(t, expScans[0], scan.Timestamp)
   128  				assert.Equal(t, tc.withDiff, scan.WithDiff)
   129  			}
   130  			return nil
   131  		})
   132  		testG.GoCtx(func(ctx context.Context) error {
   133  			for events := 0; events < tc.expEvents; events++ {
   134  				_, err := buf.Get(ctx)
   135  				assert.NoError(t, err)
   136  			}
   137  			return nil
   138  		})
   139  		// Wait for the feed to fail rather than canceling it.
   140  		if tc.schemaChangePolicy == changefeedbase.OptSchemaChangePolicyStop {
   141  			testG.Go(func() error {
   142  				_ = g.Wait()
   143  				return nil
   144  			})
   145  		}
   146  		require.NoError(t, testG.Wait())
   147  		cancel()
   148  		if runErr := g.Wait(); tc.expErrRE != "" {
   149  			require.Regexp(t, tc.expErrRE, runErr)
   150  		} else {
   151  			require.Regexp(t, context.Canceled, runErr)
   152  		}
   153  	}
   154  	makeTableDesc := schematestutils.MakeTableDesc
   155  	addColumnDropBackfillMutation := schematestutils.AddColumnDropBackfillMutation
   156  	for _, tc := range []testCase{
   157  		{
   158  			name:               "no events - backfill",
   159  			schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault,
   160  			schemaChangePolicy: changefeedbase.OptSchemaChangePolicyBackfill,
   161  			needsInitialScan:   true,
   162  			initialHighWater:   ts(2),
   163  			spans: []roachpb.Span{
   164  				tableSpan(42),
   165  			},
   166  			events: []roachpb.RangeFeedEvent{
   167  				kvEvent(42, "a", "b", ts(3)),
   168  			},
   169  			expScans: []hlc.Timestamp{
   170  				ts(2),
   171  			},
   172  			expEvents: 1,
   173  		},
   174  		{
   175  			name:               "one table event - backfill",
   176  			schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault,
   177  			schemaChangePolicy: changefeedbase.OptSchemaChangePolicyBackfill,
   178  			needsInitialScan:   true,
   179  			initialHighWater:   ts(2),
   180  			spans: []roachpb.Span{
   181  				tableSpan(42),
   182  			},
   183  			events: []roachpb.RangeFeedEvent{
   184  				kvEvent(42, "a", "b", ts(3)),
   185  				checkpointEvent(tableSpan(42), ts(4)),
   186  				kvEvent(42, "a", "b", ts(5)),
   187  				checkpointEvent(tableSpan(42), ts(2)), // ensure that events are filtered
   188  				checkpointEvent(tableSpan(42), ts(5)),
   189  			},
   190  			expScans: []hlc.Timestamp{
   191  				ts(2),
   192  				ts(3),
   193  			},
   194  			descs: []*sqlbase.TableDescriptor{
   195  				makeTableDesc(42, 1, ts(1), 2),
   196  				addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(3), 1)),
   197  			},
   198  			expEvents: 2,
   199  		},
   200  		{
   201  			name:               "one table event - skip",
   202  			schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault,
   203  			schemaChangePolicy: changefeedbase.OptSchemaChangePolicyNoBackfill,
   204  			needsInitialScan:   true,
   205  			initialHighWater:   ts(2),
   206  			spans: []roachpb.Span{
   207  				tableSpan(42),
   208  			},
   209  			events: []roachpb.RangeFeedEvent{
   210  				kvEvent(42, "a", "b", ts(3)),
   211  				checkpointEvent(tableSpan(42), ts(4)),
   212  				kvEvent(42, "a", "b", ts(5)),
   213  				checkpointEvent(tableSpan(42), ts(6)),
   214  			},
   215  			expScans: []hlc.Timestamp{
   216  				ts(2),
   217  			},
   218  			descs: []*sqlbase.TableDescriptor{
   219  				makeTableDesc(42, 1, ts(1), 2),
   220  				addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(3), 1)),
   221  			},
   222  			expEvents: 4,
   223  		},
   224  		{
   225  			name:               "one table event - stop",
   226  			schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault,
   227  			schemaChangePolicy: changefeedbase.OptSchemaChangePolicyStop,
   228  			needsInitialScan:   true,
   229  			initialHighWater:   ts(2),
   230  			spans: []roachpb.Span{
   231  				tableSpan(42),
   232  			},
   233  			events: []roachpb.RangeFeedEvent{
   234  				kvEvent(42, "a", "b", ts(3)),
   235  				checkpointEvent(tableSpan(42), ts(4)),
   236  				kvEvent(42, "a", "b", ts(5)),
   237  				checkpointEvent(tableSpan(42), ts(2)), // ensure that events are filtered
   238  				checkpointEvent(tableSpan(42), ts(5)),
   239  			},
   240  			expScans: []hlc.Timestamp{
   241  				ts(2),
   242  			},
   243  			descs: []*sqlbase.TableDescriptor{
   244  				makeTableDesc(42, 1, ts(1), 2),
   245  				addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(4), 1)),
   246  			},
   247  			expEvents: 2,
   248  			expErrRE:  "schema change ...",
   249  		},
   250  	} {
   251  		t.Run(tc.name, func(t *testing.T) {
   252  			runTest(t, tc)
   253  		})
   254  	}
   255  }
   256  
   257  type scannerFunc func(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error
   258  
   259  func (s scannerFunc) Scan(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error {
   260  	return s(ctx, sink, cfg)
   261  }
   262  
   263  var _ kvScanner = (scannerFunc)(nil)
   264  
   265  type rawTableFeed struct {
   266  	events []schemafeed.TableEvent
   267  }
   268  
   269  func newRawTableFeed(
   270  	descs []*sqlbase.TableDescriptor, initialHighWater hlc.Timestamp,
   271  ) rawTableFeed {
   272  	sort.Slice(descs, func(i, j int) bool {
   273  		if descs[i].ID != descs[j].ID {
   274  			return descs[i].ID < descs[j].ID
   275  		}
   276  		return descs[i].ModificationTime.Less(descs[j].ModificationTime)
   277  	})
   278  	f := rawTableFeed{}
   279  	curID := sqlbase.ID(math.MaxUint32)
   280  	for i, d := range descs {
   281  		if d.ID != curID {
   282  			curID = d.ID
   283  			continue
   284  		}
   285  		if d.ModificationTime.Less(initialHighWater) {
   286  			continue
   287  		}
   288  		f.events = append(f.events, schemafeed.TableEvent{
   289  			Before: descs[i-1],
   290  			After:  d,
   291  		})
   292  	}
   293  	return f
   294  }
   295  
   296  func (r *rawTableFeed) Peek(
   297  	ctx context.Context, atOrBefore hlc.Timestamp,
   298  ) (events []schemafeed.TableEvent, err error) {
   299  	return r.peekOrPop(ctx, atOrBefore, false /* pop */)
   300  }
   301  
   302  func (r *rawTableFeed) Pop(
   303  	ctx context.Context, atOrBefore hlc.Timestamp,
   304  ) (events []schemafeed.TableEvent, err error) {
   305  	return r.peekOrPop(ctx, atOrBefore, true /* pop */)
   306  }
   307  
   308  func (r *rawTableFeed) peekOrPop(
   309  	ctx context.Context, atOrBefore hlc.Timestamp, pop bool,
   310  ) (events []schemafeed.TableEvent, err error) {
   311  	i := sort.Search(len(r.events), func(i int) bool {
   312  		return !r.events[i].Timestamp().LessEq(atOrBefore)
   313  	})
   314  	if i == -1 {
   315  		i = 0
   316  	}
   317  	events = r.events[:i]
   318  	if pop {
   319  		r.events = r.events[i:]
   320  	}
   321  	return events, nil
   322  }
   323  
   324  type rawEventFeed []roachpb.RangeFeedEvent
   325  
   326  func (f rawEventFeed) run(
   327  	ctx context.Context,
   328  	span roachpb.Span,
   329  	startFrom hlc.Timestamp,
   330  	withDiff bool,
   331  	eventC chan<- *roachpb.RangeFeedEvent,
   332  ) error {
   333  	// We can't use binary search because the errors don't have timestamps.
   334  	// Instead we just search for the first event which comes after the start time.
   335  	var i int
   336  	for i = range f {
   337  		ev := f[i]
   338  		if ev.Val != nil && startFrom.Less(ev.Val.Value.Timestamp) ||
   339  			ev.Checkpoint != nil && startFrom.Less(ev.Checkpoint.ResolvedTS) {
   340  			break
   341  		}
   342  
   343  	}
   344  	f = f[i:]
   345  	for i := range f {
   346  		select {
   347  		case eventC <- &f[i]:
   348  		case <-ctx.Done():
   349  			return ctx.Err()
   350  		}
   351  	}
   352  	return nil
   353  }
   354  
   355  var _ schemaFeed = (*rawTableFeed)(nil)
   356  
   357  func tableSpan(tableID uint32) roachpb.Span {
   358  	return roachpb.Span{
   359  		Key:    keys.SystemSQLCodec.TablePrefix(tableID),
   360  		EndKey: keys.SystemSQLCodec.TablePrefix(tableID).PrefixEnd(),
   361  	}
   362  }