New to Claude Skills? Learn how to install them →

posthog on GitHub

ClickHouse Migrations

Free

Streamline your ClickHouse migration processes with best practices.

by posthog37.6k stars on posthog/posthog
1 views
Updated Aug 11, 2026
Get this skill

Free · Opens the source repo

What ClickHouse Migrations does

The ClickHouse Migrations skill provides a comprehensive guide to structuring and executing migrations in ClickHouse, a powerful columnar database management system. This skill is essential for developers and data engineers who are involved in creating or modifying ClickHouse migrations, ensuring that they adhere to best practices and avoid common pitfalls. By following the detailed instructions and patterns outlined in this skill, users can effectively manage their database schema changes while maintaining data integrity and performance.

Central to the skill is a clear migration structure that emphasizes the use of specific SQL functions and node roles, tailored to different table types. Users will find quick references for various MergeTree engines and distributed tables, enabling them to select the appropriate configurations for their needs. The skill also highlights critical rules that must be followed during migrations, such as avoiding the use of the ON CLUSTER clause and ensuring that migrations are migration-only, which helps to prevent issues that can arise from mixing different types of changes.

Additionally, the skill addresses local setup parity, ensuring that all tables created via migration exist in both cloud and local development environments. This is crucial for maintaining consistency across different stages of development and deployment. The skill also provides testing guidelines, allowing users to easily re-run migrations when necessary. Overall, this skill is a valuable resource for anyone working with ClickHouse migrations, providing clarity and structure to a complex process.

When to use it

Use this skill when creating or modifying ClickHouse migrations to ensure adherence to established patterns and rules.

When not to use it

This skill may not be suitable for users who are not working with ClickHouse or those who require a more general-purpose migration tool.

What you can build with it

Creating a New Migration

When you need to create a new migration for a ClickHouse table, refer to the provided structure and critical rules to ensure compliance.

Modifying an Existing Table

If you're altering an existing ClickHouse table, use this skill to understand the necessary SQL functions and node roles.

Testing Migration Changes

Before deploying changes, utilize the testing guidelines to verify that your migration behaves as expected in both local and cloud environments.

How to install ClickHouse Migrations

View source

1. Install with the skills CLI

npx skills add posthog/posthog/clickhouse-migrations --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by posthog

ClickHouse Migrations

Read posthog/clickhouse/migrations/AGENTS.md for comprehensive patterns, cluster setup, examples, and ingestion layer details.

Quick reference

Migration structure

operations = [
    run_sql_with_exceptions(
        SQL_FUNCTION(),
        node_roles=[...],
        sharded=False,  # True for sharded tables
        is_alter_on_replicated_table=False  # True for ALTER on replicated tables
    ),
]

Node roles (choose based on table type)

  • [NodeRole.DATA]: Sharded tables (data nodes only)
  • [NodeRole.DATA, NodeRole.COORDINATOR]: Non-sharded data tables, distributed read tables, replicated tables, views, dictionaries
  • [NodeRole.INGESTION_SMALL]: Writable tables, Kafka tables, materialized views on ingestion layer

Table engines quick reference

MergeTree engines:

  • AggregatingMergeTree(table, replication_scheme=ReplicationScheme.SHARDED) for sharded tables
  • ReplacingMergeTree(table, replication_scheme=ReplicationScheme.REPLICATED) for non-sharded
  • Other variants: CollapsingMergeTree, ReplacingMergeTreeDeleted

Distributed engine:

  • Sharded: Distributed(data_table="sharded_events", sharding_key="sipHash64(person_id)")
  • Non-sharded: Distributed(data_table="my_table", cluster=settings.CLICKHOUSE_SINGLE_SHARD_CLUSTER)

Critical rules

  • NEVER use ON CLUSTER clause in SQL statements
  • Always use IF EXISTS / IF NOT EXISTS clauses
  • When dropping and recreating replicated table in same migration, use DROP TABLE IF EXISTS ... SYNC
  • If a function generating SQL has on_cluster param, always set on_cluster=False
  • Use sharded=True when altering sharded tables
  • Use is_alter_on_replicated_table=True when altering non-sharded replicated tables
  • Never write CODEC(ZSTD(1)) on a column — the server already compresses every column with ZSTD, so it buys nothing. Declare a CODEC only where it beats that default, and check the ORDER BY first: Delta/DoubleDelta need the column near-sorted in storage order (a leading sort-key prefix), and lose on a column the key only buckets or omits. T64/Gorilla are ordering-independent. Put it on the storage table only — a CODEC on a Distributed or Kafka table is inert metadata that drifts from the sharded table it fronts.
  • Never write a DROP COLUMN migration yourselfDROP COLUMN can get stuck in ClickHouse and block releases. Column removal is a two-step process: (1) the ClickHouse team drops the column directly on the cluster, then (2) you write a migration with the matching DROP COLUMN so the codebase schema stays in sync. Never initiate the drop from a migration without the ClickHouse team having done step 1 first.
  • Never drop or recreate kafka_events_json_ws or events_json_ws_mv — these tables are a no-go zone. The MV definition differs significantly between US prod, EU prod, and dev (dozens of environment-specific mat_* columns) and those differences are not reflected in the repo. Dropping and recreating from repo SQL would destroy the environment-specific schema and break event ingestion. Any change must go through the ClickHouse team.

PR scope

A PR that contains a ClickHouse migration must be migration-only. Do not mix migration files with feature code, API changes, model changes, or frontend changes in the same PR. Migration-related files are:

  • The migration file itself (posthog/clickhouse/migrations/0NNN_*.py)
  • SQL definition files the migration depends on (e.g. posthog/clickhouse/sql/*.py, table engine helpers)
  • Tests that directly exercise the migration or the SQL definitions it touches

If you need both a schema change and application code that uses the new schema, ship the migration first in its own PR and merge it before the application-code PR.

Local setup parity

No table should exist only in the cloud. Every table created via migration must also exist in a local dev environment.

Some migrations are cloud-guarded and skipped in local/hobby dev:

operations = (
    []
    if settings.CLOUD_DEPLOYMENT not in ("US", "EU", "DEV")
    else [...]
)

If you create a new table inside such a guard, you must also add its SQL function to posthog/clickhouse/schema.py in the appropriate tuple so the table gets created locally:

Table typeTuple in schema.py
MergeTree / base tableCREATE_MERGETREE_TABLE_QUERIES
Distributed / writableCREATE_DISTRIBUTED_TABLE_QUERIES
Kafka consumerCREATE_KAFKA_TABLE_QUERIES
Materialized viewCREATE_MV_TABLE_QUERIES
Non-materialized viewCREATE_VIEW_QUERIES
DictionaryCREATE_DICTIONARY_QUERIES

The only exception is tables whose definition intentionally differs per environment and is not tracked in the repo (e.g. the no-go zone events_json_ws_mv table).

Dictionary credentials: when a dictionary uses a SOURCE(CLICKHOUSE(...)), resolve the source user/password via get_clickhouse_creds(ClickHouseUser.DICT_READER) and interpolate them into the USER/PASSWORD clause — do not hardcode default/CLICKHOUSE_USER or omit credentials. This keeps dictionary auth on the dedicated low-privilege dict_reader user, decoupled from default; it falls back to default creds when the env vars are unset. See posthog/models/exchange_rate/sql.py for the pattern.

Testing

Delete entry from infi_clickhouse_orm_migrations table to re-run a migration.

Frequently asked questions about ClickHouse Migrations

Similar skills