ClickHouse desde cero (V): producción, replicación y clusters
Quinta y última entrega de la serie ClickHouse desde cero a pro. Tiempo de lectura estimado: 14 minutos.
Llegamos al final. En las entregas anteriores has pasado de arrancar un ClickHouse en Docker (I) a diseñar tablas con MergeTree (II), escribir consultas analíticas serias (III) y pre-agregar con materialized views (IV).
Ahora toca ponerlo en producción. Este post cubre cuatro cosas que todo equipo acaba necesitando: replicación, sharding, backups y monitorización. No vas a salir de aquí sabiendo operar un cluster de 50 nodos, pero sí con la cabeza puesta para diseñar algo que no se caiga a la primera.
Cuándo replicar y cuándo sharding
Empieza por entender qué problema resuelve cada cosa. Las decisiones tempranas son las que más duelen si te equivocas.
- Replicación: copias de los mismos datos en varios nodos. Resuelve alta disponibilidad, durabilidad y escalado de lecturas. No resuelve el problema de que los datos no te caben en un nodo.
- Sharding: particiona los datos entre varios nodos. Resuelve escalado horizontal: cada nodo guarda una fracción. No da HA por sí solo; hay que combinar con replicación.
Para la mayoría de proyectos medianos, 2 réplicas sin sharding es la respuesta correcta. Un ClickHouse con 2 nodos de 64 vCPU y 256 GB RAM aguanta decenas de miles de millones de filas sin despeinarse. No empieces por un cluster de 12 nodos porque "por si acaso".
Si llegas a necesitar sharding, lo sabrás: tu disco no da más, los merges no acaban, las consultas paralelas saturan CPU en un solo nodo.
ClickHouse Keeper
Históricamente, la replicación en ClickHouse dependía de ZooKeeper. Hoy existe ClickHouse Keeper, un reemplazo compatible escrito en C++ y distribuido con el propio ClickHouse. Mismo protocolo, menos memoria, menos quebraderos.
Lo necesitas obligatoriamente si vas a usar ReplicatedMergeTree. Puedes desplegarlo:
- Como proceso separado (recomendado en producción): 3 instancias de Keeper en máquinas distintas para quórum.
- Dentro de clickhouse-server (modo embebido): útil para pruebas y clusters pequeños.
Ejemplo de configuración embebida en /etc/clickhouse-server/config.d/keeper.xml:
<clickhouse>
<keeper_server>
<tcp_port>9181</tcp_port>
<server_id>1</server_id>
<log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
<snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
<raft_configuration>
<server><id>1</id><hostname>ch-01</hostname><port>9234</port></server>
<server><id>2</id><hostname>ch-02</hostname><port>9234</port></server>
<server><id>3</id><hostname>ch-03</hostname><port>9234</port></server>
</raft_configuration>
</keeper_server>
<zookeeper>
<node><host>ch-01</host><port>9181</port></node>
<node><host>ch-02</host><port>9181</port></node>
<node><host>ch-03</host><port>9181</port></node>
</zookeeper>
</clickhouse>
Tres instancias es el mínimo razonable. Con dos no hay quórum; con una no hay HA.
Comprueba el estado desde cualquier nodo:
echo mntr | nc localhost 9181
echo stat | nc localhost 9181
ReplicatedMergeTree
Una vez Keeper está en marcha, cambias MergeTree por ReplicatedMergeTree y automáticamente tienes replicación multi-master: cualquier réplica acepta escrituras y todas acaban sincronizadas.
CREATE TABLE blog.pageviews ON CLUSTER my_cluster (
ts DateTime,
user_id UInt64,
path String,
country LowCardinality(String),
duration_ms UInt32
)
ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/blog/pageviews',
'{replica}'
)
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, user_id);
Dos detalles:
- El primer parámetro es la ruta en Keeper. Debe ser única por tabla y compartida entre todas las réplicas del mismo shard.
- El segundo es el nombre de la réplica, único dentro del shard.
{shard}y{replica}son macros resueltas por cada nodo, declaradas enconfig.xml/macros.xml.
Archivo macros.xml en cada nodo:
<clickhouse>
<macros>
<shard>01</shard>
<replica>ch-01</replica>
<cluster>my_cluster</cluster>
</macros>
</clickhouse>
ON CLUSTER my_cluster es una comodidad para que la consulta se ejecute en todos los nodos del cluster definido en remote_servers. Sin ella, tienes que crear la tabla a mano en cada nodo.
Inserts y consistencia
Por defecto, un INSERT en una réplica devuelve OK en cuanto el dato está escrito localmente. La réplica lo propaga de forma asíncrona al resto.
Si tu caso exige que el INSERT no retorne hasta que N réplicas tengan el dato:
SET insert_quorum = 2;
INSERT INTO blog.pageviews ...;
Con 3 réplicas, insert_quorum = 2 da "escritura durable en mayoría" al estilo Raft. Es más lento, pero ninguna escritura confirmada puede perderse al caerse una réplica.
Sharding con Distributed
Cuando llega el momento del sharding, se trabaja con dos tablas:
- Una local (normalmente
ReplicatedMergeTree) que guarda el trozo correspondiente. - Una Distributed que es un puntero a las locales y presenta una vista única.
-- En cada nodo
CREATE TABLE blog.pageviews_local ON CLUSTER my_cluster (
ts DateTime,
user_id UInt64,
...
)
ENGINE = ReplicatedMergeTree(...)
ORDER BY (ts, user_id);
-- También en cada nodo
CREATE TABLE blog.pageviews ON CLUSTER my_cluster AS blog.pageviews_local
ENGINE = Distributed(
my_cluster, -- nombre del cluster
blog, -- base de datos
pageviews_local, -- tabla local
cityHash64(user_id) -- clave de sharding
);
Cuando consultas SELECT ... FROM blog.pageviews, ClickHouse:
- Contacta con una réplica de cada shard.
- Les manda la consulta en paralelo.
- Mergea los resultados.
La clave de sharding determina cómo se distribuyen los datos. Elígela bien: algo con alta cardinalidad (como user_id) evita hotspots. No uses country porque "España" acabaría inflada en un shard y vacía en el resto.
remote_servers
Es la definición del cluster, en config.xml o en un fichero .xml bajo config.d/:
<remote_servers>
<my_cluster>
<shard>
<replica><host>ch-01</host><port>9000</port></replica>
<replica><host>ch-02</host><port>9000</port></replica>
</shard>
<shard>
<replica><host>ch-03</host><port>9000</port></replica>
<replica><host>ch-04</host><port>9000</port></replica>
</shard>
</my_cluster>
</remote_servers>
Dos shards, dos réplicas cada uno. Total: 4 nodos. Cuando insertas en la Distributed, ClickHouse enruta a un shard concreto según la clave; cuando lees, pregunta a los dos.
Configuración esencial de producción
Una muestra de cambios típicos respecto a los defaults:
<clickhouse>
<!-- Logs rotados y con nivel razonable -->
<logger>
<level>information</level>
<size>1000M</size>
<count>10</count>
</logger>
<!-- Compresión por defecto, zstd para datos fríos -->
<compression>
<case>
<min_part_size>100000000</min_part_size>
<method>zstd</method>
<level>3</level>
</case>
</compression>
<!-- Límites de memoria por consulta -->
<profiles>
<default>
<max_memory_usage>30000000000</max_memory_usage> <!-- 30 GB por consulta -->
<max_memory_usage_for_user>60000000000</max_memory_usage_for_user>
<max_bytes_before_external_group_by>20000000000</max_bytes_before_external_group_by>
<max_execution_time>300</max_execution_time>
</default>
</profiles>
<!-- Retención de query_log -->
<query_log>
<database>system</database>
<table>query_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<ttl>event_date + INTERVAL 30 DAY DELETE</ttl>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</query_log>
</clickhouse>
Recomendaciones específicas de sistema operativo:
- Filesystem: ext4 o XFS. Evita ZFS y btrfs si puedes, hay caveats.
- Huge pages transparentes: desactiva THP. Degradan rendimiento en cargas grandes.
- Ulimits:
nofilea 262144,memlockaunlimited. - Swap: desactivado o
swappiness = 1.
Monitorización
ClickHouse ya incluye métricas detalladas. Solo hay que exponerlas.
Endpoint Prometheus nativo
En config.xml:
<prometheus>
<endpoint>/metrics</endpoint>
<port>9363</port>
<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>true</asynchronous_metrics>
</prometheus>
Prometheus scrapea http://nodo:9363/metrics. Si ya usas Prometheus y Grafana para servicios pequeños, encaja igual.
Tablas de system más útiles
-- ¿Están las réplicas al día?
SELECT database, table, is_leader, absolute_delay, queue_size
FROM system.replicas;
-- Consultas lentas de la última hora
SELECT query, query_duration_ms, read_rows, memory_usage
FROM system.query_log
WHERE event_time >= now() - INTERVAL 1 HOUR
AND type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 20;
-- Errores recientes
SELECT name, value, last_error_time, last_error_message
FROM system.errors
WHERE last_error_time >= now() - INTERVAL 1 HOUR;
-- Parts por tabla (alerta si explotan)
SELECT database, table, count() AS parts
FROM system.parts
WHERE active
GROUP BY database, table
ORDER BY parts DESC
LIMIT 20;
-- Mutations en curso (pueden ser largas y pesadas)
SELECT * FROM system.mutations WHERE NOT is_done;
Alertas mínimas que deberías tener:
absolute_delaydesystem.replicas> 60 segundos.- Número de parts activas > ~300 por tabla. Señal de inserts mal dimensionados.
- Errores nuevos en
system.errors. - Espacio en disco < 20%.
- Uso de memoria > 85% sostenido.
- Latencia p99 de consultas por encima de tu SLO.
Backups
ClickHouse tiene un comando BACKUP incorporado desde la versión 22.8:
BACKUP TABLE blog.pageviews TO S3('https://bucket.s3.amazonaws.com/backups/pageviews', 'key', 'secret');
BACKUP DATABASE blog TO Disk('backups', 'blog-2026-04-20.zip');
-- Restaurar
RESTORE TABLE blog.pageviews FROM S3(...);
Es incremental si el destino ya tiene un backup previo compatible. Para clusters con múltiples shards, necesitas orquestarlo nodo a nodo o usar BACKUP ON CLUSTER.
Alternativas clásicas:
clickhouse-backup(Altinity): herramienta dedicada, muy madura, integración S3/GCS/Azure nativa.- Snapshots de volumen: si tienes LVM/ZFS o discos en la nube, un snapshot atómico mientras haces
SYSTEM STOP MERGESfunciona.
Regla de oro: prueba tus restores. Un backup sin restore probado es placebo. Si no has restaurado nunca, no tienes backup.
Retos típicos en producción
Una lista de cosas con las que casi todo el mundo acaba tropezando:
- "Too many parts". Significa que haces inserts demasiado pequeños o demasiado frecuentes. Agrupa en batches más grandes o usa
Bufferengine delante. - MVs que explotan en un insert. Los MVs se ejecutan en el contexto del insert; si el MV falla, el insert falla. Prueba los MVs con datos representativos antes de activarlos en producción.
- Queries de usuarios consumiendo toda la memoria. Usa
max_memory_usagepor perfil de usuario y obliga a que consultas ad hoc vayan por un perfil más restrictivo. - Réplicas que se desincronizan. Casi siempre es Keeper que no tiene quórum o que tiene latencia alta entre nodos. Mide Keeper.
- DDL
ON CLUSTERque se queda bloqueado. Usadistributed_ddl_task_timeouty monitorizasystem.distributed_ddl_queue. - Disco lleno tras un merge grande. Un merge puede necesitar temporalmente 2× el espacio del part resultante. Mantén siempre margen.
Recursos imprescindibles
- Documentación oficial: clickhouse.com/docs.
- Blog de ClickHouse, Inc.: artículos técnicos muy buenos sobre internals.
- Repositorio de GitHub ClickHouse/ClickHouse: leer los issues enseña más que muchos tutoriales.
- El blog de Altinity sigue siendo una referencia para how-tos avanzados.
Cierre de la serie
En cinco posts hemos recorrido ClickHouse desde instalarlo hasta operarlo en cluster. No hace falta dominarlo todo a la primera: cada proyecto te forzará a profundizar en uno u otro aspecto.
Si hay algo que me llevo de usar ClickHouse en producción durante años es esto: el 80% del rendimiento viene de decisiones tomadas al crear la tabla. Tipos adecuados, ORDER BY bien pensada, particionado sensato y un par de materialized views cubren la inmensa mayoría de los casos. El resto es operación, que es donde este último post intenta poner un poco de base.
Serie completa, por si estás aterrizando:
- I: instalación y primeros pasos
- II: tipos de datos y MergeTree
- III: consultas analíticas en profundidad
- IV: materialized views, projections y TTL
- V: producción, replicación y clusters (estás aquí)
Y como lectura complementaria, el post introductorio ClickHouse para desarrolladores que vienen de PostgreSQL sigue siendo útil como resumen en una única página para compartir con el equipo cuando tengas que justificar por qué vais a meter un motor nuevo.
Si has llegado hasta aquí, gracias por el rato. Si tienes dudas concretas sobre diseñar una tabla, migrar desde otro sistema o depurar una consulta lenta, mis DMs siguen abiertos.