|
| 1 | +# RQG: grammar-based random query testing |
| 2 | + |
| 3 | +Runs the [MaterializeInc/RQG](https://github.com/MaterializeInc/RQG) fork of |
| 4 | +the Random Query Generator against Materialize. Depending on the workload, |
| 5 | +generated queries are compared against Postgres, against another Materialize |
| 6 | +version, or checked against invariants embedded in the grammar. This finds |
| 7 | +query errors, panics, and (for the comparison workloads) correctness issues. |
| 8 | + |
| 9 | +## Running |
| 10 | + |
| 11 | +```shell |
| 12 | +# All workloads that are not disabled |
| 13 | +bin/mzcompose --find rqg run default |
| 14 | + |
| 15 | +# One workload, with a fixed seed and shorter duration |
| 16 | +bin/mzcompose --find rqg run default subqueries --seed=12345 --duration=300 |
| 17 | + |
| 18 | +# Compare against another Materialize version instead of Postgres |
| 19 | +bin/mzcompose --find rqg run default wmr --other-tag=common-ancestor |
| 20 | +``` |
| 21 | + |
| 22 | +Workloads are defined in `mzcompose.py`. Each pairs a grammar with a dataset |
| 23 | +and a validator: |
| 24 | + |
| 25 | +* `ResultsetComparatorSimplify` executes every query on both servers, |
| 26 | + compares the (sorted) result sets, and on a mismatch automatically shrinks |
| 27 | + the query to a minimal reproducer, printed between |
| 28 | + `RESULT COMPARISON ISSUE START/END` markers. |
| 29 | +* `QueryProperties,RepeatableRead` (banking workload) checks invariant |
| 30 | + assertions embedded in the grammar as `/* RESULTSET_... */` comments and |
| 31 | + re-executes SELECTs inside transactions to check snapshot stability. |
| 32 | + |
| 33 | +## Reproducing a failure |
| 34 | + |
| 35 | +Every run prints its effective integer seed and a ready-to-run repro command |
| 36 | +(`--- Reproduce with: ...`). Re-running with the same seed regenerates the |
| 37 | +same query stream. The seed must reach gentest.pl as a real integer; the |
| 38 | +harness hashes non-integer `--seed` values (such as `$BUILDKITE_JOB_ID`) |
| 39 | +because Perl would otherwise silently numify the string and collapse the |
| 40 | +seed space. |
| 41 | + |
| 42 | +To shrink a reproducer further, `util/simplify-psql.pl` in the RQG repository |
| 43 | +delta-debugs a SQL file against a running Materialize: |
| 44 | + |
| 45 | +```shell |
| 46 | +perl util/simplify-psql.pl --input-file=repro.sql --expected-output='internal error' |
| 47 | +``` |
| 48 | + |
| 49 | +Note that `--sqltrace` is currently only implemented in RQG's MySQL executor, |
| 50 | +so it has no effect here; use the seed for reproduction. |
| 51 | + |
| 52 | +## Where grammars and datasets live |
| 53 | + |
| 54 | +* Grammars and their datasets live in the |
| 55 | + [MaterializeInc/RQG](https://github.com/MaterializeInc/RQG) repository |
| 56 | + under `conf/mz/`, together with the RQG engine, pinned by commit in |
| 57 | + `Dockerfile`. Landing a change there means pushing to that repository and |
| 58 | + bumping the pin. For local iteration, mount your checkout with |
| 59 | + `RQG_CHECKOUT` (see below), which bypasses the pin. |
| 60 | +* `grammars/` and `datasets/` in this directory (the left-join-stacks |
| 61 | + workload) are mounted into the rqg container at `/workdir` and can be |
| 62 | + changed like any other test file, with no image rebuild. |
| 63 | + |
| 64 | +Dataset files are loaded with `psql -v ON_ERROR_STOP=1` into every |
| 65 | +participating server, so they must be idempotent and valid in both the |
| 66 | +Materialize and Postgres dialects (dialect-specific files can be marked |
| 67 | +`Target.POSTGRES_ONLY` in `mzcompose.py`). After loading, the harness asserts |
| 68 | +row-count parity across both servers, so a load that silently diverged fails |
| 69 | +immediately instead of surfacing as a bogus result mismatch later. |
| 70 | + |
| 71 | +Grammar rules that create or drop tables mid-run are a known trap: an init |
| 72 | +rule executes while other workers are already issuing queries, and that race |
| 73 | +produces spurious result differences (this is why the wmr and banking DDL |
| 74 | +lives in dataset files). Also keep result comparison deterministic: total |
| 75 | +ORDER BY when a query's row order matters (the comparator sorts rows, so |
| 76 | +plain ORDER BY correctness is not checked), no float accumulation whose |
| 77 | +result depends on evaluation order, and ORDER BY the aggregated expression |
| 78 | +itself inside STRING_AGG and friends. |
| 79 | + |
| 80 | +## Developing against a local RQG checkout |
| 81 | + |
| 82 | +Set `RQG_CHECKOUT` to mount a local clone of MaterializeInc/RQG over the |
| 83 | +pinned checkout in the image, e.g. to test changes to the RQG library or to |
| 84 | +the `conf/mz/` grammars without rebuilding the image: |
| 85 | + |
| 86 | +```shell |
| 87 | +RQG_CHECKOUT=~/git/rqg bin/mzcompose --find rqg run default banking --duration=60 |
| 88 | +``` |
| 89 | + |
| 90 | +CI always runs the commit pinned in `Dockerfile`; after changing the RQG |
| 91 | +repository, push there and update the pin. |
0 commit comments