crtdrops
NOTA · ESPAÑOL

Por qué tu plataforma de telemetría pierde tramas y nadie se entera

Hay una suposición que casi todo backend que recibe dispositivos hace sin saber que la hace. Sobrevive a todas las pruebas que se le ocurren a quien lo escribió, y se puede enunciar en una línea:

Cada vez que leo del socket, me llega exactamente una trama.

Es falsa. No a veces, ni bajo condiciones raras: es falsa por definición del transporte. Y aun así un sistema que la asume puede operar meses sin que nadie lo note, porque las condiciones que la desmienten son justamente las que no ocurren sobre una mesa.

Lo que TCP promete y lo que no

TCP entrega una secuencia de bytes, en orden y sin pérdida. Eso es todo lo que promete.

No promete conservar las fronteras de lo que el emisor escribió. Si un dispositivo hace dos escrituras de veintinueve bytes, el receptor no tiene forma de saber, por el transporte, que fueron dos y no una de cincuenta y ocho. Las fronteras de mensaje no existen en TCP. Existen en el protocolo que viaja encima, y reconstruirlas es trabajo del receptor.

Una lectura devuelve lo que haya disponible en ese instante. Y lo que haya disponible depende de cosas que ni el emisor ni el receptor controlan: cómo se segmentó el flujo, si dos escrituras pequeñas viajaron en el mismo segmento, si un segmento llegó antes de que el receptor leyera el anterior, si el enlace retransmitió.

Con un emisor lento y un receptor ocioso, cada lectura suele traer exactamente una escritura. Esa es la condición del laboratorio. Es la excepción, no la regla.

Por qué la suposición sobrevive a las pruebas

Un equipo sobre la mesa transmite cada diez segundos por un enlace local. El receptor está desocupado, lee en cuanto llega algo, y lo que llega es una trama entera, porque nada la partió y nada tuvo tiempo de pegarse detrás. La prueba pasa. Se repite cien veces y pasa cien veces.

Nada de eso ocurre en campo.

Un dispositivo en un vehículo transmite por un enlace celular, con latencia variable y cobertura intermitente. Cuando pierde el enlace no descarta lo que tenía que enviar: lo almacena. Cuando el enlace vuelve, manda lo almacenado de corrido, una trama detrás de otra, sin pausa. Del lado del backend eso llega como un solo bloque de bytes con seis, diez o cuarenta tramas consecutivas, o con la última cortada a la mitad porque el segmento terminó ahí.

Y no hace falta que se caiga nada. Un enlace con latencia alta y escrituras pequeñas tiende a agruparlas: el transporte espera un poco antes de enviar, por eficiencia, y dos tramas separadas por doscientos milisegundos llegan juntas. Al revés, una trama más larga que el tamaño máximo de segmento llega en dos partes por construcción.

La suposición no falla por mala suerte. Falla en cuanto el tráfico se parece al real, y eso es lo primero que pasa cuando la flota sale del laboratorio.

Las tres cosas que hace un parser que la asume

El resumen habitual, “se pierden tramas”, esconde lo importante, que es cuáles y cómo. Un backend que trata cada lectura como una trama hace una de estas tres cosas cuando lo que leyó no mide lo que esperaba.

La descarta. Comprueba la longitud, no coincide, la ignora. Es la variante más común y la peor, porque es silenciosa. No hay error, no hay cierre, no hay registro. Las cuarenta tramas que el dispositivo guardó durante la pérdida de cobertura desaparecen, y el dispositivo las da por entregadas. Semanas después alguien nota huecos en un historial y ya no hay forma de reconstruir por qué.

Cierra la conexión. Interpreta el tamaño inesperado como protocolo inválido y corta. Es más ruidoso y por eso más benigno: el dispositivo reconecta y en los registros queda un cierre que alguien puede llegar a mirar. Pero si el dispositivo reenvía lo mismo al reconectar, el ciclo se repite hasta que por azar la lectura coincide con una trama.

La procesa de todos modos. Toma los primeros bytes como cabecera, lee los campos donde espera encontrarlos, y sigue. Si lo que leyó era una trama y media, la primera sale bien y la segunda se pierde. Si era media trama, los campos salen con basura. Y si el parser no vuelve a sincronizarse, cada lectura posterior interpreta bytes de carga como si fueran cabecera, y el sistema registra posiciones, velocidades y estados que ningún dispositivo emitió.

Ese último caso es el que un checksum está para atrapar. Pero eso supone que el backend valida antes de procesar, que es otra suposición que conviene no dar por buena.

La solución no tiene nada de sofisticada

Acumular. Lo que llega se agrega a un búfer, y del búfer se extraen tramas completas según lo que el protocolo permita saber.

Si las tramas son de longitud fija, se extrae cada vez que el búfer alcanza esa longitud, y lo que sobra espera. Si terminan en un delimitador, se extrae hasta el delimitador. Si llevan un campo de longitud, se lee ese campo, se espera a tener esa cantidad de bytes, y se extrae.

En los tres casos la regla es la misma: el búfer decide cuándo hay una trama, y la lectura solo aporta bytes.

Un receptor construido así no distingue entre una trama que llegó entera, una que llegó en dos partes con medio segundo de diferencia, y cuarenta que llegaron pegadas. Para él son bytes que entran y tramas que salen.

Cómo se ve desde fuera, cuando no tienes acceso al backend

Un dispositivo no puede ver el búfer del otro lado. Solo ve lo que le responden.

Si el protocolo tiene acuse, una trama descartada en silencio es una trama sin respuesta. Una trama que provocó un cierre es una conexión que se cae sin que el dispositivo la haya cerrado. Y una trama procesada a medias no se ve desde fuera de ninguna manera: el acuse llega, y lo que se guardó del otro lado es incorrecto.

De los tres modos de falla, el que menos daño hace es el único que se detecta con certeza desde el lado del cliente. Los otros dos dejan un indicio, y el indicio se confirma en los registros del backend o no se confirma. Conviene no confundir las dos cosas: lo que se observa desde fuera es la mitad de lo que ocurre.

A quién le pasa

Sería cómodo presentar esto como un error de principiantes. No lo es.

Escribí una herramienta para provocar exactamente este defecto en backends ajenos: un generador que emite tramas, las parte, las pega y cuenta las respuestas. Su propio cliente tenía el bug. Contaba un acuse por cada lectura. Cuando el backend respondía dos veces seguidas y las dos respuestas llegaban en el mismo segmento, la lectura traía cuatro bytes, el cliente contaba un acuse, y daba el segundo por perdido.

El backend estaba bien. El instrumento que lo juzgaba, no.

Lo encontré porque probé la herramienta contra un receptor que se portaba correctamente y me negué a creer el resultado. Es la única razón por la que hoy puedo escribir sobre esto sin sentirme un impostor.


Si operas o construyes una plataforma que recibe rastreadores, registradores o medidores por TCP, hay una probabilidad alta de que esto describa un bug que tu plataforma tiene hoy y que nadie ha visto todavía. La forma de saberlo no es leer el código: es producir el tráfico que lo desmiente y mirar qué hace.

Esto que acabas de leer es el capítulo 4 de una guía. Si quieres pasárselo a tu equipo, el capítulo completo está gratis en PDF, con el diagrama que aquí no cabe:

israelnegretelepe.gumroad.com/l/un-read-no-es-una-trama

Y si lo que quieres es producir ese tráfico contra tu propia plataforma, la guía entera viene con el software que lo genera: veintitrés capítulos, ocho anexos y un generador de flotas virtuales que habla el protocolo exacto de tus equipos.

Flotas virtuales — israelnegretelepe.gumroad.com/l/flotas-virtuales

Israel Negrete Lepe es ingeniero en electrónica. Veinticinco años construyendo sistemas que llegan a producción: telemetría, rastreo vehicular, videovigilancia municipal, control de activos por RFID y plataformas transaccionales.