Qué Cambia Cuando Tu Esquema de ClickHouse® Es Código
Si construyes sobre Postgres o MySQL, seguramente llevas años sin escribir una migración a mano. Cambias un fichero de esquema, una herramienta saca el diff, genera el SQL y te tumba la CI si producción se ha desviado sin avisar de lo que dice tu repo. Esa es la versión aburrida y ya resuelta de gestionar el esquema. Y aburrido es el mayor piropo que le puedes echar a lo que le cambia las tripas a tu base de datos.
Ahora abre tu proyecto de ClickHouse®. Vuelves a escribir DDL a mano, a leer diffs con los ojos, y a enterarte del drift cuando una query se rompe en producción.
No es que ClickHouse no tenga herramientas de migración, es que nunca ha habido una por defecto, esa cosa evidente a la que un equipo echa mano igual que el mundo relacional echa mano de las suyas. Casi todo lo que existe son SQL runners: aplican los ficheros que escribes y apuntan cuáles se ejecutaron, pero el motor de diffs sigues siendo tú. Sigues decidiendo qué cambió, y sigues rezando para que dev y prod no se hayan separado.
Nos cansamos de ser el motor de diffs. Así que construimos CHKit, y lo sacamos como open source. Este post no es un repaso de features. Va de lo que cambia de verdad en tu teclado cuando el esquema de ClickHouse vive en código.
Por qué lo construimos
Corrimos ClickHouse en nuestra empresa anterior, Numia, a una escala cercana al petabyte, moviendo APIs en tiempo real sobre datos de blockchain. El motor en sí casi nunca era el problema. El problema era todo lo que había alrededor, y la gestión del esquema estaba de las primeras de la lista.
Cada cambio era DDL escrito a mano y revisado a ojo. La forma real de una tabla era lo que saliera del montón de ficheros de migración. Un ALTER manual lanzado en mitad de un incidente no volvía nunca al control de versiones, y nada nos avisaba de que el código y la base de datos ya no coincidían. Escribimos un montón de herramientas para ir tapando esto: health checks, scripts, guardarraíles.
Pero el problema del esquema seguía ahí, y la raíz era no tener una historia de schema-as-code como la de Postgres. Así que nos hicimos una. Si quieres la versión larga del porqué, y de por qué decidimos abrirlo, hay un post que lo acompaña en el blog de CHKit. Aquí me quiero quedar en lo práctico.
Tu esquema de ClickHouse como código
chkit define tus tablas, vistas y materialized views de ClickHouse como TypeScript (Python al caer). Aquí tienes una tabla real, con las cosas que de verdad configuras en producción:
import { schema, table } from '@chkit/core'
const events = table({
database: 'analytics',
name: 'events',
columns: [
{ name: 'id', type: 'UInt64' },
{ name: 'org_id', type: 'String' },
{ name: 'event', type: 'LowCardinality(String)' },
{ name: 'received_at', type: 'DateTime64(3)' },
{ name: 'payload', type: 'String', codec: [{ kind: 'ZSTD', level: 3 }] },
],
engine: 'MergeTree()',
orderBy: ['org_id', 'received_at'],
primaryKey: ['org_id', 'received_at'],
partitionBy: 'toYYYYMM(received_at)',
ttl: 'received_at + INTERVAL 90 DAY',
})
export default schema(events)
Ese es el estado que quieres para tu base de datos, con tipos comprobados y revisable en un pull request. El bucle que lo rodea son tres comandos. chkit generate compara tus definiciones con el último snapshot y escribe un fichero de migración SQL. chkit migrate --apply aplica las migraciones pendientes. chkit drift inspecciona la base de datos en vivo y te dice en qué se aparta de tu código. Otros dos se ganan su sitio en CI: chkit check tumba el build si hay migraciones pendientes, drift o una migración editada a mano, y chkit codegen genera los tipos de fila en TypeScript a partir de esas mismas definiciones.
No es un ORM. El SQL de tus queries lo sigues escribiendo tú. CHKit se encarga del esquema, las migraciones y los guardarraíles, y de nada más.
Una cosa por delante: está en beta. La CLI y el DSL de esquema son estables y mueven nuestras propias cargas de producción, pero puede que aún hagamos algún cambio pequeño que rompa cosas antes de la 1.0. La referencia a fondo está en la documentación de chkit.
Qué cambia cuando el esquema es código
Aquí viene lo que importa, y el motivo por el que esto merece un post.
Dejas de escribir migraciones a mano
Cambias el TypeScript y ejecutas chkit generate. Hace el diff del estado nuevo contra el viejo, calcula el conjunto ordenado de operaciones y te escribe el SQL. Si no cambió nada, no escribe nada.
El fichero de migración deja de ser algo que escribes y pasa a ser algo que revisas. El trabajo mental cambia de «escribir el ALTER correcto para este cambio» a «leer lo que propone CHKit y darle el visto bueno». En un esquema con mucho trajín, esa es la diferencia entre una tarde con lupa y un pull request de treinta segundos.
El drift pasa a ser un gate de CI y no una sorpresa a las 3 de la mañana
Este es el que paga la herramienta entera.
El fallo siempre es el mismo. Alguien arregla un incidente a las 3 de la mañana lanzando un ALTER directo contra producción. Funciona. El incidente se cierra. El cambio no llega nunca a un fichero de migración, y ahora tu repo y tu base de datos no coinciden, en silencio, hasta que semanas después un deploy hace algo que nadie sabe explicar.
chkit drift lee la base de datos en vivo y la compara, columna a columna, con tu código. Pilla la columna que falta, el TTL que cambió y la sorting key que no es la que tu repo se cree. Mete chkit check en CI y esa comparación corre en cada pull request, así que el ALTER de las 3 de la mañana aparece a la mañana siguiente como una diferencia concreta y con nombre, en vez de como una vaga sensación de que algo va raro. La idea no es que la gente deje de tocar producción. No lo va a hacer. La idea es que la base de datos deje de poder guardarte un secreto.
Los cambios destructivos no saltan por accidente, y los estructurales avisan
Dentro de «cambio de esquema» se esconden dos problemas distintos, y ClickHouse los trata de forma muy distinta.
Algunos cambios son ediciones de metadatos baratas: añadir una columna, cambiar un TTL, meter un índice. Otros reescriben la tabla. Cambiar el engine, el ORDER BY, el particionado o la primary key no es una edición en el sitio, ni de lejos. ClickHouse no tiene un ALTER para eso. El único camino es crear una tabla nueva, copiar los datos y cambiarlas. Si alguna vez has cambiado una sorting key en una tabla grande y lo has visto convertirse en una tarde entera de INSERT SELECT, sabes justo de qué hablo. (Nos metimos en por qué el orden de la sorting key importa tanto en nuestro post de optimización de queries.)
CHKit etiqueta cada operación de un plan como safe, caution o danger, y sabe cuáles son reescrituras estructurales y cuáles ediciones en el sitio. Un DROP COLUMN, un DROP TABLE, cualquier cosa que destruya datos, va etiquetada como danger y bloqueada. En una terminal interactiva te salta un aviso. En CI sale con código distinto de cero y se niega a ejecutar salvo que pases --allow-destructive a propósito. Y ese control también revisa el SQL escrito a mano, no solo las migraciones que generó el propio CHKit, así que no puedes colarle un DROP a pelo.
Los tipos de tu aplicación dejan de separarse de tu esquema
chkit codegen genera los tipos de fila en TypeScript a partir de las mismas definiciones de las que salen tus tablas. Corre chkit codegen --check en CI y el build falla cuando los tipos de tu aplicación y el esquema de tu base de datos se salen de sincronía. Es el mismo problema de drift de antes, un piso más arriba, cerrado igual.
¿Qué no hace CHKit?
Un post de lanzamiento que solo enumera victorias es marketing, así que aquí van los bordes.
CHKit gestiona el esquema, o sea DDL. No mueve tus datos. Hay un plugin de backfill opcional para copias por ventanas de tiempo con checkpoints, pero es algo que eliges tú, no forma parte del bucle de migrate del core, y no te hace fácil la parte difícil. Reformar una tabla grande sigue siendo una migración de datos de verdad que tienes que planear. En este terreno todo el mundo sufre con esa.
Además, por defecto emite DDL de un solo nodo. Si corres un clúster replicado con Keeper, esa replicación la gestionas tú (tenemos pensado meter esta feature pronto). Y un par de comodidades, como el uniqueKey gestionado, solo valen para el engine de ObsessionDB. CHKit funciona contra cualquier ClickHouse, pero las features de Cloud son eso, de Cloud.
Por último, CHKit no puede deshacer una mutación de ClickHouse. Lo que hace es avisarte, antes de que ejecutes nada, de que ese cambio de aspecto inofensivo en tu diff es en realidad una reescritura de toda la tabla. Los datos se mueven igual. Solo que dejas de enterarte un viernes por la tarde.
La regla que merece la pena guardar
Si te llevas una sola cosa: schema-as-code se gana el sueldo en cuanto más de una persona, o más de un entorno, puede cambiar tus tablas. Un ingeniero en un solo clúster tira con una carpeta de migraciones y buenos hábitos. Un equipo con staging y prod separados, y una pipeline de CI capaz de lanzar un ALTER, no. Ahí es donde el drift deja de ser algo hipotético.
Y cuando un diff vuelve etiquetado como danger o estructural, para y léelo. Eso es CHKit diciéndote que ese cambio mueve datos. Yo planearía el backfill antes de aprobarlo, siempre, en vez de descubrir la reescritura en producción.
El mundo relacional dejó de escribir migraciones a mano hace mucho. ClickHouse no tiene por qué ser la excepción.
Pruébalo
CHKit tiene licencia MIT y funciona con cualquier ClickHouse: Cloud, Altinity, autoalojado y ObsessionDB. Puedes montar un proyecto a partir de un ejemplo que funciona con un solo comando:
npm create chkit@latest
El código está en GitHub. Si corres ClickHouse y sigues gestionando el esquema a mano, construimos esto para que no tengas que hacerlo. Y si quieres ClickHouse gestionado sin el peaje operativo que dio pie a toda esta historia, eso es lo que hacemos en ObsessionDB.
Nos interesa de verdad saber cómo llevas hoy los cambios de esquema en ClickHouse, sobre todo el drift y los ALTER destructivos. Abre un issue y cuéntanos qué falta.
Seguir Leyendo
Publicado originalmente en obsessionDB. Lee el artículo original aquí.
ClickHouse is a registered trademark of ClickHouse, Inc. https://clickhouse.com