feat: ClickHouse cluster sharding by entity_id (Scale Architecture §2)
Open-source Excel-native EPM and consolidation for SAP & Dynamics
Brought to you by:
konsolid-at
Originally created by: grynn-in
Closes [#53]
Implements Scale Architecture §2 — ClickHouse cluster sharding by entity_id.
This is a BUILD-ONLY PR: the single-node default is unchanged and dbt parse
passes. End-to-end cluster correctness requires a real multi-node setup and is
explicitly called out below.
| Check | Result |
|---|---|
dbt parse |
✅ passes — no ClickHouse connection needed |
| Default engine values unchanged | ✅ cluster_engine('MergeTree()') → 'MergeTree()'; cluster_engine('ReplacingMergeTree(x)') → 'ReplacingMergeTree(x)' |
cluster_name() default |
✅ returns None → no ON CLUSTER DDL in single-node mode |
cluster_enabled var default |
✅ false — manifest confirms engine/cluster fields identical to pre-PR values |
| Three opted-in models | ✅ SELECT query body unchanged; only config header updated |
| Single-node deploy path | ✅ unaffected — all macros are pass-through when cluster_enabled=false |
ON CLUSTER DDL correctness on real nodesReplicatedMergeTree ZooKeeper/Keeper path expansion and replica syncDistributed table routing (cityHash64(entity_id) → correct shard)dbt run-operation create_distributed_tables executiongold_trial_balance, gold_consolidated_trial_balance, IC eliminations, NCI)cityHash64)dbt_project/macros/cluster.sql (new)Core opt-in macros — all return unchanged values when cluster_enabled=false:
| Macro | Purpose |
|---|---|
cluster_enabled() |
reads var('cluster_enabled', false) |
cluster_name() |
'konsol_cluster' or none |
cluster_engine(base) |
converts MergeTree → ReplicatedMergeTree family |
cluster_sharding_key() |
cityHash64(entity_id) |
create_distributed_tables() |
run-operation: creates Distributed overlays over _local tables |
drop_distributed_tables() |
run-operation: idempotent teardown |
dbt_project.yml: cluster_enabled: false var (default OFF, documented)profiles.yml: new cluster target with cluster: konsol_clusterbronze_general_journal_account_entries — highest-volume GL entriesbronze_general_journal_entries — journal headersgold_trial_balance — primary gold query surfaceclickhouse/cluster/remote_servers.xml — 3-shard konsol_cluster topologyclickhouse/cluster/keeper.xml — embedded ClickHouse Keeper + ZK-compat clientclickhouse/cluster/macros-shard1-replica1.xml — per-node {shard} / {replica} macrosdocker-compose.cluster.yml — 3-node cluster compose overlaydocs/admin-guide/cluster-setup.md (new) — step-by-step setup procedure, model opt-in pattern, open questions (hot-shard risk, rebalancing, cross-shard aggregation, Cube.js)docs/prd/PRD-SCALE-ARCHITECTURE.md §2 — implementation status table, VERIFIED / NEEDS-A-CLUSTER callout# Build with cluster engines + ON CLUSTER DDL
dbt build --target cluster --vars '{"cluster_enabled": true}'
# Create Distributed overlay tables
dbt run-operation create_distributed_tables --vars '{"cluster_enabled": true}'
cityHash64(entity_id) — composite key (entity_id, fiscal_year) may be neededGLOBAL IN/GLOBAL JOIN requirements for IC eliminations and NCI aggregations on Distributed readsGenerated by Claude Code
Originally posted by: pyy3
🔍 Review round (2 passes: no-regression correctness + design/docs/tests)
The single-node default path is provably SAFE — verified against dbt-clickhouse 1.10:
cluster_enableddefaults false and never leaks true;cluster_engine()returns its input verbatim when off;cluster_name()→none ⇒ noON CLUSTER; profilesdev/prodbyte-identical; touched models' SELECT bodies unchanged. Merging is safe for today's deploy.But the cluster feature as written would NOT run on a real cluster:
BLOCKER — shard key
cityHash64(entity_id)references a column that doesn't exist. Bronze renamesentity_id→data_area_id; no materialized bronze/silver/gold table outputsentity_id(it's only in the unmaterializedstaging/canonical/*).create_distributed_tableswould fail at the firstCREATE ... Distributed(..., cityHash64(entity_id)). The PR's own admin-guide Step 7 verify usesWHERE data_area_id = 'USSI'— contradicting theentity_idkey. Fix: shard ondata_area_ideverywhere.MAJOR — model-level
config(cluster=...)is dead under dbt-clickhouse 1.10 (it drivesON CLUSTERfrom the profilecluster:key, not model config); plainincremental/tablemakes no_localtable, so the run-operation that assumes<model>_localis inconsistent. Recommend the adapter's nativedistributed_table/distributed_incrementalmaterializations.MAJOR — create/drop lists diverge (55 vs 26) and ~52 of 55 have no
_localbacking (only 3 models are cluster-aware). Derive from the graph / aclustertag.MAJOR — "default unchanged" is prose, never codified. Add a unit/run-operation self-check of
cluster_engine()pass-through with the flag off.MINOR —
keeper.xmlhardcodesserver_id=1but compose mounts it on all nodes → broken quorum;macros-shard{2,3}referenced but absent.I'll fix the unambiguous bugs (shard key →
data_area_id, keeperserver_id, create/drop symmetry, codified default-unchanged test) and flag the materialization rework as the real path to a working cluster — that part needs a multi-node cluster to validate.Originally posted by: grynn-in
✅ Fixes applied (b3e0523); verified
dbt parseOK + both run-operations no-op cleanly with cluster off (single-node default unchanged):data_area_id(the LE column materialized across bronze/silver/gold) instead of the non-existententity_id. Updated macro defaults,remote_servers.xml, compose, and the docs/AC verification queries.cluster_sharded_tables()source of truth, scoped to the 3 actually-cluster-aware models (those carryingcluster_engine/cluster_name→ dbt builds a_localtable). This fixes both the 55-vs-26 divergence and the "CREATE over a non-existent_local" failure for the ~52 non-cluster-aware tables.Still NEEDS-A-CLUSTER (deferred to your live verification — can't be done/validated here):
config(cluster=...)is inert under dbt-clickhouse 1.10 (it drivesON CLUSTERfrom the profilecluster:key); plainincremental/tablemakes no_localsuffix. The clean path is the adapter's nativedistributed_table/distributed_incrementalmaterializations — flagged in the macro + PRD §2. This needs a real cluster to design+validate.server_id—keeper.xmlhardcodesserver_id=1while compose mounts it on all nodes → broken quorum; needs per-node config/templating.Recommendation: the single-node default is provably safe to merge, but do not enable cluster mode until B2 + Keeper are reworked against a real multi-node cluster.
Ticket changed by: grynn-in