MySQL vs ClickHouse: Evita que la analítica mate al OLTP (El plan de separación limpia)

¿Te fue útil?

En algún lugar de tu empresa, un analista bienintencionado acaba de actualizar un panel. Ahora el checkout va lento, la API agota el tiempo de espera y el canal on-call se ha convertido en una sesión grupal de terapia.

Esto no es un problema de “consulta mala”. Es un problema de arquitectura: OLTP y analítica son animales distintos, y ponerlos en la misma jaula termina de forma predecible. La solución es una separación limpia: MySQL hace transacciones, ClickHouse hace analítica, y dejas de permitir que la curiosidad haga DDoS a tu ruta de ingresos.

El problema real: OLTP y analítica chocan en la capa de almacenamiento

OLTP trata sobre latencia, exactitud y concurrencia predecible. Se optimiza para miles de lecturas/escrituras pequeñas por segundo, índices ajustados y conjuntos de trabajo calientes que caben en memoria. El costo de una sola petición lenta se paga de inmediato—en la experiencia del cliente, en timeouts y en reintentos que amplifican la carga.

La analítica trata sobre rendimiento, escaneos amplios y agregación. Se optimiza para leer grandes volúmenes de datos, comprimirlos bien y usar ejecución vectorizada para convertir CPU en respuestas. Las consultas analíticas a menudo son “embarazosa y naturalmente paralelas” y no les importa tardar algunos segundos—hasta que apuntan a tu base transaccional y se convierten en una denegación de servicio con una tabla dinámica adjunta.

La conclusión: OLTP y analítica compiten por los mismos recursos finitos—ciclos de CPU, I/O de disco, caché de páginas, buffer pools, locks/latches y mantenimiento de fondo (flushes, checkpoints, merges). Incluso si añades una réplica de lectura, frecuentemente compartes el mismo dolor fundamental: lag de replicación, saturación de I/O y rendimiento inconsistente causado por escaneos impredecibles.

Dónde duele: contención de recursos en MySQL

  • Contaminación del buffer pool: Una gran consulta de reporte lee un segmento frío de historial, expulsa páginas calientes y de repente tu carga primaria queda limitada por disco.
  • Presión de fondo de InnoDB: Escaneos largos + tablas temporales + ordenamientos pueden incrementar páginas sucias y la presión de redo. Las tormentas de flush no son amables.
  • Bloqueos y metadata locks: Algunos patrones de reporte desencadenan interacciones feas (piensa en “ALTER TABLE durante horas de negocio” encontrándose con un “SELECT …” que mantiene MDL).
  • Lag de replicación: Lecturas pesadas en una réplica roban I/O y CPU del hilo SQL que aplica cambios.

Dónde encaja ClickHouse

ClickHouse está diseñado para analítica: almacenamiento columnar, compresión, ejecución vectorizada y paralelismo agresivo. Espera que leas muchas filas, pero solo unas pocas columnas, y te recompensa por agrupar trabajo en particiones y claves ordenadas.

La disciplina es simple: trata MySQL como el sistema de registro para transacciones. Trata ClickHouse como el sistema de verdad para analítica—“verdad” en el sentido de “derivado del registro, reproducible y consultable a escala”.

Idea parafraseada de Werner Vogels: “Todo falla; diseña para la falla.” También aplica a los datos: diseña para modos de fallo como tormentas de consultas, lag y backfills.

MySQL vs ClickHouse: las diferencias reales que importan en producción

Disposición del almacenamiento: fila vs columna

MySQL/InnoDB es orientado a filas. Excelente para obtener una fila por clave primaria, actualizar un par de columnas, mantener índices secundarios y aplicar restricciones. Pero escanear mil millones de filas para calcular agregados significa arrastrar filas completas por el motor, tocar páginas que no necesitabas y consumir caché.

ClickHouse es orientado a columnas. Lee solo las columnas que pides, las comprime bien (a menudo de forma dramática) y las procesa en vectores. Pagas por adelantado con restricciones de modelado distintas—desnormalización, claves de ordenación cuidadosas y un proceso de merge que debes respetar.

Modelo de concurrencia: transaccional vs paralelismo analítico

MySQL maneja muchas transacciones cortas concurrentes bien—hasta los límites de tu esquema, índices y hardware. ClickHouse también maneja muchas lecturas concurrentes, pero la magia está en paralelizar lecturas grandes y agregaciones eficientemente. Si apuntas una herramienta BI a ClickHouse y permites concurrencia ilimitada sin límites de consulta, intentará prender fuego a tu CPU. Puedes y debes gobernarlo.

Consistencia y corrección

MySQL es ACID (con las advertencias habituales, pero sí, es tu ancla transaccional). ClickHouse es típicamente eventualmente consistente para datos ingeridos y orientado a append. Puedes modelar updates/deletes, pero lo haces en los términos de ClickHouse (ReplacingMergeTree, CollapsingMergeTree, columnas de versión, o deletes asíncronos). Eso está bien: la analítica suele querer la verdad actual y una serie temporal de cambios, no la semántica transaccional por fila.

Indexación y patrones de consulta

Los índices de MySQL son B-trees que soportan búsquedas por punto y escaneos por rango. ClickHouse usa ordenación por clave primaria e índices dispersos, además de índices de salto de datos (como bloom filters) donde ayuda. La mejor consulta en ClickHouse es la que puede saltarse grandes trozos de datos porque tu particionado y ordenamiento coinciden con los patrones de acceso.

Postura operativa

La operación de MySQL gira en torno a la salud de la replicación, backups, migraciones de esquema y estabilidad de consultas. La operación de ClickHouse gira en torno a merges, utilización de disco, recuento de partes, TTL y gobernanza de consultas. En otras palabras: cambias un conjunto de dragones por otro. La ventaja sigue siendo válida porque dejas de permitir que la analítica arruine tu flujo de checkout.

Broma #1: Un refresco de panel es el único tipo de “participación de usuario” que puede aumentar las tasas de error y la pérdida de clientes al mismo tiempo.

Hechos y contexto histórico (útil, no trivia)

  1. InnoDB de MySQL se volvió predeterminado en MySQL 5.5 (era 2010), afianzando el comportamiento de almacén por filas para la mayoría de despliegues.
  2. ClickHouse comenzó en Yandex para soportar cargas analíticas a escala; creció en un mundo donde escanear grandes datos rápidamente era el trabajo completo.
  3. Los almacenes columnar se popularizaron porque la CPU se volvió más rápida que los discos, y compresión + ejecución vectorizada te permiten gastar CPU para evitar I/O.
  4. La “contaminación” del buffer pool de InnoDB es un modo clásico de fallo cuando escaneos largos expulsan páginas calientes; el motor no está “roto”, está haciendo lo que pediste.
  5. La analítica basada en replicación existe desde hace décadas: la gente ha estado enviando cambios OLTP a data warehouses desde antes de que “data lake” fuera una palabra de moda.
  6. El query cache de MySQL fue removido en MySQL 8.0 porque causaba contención y no escalaba bien; caché no es gratis, y los locks globales son caros.
  7. La familia MergeTree de ClickHouse almacena datos en partes y las combina en segundo plano—excelente para escrituras y compresión, pero crea señales operativas (recuento de partes, backlog de merges) que debes monitorear.
  8. El esquema estrella y el modelado dimensional preceden a las herramientas modernas; ClickHouse a menudo empuja a los equipos de vuelta hacia formas desnormalizadas y amigables para consultas porque los joins a escala tienen costos reales.

El plan de separación limpia: patrones que no derriten producción

Principio 1: MySQL es para servir usuarios, no curiosidad

Hazlo política: MySQL de producción no es una base de datos de reporting. No “usualmente.” No “excepto por una consulta rápida.” Nunca. Si alguien necesita un one-off, que lo ejecute contra ClickHouse o un entorno de snapshot controlado.

Recibirás resistencias. Es normal. El truco es reemplazar el “no” con “aquí está la forma segura.” Proporciona el camino seguro: acceso a ClickHouse, conjuntos de datos curados y un flujo de trabajo que no implique rogar al on-call por permiso para ejecutar un JOIN sobre un año de pedidos.

Principio 2: Elige una estrategia de movimiento de datos que coincida con tu tolerancia a fallos

Hay tres formas comunes de alimentar ClickHouse desde MySQL. Cada una tiene bordes afilados.

Opción A: ETL por lotes (dump y carga)

Extraes snapshots cada hora/diarios (mysqldump, exportaciones CSV, jobs de Spark), cargas en ClickHouse y aceptas la obsolescencia. Es lo más sencillo operacionalmente pero puede ser doloroso cuando necesitas métricas casi en tiempo real, y los backfills pueden ser pesados.

Opción B: Ingestión basada en replicación (CDC)

Capturas cambios del binlog de MySQL y los streamas a ClickHouse. Esto te da analítica casi en tiempo real mientras mantienes MySQL aislado de la carga de consulta. Pero introduce la salud del pipeline como una preocupación de producción de primera clase: lag, drift de esquema y re-procesamiento se vuelven tu nuevo hobby.

Opción C: Escritura dual (la aplicación escribe en ambos)

No lo hagas. O, si absolutamente debes, hazlo solo con idempotencia robusta, entrega asíncrona y un job de reconciliación que asuma que la escritura dual te mentirá ocasionalmente.

El plan de separación limpia suele significar CDC más modelos de datos curados en ClickHouse. ETL por lotes es aceptable cuando puedes tolerar obsolescencia. La escritura dual es una trampa a menos que disfrutes explicar desajustes de datos en los postmortems de incidentes.

Principio 3: Modela ClickHouse para tus preguntas, no para tu esquema

La mayoría de esquemas OLTP están normalizados. La analítica quiere menos joins, claves estables y tablas de tipo evento. Tu trabajo es construir una representación analítica que sea fácil de consultar y difícil de usar mal.

  • Prefiere tablas de eventos: orders_events, sessions, payments, shipments, support_tickets. Registra eventos append-only. Deriva métricas.
  • Particiona por tiempo: normalmente por día o mes. Esto te da pruning predecible y TTL manejable.
  • Ordena por dimensiones de consulta: coloca las claves de filtro/agrupación más comunes al inicio de ORDER BY (después de la clave de tiempo si siempre filtras por tiempo).
  • Pre-agrega donde sea estable: las materialized views pueden producir rollups para que los paneles no escaneen repetidamente datos crudos.

Principio 4: Gobernanza vence a las heroicas

ClickHouse puede responder preguntas lo suficientemente rápido como para que la gente haga preguntas peores más frecuentemente. Necesitas barandillas:

  • Separa usuarios y cuotas: los usuarios de BI obtienen timeouts y memoria máxima. ETL obtiene un perfil diferente.
  • Establece max threads y concurrencia: evita una “manada de cieno” de consultas paralelas.
  • Usa datasets “gold” dedicados: vistas o tablas estables de las que dependen los dashboards, versionadas si es necesario.
  • Define SLOs: el SLO de latencia de MySQL es sagrado. El SLO de frescura de ClickHouse es negociable pero medible.

Tareas prácticas (comandos, salidas, decisiones)

Estos son los movimientos que realmente haces a las 02:13. Cada tarea incluye un comando, salida de ejemplo, qué significa y la decisión que obtienes de ello.

Task 1: Confirmar que MySQL sufre escaneos analíticos (top digests)

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY customer_id
COUNT_STAR: 9421
total_s: 18873.214
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 110233
total_s: 8211.532

Qué significa: Tu mayor tiempo viene de un agregado clásico de reporting sobre un rango de fechas. No es “una consulta lenta”, es dolor repetido.

Decisión: Bloquea o redirige el patrón de consulta analítica. No conviertas MySQL en un motor OLAP. Empieza moviendo ese panel a ClickHouse o a una tabla de rollup.

Task 2: Comprobar actividad actual de threads en MySQL (¿es un dogpile?)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
31	app	10.0.2.14:51234	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
44	app	10.0.2.14:51239	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
57	app	10.0.2.14:51241	prod	Query	1	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id

Qué significa: Muchas consultas idénticas se están ejecutando concurrentemente. Es un panel o una flota de workers haciendo el mismo trabajo caro.

Decisión: Limita en la capa de app/BI e introduce caching o pre-aggregación en ClickHouse. Considera también límites de conexión de MySQL y controles de recursos por usuario.

Task 3: Validar presión en el buffer pool de InnoDB (páginas calientes expulsadas)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
Variable_name	Value
Innodb_buffer_pool_read_requests	987654321
Innodb_buffer_pool_reads	12345678

Qué significa: Un número alto de lecturas físicas (Innodb_buffer_pool_reads) relativo a lecturas lógicas sugiere que tu conjunto de trabajo no se mantiene en memoria—a menudo debido a escaneos grandes.

Decisión: Detén los escaneos (mueve la analítica fuera) y solo entonces considera aumentar el buffer pool o ajustar la carga. El hardware no puede sobrepasar una mezcla de carga mala para siempre.

Task 4: Detectar saturación de I/O de disco en el host MySQL

cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (mysql01) 	12/30/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.12    0.00    6.44   31.55    0.00   43.89

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
nvme0n1         820.0  64200.0     0.0    0.0   12.4    78.3     410.0  18800.0    9.8   18.2   98.7

Qué significa: %util cercano a 100% y alto iowait significa que el disco es el cuello de botella. A los escaneos analíticos les encanta este resultado.

Decisión: Inmediato: reduce la concurrencia de consultas, mata los peores culpables, cambia la analítica a ClickHouse. A largo plazo: separa almacenamiento y cargas; no confíes en “NVMe más rápido” como estrategia.

Task 5: Identificar lag de replicación en MySQL (tu “réplica de lectura” no está ayudando)

cr0x@server:~$ mysql -h mysql-replica01 -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_SQL_Running|Slave_IO_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 487

Qué significa: La réplica está ~8 minutos detrás. Los paneles que la leen están mintiendo. Peor: si haces failover, podrías perder transacciones recientes.

Decisión: No uses la réplica como sumidero analítico. Usa CDC hacia ClickHouse, o al menos una réplica dedicada con acceso de consulta controlado y recursos garantizados.

Task 6: Mostrar el plan de la consulta cara (deja de adivinar)

cr0x@server:~$ mysql -e "EXPLAIN SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: range
possible_keys: idx_created_at
key: idx_created_at
rows: 98234123
Extra: Using where; Using temporary; Using filesort

Qué significa: Incluso con un índice, estás escaneando ~98M filas y usando temp/filesort. Eso no es una consulta OLTP; es un trabajo OLAP.

Decisión: Muévela. Si debes mantener algunos agregados en MySQL, usa tablas resumen actualizadas incrementalmente, no GROUP BY ad hoc sobre hechos crudos.

Task 7: Confirmar lo básico de salud de ClickHouse (¿son merges o disco el problema?)

cr0x@server:~$ clickhouse-client -q "SELECT hostName(), uptime()"
ch01
345678

Qué significa: Puedes conectar y el servidor ha estado vivo suficiente tiempo para ser útil.

Decisión: Procede a verificaciones más profundas: partes/merges, carga de consultas y disco.

Task 8: Comprobar consultas activas en ClickHouse y su uso de recursos

cr0x@server:~$ clickhouse-client -q "SELECT user, query_id, elapsed, read_rows, formatReadableSize(memory_usage) AS mem, left(query, 80) AS q FROM system.processes ORDER BY memory_usage DESC LIMIT 5 FORMAT TabSeparated"
bi_user	0f2a...	12.4	184001234	6.31 GiB	SELECT customer_id, sum(total) FROM orders_events WHERE event_date >= toDate('2025-12-01')
etl	9b10...	3.1	0	512.00 MiB	INSERT INTO orders_events FORMAT JSONEachRow

Qué significa: BI está consumiendo memoria. Está bien si está presupuestado. Es un problema si roba merges o provoca OOM.

Decisión: Establece max_memory_usage por usuario, max_threads y posiblemente max_concurrent_queries. Mantén ETL fiable.

Task 9: Ver backlog de merges en ClickHouse (partes creciendo como maleza)

cr0x@server:~$ clickhouse-client -q "SELECT database, table, sum(parts) AS parts, formatReadableSize(sum(bytes_on_disk)) AS disk FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(parts) DESC LIMIT 10 FORMAT TabSeparated"
analytics	orders_events	1842	1.27 TiB
analytics	sessions	936	640.12 GiB

Qué significa: Miles de partes pueden significar fragmentación de inserts o merges quedando atrás. El rendimiento de consultas se degradará, y el arranque/metadata se vuelve más pesado.

Decisión: Ajusta el batching de inserts, afina configuraciones de merge con cautela y considera la estrategia de particionado. Si las partes siguen subiendo, trátalo como un incidente en cámara lenta.

Task 10: Validar pruning de particiones (si escanea todo, lo modelaste mal)

cr0x@server:~$ clickhouse-client -q "EXPLAIN indexes=1 SELECT customer_id, sum(total) FROM analytics.orders_events WHERE event_date BETWEEN toDate('2025-12-01') AND toDate('2025-12-30') GROUP BY customer_id"
Expression ((Projection + Before ORDER BY))
  Aggregating
    Filter (WHERE)
      ReadFromMergeTree (analytics.orders_events)
        Indexes:
          MinMax
            Keys: event_date
            Condition: (event_date in [2025-12-01, 2025-12-30])
            Parts: 30/365
            Granules: 8123/104220

Qué significa: Está leyendo 30/365 partes gracias al filtro de fecha. Eso es lo que “funciona según el diseño” parece.

Decisión: Si las partes leídas se acercan al total, cambia el particionado y/o exige filtros de tiempo en los dashboards.

Task 11: Monitorizar uso de disco en ClickHouse y predecir problemas de capacidad

cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme1n1    3.5T  3.1T  330G  91% /var/lib/clickhouse

Qué significa: 91% usado. Estás a un backfill de un mal día, y los merges necesitan espacio libre.

Decisión: Detén backfills no esenciales, amplía almacenamiento, aplica TTL y comprime/optimiza el modelo de datos. ClickHouse bajo presión de disco se vuelve impredeciblemente lento y arriesgado.

Task 12: Verificar lag del pipeline CDC en el consumidor (¿la analítica está obsoleta?)

cr0x@server:~$ clickhouse-client -q "SELECT max(ingested_at) AS last_ingest, now() AS now, dateDiff('second', max(ingested_at), now()) AS lag_s FROM analytics.orders_events"
2025-12-30 19:03:12	2025-12-30 19:03:29	17

Qué significa: ~17 segundos de lag. Eso es saludable para analítica “casi en tiempo real”.

Decisión: Si el lag sube, pausa consultas pesadas, revisa el throughput del pipeline y decide si degradar dashboards o arriesgar OLTP.

Task 13: Comprobar formato de binlog de MySQL para corrección de CDC

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format';"
Variable_name	Value
binlog_format	ROW

Qué significa: ROW suele ser lo que las herramientas CDC quieren para corrección. STATEMENT puede ser ambiguo para queries no deterministas.

Decisión: Si no estás en ROW, planifica una ventana de cambios. La corrección de CDC no es algo por lo que debas “tener esperanza”.

Task 14: Confirmar que MySQL tiene logging de consultas lentas sensato (para probar causalidad)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
Variable_name	Value
slow_query_log	ON
slow_query_log_file	/var/log/mysql/mysql-slow.log
Variable_name	Value
long_query_time	0.500000

Qué significa: Capturarás consultas más lentas que 500ms. Es agresivo, pero útil durante un periodo ruidoso.

Decisión: Durante incidentes, baja long_query_time temporalmente y muestrea. Después, ponlo a un umbral estable y usa resúmenes por digest.

Task 15: Verificar límites de usuario en ClickHouse (evitar una “fiesta de paralelismo” BI)

cr0x@server:~$ clickhouse-client -q "SHOW CREATE USER bi_user"
CREATE USER bi_user IDENTIFIED WITH sha256_password SETTINGS max_memory_usage = 4000000000, max_threads = 8, max_execution_time = 60, max_concurrent_queries = 5

Qué significa: BI está acotado: 4GB de memoria, 8 threads, 60s de runtime, 5 consultas concurrentes. Esa es la diferencia entre un dashboard y una prueba de estrés.

Decisión: Si no puedes poner límites porque “negocio lo necesita”, no estás corriendo analítica, estás jugando a la ruleta.

Guía de diagnóstico rápido

Este es el orden que encuentra el cuello de botella rápido, sin convertir el incidente en un debate filosófico.

Primero: ¿MySQL está sobrecargado por lecturas, escrituras, locks o I/O?

  1. Top query digests (performance_schema digests o resumen del slow log): identifica las familias de consultas que consumen tiempo.
  2. Estados de threads (SHOW PROCESSLIST): “Sending data” sugiere scan/aggregación; “Locked” sugiere contención; “Waiting for table metadata lock” sugiere colisión de DDL.
  3. I/O de disco (iostat): si iowait es alto y %util del disco está al máximo, detén los escaneos antes de ajustar cualquier otra cosa.

Segundo: ¿la “solución” (réplica) realmente lo empeora?

  1. Lag de replicación (SHOW SLAVE STATUS): si el lag es de minutos, los usuarios analíticos están tomando decisiones sobre datos obsoletos y te culpan por ello.
  2. Contención en la réplica: consultas pesadas pueden dejar sin recursos al hilo SQL y aumentar aún más el lag.

Tercero: Si ClickHouse existe, ¿está sano y gobernado?

  1. system.processes: identifica consultas BI desbocadas y consumidores de memoria.
  2. Parts y merges (system.parts): demasiadas parts significa problemas de forma de ingestión o backlog de merges.
  3. Espacio en disco (df): merges y TTL necesitan espacio; 90% lleno es deuda operativa con intereses.

Cuarto: ¿la frescura de los datos es la queja real?

  1. Lag de CDC (max ingested_at): cuantifica la obsolescencia.
  2. Comunica una alternativa: si la frescura se degrada, degrada los dashboards—no el checkout.

Tres mini-historias corporativas desde la trinchera

Incidente causado por una suposición errónea: “Las réplicas de lectura son para reporting”

Una empresa de suscripción mediana tenía un cluster MySQL primario y dos réplicas de lectura. Su herramienta BI apuntaba a una réplica porque “las lecturas no afectan las escrituras”. Esa frase ha causado más incidentes que la cafeína ha prevenido.

Durante cierre de mes, finanzas ejecutó un conjunto de informes de cohortes y de ingresos. El disco de la réplica alcanzó saturación: escaneos pesados más tablas temporales. El lag de replicación subió de segundos a decenas de minutos. Nadie lo notó al principio porque el tráfico de la aplicación estaba bien; el primario no se vio afectado directamente.

Entonces alguien hizo la segunda suposición: “Si el primario falla, podemos hacer failover a una réplica.” Justo cuando el lag era peor, el primario tuvo un problema de host no relacionado y entró en estado no saludable. La automatización intentó promover la “mejor” réplica—excepto que la “mejor” tenía 20 minutos de retraso.

No perdieron toda la base de datos. Perdieron suficientes transacciones recientes como para crear una pesadilla en soporte al cliente: pagos que “sucedieron” externamente pero no existían internamente, y sesiones que no coincidían con facturación. La recuperación fue una mezcla cuidadosa de rastreo de binlogs y reconciliación contra el proveedor de pagos.

La solución no fue heroica. Separaron responsabilidades: una réplica dedicada para failover con bloqueo estricto de consultas, y la analítica se movió a ClickHouse vía CDC. El reporting se volvió rápido y el failover confiable porque la réplica ya no era un saco de boxeo.

Optimización que salió mal: “Añadamos un índice”

Un equipo de e-commerce tenía una consulta de reporte lenta en orders: filtro por rango de tiempo más group-by. Alguien añadió un índice en created_at y otro índice compuesto en (created_at, customer_id). La consulta fue más rápida en aislamiento, así que lo pusieron en producción y celebraron.

Dos semanas después, la latencia de escritura empezó a subir. Los inserts en orders se ralentizaron y la tasa de flush de fondo aumentó. Los nuevos índices incrementaron la amplificación de escritura—cada insert ahora mantenía más estructuras B-tree. En picos de tráfico, pagaban una “tasa de índice” en cada transacción para hacer más baratas unas pocas consultas.

Luego la herramienta BI recibió un nuevo panel que ejecutaba la misma consulta cada minuto. La consulta era más rápida, así que la concurrencia aumentó (a la gente le encanta refrescar cuando refrescar es rápido). El sistema cambió una consulta lenta por muchas medianamente rápidas y aún así quedó limitado por I/O.

La solución real fue quitar el bloat de índices, mantener OLTP ágil y construir una tabla rollup en ClickHouse actualizada continuamente. Los dashboards consultan ClickHouse. Las transacciones permanecieron fluidas. El equipo aprendió la lección: indexar no es “velocidad gratis”, es un coste en tiempo de escritura que pagas por siempre.

Práctica aburrida pero correcta que salvó el día: cuotas y backfills escalonados

Una compañía B2B SaaS usaba ClickHouse para analítica con perfiles de usuario estrictos. Los usuarios BI tenían max_execution_time, max_memory_usage y límites de concurrencia. ETL tenía límites distintos y corría en una cola controlada. A nadie le gustaban esas restricciones. Todos se beneficiaron de ellas.

Una tarde, un analista intentó ejecutar una consulta amplia sobre dos años de eventos crudos sin filtro de fecha. ClickHouse empezó a escanear, alcanzó el límite de tiempo de ejecución y mató la consulta. El analista se quejó. On-call no fue paginado. Eso es un buen intercambio.

Más tarde ese mes, el equipo de datos necesitó un backfill por un cambio de esquema en el CDC upstream. Lo planificaron por etapas: un día a la vez, verificando recuentos de parts, espacio en disco y lag después de cada bloque. Lento, cuidadoso, medible. El backfill terminó sin amenazar los dashboards de producción.

La práctica aburrida no fue un algoritmo sofisticado. Fue gobernanza y disciplina operativa: límites, colas y backfills incrementales. Les salvó porque el sistema se comportó de forma predecible cuando los humanos se comportaron de forma impredecible.

Broma #2: Lo único más permanente que un panel temporal es el canal de incidentes que crea.

Errores comunes: síntoma → causa raíz → solución

  • Síntoma: Latencia p95 de MySQL sube durante “horas de reporting”
    Causa raíz: Escaneos largos y consultas GROUP BY compitiendo con OLTP por buffer pool e I/O
    Solución: Mueve el reporting a ClickHouse; aplica política; añade rollups curados; bloquea usuarios BI en MySQL.
  • Síntoma: El lag de la réplica de lectura aumenta cuando los analistas ejecutan reportes
    Causa raíz: I/O y CPU de la réplica saturados; el hilo SQL no puede aplicar binlog lo suficientemente rápido
    Solución: Quita acceso analítico de réplicas de failover; usa CDC a ClickHouse; limita la concurrencia de consultas.
  • Síntoma: Las consultas en ClickHouse se vuelven más lentas con el tiempo sin cambio en el tamaño de datos
    Causa raíz: Explosión de parts; merges retrasados debido a inserts fragmentados o presión de disco
    Solución: Batch de inserts; ajustar settings de merge con cuidado; monitorear parts; asegurar espacio en disco; considerar reparticionado.
  • Síntoma: Los dashboards son “rápidos a veces” y fallan con timeouts aleatorios en ClickHouse
    Causa raíz: Concurrencia BI no acotada; presión de memoria; consultas vecinas ruidosas
    Solución: Establecer límites por usuario (memoria, threads, tiempo de ejecución, concurrencia); crear tablas pre-agregadas; añadir enrutamiento de consultas.
  • Síntoma: Los datos analíticos tienen duplicados o “estado latest equivocado”
    Causa raíz: CDC aplicado como append-only sin deduplicación/versionado; updates/deletes no modelados correctamente
    Solución: Usa columnas de versión y ReplacingMergeTree donde proceda; almacena eventos y deriva el estado actual vía materialized views.
  • Síntoma: El uso de disco en ClickHouse sigue subiendo hasta convertirse en emergencia
    Causa raíz: Sin TTL; almacenamiento de crudos para siempre; backfills pesados; sin guardarraíles de capacidad
    Solución: Aplica TTL para datos fríos; downsample; archiva; comprime; aplica cuotas y procedimientos de backfill.
  • Síntoma: “Nos mudamos a ClickHouse pero MySQL sigue lento”
    Causa raíz: El pipeline CDC aún lee MySQL de forma pesada (extracts full-table, snapshots frecuentes), o la app sigue ejecutando reports en MySQL
    Solución: Usa CDC basado en binlog; revisa fuentes de consultas en MySQL; firewall/credenciales de reporting; valida con datos digest.
  • Síntoma: La frescura en ClickHouse se retrasa en picos
    Causa raíz: Cuello de botella de ingestión (throughput del pipeline), merges o presión de disco; a veces demasiados inserts pequeños
    Solución: Batch de inserts; escala la ingestión; monitorea lag; reduce temporalmente la concurrencia BI; prioriza recursos ETL.

Listas de verificación / plan paso a paso

Paso a paso: el plan de implementación de la separación limpia

  1. Declara el límite: MySQL de producción es solo OLTP. Escríbelo. Hazlo cumplir con cuentas y políticas de red.
  2. Inventaría consultas analíticas: usa tablas digest de MySQL y resúmenes del slow log para listar las 20 familias de consultas principales.
  3. Elige el método de ingestión: CDC para near-real-time; batch para diario/horario; evita la escritura dual.
  4. Define tablas analíticas en ClickHouse: empieza con tablas de eventos, particionado temporal y claves ORDER BY alineadas con filtros.
  5. Construye datasets “gold”: materialized views o tablas rollup para dashboards; conserva datos crudos para análisis profundo.
  6. Establece gobernanza desde el día uno: perfiles de usuario, cuotas, max_execution_time, max_memory_usage, max_concurrent_queries.
  7. Mide la frescura: monitorea lag de ingestión y publica el SLO a stakeholders. La gente tolera obsolescencia cuando es explícita.
  8. Corta el switch de dashboards: migra primero los dashboards de mayor impacto (los que indirectamente despiertan on-call).
  9. Bloquea la vía antigua: elimina credenciales BI de MySQL; firewall si es necesario; evita regresiones.
  10. Backfill seguro: incremental, medible, con comprobaciones de espacio en disco; nada de “corrámoslo toda la noche” sin control.
  11. Prueba carga en analítica: simula concurrencia de dashboards. ClickHouse aceptará tu optimismo y luego te lo recordará.
  12. Operacionaliza: alertas sobre recuento de parts en ClickHouse, uso de disco, fallos de consultas, lag de ingestión; y sobre latencia/I/O en MySQL.

Checklist de lanzamiento: mover un dashboard de MySQL a ClickHouse

  • ¿La consulta del dashboard incluye un filtro temporal que coincida con el particionado?
  • ¿Existe una tabla rollup/materialized view para evitar escanear eventos crudos repetidamente?
  • ¿El usuario de ClickHouse está limitado (memoria, threads, tiempo de ejecución, concurrencia)?
  • ¿La métrica de lag de CDC es visible para los usuarios del dashboard?
  • ¿La consulta antigua en MySQL está bloqueada o al menos eliminada de la app/herramienta BI?
  • ¿Validaste los resultados para un periodo de tiempo conocido (comprobar totales y recuentos)?

Checklist operativo: higiene semanal que previene desastres lentos

  • Revisar las parts activas en ClickHouse por tabla; investigar crecimientos rápidos.
  • Revisar el espacio en disco de ClickHouse; mantener suficiente espacio libre para merges y backfills.
  • Revisar las consultas BI principales por read_rows y uso de memoria; optimizar o pre-agregar.
  • Revisar los top digests de MySQL para asegurar que la analítica no haya regresado.
  • Probar rutas de restauración: backups de MySQL, expectativas de recuperación de metadata y datos de ClickHouse.

Preguntas frecuentes

1) ¿No puedo simplemente escalar MySQL verticalmente y listo?

Puedes, y obtendrás alivio temporal. El modo de fallo vuelve cuando aparece el siguiente dashboard o consulta de cohortes. El problema es la desalineación de carga, no solo la potencia.

2) Si ya tengo réplicas de MySQL—¿debería apuntar BI a ellas?

Sólo si estás cómodo con el lag y no usas esas réplicas para failover. Incluso entonces, limita la concurrencia y trátalo como un puente temporal, no como el estado final.

3) ¿ClickHouse es lo suficientemente “tiempo real” para dashboards operativos?

Muchas veces sí, con CDC. Mide el lag de ingestión explícitamente y diseña dashboards para tolerar pequeños retrasos. Si necesitas verdad transaccional sub-segundo, ese es territorio de MySQL.

4) ¿Cómo manejo updates y deletes de MySQL en ClickHouse?

Prefiere el modelado por eventos (append). Si necesitas “estado actual”, usa filas versionadas con motores como ReplacingMergeTree y diseña consultas/materialized views en consecuencia.

5) ¿ClickHouse reemplazará mi data warehouse?

A veces. Para muchas compañías se convierte en el almacén analítico principal. Pero si necesitas transformaciones pesadas, gobernanza o modelado entre sistemas, puede que aún mantengas una capa de warehouse. No fuerces una conversión religiosa.

6) ¿Cuál es la ganancia más rápida si estamos en llamas hoy?

Deja de ejecutar analítica en MySQL inmediatamente: mata las peores consultas, remueve acceso BI y mueve el dashboard a ClickHouse o a un rollup en caché. Luego arréglalo correctamente.

7) ¿Cuál es la mayor sorpresa operativa de ClickHouse para equipos MySQL?

Merges y parts. Los acostumbrados a row-store esperan “lo inserté, ya está.” ClickHouse sigue trabajando en segundo plano y debes monitorear ese trabajo.

8) ¿Cómo evito que los analistas escriban consultas caras en ClickHouse?

Usa perfiles de usuario con cuotas y timeouts, proporciona tablas “gold” curadas y enseña a la gente que faltar filtros temporales no es “explorar”, es incendio intencionado.

9) ¿Las materialized views lo solucionan todo?

No. Son excelentes para rollups estables y agregados comunes. Pero pueden añadir complejidad y coste de almacenamiento. Úsalas donde reduzcan trabajo repetido de forma medible.

10) ¿Y si mis consultas analíticas requieren joins complejos entre muchas tablas?

Desnormaliza para los caminos comunes, pre-calcula dimensiones y limita los joins. ClickHouse puede hacer joins, pero los mejores sistemas analíticos de producción evitan hacerlo repetidamente en tiempo de consulta.

Conclusión: pasos prácticos siguientes

Si haces una sola acción esta semana, haz esta: quita la carga analítica de MySQL. No pidiéndole a los usuarios que “tengan cuidado”, sino proporcionando un lugar mejor para hacer preguntas.

  1. Bloquea MySQL: cuentas separadas, bloquear redes BI y hacer cumplir que MySQL de producción atienda primero a los usuarios.
  2. Establece gobernanza en ClickHouse: límites, cuotas y datasets curados antes de invitar a toda la compañía.
  3. Mueve las 5 consultas peores: replica los datos necesarios vía CDC o batch, luego construye rollups para que los dashboards sigan siendo baratos.
  4. Operacionaliza la frescura: publica el lag de ingestión y trátalo como un requisito de producto. Es mejor estar sinceramente 60 segundos detrás que desconocidamente equivocado.
  5. Practica backfills: por etapas, medibles y reversibles. Tu yo futuro apreciará la contención de tu yo presente.

La separación limpia no es glamorosa. Es simplemente la diferencia entre una base de datos que sirve clientes y una base de datos que alberga una pelea diaria de analítica. Elige la vida más tranquila.

← Anterior
SLI/CrossFire: Por qué el multi-GPU fue un sueño — y por qué murió
Siguiente →
MySQL vs PostgreSQL en un VPS de 4 GB RAM: qué configurar primero para sitios web

Deja un comentario