Skip to content

native: analyse the tic statements with optimize_and_compare_chain = 0 - #598

Merged
MarcusKainth merged 1 commit into
mainfrom
native/and-compare-chain-setting
Sep 25, 2026
Merged

MarcusKainth merged 1 commit into
mainfrom
native/and-compare-chain-setting

Conversation

@MarcusKainth

Copy link
Copy Markdown
Owner

What this changes, and why

Closes #581.

The resident sessions and the tic statements a test issues both send optimize_and_compare_chain = 0. The value lives in one place, ANALYSIS_SETTINGS in native/src/resident/settings.rs. resident_settings appends it, and stage1_over and run_statement in tick.rs attach it with Statement::with. That covers run_statement, demo_statement and the bench cuts, because all of them build their first statement through stage1_over.

Statement::with appends to a statement's settings instead of replacing them, so a tic statement carries its parse settings and the analysis settings together. Its only callers are in tick.rs.

NATIVE.md takes the contract text from the issue as written.

Evidence

All live runs are against a throwaway clickhouse/clickhouse-server:26.8.2.7 container on port 18140, on an Apple M5 Max. The "without" binary is this commit with the five settings.extend(ANALYSIS_SETTINGS...) lines removed from resident_settings, rebuilt and then restored with git checkout HEAD --. That means the only difference between the two resident sessions is the setting. The statement text is the same in both.

The unit test fails when either path loses the setting

a_test_s_statements_are_analysed_as_a_session_s_are in tick.rs. The test was committed first, then one path was broken at a time, the test was run, and the file was restored with git checkout HEAD --.

The .with(&ANALYSIS_SETTINGS) removed from the second statement in run_statement:

$ cargo test -p clickdoom-native --lib a_test_s_statements; echo "exit=$?"
thread 'sql::sim::tick::tests::a_test_s_statements_are_analysed_as_a_session_s_are' panicked at native/src/sql/sim/tick.rs:630:13:
the second statement run_statement builds does not carry ("optimize_and_compare_chain", "0")
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 325 filtered out; finished in 0.43s
exit=101

The settings.extend(...) removed from resident_settings:

$ cargo test -p clickdoom-native --lib a_test_s_statements; echo "exit=$?"
thread 'sql::sim::tick::tests::a_test_s_statements_are_analysed_as_a_session_s_are' panicked at native/src/sql/sim/tick.rs:625:9:
a resident session does not send ("optimize_and_compare_chain", "0")
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 325 filtered out; finished in 0.00s
exit=101

A 300-tic resident walk writes the same rows with and without the setting

The probe trace comes from make gen-probe-trace at this commit (# wrote 2172 rows over 2172 frame commits, exit 0). native load --fresh and native load --probe both exited 0. Each arm then ran native diff 300 --probe <trace> --record <file>. Each run exits 3 at the refused tic 275, but only after it has fed and written all 300 tics. After each run, native_state and native_stage were copied into MergeTree snapshots.

native diff 300 (with) exit=3
clickdoom: error: tic 275 unresolved: CHASE_STUCK
snap_native_state_with: 301	0	300
snap_native_stage_with: 300	1	300
native diff 300 (without) exit=3
clickdoom: error: tic 275 unresolved: CHASE_STUCK
snap_native_state_without: 301	0	300
snap_native_stage_without: 300	1	300

The comparison takes each (tic, column) of the two snapshots, joined on tic, and compares cityHash64 of the two cells. The column list is built from system.columns: 148 columns for native_state and 151 for native_stage, which match the live tables. The positive control copies the "without" snapshot and adds 1 to leveltime at tic 150.

native_state: with vs without
rows_a: 301  rows_b: 301  cells: 44548  tics: 301  columns: 148  differing_cells: 0  first_differing: []
native_stage: with vs without
rows_a: 300  rows_b: 300  cells: 45300  tics: 300  columns: 151  differing_cells: 0  first_differing: []
native_state: with vs control
rows_a: 301  rows_b: 301  cells: 44548  tics: 301  columns: 148  differing_cells: 1  first_differing: [(150,'leveltime')]
native_stage: with vs control
rows_a: 300  rows_b: 300  cells: 45300  tics: 300  columns: 151  differing_cells: 1  first_differing: [(150,'leveltime')]

(The output is FORMAT Vertical, folded here onto one line per comparison.)

Parity against the probe trace

This is the committed binary against the same full trace:

native diff 274 exit=0
no divergence: every field agrees over the 273 tics both sides hold
native diff 283 exit=3
clickdoom: error: tic 275 unresolved: CHASE_STUCK

The "without" walk above refuses at 275 with the same unresolved: CHASE_STUCK bits.

The setting appears in system.query_log for both resident query ids

These are the QueryFinish rows of the two walks. Pid 1034 is the "with" binary and pid 1608 is the "without" binary.

   ┌─query_id─────────────────────┬─optimize_and_compare_chain─┬─analysis_s─┐
1. │ clickdoom_native-sim-1034-0  │ 0                          │      0.955 │
2. │ clickdoom_native-sim2-1034-1 │ 0                          │      0.157 │
3. │ clickdoom_native-sim-1608-0  │                            │      1.198 │
4. │ clickdoom_native-sim2-1608-1 │                            │      0.351 │
   └──────────────────────────────┴────────────────────────────┴────────────┘

The --record lines give the same analysis times: stage1_analysis_s 0.955 and stage2_analysis_s 0.157 with the setting, 1.198 and 0.351 without. These are single runs taken outside the machine lock.

What stage1's analysis costs with and without the setting

The machine lock was taken with scripts/machine-lock.sh acquire --force andchain "owner approved running under background load on 18 cores" (exit 0). The runs use a throwaway live test, not committed. It walks the level to tic 19 through a resident session. It then issues tick::bench::stage1(db, None, &[Input::demo(20)]) ten times, interleaved ABBA. The "with" arm is the statement as built here. The "without" arm is the same Statement with optimize_and_compare_chain removed from its settings. Each figure is ProfileEvents['QueryAnalysisMicroseconds'] from system.query_log, together with the Settings map value the server logged and the 1-minute load average before and after the run. No run was above 8, so none was discarded.

start load: { 1.60 3.84 3.00 }
timing test exit=0
run 0 arm with     settings_map=0   analysis_us   944313 load1 1.58 -> 1.58
run 1 arm without  settings_map=-   analysis_us  1187105 load1 1.58 -> 2.34
run 2 arm without  settings_map=-   analysis_us  1151932 load1 2.34 -> 2.39
run 3 arm with     settings_map=0   analysis_us   939245 load1 2.39 -> 2.39
run 4 arm with     settings_map=0   analysis_us   956156 load1 2.39 -> 2.36
run 5 arm without  settings_map=-   analysis_us  1143529 load1 2.36 -> 2.33
run 6 arm without  settings_map=-   analysis_us  1101787 load1 2.33 -> 2.38
run 7 arm with     settings_map=0   analysis_us   891564 load1 2.38 -> 2.38
run 8 arm with     settings_map=0   analysis_us   914514 load1 2.38 -> 2.27
run 9 arm without  settings_map=-   analysis_us  1132263 load1 2.27 -> 2.33
end load: { 2.33 3.73 3.00 }
runs (s) median
as sent on main 1.187, 1.152, 1.144, 1.102, 1.132 1.14 s
optimize_and_compare_chain = 0 0.944, 0.939, 0.956, 0.892, 0.915 0.94 s

The setting saves about 0.2 s per analysis. The issue measured about 0.3 s from a higher baseline of 1.32 s, at a load of 3.3 to 7.8.

Unit tests and lint

These were run on the committed tree after the scratch test was deleted:

$ make lint ...; echo "make lint exit=$?"
make lint exit=0
$ cargo test -p clickdoom-native -p clickdoom-driver ...; echo "cargo test exit=$?"
cargo test exit=0
passed 493 failed 0   (summed over the 75 "test result" lines)
test sql::sim::tick::tests::a_test_s_statements_are_analysed_as_a_session_s_are ... ok

Invariants

None. The change adds one server setting, which affects how a statement is analysed. No computation moves out of ClickHouse, and the statement text is unchanged. The walk comparison above shows every cell is the same with and without the setting.

Spec impact

Checks

  • make gates: not run. The checks run instead were make lint, the unit tests of clickdoom-native and clickdoom-driver, and the live runs above. The live suites were not run.
  • make native-smoke: not run. native diff 274 against the full probe trace exercises the simulation. The renderer's statement also runs under resident_settings, and no render run was made.
  • No AI attribution trailers in the commits

Anything else

The render resident also takes resident_settings, so it now runs with the setting too. No frame comparison was made with and without the setting.

Written mostly by Claude Opus 5.5.

With each comparison in an AND or OR chain wrapped in identity(), the
first tic statement still spends part of its analysis in
LogicalExpressionOptimizerVisitor::tryOptimizeAndCompareChain, which
infers comparisons from chains such as x = y AND y = 5. The setting
optimize_and_compare_chain gates it. Turning it off takes the bench
stage1 analysis from a median of 1.14 s to 0.94 s on 26.8.2.7 (five
interleaved runs each), paid once per resident session and once per
statement pair a test issues.

The resident sessions send it through resident_settings, and the
statements run_statement, demo_statement and the bench cuts build carry
it as a URL setting, both from one ANALYSIS_SETTINGS constant, so a
test's analysis matches a session's. Statement::with adds to the
settings a statement already has instead of replacing them, so a tic
statement carries both its parse settings and the analysis settings.

NATIVE.md lists the setting beside the others a resident sends. The
rows are unchanged: a 300-tic resident walk with and without the setting
writes identical native_state and native_stage cells, and native diff
274 still agrees with the probe trace over 273 tics.

Closes #581.
@github-actions github-actions Bot added area: docs The prose: READMEs, ADRs, and the contributor documents area: native Native mode: the tic simulation and renderer as SQL, and the WAD loader labels Sep 25, 2026
@MarcusKainth
MarcusKainth marked this pull request as ready for review September 25, 2026 14:58
@MarcusKainth
MarcusKainth merged commit 9b4c737 into main Sep 25, 2026
20 checks passed
@MarcusKainth
MarcusKainth deleted the native/and-compare-chain-setting branch September 25, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs The prose: READMEs, ADRs, and the contributor documents area: native Native mode: the tic simulation and renderer as SQL, and the WAD loader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: send optimize_and_compare_chain = 0 with the resident statements

1 participant