Una alerta que nadie veía
La plataforma guardaba logs, traces y métricas de producción en SigNoz, que corre sobre ClickHouse con coordinación por ZooKeeper. Los datos se repartían por hash entre dos shards.
Hacía días que una alerta de volumen casi lleno se disparaba, pero no le llegaba a nadie. Solo apareció porque, en otro trabajo, yo había reconectado la entrega de alertas de ese entorno. Cuando fui a mirar, el problema era mucho más grande que un disco lleno: uno de los dos shards no escribía nada desde hacía más de 50 días. Como la distribución era por hash, cerca de la mitad de todos los logs, traces y métricas de producción se estaba tirando en silencio.
Medir antes de borrar
El reflejo, en estos casos, es subir la retención o borrar datos viejos. Antes de nada, inventarié el disco por base de datos de ClickHouse. La telemetría de los clientes ocupaba unos 241 MiB, el 0,12% del disco. Los otros 186 GiB eran el log interno de operaciones de ZooKeeper, una tabla de la propia base que crecía sin control.
Con autorización, expandí el volumen del shard de 200 a 400 GiB con el pod en marcha, sin reiniciar. El uso bajó del 100% al 50% y el shard volvió a escribir en el momento. Dejé registrado lo que no tenía vuelta atrás: los 50 días de datos de ese shard estaban perdidos.
La causa de la causa
Poco después llegó otro incidente crítico. El NOC limpió la tabla, y en menos de 10 horas había vuelto a 85 GiB. Eso no era acumulación: era una canilla abierta. Reconstruí la cadena entera.
El colector de telemetría insertaba en micro-lotes, con el flush disparado cada 200 ms y nunca por tamaño. Eso generaba unas 65 mil partes nuevas por hora en ClickHouse, y cada parte generaba decenas de operaciones en ZooKeeper: unos 15 millones de operaciones por hora por pod. Cada operación escribía dos filas en el log interno, cerca de 9 GiB por hora. El TTL de la tabla era de 30 días, pero el disco se llenaba en dos.
Para empeorar, ZooKeeper corría con el heap igual al límite de memoria del contenedor, sin ningún margen. Bajo presión, el liveness probe fallaba y el pod moría, cientos de veces. En cada caída, ClickHouse rehacía las operaciones en masa y volvía a alimentar el ciclo.
Propuse las correcciones en orden de impacto y riesgo. Primero, ampliar la ventana del flush, la palanca más fuerte y más segura. Después, acortar el TTL del log interno en el archivo de configuración que ya estaba versionado, darle margen de memoria a ZooKeeper y aflojar el probe. Por último, como solución definitiva, migrar a ClickHouse Keeper, que ya corría estable en el mismo clúster.
Observar a quien observa
ClickHouse era el mayor consumidor de CPU del clúster y el único componente grande sin ninguna métrica. Descubrí que los exporters ya estaban ahí y nadie los recolectaba desde hacía 160 días. Bastó con crear los ServiceMonitors.
Armé el monitoreo con el método USE (utilización, saturación y errores) para cada recurso de ClickHouse, con umbrales tomados de una línea base medida en el propio entorno. En la primera lectura de la parte de errores aparecieron 57 entradas atascadas en la cola de replicación, algunas con más de 100 días, que generaban unas 142 mil fallas de búsqueda por día. Eran partes perdidas en los incidentes de disco lleno que ClickHouse intentaba recuperar en bucle.
También dejé documentada una trampa: la métrica de retraso de replicación marcaba 124 días en todas las tablas, pero eso era solo la edad de la entrada atascada más antigua. Una alerta sobre ella se dispararía para siempre, sin ninguna acción posible.
Qué cambió
El shard volvió a escribir y la pérdida de la mitad de la telemetría se detuvo, después de 50,6 días. La cadena completa de la causa quedó documentada, con un plan de corrección ordenado. ClickHouse y ZooKeeper pasaron de no tener ninguna métrica a un monitoreo con umbrales medidos, que en su primera lectura ya reveló un problema escondido durante meses.
Lo que me llevo
Cuando el disco de una base se llena, la primera pregunta es qué está ocupando el espacio, y no qué borrar. Aquí, la "falta de retención" era un síntoma: la causa estaba en ZooKeeper y en la forma de insertar los datos.
Y la observabilidad también necesita ser observada. La herramienta que debía avisar de los problemas estaba perdiendo la mitad de los datos, y la única alerta sobre eso no le llegaba a nadie.