mavlitov98/clickhouse-migrations
A Laravel package for managing ClickHouse database migrations. Create, run, and roll back schema changes for ClickHouse tables using familiar migration workflows, helping you version your analytics database alongside your application.
Framing correction up front: despite the request's "Laravel/PHP package" label, this is a Symfony bundle. It is a PHP package whose only integration layer is Symfony's bundle system (DI extension, services.yaml, framework-bundle constraints). For a Laravel shop, that distinction is the single most important finding: there is no ServiceProvider, no config/*.php publishable config, and no Artisan commands. Adoption is a fork-and-adapt exercise, not a composer require.
Architecture fit. ClickHouse is an OLAP columnar store, so "migrations" here mean versioned DDL plus backfill orchestration — not the transactional, reversible schema changes Laravel developers expect from php artisan migrate. The package is a reasonable fit if the need is "keep ClickHouse table definitions under version control next to the Laravel application code." It is a poor fit if the implicit expectation is Eloquent-level model/ORM parity with ClickHouse, or transactional rollback. Structurally the package appears to be a thin Symfony wrapper over a clickhouse-migrations core runner plus console commands. Only two things are portable to Laravel: the migration runner core and the Symfony Console command classes (Artisan is itself built on Symfony Console, so those classes can be registered almost directly).
Integration feasibility: medium-to-low. The mechanical work is small — a Laravel service provider binding the runner, plus Artisan command wrappers — but the surrounding risk is not mechanical:
down() migrations are best-effort at best; an interrupted ALTER leaves partial state. Any rollback story is a runbook, not a command.ON CLUSTER, ReplicatedMergeTree parameters, and the Distributed DDL queue (system.distributed_ddl_queue) are the hard parts of ClickHouse schema management and are unlikely to be handled in a 9-star bundle from early 2023.migrations table lock.Key questions to resolve before committing:
symfony/framework-bundle at runtime, or only symfony/console? (Determines whether commands are liftable.)ON CLUSTER supported, and does the runner wait on / verify system.distributed_ddl_queue before marking success?Replicated* engine parameters templated per environment, or hardcoded?down() implemented as a real inverse DDL, a no-op, or a destructive drop?composer.json?Stack fit. Laravel 10/11 on PHP 8.2–8.3 against ClickHouse 23.x/24.x is the target. The honest assessment is that this package occupies roughly 10% of the total integration surface — the versioned-file runner — while the other 90% (connection management, cluster DDL, locking, observability) is work you will do regardless.
Two viable paths, and I would default to the second:
ServiceProvider, one config file, three Artisan commands (clickhouse:migrate, :status, :make). Feasible in roughly 1–2 engineer-weeks including tests. Only sensible if the core runner genuinely handles cluster DDL — verify with question 3 first.Migration path (brownfield). Most teams already have ClickHouse tables created by hand or by an analytics engineer. The adoption step is a baseline: insert rows into the tracking table representing the current known-good schema state, so the runner starts cleanly from "now" rather than replaying history. Do not let a tool attempt to reconcile or recreate existing tables — that is how production data gets dropped.
Compatibility. Pin symfony/console to the version Artisan already ships; do not add symfony/framework-bundle to a Laravel app under any circumstances. Confirm the ClickHouse client version aligns with your server version's protocol changes. Vendor the fork (or pin an exact commit) — never a floating Composer constraint against an unmaintained upstream.
Sequencing.
ON CLUSTER SQL for a 2-shard Replicated setup?Keep the two migration regimes strictly separate: Laravel's relational migrations on MySQL/Postgres continue unchanged, with their own tracking table, CI step, and deploy hook. The ClickHouse runner is a second, independent pipeline — do not attempt to interleave them in a single artisan migrate invocation.
Maintenance. You own this code, fully. Budget ~0.1 FTE steady state for the fork plus ClickHouse schema review. Expect zero upstream commits; the correct posture is to fork once, vendor it, and treat upstream as dormant. The mitigating factor — and it is a genuine one — is that the artifact is small and MIT-licensed, so the exit cost is low. A 2,000-line PHP runner is replaceable; a proprietary migration appliance would not be. That asymmetry is what makes Path A defensible at all.
Support. No vendor, no community (9 stars means effectively no issue triage), no security disclosure process, no signed releases. Supply-chain exposure must be handled by vendoring into your own repo behind your own CI, which also removes the package from your dependency graph for audit purposes. Identify one internal ClickHouse SME as the named owner; there is no external escalation path.
Scaling. Migration runtime scales with cluster fan-out and queue depth, not with PHP throughput. Two hard ceilings to plan around:
system.distributed_ddl_queue to completion will produce false greens and cross-shard drift.ALTER on large tables is a mutation, not metadata-only, in most cases — it queues work proportional to parts/rows and can run for hours while competing with ingestion. For large changes, prefer create-new-table + EXCHANGE TABLES / RENAME, or partition-level operations, over in-place ALTER. Schedule production migrations in off-peak windows and gate on system.mutations draining.Failure modes to design against explicitly:
| Failure | Consequence | Mitigation |
|---|---|---|
Migration killed mid-ALTER |
Partial schema across shards, tracking table out of sync | Idempotent DDL (IF NOT EXISTS), manual reconciliation runbook |
| Concurrent deploys | Double-applied DDL, duplicate columns | External advisory lock (Redis/DB) around the command |
| Timeout on cluster DDL | False "applied" state | Poll DDL queue before recording success |
| Mutation backlog | Ingestion latency, memory pressure, OOM | Gate on system.mutations, size changes to off-peak |
| ZooKeeper session loss | Replica divergence | Alerting on system.replicas; verify post-run |
| Driver/protocol mismatch after CH upgrade | Runner fails at deploy time | Pin client version, test against staging cluster first |
Ramp-up. Low for the PHP side — a mid-level Laravel developer can operate the fork within a day. The real cost is ClickHouse operational literacy: 2–3 weeks before an engineer can safely author and reason about cluster DDL, replication parameters, and mutation behavior. Budget that training regardless of whether you adopt, fork, or rebuild this package — it is the dominant cost in the whole initiative and it is entirely independent of the build-vs-buy decision.
Bottom line for a TPM: the package is a useful reference architecture and a plausible fork base, not an adoptable dependency. Approve a two-day spike to answer the cluster-DDL question (question 3), then expect to own the code permanently. The decision is low-regret precisely because the artifact is small and permissively licensed — but scope the effort as "build a ClickHouse migration capability" (2–3 weeks, including ClickHouse ramp-up), never as "install a package."
How can I help you explore Laravel packages today?