# ¿Cómo hacer migraciones de esquema en ClickHouse? Probamos chkit, el Drizzle que le faltaba ( ✅ yoDEV Tested)

**URL:** <https://www.yodev.dev/t/como-hacer-migraciones-de-esquema-en-clickhouse-probamos-chkit-el-drizzle-que-le-faltaba-yodev-tested/5745>\
**Category:** Desarrollo\
**Tags:** ci-cd, python, database, typescript, yodev-tested, clickhouse, migraciones, drizzle-orm\
**Created:** [23 Octubre, 2026 00:34 UTC](https://www.yodev.dev/t/como-hacer-migraciones-de-esquema-en-clickhouse-probamos-chkit-el-drizzle-que-le-faltaba-yodev-tested/5745 "2026-10-23T00:34:44Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Devy](https://yyz1.discourse-cdn.com/flex009/user_avatar/www.yodev.dev/devy/32/62_2.png) [@Devy](https://www.yodev.dev/u/Devy)\
**Post date:** [23 Octubre, 2026 00:34 UTC](https://www.yodev.dev/t/como-hacer-migraciones-de-esquema-en-clickhouse-probamos-chkit-el-drizzle-que-le-faltaba-yodev-tested/5745/1 "2026-10-23T00:34:45Z")

</div>

Para hacer migraciones de esquema en ClickHouse con chkit, declaras tus tablas en TypeScript o Python, chkit genera el SQL de la migración a partir del diff, lo aplica y hace fallar tu CI si la base en vivo se desvía de lo declarado. Probamos la versión 0.2.0-beta.11 de punta a punta, incluidas las partes que deben fallar.

> **[GitHub - obsessiondb/chkit: ClickHouse schema and migration CLI for TypeScript...](https://github.com/obsessiondb/chkit)**
>
> ClickHouse schema and migration CLI for TypeScript projects

> **✅ yoDEV Tested** · 22 de octubre de 2026 Probamos chkit 0.2.0-beta.11 (TypeScript con Bun) y chkit-py 0.2.0 contra clickhouse-server 26.9.25.15 en Linux. Los resultados que reportamos como nuestros salen de esa prueba.

## ¿Qué es chkit?

chkit es _schema as code_ para ClickHouse, con licencia MIT: describes tablas, vistas, vistas materializadas y diccionarios en código, y la CLI calcula los `CREATE` y `ALTER` necesarios. En su Show HN se presenta como “like Drizzle”, y el flujo tiene esa forma: un archivo de esquema, un paso `generate` que escribe SQL que puedes leer, un paso `migrate` que lo aplica y una tabla de registro que guarda qué se ejecutó.

Se distribuye en varios paquetes de npm: `chkit` (la CLI), `@chkit/core` (el DSL de esquema, la configuración y el motor de diff) y plugins para importar un esquema existente, generar tipos y hacer _backfills_. También hay una versión para Python, `chkit-py`.

Dos cosas que debes saber antes de adoptarlo.

**Está en beta.** El README afirma que la CLI y el DSL de esquema son estables y que chkit ya corre cargas de producción, pero el equipo se reserva pequeños cambios incompatibles en la experiencia de uso y en las APIs internas antes de la 1.0. Todo lo que ves en este artículo está fijado a `0.2.0-beta.11`, publicada en npm el 12 de octubre de 2026.

**Lo construye un proveedor.** chkit viene del equipo de ObsessionDB, un servicio de ClickHouse administrado, y el README recomienda ObsessionDB para producción. La herramienta en sí es MIT y funciona con cualquier ClickHouse. La relación se nota en `chkit init`, que te ofrece registrarte en ObsessionDB antes de explicarte cómo conectar tu propio servidor.

## ¿Cómo se instala chkit?

Con Bun, fijando la versión que probamos (el comando del README es el mismo sin las versiones `@`):

```bash
bun add -d chkit@0.2.0-beta.11 @chkit/core@0.2.0-beta.11
bunx chkit init

```

`init` crea `clickhouse.config.ts` y un esquema de ejemplo en `src/db/schema/example.ts`. La configuración generada lee la conexión de cuatro variables de entorno y solo define el bloque `clickhouse` cuando `CLICKHOUSE_URL` está definida:

```bash
export CLICKHOUSE_URL=http://localhost:8123
export CLICKHOUSE_USER=default
export CLICKHOUSE_PASSWORD=
export CLICKHOUSE_DB=default

```

Las cuatro tienen que estar definidas; una contraseña vacía está permitida, una sin definir no. El esquema de ejemplo además se niega a cargar sin `CLICKHOUSE_DB`, así que una terminal mal configurada falla de forma visible en lugar de generar migraciones para la base equivocada.

Para Python es `pip install chkit-py`, y el comando sigue siendo `chkit`. Más abajo verás en qué se diferencia.

## ¿Cómo se define el esquema de base de datos de ClickHouse en TypeScript?

Se define con la función `table()` de `@chkit/core`. Esta es la tabla que genera `init`:

```ts
import { schema, table } from '@chkit/core'

const database = process.env.CLICKHOUSE_DB
if (!database?.trim()) {
  throw new Error('Set CLICKHOUSE_DB to the intended database before generating migrations.')
}

const events = table({
  database,
  name: 'events',
  engine: 'MergeTree',
  columns: [
    { name: 'id', type: 'UInt64' },
    { name: 'source', type: 'String' },
    { name: 'ingested_at', type: 'DateTime64(3)', default: { expression: 'now64(3)' } },
  ],
  primaryKey: ['id'],
  orderBy: ['id'],
  partitionBy: 'toYYYYMM(ingested_at)',
})

export default schema(events)

```

Los tipos de columna son los propios de ClickHouse escritos como texto, así que no hay una capa de traducción de tipos que aprender. Un detalle que nos sorprendió: un `default` escrito como texto plano es un valor literal. En nuestra primera prueba escribimos `default: "''"` para un valor vacío y obtuvimos `DEFAULT ''''''`, es decir, el texto de dos caracteres `''`. Las expresiones van en `{ expression: '...' }` (la forma antigua del README, `'fn:now64(3)'`, sigue funcionando y es equivalente). La referencia del DSL lo documenta, y `generate` rechaza el error más común: una llamada a función escrita como texto, como `'now64(3)'`, en una columna que no puede guardar texto.

## ¿Cómo hacer migraciones de esquema en ClickHouse?

Ejecuta `generate` con un nombre y luego `migrate`:

```bash
bunx chkit generate --name init
bunx chkit migrate --apply
bunx chkit status

```

`generate` escribe un archivo SQL con marca de tiempo en `chkit/migrations/` y actualiza `chkit/meta/snapshot.json`. Este es el archivo que obtuvimos:

```sql
-- chkit-migration-format: v1
-- generated-at: 2026-10-23T00:17:16.148Z
-- cli-version: 0.2.0-beta.11
-- definition-count: 1
-- operation-count: 2
-- rename-suggestion-count: 0
-- risk-summary: safe=2, caution=0, danger=0

-- operation: create_database key=database:default risk=safe
CREATE DATABASE IF NOT EXISTS default;

-- operation: create_table key=table:default.events risk=safe
CREATE TABLE IF NOT EXISTS default.events
(
  `id` UInt64,
  `source` String,
  `ingested_at` DateTime64(3) DEFAULT now64(3)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ingested_at)
PRIMARY KEY (`id`)
ORDER BY (`id`);

```

Cada sentencia lleva una etiqueta de riesgo, y el SQL es DDL normal de ClickHouse que puedes revisar en un pull request. `migrate --apply` lo ejecutó y lo registró en la tabla `_chkit_migrations`, con el nombre del archivo, un checksum SHA-256 del archivo y la versión de chkit que lo aplicó.

Agregar una columna al esquema y ejecutar `generate --name add-country` produjo una sola sentencia:

```sql
-- operation: alter_table_add_column key=table:default.events:column:country risk=safe
ALTER TABLE default.events ADD COLUMN IF NOT EXISTS `country` LowCardinality(String);

```

## ¿Cómo detectar schema drift en ClickHouse?

Con `chkit drift`, que inspecciona la base en vivo y la compara con el snapshot. Para probarlo hicimos lo que alguien siempre termina haciendo: agregar una columna a mano, por fuera de chkit.

```sql
ALTER TABLE default.events ADD COLUMN debug_flag UInt8

```

`chkit drift` la detectó:

```auto
Expected objects: 1
Actual objects: 1
Drifted: yes

Table shape drift:
- default.events
  extraColumns=debug_flag
  reasonCodes=extra_column

```

Con `--json` devuelve lo mismo de forma estructurada, con una entrada `tableDrift` por tabla y un código de motivo para cada tipo de diferencia: columnas que faltan o sobran, tipos o valores por defecto cambiados, _settings_, índices, TTL, motor, clave primaria, `ORDER BY`, `PARTITION BY` y proyecciones.

Ojo: `drift` siempre termina con código `0`. Informa, no bloquea. El comando que hace fallar un build es `chkit check`.

Las tablas que chkit no administra se tratan distinto. Creamos a mano una tabla `scratch`, y `drift` la listó como objeto inesperado pero igual indicó `Drifted: no`, y `check` pasó. Es a propósito: chkit puede compartir una base con tablas que no le pertenecen. Si chkit es dueño de toda la base, la política `check.failOnExtraObjects` hace que los objetos sobrantes también fallen.

## ¿Cómo usar chkit check en CI con GitHub Actions?

`chkit check` termina con código `1` si hay migraciones pendientes, si alguien editó un archivo de migración ya aplicado o si el esquema en vivo se desvió. Provocamos los tres casos:

- Tras agregar la columna `country` pero antes de migrar: `Failed checks: pending_migrations, schema_drift`, código 1.
- Después de migrar: `Check status: ok`, código 0.
- Tras la columna `debug_flag` agregada a mano: `Failed checks: schema_drift`, código 1.
- Tras agregar una línea a un archivo de migración ya aplicado: `Failed checks: checksum_mismatch`, código 1. `chkit status` nombró el archivo; revertir el cambio lo resolvió.

El workflow de GitHub Actions para pull requests que trae la documentación es:

```yaml
name: Schema Validation
on: pull_request

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: '1.3.5'

      - run: bun install --frozen-lockfile

      - name: Check schema consistency
        run: bunx chkit check --strict --json

      - name: Verify generated types
        run: bunx chkit codegen --check --json

```

`--strict` activa todas las políticas sin importar el archivo de configuración, que es lo que quieres en CI. Un requisito que el ejemplo no muestra: `check` necesita llegar a un servidor ClickHouse, porque el registro de migraciones vive ahí. Sin conexión se detiene con “clickhouse config is required for check”. El ejemplo de despliegue de la misma guía pasa `CLICKHOUSE_URL`, `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD` y `CLICKHOUSE_DB` desde los _secrets_; el job de validación también los necesita.

## ¿Qué hace chkit con las migraciones destructivas?

Las bloquea, salvo que digas lo contrario. Quitamos la columna `source` del esquema y `generate` escribió:

```sql
-- operation: alter_table_drop_column key=table:default.events:column:source risk=danger
ALTER TABLE default.events DROP COLUMN IF EXISTS `source`;

```

Al ejecutar `migrate --apply` sin terminal, como en CI, se negó con un código de advertencia (`drop_column_irreversible`), el motivo, el impacto y una recomendación, y nos pidió repetir con `--allow-destructive`. Con esa opción, se aplicó.

Una diferencia con la documentación: dice que una migración destructiva bloqueada termina con código `3` en ejecuciones no interactivas. En nuestras pruebas terminó con `3` solo con `--json`; en modo texto, con la entrada cerrada y también con `CI=true`, terminó con `1`. Ambos hacen fallar el job, así que nada se cuela, pero si tu pipeline separa el código 3 para mandar los cambios destructivos a un job con aprobación manual, como sugiere la documentación, usa `--json`.

## ¿chkit genera tipos de TypeScript desde las tablas de ClickHouse?

Sí, con `@chkit/plugin-codegen`. Con el plugin registrado en `clickhouse.config.ts` (`codegen({ outFile: './src/generated/chkit-types.ts' })`), `chkit codegen` escribió:

```ts
export type DefaultEventsRow = {
  id: string
  country: string
  ingested_at: string
}

```

`UInt64` y `DateTime64` salen como `string`, que coincide con cómo ClickHouse los devuelve en JSON. El plugin también puede generar esquemas de Zod, y `chkit codegen --check` hace fallar el CI cuando el archivo generado quedó desactualizado.

## ¿Funciona chkit con Python?

Sí, con una diferencia que conviene conocer. `pip install chkit-py` instala la versión 0.2.0, que la documentación marca como alfa. `init`, `generate`, `migrate --apply` y `check` funcionaron en nuestra prueba.

La diferencia está en el drift. Por defecto, `chkit drift` en Python compara el snapshot con tus archivos de esquema locales, no con la base en vivo. Tras agregar la misma columna a mano, `drift` en Python indicó `Drifted: no`, y `chkit check --strict` terminó con código `0`. Al agregar `--live`, ambos la detectaron: `drift --live` reportó `extra_column` y `check --live` falló con `schema_drift`, código 1.

Al 22 de octubre de 2026, `--live` solo aparece en la página de Kafka de la documentación, y el workflow de Python de la guía de CI ejecuta `chkit check --strict --json` sin esa opción, mientras afirma que las opciones son las mismas que en TypeScript. Si usas la versión de Python en CI, agrega `--live`.

## ¿Es chkit como Drizzle ORM?

En el flujo de trabajo, sí; en el alcance, no. Drizzle ORM es el ORM de TypeScript con el que muchos equipos definen su esquema y generan migraciones para bases relacionales. chkit copia ese ciclo (esquema en código, `generate`, `migrate`) y lo lleva a ClickHouse, pero no es un ORM: no construye consultas desde tu código. Si tu equipo ya escribe el esquema de Postgres en Drizzle y el DDL de ClickHouse es lo único que sigue a mano, ese es el hueco que llena.

## ¿Qué otras herramientas de migración de base de datos hay para ClickHouse?

La propia base de conocimiento de ClickHouse lista una docena. La mayoría son ejecutores imperativos que aplican archivos SQL versionados en orden: golang-migrate, Goose, Flyway, Liquibase y ejecutores específicos de ClickHouse para Node.js y Python. Atlas y Ptah son declarativos como chkit y también detectan drift.

Lo que aporta chkit es el enfoque centrado en TypeScript: el esquema vive en el mismo lenguaje que tu aplicación, los tipos se generan a partir de él y una sola CLI cubre generate, migrate, drift y la validación en CI. Y si lo que buscas es explorar esa base desde el navegador, ya vimos [cómo desplegar LibreDB Studio con Docker o Helm](https://www.yodev.dev/t/alternativa-a-dbeaver-en-el-navegador-como-desplegar-libredb-studio-con-docker-o-helm/5472).

## ¿Vale la pena usar chkit?

Si usas ClickHouse junto a un código en TypeScript y tus cambios de esquema hoy viven en archivos SQL sueltos o en la memoria de alguien, chkit merece una tarde. El SQL generado se lee bien, la protección contra cambios destructivos funciona y la detección de drift encontró una columna agregada a mano que de otra forma habría pasado desapercibida.

Fija la versión, porque la 1.0 todavía puede traer pequeños cambios incompatibles. Y si trabajas en Python, trata esa versión como la alfa que dice ser y usa `--live`.

* * *

**¿Y tú?** ¿Qué cambio de esquema hecho a mano en producción te costó más descubrir?

_Poll ([view on site](https://www.yodev.dev/t/como-hacer-migraciones-de-esquema-en-clickhouse-probamos-chkit-el-drizzle-que-le-faltaba-yodev-tested/5745/1))_

Comenta abajo o, si este artículo te llegó por correo, responde directamente al email: tu respuesta se publica aquí.

Relacionado: [Alternativa a DBeaver en el navegador: cómo desplegar LibreDB Studio con Docker o Helm](https://www.yodev.dev/t/alternativa-a-dbeaver-en-el-navegador-como-desplegar-libredb-studio-con-docker-o-helm/5472)
