Skip to content

Host engine: schema DDL runs once per process, so a second engine's table is never created #103

Description

@zaoxing

Summary

ClickHouseInsertStage guards schema creation with a process-global
std::once_flag, so DDL runs exactly once per process — for whichever engine
starts first. Every later DMXHostEngine in the same process that targets a
different database/table silently skips CREATE DATABASE/CREATE TABLE and
then dies on the first real INSERT.

native/csrc/clickhouse_client.cpp:30

std::once_flag g_schema_once;

native/csrc/clickhouse_client.cpp:419-420

// DDL init once globally (copy cfg/opts into the call_once closure)
std::call_once(g_schema_once, [cfg, opts]() { RunSchemaInitOnce(cfg, opts); });

The closure captures the first engine's cfg, so the flag is keyed to the
process rather than to the target table.

Reproduction

Three engines in one process, each with its own table, payloads large enough
to force an actual INSERT (a small run hides the bug because no batch flushes):

engine_1: submitted all | no worker failure
engine_2: submit aborted at 6181 | DB::Exception: Table dmi_bug2.engine_2 does not exist
engine_3: submit aborted at 2265 | DB::Exception: Table dmi_bug2.engine_3 does not exist

SELECT name FROM system.tables WHERE database='dmi_bug2'  ->  engine_1

Verified against a live ClickHouse 26.9.1 with the CPU-only _host_backend
build (make host).

Impact

  • Any process creating a second host engine against a different table fails.
  • The failure surfaces late and confusingly, as a worker exception during
    insert rather than at engine construction.
  • ThreadExceptionPolicy::STOP_ENGINE then aborts the engine, so subsequent
    submit_direct() calls raise Engine is stopping/aborted.
  • This is very likely why the benchmark suite in Add CPU-only ClickHouse host build and benchmark suite #102 needs subprocess
    isolation — it works around this rather than fixing it.

Suggested fix

Key the guard to the resolved target instead of the process — e.g. a
std::mutex plus a std::set<std::string> of already-initialised
database.table pairs, or move the DDL into per-thread stage init keyed by
config. A process-wide call_once cannot express "once per table".

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions