.ai/principles/distilled/clickhouse.md
Prerequisite: If you haven't already, also read .ai/principles/distilled/database-fundamentals.md - it contains foundational rules that apply to all database work.
clickhouse:check-schema CI job logs; if it fails, inspect differences carefully and discuss non-whitespace discrepancies with the MR author.clickhouse:check-schema job to fail without investigation — it is not allowed to fail and will block the MR pipeline.pipeline:skip-check-clickhouse-schema label only for confirmed false positives (e.g., ClickHouse version mismatches).db/click_house/main.sql is updated and committed in the MR; if missing, ask the author to run bundle exec rake gitlab:clickhouse:migrate; bundle exec rake gitlab:clickhouse:schema:dump.db/click_house/schema_migrations/ are auto-generated and do not require a newline at the end — do not flag missing newlinesbundle exec rails generate gitlab:click_house:migration MIGRATION_CLASS_NAME rather than creating migration files manually.bundle exec rails generate gitlab:click_house:post_deployment_migration MIGRATION_CLASS_NAME.YYYYMMDDHHMMSS_description_of_migration.rb timestamp prefix and place them in db/click_house/migrate/.up and down methods in every migration.db/click_house/schema_migrations/main/<version> together with the migration — the clickhouse:check-schema CI job fails when a migration is merged without its marker file; if you lack a local ClickHouse instance, run the migration to generate the marker file before committing.create_dictionary (not raw CREATE DICTIONARY) when defining ClickHouse dictionaries in migrations — it injects credentials and prepends the database name to tables automatically.source_tables argument of create_dictionary so the database name is correctly prepended in the QUERY.CLICKHOUSE source referencing the main database for dictionaries; DO NOT reference external dictionary sources.sql = 'SELECT * FROM events WHERE id > {min_id:UInt64}'.ClickHouse::Client::Quoting.quote(...) for fixed string interpolation assigned to Ruby constants to prevent SQL injection.ClickHouse::Client::Query with named placeholders and a placeholders hash for parameterised queries; use the Subquery type to compose nested queries safely.ClickHouse::Client::QueryBuilder for queries with multiple filter conditions instead of concatenating query fragments as strings.INSERT statements; DO NOT build large INSERT queries in memory.CsvBuilder::Gzip with ClickHouse::Client.insert_csv to compress data and reduce memory usage during batch inserts.INSERT via ClickHouse::Client.execute only for settings/configuration rows or test data setup.ClickHouse::Iterator for batching over large volumes of ClickHouse data; ensure the iteration column is a single integer with no huge gaps.:order_limit min-max strategy (min_max_strategy: :order_limit) for partitioned tables where MIN/MAX aggregations cause full table scans.SHOW CREATE TABLE table_name FORMAT raw) to understand partitioning and primary keys.EXPLAIN indexes=1 to verify that filters use primary key indexes; check the Granules ratio in the PrimaryKey section.gitlab-org or gitlab-org/gitlab).POPULATE keyword or have a backfill migration for large datasets.MergeTree only when data is strictly append-only and duplicates cannot occur.ReplacingMergeTree tables provide a monotonic version column (typically DateTime64) and an optional deleted flag (Bool) for soft deletes.ReplacingMergeTree — without it, the deduplicated row after a merge is arbitrary.argMax by the version column with GROUP BY on the primary key for query-time deduplication in production queries.FINAL in production queries — it forces on-the-fly collapsing/merging and can be very expensive I/O-wise; prefer the query-time dedup pattern instead.CODEC(DoubleDelta, ZSTD) to integer and timestamp primary key / sorted columns; apply CODEC(ZSTD(3)) to high-entropy string primary key columns.CODEC(Delta, ZSTD(1)) to incremental timestamp columns (created_at, updated_at); apply CODEC(ZSTD(1)) to boolean, UUID, and hash columns; apply CODEC(ZSTD(3)) to longer text or JSON columns.system.columns query comparing data_compressed_bytes to data_uncompressed_bytes before choosing a non-default codec.ClickHouseWorker module in every Sidekiq worker that interacts with ClickHouse to pause the worker during migrations and prevent migrations from running while the worker is active.tags :clickhouse metadata to all Sidekiq workers that interact with ClickHouse to enable routing to a dedicated Sidekiq shard.ClickHouse::Client::QueryBuilder object from GraphQL resolvers that paginate ClickHouse queries.ORDER BY columns are NOT NULL and uniquely identify each row for keyset pagination compatibility.SELECT with GROUP BY and argMax for deduplication when querying ReplacingMergeTree tables from a GraphQL resolver.siphon_-prefixed table.Gitlab::ClickHouse::SiphonGenerator::PG_TYPE_MAP for the correct ClickHouse type mapping when adding Siphon columns; using the wrong type triggers a CI error.LowCardinality where appropriate and use Nullable sparingly — prefer default values over Nullable columns in Siphon tables.:click_house to ensure the database schema is set up before the test case.When a diff modifies or replaces an existing structure, always verify the current state from an authoritative source before flagging a discrepancy. Never infer the pre-change state solely from diff context — check the actual source of truth. For example:
down methods: verify the down schema against the actual pre-migration schema by
querying the local ClickHouse database (SHOW CREATE TABLE tablename) or, if unavailable, reading
the schema from the base branch (git show master:db/click_house/main.sql). Compare
column-by-column: names, types, defaults, engine, primary key, ORDER BY, and SETTINGS.DROP TABLE IF EXISTS + CREATE TABLE): verify the old table definition
the same way before claiming columns or settings are missing.For the full picture, see: