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

     1  // Copyright 2019 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 cdctest
    10  
    11  import (
    12  	"bytes"
    13  	"context"
    14  	gosql "database/sql"
    15  	"fmt"
    16  	"math/rand"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/util/fsm"
    20  	"github.com/cockroachdb/cockroach/pkg/util/log"
    21  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  // RunNemesis runs a jepsen-style validation of whether a changefeed meets our
    26  // user-facing guarantees. It's driven by a state machine with various nemeses:
    27  // txn begin/commit/rollback, job pause/unpause.
    28  //
    29  // Changefeeds have a set of user-facing guarantees about ordering and
    30  // duplicates, which the two cdctest.Validator implementations verify for the
    31  // real output of a changefeed. The output rows and resolved timestamps of the
    32  // tested feed are fed into them to check for anomalies.
    33  func RunNemesis(f TestFeedFactory, db *gosql.DB, isSinkless bool) (Validator, error) {
    34  	// possible additional nemeses:
    35  	// - schema changes
    36  	// - merges
    37  	// - rebalancing
    38  	// - lease transfers
    39  	// - receiving snapshots
    40  	// mostly redundant with the pause/unpause nemesis, but might be nice to have:
    41  	// - crdb chaos
    42  	// - sink chaos
    43  
    44  	ctx := context.Background()
    45  	rng, _ := randutil.NewPseudoRand()
    46  
    47  	eventPauseCount := 10
    48  	if isSinkless {
    49  		// Disable eventPause for sinkless changefeeds because we currently do not
    50  		// have "correct" pause and unpause mechanisms for changefeeds that aren't
    51  		// based on the jobs infrastructure. Enabling it for sinkless might require
    52  		// using "AS OF SYSTEM TIME" for sinkless changefeeds. See #41006 for more
    53  		// details.
    54  		eventPauseCount = 0
    55  	}
    56  	ns := &nemeses{
    57  		maxTestColumnCount: 10,
    58  		rowCount:           4,
    59  		db:                 db,
    60  		// eventMix does not have to add to 100
    61  		eventMix: map[fsm.Event]int{
    62  			// We don't want `eventFinished` to ever be returned by `nextEvent` so we set
    63  			// its weight to 0.
    64  			eventFinished{}: 0,
    65  
    66  			// eventFeedMessage reads a message from the feed, or if the state machine
    67  			// thinks there will be no message available, it falls back to eventOpenTxn or
    68  			// eventCommit (if there is already a txn open).
    69  			eventFeedMessage{}: 50,
    70  
    71  			// eventSplit splits between two random rows (the split is a no-op if it
    72  			// already exists).
    73  			eventSplit{}: 5,
    74  
    75  			// TRANSACTIONS
    76  			// eventOpenTxn opens an UPSERT or DELETE transaction.
    77  			eventOpenTxn{}: 10,
    78  
    79  			// eventCommit commits the outstanding transaction.
    80  			eventCommit{}: 5,
    81  
    82  			// eventRollback simply rolls the outstanding transaction back.
    83  			eventRollback{}: 5,
    84  
    85  			// eventPush pushes every open transaction by running a high priority SELECT.
    86  			eventPush{}: 5,
    87  
    88  			// eventAbort aborts every open transaction by running a high priority DELETE.
    89  			eventAbort{}: 5,
    90  
    91  			// PAUSE / RESUME
    92  			// eventPause PAUSEs the changefeed.
    93  			eventPause{}: eventPauseCount,
    94  
    95  			// eventResume RESUMEs the changefeed.
    96  			eventResume{}: 50,
    97  
    98  			// SCHEMA CHANGES
    99  			// eventAddColumn performs a schema change by adding a new column with a default
   100  			// value in order to trigger a backfill.
   101  			eventAddColumn{
   102  				CanAddColumnAfter: fsm.True,
   103  			}: 5,
   104  
   105  			eventAddColumn{
   106  				CanAddColumnAfter: fsm.False,
   107  			}: 5,
   108  
   109  			// eventRemoveColumn performs a schema change by removing a column.
   110  			eventRemoveColumn{
   111  				CanRemoveColumnAfter: fsm.True,
   112  			}: 5,
   113  
   114  			eventRemoveColumn{
   115  				CanRemoveColumnAfter: fsm.False,
   116  			}: 5,
   117  		},
   118  	}
   119  
   120  	// Create the table and set up some initial splits.
   121  	if _, err := db.Exec(`CREATE TABLE foo (id INT PRIMARY KEY, ts STRING DEFAULT '0')`); err != nil {
   122  		return nil, err
   123  	}
   124  	if _, err := db.Exec(`SET CLUSTER SETTING kv.range_merge.queue_enabled = false`); err != nil {
   125  		return nil, err
   126  	}
   127  	if _, err := db.Exec(`ALTER TABLE foo SPLIT AT VALUES ($1)`, ns.rowCount/2); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Initialize table rows by repeatedly running the `openTxn` transition,
   132  	// then randomly either committing or rolling back transactions. This will
   133  	// leave some committed rows.
   134  	for i := 0; i < ns.rowCount*5; i++ {
   135  		if err := openTxn(fsm.Args{Ctx: ctx, Extended: ns}); err != nil {
   136  			return nil, err
   137  		}
   138  		// Randomly commit or rollback, but commit at least one row to the table.
   139  		if rand.Intn(3) < 2 || i == 0 {
   140  			if err := commit(fsm.Args{Ctx: ctx, Extended: ns}); err != nil {
   141  				return nil, err
   142  			}
   143  		} else {
   144  			if err := rollback(fsm.Args{Ctx: ctx, Extended: ns}); err != nil {
   145  				return nil, err
   146  			}
   147  		}
   148  	}
   149  
   150  	foo, err := f.Feed(`CREATE CHANGEFEED FOR foo WITH updated, resolved, diff`)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	ns.f = foo
   155  	defer func() { _ = foo.Close() }()
   156  
   157  	// Create scratch table with a pre-specified set of test columns to avoid having to
   158  	// accommodate schema changes on-the-fly.
   159  	scratchTableName := `fprint`
   160  	var createFprintStmtBuf bytes.Buffer
   161  	fmt.Fprintf(&createFprintStmtBuf, `CREATE TABLE %s (id INT PRIMARY KEY, ts STRING)`, scratchTableName)
   162  	if _, err := db.Exec(createFprintStmtBuf.String()); err != nil {
   163  		return nil, err
   164  	}
   165  	baV, err := NewBeforeAfterValidator(db, `foo`)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	fprintV, err := NewFingerprintValidator(db, `foo`, scratchTableName, foo.Partitions(), ns.maxTestColumnCount)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	ns.v = MakeCountValidator(Validators{
   174  		NewOrderValidator(`foo`),
   175  		baV,
   176  		fprintV,
   177  	})
   178  
   179  	// Initialize the actual row count, overwriting what the initialization loop did. That
   180  	// loop has set this to the number of modified rows, which is correct during
   181  	// changefeed operation, but not for the initial scan, because some of the rows may
   182  	// have had the same primary key.
   183  	if err := db.QueryRow(`SELECT count(*) FROM foo`).Scan(&ns.availableRows); err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	txnOpenBeforeInitialScan := false
   188  	// Maybe open an intent.
   189  	if rand.Intn(2) < 1 {
   190  		txnOpenBeforeInitialScan = true
   191  		if err := openTxn(fsm.Args{Ctx: ctx, Extended: ns}); err != nil {
   192  			return nil, err
   193  		}
   194  	}
   195  
   196  	// Run the state machine until it finishes. Exit criteria is in `nextEvent`
   197  	// and is based on the number of rows that have been resolved and the number
   198  	// of resolved timestamp messages.
   199  	initialState := stateRunning{
   200  		FeedPaused:      fsm.False,
   201  		TxnOpen:         fsm.FromBool(txnOpenBeforeInitialScan),
   202  		CanAddColumn:    fsm.True,
   203  		CanRemoveColumn: fsm.False,
   204  	}
   205  	m := fsm.MakeMachine(compiledStateTransitions, initialState, ns)
   206  	for {
   207  		state := m.CurState()
   208  		if _, ok := state.(stateDone); ok {
   209  			return ns.v, nil
   210  		}
   211  		event, err := ns.nextEvent(rng, state, foo, &m)
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  		if err := m.Apply(ctx, event); err != nil {
   216  			return nil, err
   217  		}
   218  	}
   219  }
   220  
   221  type openTxnType string
   222  
   223  const (
   224  	openTxnTypeUpsert openTxnType = `UPSERT`
   225  	openTxnTypeDelete openTxnType = `DELETE`
   226  )
   227  
   228  type nemeses struct {
   229  	rowCount           int
   230  	maxTestColumnCount int
   231  	eventMix           map[fsm.Event]int
   232  
   233  	v  *CountValidator
   234  	db *gosql.DB
   235  	f  TestFeed
   236  
   237  	availableRows          int
   238  	currentTestColumnCount int
   239  	txn                    *gosql.Tx
   240  	openTxnType            openTxnType
   241  	openTxnID              int
   242  	openTxnTs              string
   243  }
   244  
   245  // nextEvent selects the next state transition.
   246  func (ns *nemeses) nextEvent(
   247  	rng *rand.Rand, state fsm.State, f TestFeed, m *fsm.Machine,
   248  ) (se fsm.Event, err error) {
   249  	if ns.v.NumResolvedWithRows >= 6 && ns.v.NumResolvedRows >= 10 {
   250  		return eventFinished{}, nil
   251  	}
   252  	possibleEvents, ok := compiledStateTransitions.GetExpanded()[state]
   253  	if !ok {
   254  		return nil, errors.Errorf(`unknown state: %T %s`, state, state)
   255  	}
   256  	mixTotal := 0
   257  	for event := range possibleEvents {
   258  		weight, ok := ns.eventMix[event]
   259  		if !ok {
   260  			return nil, errors.Errorf(`unknown event: %T`, event)
   261  		}
   262  		mixTotal += weight
   263  	}
   264  	r, t := rng.Intn(mixTotal), 0
   265  	for event := range possibleEvents {
   266  		t += ns.eventMix[event]
   267  		if r >= t {
   268  			continue
   269  		}
   270  		if _, ok := event.(eventFeedMessage); ok {
   271  			// If there are no available rows, openTxn or commit outstanding txn instead
   272  			// of reading.
   273  			if ns.availableRows < 1 {
   274  				s := state.(stateRunning)
   275  				if s.TxnOpen.Get() {
   276  					return eventCommit{}, nil
   277  				}
   278  				return eventOpenTxn{}, nil
   279  			}
   280  			return eventFeedMessage{}, nil
   281  		}
   282  		if e, ok := event.(eventAddColumn); ok {
   283  			e.CanAddColumnAfter = fsm.FromBool(ns.currentTestColumnCount < ns.maxTestColumnCount-1)
   284  			return e, nil
   285  		}
   286  		if e, ok := event.(eventRemoveColumn); ok {
   287  			e.CanRemoveColumnAfter = fsm.FromBool(ns.currentTestColumnCount > 1)
   288  			return e, nil
   289  		}
   290  		return event, nil
   291  	}
   292  
   293  	panic(`unreachable`)
   294  }
   295  
   296  type stateRunning struct {
   297  	FeedPaused      fsm.Bool
   298  	TxnOpen         fsm.Bool
   299  	CanRemoveColumn fsm.Bool
   300  	CanAddColumn    fsm.Bool
   301  }
   302  type stateDone struct{}
   303  
   304  func (stateRunning) State() {}
   305  func (stateDone) State()    {}
   306  
   307  type eventOpenTxn struct{}
   308  type eventFeedMessage struct{}
   309  type eventPause struct{}
   310  type eventResume struct{}
   311  type eventCommit struct{}
   312  type eventPush struct{}
   313  type eventAbort struct{}
   314  type eventRollback struct{}
   315  type eventSplit struct{}
   316  type eventAddColumn struct {
   317  	CanAddColumnAfter fsm.Bool
   318  }
   319  type eventRemoveColumn struct {
   320  	CanRemoveColumnAfter fsm.Bool
   321  }
   322  type eventFinished struct{}
   323  
   324  func (eventOpenTxn) Event()      {}
   325  func (eventFeedMessage) Event()  {}
   326  func (eventPause) Event()        {}
   327  func (eventResume) Event()       {}
   328  func (eventCommit) Event()       {}
   329  func (eventPush) Event()         {}
   330  func (eventAbort) Event()        {}
   331  func (eventRollback) Event()     {}
   332  func (eventSplit) Event()        {}
   333  func (eventAddColumn) Event()    {}
   334  func (eventRemoveColumn) Event() {}
   335  func (eventFinished) Event()     {}
   336  
   337  var stateTransitions = fsm.Pattern{
   338  	stateRunning{
   339  		FeedPaused:      fsm.Var("FeedPaused"),
   340  		TxnOpen:         fsm.Var("TxnOpen"),
   341  		CanAddColumn:    fsm.Var("CanAddColumn"),
   342  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   343  	}: {
   344  		eventSplit{}: {
   345  			Next: stateRunning{
   346  				FeedPaused:      fsm.Var("FeedPaused"),
   347  				TxnOpen:         fsm.Var("TxnOpen"),
   348  				CanAddColumn:    fsm.Var("CanAddColumn"),
   349  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   350  			Action: logEvent(split),
   351  		},
   352  		eventFinished{}: {
   353  			Next:   stateDone{},
   354  			Action: logEvent(cleanup),
   355  		},
   356  	},
   357  	stateRunning{
   358  		FeedPaused:      fsm.Var("FeedPaused"),
   359  		TxnOpen:         fsm.False,
   360  		CanAddColumn:    fsm.True,
   361  		CanRemoveColumn: fsm.Any,
   362  	}: {
   363  		eventAddColumn{
   364  			CanAddColumnAfter: fsm.Var("CanAddColumnAfter"),
   365  		}: {
   366  			Next: stateRunning{
   367  				FeedPaused:      fsm.Var("FeedPaused"),
   368  				TxnOpen:         fsm.False,
   369  				CanAddColumn:    fsm.Var("CanAddColumnAfter"),
   370  				CanRemoveColumn: fsm.True},
   371  			Action: logEvent(addColumn),
   372  		},
   373  	},
   374  	stateRunning{
   375  		FeedPaused:      fsm.Var("FeedPaused"),
   376  		TxnOpen:         fsm.False,
   377  		CanAddColumn:    fsm.Any,
   378  		CanRemoveColumn: fsm.True,
   379  	}: {
   380  		eventRemoveColumn{
   381  			CanRemoveColumnAfter: fsm.Var("CanRemoveColumnAfter"),
   382  		}: {
   383  			Next: stateRunning{
   384  				FeedPaused:      fsm.Var("FeedPaused"),
   385  				TxnOpen:         fsm.False,
   386  				CanAddColumn:    fsm.True,
   387  				CanRemoveColumn: fsm.Var("CanRemoveColumnAfter")},
   388  			Action: logEvent(removeColumn),
   389  		},
   390  	},
   391  	stateRunning{
   392  		FeedPaused:      fsm.False,
   393  		TxnOpen:         fsm.False,
   394  		CanAddColumn:    fsm.Var("CanAddColumn"),
   395  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   396  	}: {
   397  		eventFeedMessage{}: {
   398  			Next: stateRunning{
   399  				FeedPaused:      fsm.False,
   400  				TxnOpen:         fsm.False,
   401  				CanAddColumn:    fsm.Var("CanAddColumn"),
   402  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   403  			Action: logEvent(noteFeedMessage),
   404  		},
   405  	},
   406  	stateRunning{
   407  		FeedPaused:      fsm.Var("FeedPaused"),
   408  		TxnOpen:         fsm.False,
   409  		CanAddColumn:    fsm.Var("CanAddColumn"),
   410  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   411  	}: {
   412  		eventOpenTxn{}: {
   413  			Next: stateRunning{
   414  				FeedPaused:      fsm.Var("FeedPaused"),
   415  				TxnOpen:         fsm.True,
   416  				CanAddColumn:    fsm.Var("CanAddColumn"),
   417  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   418  			Action: logEvent(openTxn),
   419  		},
   420  	},
   421  	stateRunning{
   422  		FeedPaused:      fsm.Var("FeedPaused"),
   423  		TxnOpen:         fsm.True,
   424  		CanAddColumn:    fsm.Var("CanAddColumn"),
   425  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   426  	}: {
   427  		eventCommit{}: {
   428  			Next: stateRunning{
   429  				FeedPaused:      fsm.Var("FeedPaused"),
   430  				TxnOpen:         fsm.False,
   431  				CanAddColumn:    fsm.Var("CanAddColumn"),
   432  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   433  			Action: logEvent(commit),
   434  		},
   435  		eventRollback{}: {
   436  			Next: stateRunning{
   437  				FeedPaused:      fsm.Var("FeedPaused"),
   438  				TxnOpen:         fsm.False,
   439  				CanAddColumn:    fsm.Var("CanAddColumn"),
   440  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   441  			Action: logEvent(rollback),
   442  		},
   443  		eventAbort{}: {
   444  			Next: stateRunning{
   445  				FeedPaused:      fsm.Var("FeedPaused"),
   446  				TxnOpen:         fsm.True,
   447  				CanAddColumn:    fsm.Var("CanAddColumn"),
   448  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   449  			Action: logEvent(abort),
   450  		},
   451  		eventPush{}: {
   452  			Next: stateRunning{
   453  				FeedPaused:      fsm.Var("FeedPaused"),
   454  				TxnOpen:         fsm.True,
   455  				CanAddColumn:    fsm.Var("CanAddColumn"),
   456  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   457  			Action: logEvent(push),
   458  		},
   459  	},
   460  	stateRunning{
   461  		FeedPaused:      fsm.False,
   462  		TxnOpen:         fsm.Var("TxnOpen"),
   463  		CanAddColumn:    fsm.Var("CanAddColumn"),
   464  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   465  	}: {
   466  		eventPause{}: {
   467  			Next: stateRunning{
   468  				FeedPaused:      fsm.True,
   469  				TxnOpen:         fsm.Var("TxnOpen"),
   470  				CanAddColumn:    fsm.Var("CanAddColumn"),
   471  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   472  			Action: logEvent(pause),
   473  		},
   474  	},
   475  	stateRunning{
   476  		FeedPaused:      fsm.True,
   477  		TxnOpen:         fsm.Var("TxnOpen"),
   478  		CanAddColumn:    fsm.Var("CanAddColumn"),
   479  		CanRemoveColumn: fsm.Var("CanRemoveColumn"),
   480  	}: {
   481  		eventResume{}: {
   482  			Next: stateRunning{
   483  				FeedPaused:      fsm.False,
   484  				TxnOpen:         fsm.Var("TxnOpen"),
   485  				CanAddColumn:    fsm.Var("CanAddColumn"),
   486  				CanRemoveColumn: fsm.Var("CanRemoveColumn")},
   487  			Action: logEvent(resume),
   488  		},
   489  	},
   490  }
   491  
   492  var compiledStateTransitions = fsm.Compile(stateTransitions)
   493  
   494  func logEvent(fn func(fsm.Args) error) func(fsm.Args) error {
   495  	return func(a fsm.Args) error {
   496  		if log.V(1) {
   497  			log.Infof(a.Ctx, "%#v\n", a.Event)
   498  		}
   499  		return fn(a)
   500  	}
   501  }
   502  
   503  func cleanup(a fsm.Args) error {
   504  	if txn := a.Extended.(*nemeses).txn; txn != nil {
   505  		return txn.Rollback()
   506  	}
   507  	return nil
   508  }
   509  
   510  func openTxn(a fsm.Args) error {
   511  	ns := a.Extended.(*nemeses)
   512  
   513  	const noDeleteSentinel = int(-1)
   514  	// 10% of the time attempt a DELETE.
   515  	deleteID := noDeleteSentinel
   516  	if rand.Intn(10) == 0 {
   517  		rows, err := ns.db.Query(`SELECT id FROM foo ORDER BY random() LIMIT 1`)
   518  		if err != nil {
   519  			return err
   520  		}
   521  		defer func() { _ = rows.Close() }()
   522  		if rows.Next() {
   523  			if err := rows.Scan(&deleteID); err != nil {
   524  				return err
   525  			}
   526  		}
   527  		// If there aren't any rows, skip the DELETE this time.
   528  	}
   529  
   530  	txn, err := ns.db.Begin()
   531  	if err != nil {
   532  		return err
   533  	}
   534  	if deleteID == noDeleteSentinel {
   535  		if err := txn.QueryRow(
   536  			`UPSERT INTO foo VALUES ((random() * $1)::int, cluster_logical_timestamp()::string) RETURNING id, ts`,
   537  			ns.rowCount,
   538  		).Scan(&ns.openTxnID, &ns.openTxnTs); err != nil {
   539  			return err
   540  		}
   541  		ns.openTxnType = openTxnTypeUpsert
   542  	} else {
   543  		if err := txn.QueryRow(
   544  			`DELETE FROM foo WHERE id = $1 RETURNING id, ts`, deleteID,
   545  		).Scan(&ns.openTxnID, &ns.openTxnTs); err != nil {
   546  			return err
   547  		}
   548  		ns.openTxnType = openTxnTypeDelete
   549  	}
   550  	ns.txn = txn
   551  	return nil
   552  }
   553  
   554  func commit(a fsm.Args) error {
   555  	ns := a.Extended.(*nemeses)
   556  	defer func() { ns.txn = nil }()
   557  	if err := ns.txn.Commit(); err != nil {
   558  		// Don't error out if we got pushed, but don't increment availableRows no
   559  		// matter what error was hit.
   560  		if strings.Contains(err.Error(), `restart transaction`) {
   561  			return nil
   562  		}
   563  	}
   564  	ns.availableRows++
   565  	return nil
   566  }
   567  
   568  func rollback(a fsm.Args) error {
   569  	ns := a.Extended.(*nemeses)
   570  	defer func() { ns.txn = nil }()
   571  	return ns.txn.Rollback()
   572  }
   573  
   574  func addColumn(a fsm.Args) error {
   575  	ns := a.Extended.(*nemeses)
   576  
   577  	if ns.currentTestColumnCount >= ns.maxTestColumnCount {
   578  		return errors.AssertionFailedf(`addColumn should be called when`+
   579  			`there are less than %d columns.`, ns.maxTestColumnCount)
   580  	}
   581  
   582  	if _, err := ns.db.Exec(fmt.Sprintf(`ALTER TABLE foo ADD COLUMN test%d STRING DEFAULT 'x'`,
   583  		ns.currentTestColumnCount)); err != nil {
   584  		return err
   585  	}
   586  	ns.currentTestColumnCount++
   587  	var rows int
   588  	// Adding a column should trigger a full table scan.
   589  	if err := ns.db.QueryRow(`SELECT count(*) FROM foo`).Scan(&rows); err != nil {
   590  		return err
   591  	}
   592  	// We expect one table scan that corresponds to the schema change backfill, and one
   593  	// scan that corresponds to the changefeed level backfill.
   594  	ns.availableRows += 2 * rows
   595  	return nil
   596  }
   597  
   598  func removeColumn(a fsm.Args) error {
   599  	ns := a.Extended.(*nemeses)
   600  
   601  	if ns.currentTestColumnCount == 0 {
   602  		return errors.AssertionFailedf(`removeColumn should be called with` +
   603  			`at least one test column.`)
   604  	}
   605  	if _, err := ns.db.Exec(fmt.Sprintf(`ALTER TABLE foo DROP COLUMN test%d`,
   606  		ns.currentTestColumnCount-1)); err != nil {
   607  		return err
   608  	}
   609  	ns.currentTestColumnCount--
   610  	var rows int
   611  	// Dropping a column should trigger a full table scan.
   612  	if err := ns.db.QueryRow(`SELECT count(*) FROM foo`).Scan(&rows); err != nil {
   613  		return err
   614  	}
   615  	// We expect one table scan that corresponds to the schema change backfill, and one
   616  	// scan that corresponds to the changefeed level backfill.
   617  	ns.availableRows += 2 * rows
   618  	return nil
   619  }
   620  
   621  func noteFeedMessage(a fsm.Args) error {
   622  	ns := a.Extended.(*nemeses)
   623  
   624  	if ns.availableRows <= 0 {
   625  		return errors.AssertionFailedf(`noteFeedMessage should be called with at` +
   626  			`least one available row.`)
   627  	}
   628  	m, err := ns.f.Next()
   629  	if err != nil {
   630  		return err
   631  	} else if m == nil {
   632  		return errors.Errorf(`expected another message`)
   633  	}
   634  
   635  	if len(m.Resolved) > 0 {
   636  		_, ts, err := ParseJSONValueTimestamps(m.Resolved)
   637  		if err != nil {
   638  			return err
   639  		}
   640  		log.Infof(a.Ctx, "%v", string(m.Resolved))
   641  		return ns.v.NoteResolved(m.Partition, ts)
   642  	}
   643  	ts, _, err := ParseJSONValueTimestamps(m.Value)
   644  	if err != nil {
   645  		return err
   646  	}
   647  
   648  	ns.availableRows--
   649  	log.Infof(a.Ctx, "%s->%s", m.Key, m.Value)
   650  	return ns.v.NoteRow(m.Partition, string(m.Key), string(m.Value), ts)
   651  }
   652  
   653  func pause(a fsm.Args) error {
   654  	return a.Extended.(*nemeses).f.Pause()
   655  }
   656  
   657  func resume(a fsm.Args) error {
   658  	return a.Extended.(*nemeses).f.Resume()
   659  }
   660  
   661  func abort(a fsm.Args) error {
   662  	ns := a.Extended.(*nemeses)
   663  	const delete = `BEGIN TRANSACTION PRIORITY HIGH; ` +
   664  		`SELECT count(*) FROM [DELETE FROM foo RETURNING *]; ` +
   665  		`COMMIT`
   666  	var deletedRows int
   667  	if err := ns.db.QueryRow(delete).Scan(&deletedRows); err != nil {
   668  		return err
   669  	}
   670  	ns.availableRows += deletedRows
   671  	return nil
   672  }
   673  
   674  func push(a fsm.Args) error {
   675  	ns := a.Extended.(*nemeses)
   676  	_, err := ns.db.Exec(`BEGIN TRANSACTION PRIORITY HIGH; SELECT * FROM foo; COMMIT`)
   677  	return err
   678  }
   679  
   680  func split(a fsm.Args) error {
   681  	ns := a.Extended.(*nemeses)
   682  	_, err := ns.db.Exec(`ALTER TABLE foo SPLIT AT VALUES ((random() * $1)::int)`, ns.rowCount)
   683  	return err
   684  }