Un conflicto de tipos en Elasticsearch borró la mitad de los logs de errores de una empresa
Un cambio menor en una librería alteró el tipo de un campo, provocando que Elasticsearch rechazara documentos de forma aleatoria durante cinco semanas.

Durante un incidente de servicio, el equipo de ingeniería de una empresa pasó cuarenta minutos buscando errores que sabían que existían pero que no aparecían en sus paneles de observabilidad. El problema no era la aplicación, sino el sistema de logs. Durante cinco semanas, aproximadamente la mitad de todas las líneas de error de la compañía se estaban perdiendo en el índice de Elasticsearch, dependiendo de qué servicio escribiera primero tras la medianoche.
La causa raíz era un conflicto de mapeo de campos. Los logs de la empresa se almacenaban en un índice nuevo cada día. Elasticsearch mapea automáticamente el tipo de un campo a partir del primer documento que lo contiene. El campo error.code había sido numérico durante años en todos los servicios. Sin embargo, una actualización de una librería interna en el servicio de fulfilment cambió este campo a un tipo de cadena de caracteres, con valores como TIMEOUT_UPSTREAM.
A partir de entonces, el tipo del campo en el índice diario dependía de la lotería de la medianoche. Si un servicio tradicional escribía un error numérico antes de las 00:00, el índice se mapeaba como number. Cualquier error de fulfilment con texto era rechazado con una excepción de mapeo. Si fulfilment escribía primero, el índice se mapeaba como keyword/string, y los errores numéricos de los demás servicios eran los que fracasaban. El log shipper, al recibir el rechazo, no bloqueaba el flujo completo, sino que descartaba el documento y seguía con el siguiente, registrando el fallo únicamente en su propia salida de depuración, que nadie miraba.
Los dashboards mostraban solo los documentos indexados. En los días 'malos', el servicios afectado aparecía simplemente como inactivo o con tráfico bajo. Al revisar las salidas del shipper, el equipo descubrió que el 50% de los errores no llegaban al índice, y la mitad que se perdía cambiaba cada día.
La solución
El equipo de plataforma ha implementado varios cambios para evitar que esto vuelva a ocurrir. Primero, ahora gestionan una plantilla de índice que define explícitamente los campos compartidos por todos los servicios. El campo error.code está mapeado como keyword, un tipo flexible que acepta tanto números como cadenas de texto sin conflicto.
Segundo, los campos que no están descritos en la plantilla ya no se mapean automáticamente al índice, aunque se conservan en el documento almacenado. Esto impide que la aparición de un nuevo campo sin tipo definido rompa la indexación de campos existentes.
Tercero, los documentos rechazados ya no van a un log de depuración invisible, sino a un índice de mensajes muertos (dead letter index). La tasa de rechazo es ahora una métrica explícita con una alerta configurada para cualquier valor superior a cero durante más de diez minutos.
Finalmente, se han añadido paneles que muestran lado a lado las líneas enviadas por el shipper y las líneas indexadas. La diferencia entre estas dos cifras es el único indicador fiable de pérdida de datos, ya que un resultado de búsqueda vacío puede interpretarse erróneamente como ausencia de errores, cuando en realidad podría ser ausencia de indexación.


