Description
com.clickhouse.jdbc.ClickHouseStatementTest.testAsyncInsert fails intermittently in the
JDBC driver + CH 26.3 (apache_http_client) and JDBC driver + CH latest (apache_http_client)
integration jobs. It has been red on unrelated PRs since about 2026-08-04.
The second half of the test (clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseStatementTest.java:541-549)
inserts one row with async_insert=1,wait_for_async_insert=0 and then asserts that the row is
not yet queryable. That only holds while the server has not flushed its async-insert buffer,
so the test depends on a server timeout it does not control. The test's own message admits it
("Server was probably busy at that time"), and the //TODO: I'm not sure this is a valid test...
comment on line 539 predicts exactly this.
Steps to reproduce
- Run the single test against a recent server, on an idle machine: it passes.
- Run it again while the machine is under CPU load (as a CI runner is):
mvn -B -pl clickhouse-jdbc -DskipUTs=true -Dit.test=ClickHouseStatementTest#testAsyncInsert -Dfailsafe.failIfNoSpecifiedTests=false verify
- Repeat step 2 a few times. It fails in a large fraction of runs.
Measured on ClickHouse 26.7.3.19, 4 vCPU:
- idle: 0 failures in 6 runs
- with 12 busy-loop processes on 4 vCPU: 3 failures in 5 runs, with the identical assertion CI reports
Error Log or Exception StackTrace
[ERROR] com.clickhouse.jdbc.ClickHouseStatementTest.testAsyncInsert -- Time elapsed: 0.237 s <<< FAILURE!
[ERROR] ClickHouseStatementTest.testAsyncInsert:548 Server was probably busy at that time, so the row was inserted before your query expected [false] but found [true]
[ERROR] Tests run: 231, Failures: 1, Errors: 0, Skipped: 0
230 of 231 tests pass in every observed failure; only testAsyncInsert fails.
Evidence of non-determinism in CI
Three independent runs, on PRs whose diffs do not touch async insert (JavaCC/ANTLR parser paths
and INSERT ... TABLE FUNCTION routing), and the same check passes on other open PRs:
Mechanism
The server flushes an async-insert buffer after async_insert_busy_timeout_*. On 26.x the
adaptive timeout is on by default and starts at the minimum:
async_insert_use_adaptive_busy_timeout 1
async_insert_busy_timeout_min_ms 50
async_insert_busy_timeout_max_ms 200
So with a single async insert on an otherwise idle server the row becomes visible about 50 ms
after the INSERT. The test's assertion therefore only passes if the driver issues the following
SELECT within that ~50 ms window. That gap is pure client-side latency (statement splitting,
two more HTTP round trips, JIT warm-up) and is not bounded by anything, so on a slow or loaded
runner it exceeds 50 ms and the row is already there.
Reproduced at the protocol level, without the driver, by inserting an equivalent gap
(TRUNCATE / async INSERT / SELECT as three HTTP requests, default settings): the row was
already visible in 24 of 30 runs. Both outcomes occur, which is the flake.
Expected Behaviour
testAsyncInsert should pass deterministically. Verifying that wait_for_async_insert=0
returns before the data is queryable is a reasonable thing to test - but the window has to be
made deterministic instead of relying on the server being slow.
Suggested fix
Pin the buffer open for the duration of the assertion, rather than adding a retry or a sleep,
by extending the custom params on line 541:
props.setProperty(ClickHouseHttpOption.CUSTOM_PARAMS.getKey(),
"async_insert=1,wait_for_async_insert=0"
+ ",async_insert_use_adaptive_busy_timeout=0"
+ ",async_insert_busy_timeout_ms=30000"
+ ",async_insert_busy_timeout_max_ms=30000");
Verified at the protocol level with those settings and a deliberate 500 ms delay before the
SELECT (10x the current window): the row was visible in 0 of 15 runs. The assertion becomes
a real invariant instead of a race.
Optionally, the test could then also prove the other half of the contract - run
SYSTEM FLUSH ASYNC INSERT QUEUE and assert the row does appear - so the test covers both
states deterministically.
Note the first half of the test (wait_for_async_insert=1, lines 524-536) is not affected and
should keep its current behavior.
Configuration
Environment
ClickHouse Server
- ClickHouse Server version: 26.7.3.19 locally; CI fails on 26.3 and on
latest
- Non-default settings: none - the defaults above are what make it race
CREATE TABLE:
CREATE TABLE test_async_insert(id UInt32, s String) ENGINE = MergeTree ORDER BY id;
Found by our PR monitor, which saw this check fail on three unrelated PRs of ours, and then
verified locally: it fails 3 of 5 runs under CPU load and 0 of 6 when idle.
Description
com.clickhouse.jdbc.ClickHouseStatementTest.testAsyncInsertfails intermittently in theJDBC driver + CH 26.3 (apache_http_client)andJDBC driver + CH latest (apache_http_client)integration jobs. It has been red on unrelated PRs since about 2026-08-04.
The second half of the test (
clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseStatementTest.java:541-549)inserts one row with
async_insert=1,wait_for_async_insert=0and then asserts that the row isnot yet queryable. That only holds while the server has not flushed its async-insert buffer,
so the test depends on a server timeout it does not control. The test's own message admits it
("Server was probably busy at that time"), and the
//TODO: I'm not sure this is a valid test...comment on line 539 predicts exactly this.
Steps to reproduce
mvn -B -pl clickhouse-jdbc -DskipUTs=true -Dit.test=ClickHouseStatementTest#testAsyncInsert -Dfailsafe.failIfNoSpecifiedTests=false verifyMeasured on ClickHouse 26.7.3.19, 4 vCPU:
Error Log or Exception StackTrace
230 of 231 tests pass in every observed failure; only
testAsyncInsertfails.Evidence of non-determinism in CI
Three independent runs, on PRs whose diffs do not touch async insert (JavaCC/ANTLR parser paths
and
INSERT ... TABLE FUNCTIONrouting), and the same check passes on other open PRs:Mechanism
The server flushes an async-insert buffer after
async_insert_busy_timeout_*. On 26.x theadaptive timeout is on by default and starts at the minimum:
So with a single async insert on an otherwise idle server the row becomes visible about 50 ms
after the INSERT. The test's assertion therefore only passes if the driver issues the following
SELECTwithin that ~50 ms window. That gap is pure client-side latency (statement splitting,two more HTTP round trips, JIT warm-up) and is not bounded by anything, so on a slow or loaded
runner it exceeds 50 ms and the row is already there.
Reproduced at the protocol level, without the driver, by inserting an equivalent gap
(TRUNCATE / async INSERT / SELECT as three HTTP requests, default settings): the row was
already visible in 24 of 30 runs. Both outcomes occur, which is the flake.
Expected Behaviour
testAsyncInsertshould pass deterministically. Verifying thatwait_for_async_insert=0returns before the data is queryable is a reasonable thing to test - but the window has to be
made deterministic instead of relying on the server being slow.
Suggested fix
Pin the buffer open for the duration of the assertion, rather than adding a retry or a sleep,
by extending the custom params on line 541:
Verified at the protocol level with those settings and a deliberate 500 ms delay before the
SELECT(10x the current window): the row was visible in 0 of 15 runs. The assertion becomesa real invariant instead of a race.
Optionally, the test could then also prove the other half of the contract - run
SYSTEM FLUSH ASYNC INSERT QUEUEand assert the row does appear - so the test covers bothstates deterministically.
Note the first half of the test (
wait_for_async_insert=1, lines 524-536) is not affected andshould keep its current behavior.
Configuration
Environment
main@ 601ade1ClickHouse Server
latestCREATE TABLE:Found by our PR monitor, which saw this check fail on three unrelated PRs of ours, and then
verified locally: it fails 3 of 5 runs under CPU load and 0 of 6 when idle.