DiseñoSistemas adaptativosRustBases de datosDepuración

Mi memoria adaptativa quedó vacía en producción, y no era un bug

Publicado el 2026-08-26 · Xiliux

Tenía una tabla en la base de datos que debía llenarse sola. Su trabajo era aprender de los fracasos: cada vez que el sistema probaba una variante de algo y no funcionaba, la guardaba, para reciclarla más tarde en otro contexto donde quizá sí sirviera. Un laboratorio de intentos fallidos, acumulándose.

En producción tenía cero filas. Llevaba días desplegada y no había guardado ni un registro. Mientras tanto, una tabla vecina —otra memoria, la que anota qué trabajo ya está agotado para no repetirlo— crecía con normalidad.

La tentación es obvia: hay un bug en la escritura. Fui a buscarlo, y no estaba.

Cero filas no es lo mismo que un error de escritura

El camino que guarda en esa tabla emite un aviso si la escritura falla. Busqué esos avisos en los registros: cero. Ninguna escritura había fallado.

Eso es un dato, no una ausencia de dato. Si el camino se hubiera recorrido y hubiera fallado, habría dejado rastro. Cero rastros y cero filas solo encajan con una explicación: el camino de escritura nunca se ejecutó. No corrió-y-falló. No corrió.

Es la diferencia entre un negativo real y un negativo que nunca se puso a prueba, y se ven igual si no buscas el positivo de control —algo que el registro SÍ mostraría si el camino se hubiera recorrido—. Sin él, "sano y quieto" y "muerto" se ven idénticos.

Dos mecanismos que se mataban de hambre

¿Por qué no se ejecutaba? Por otro mecanismo, aguas arriba, que hacía bien su trabajo.

Ese sistema tiene una memoria negativa: cuando agota todo lo que sabe probar contra un objetivo, lo anota, para no volver a gastar esfuerzo en algo que ya sabe que no da. Es una optimización sensata. Pero estaba puesta antes de la fase que generaba variantes nuevas —la fase que, al fracasar, habría alimentado la biblioteca—. En cuanto un objetivo entraba en "agotado", esa fase se saltaba entera. Y si la fase nunca corre, nunca produce un fracaso que guardar.

Cada mecanismo, por separado, es correcto. La memoria negativa evita trabajo inútil. La biblioteca aprende de los fracasos. Juntos formaban un punto muerto: el primero mataba de hambre al segundo. La biblioteca solo podía llenarse desde una fase que el otro apagaba.

Es un patrón que se repite: cuando añades un mecanismo para contener lo que hace otro, y el sistema empieza a comportarse raro, sospecha del diseño antes que del código. El síntoma —una tabla en cero— parecía un bug; era una tensión entre dos piezas que por separado están bien.

Un valor con dos significados son dos campos

Había un segundo problema, y es el más instructivo. La biblioteca solo guardaba un tipo de fracaso: aquel en el que el sistema procesaba el intento y devolvía un resultado normal, sin éxito. Al otro tipo —cuando el intento quedaba bloqueado de entrada, sin llegar a procesarse— lo descartaba, con un argumento razonable: un bloqueo dice más de la postura del objetivo que de la variante.

Pero ese argumento se cae al reciclar. La variante bloqueada en un objetivo es un candidato no probado en otro, con otra configuración. Descartarla era, precisamente, no aprender del fracaso más común.

El arreglo fue no colapsar los dos significados en uno. "Fracaso procesado" y "fracaso bloqueado" son dos señales distintas, así que van en dos campos distintos: dos contadores. Así la biblioteca guarda ambos, y el que recicla los ordena —los procesados, más informativos, primero; los bloqueados, al fondo, pero presentes—. Un valor con dos significados no es un campo: son dos.

Lo que quedó

Dos correcciones sobre el mismo síntoma. Ninguna tocó la escritura, que siempre fue correcta. Una rompió el punto muerto —dejar que la fase alimente la biblioteca aunque el objetivo esté "agotado"—; la otra separó las dos caras del fracaso. Y la tabla en cero, que parecía el bug, era el mensajero: un camino que nunca se recorría y una condición demasiado estrecha, mirados por el mismo agujero.

Preguntas frecuentes

¿Cómo sabes que no era un bug de escritura?

Porque el registro emite un aviso al fallar, y el log tenía cero de esos avisos. Es el positivo de control: si el camino se hubiera recorrido y fallado, habría dejado rastro. Cero avisos más cero filas solo encaja con 'el camino nunca corrió', no con 'corrió y falló'.

¿Qué es un punto muerto entre dos mecanismos?

Uno se añadió para no repetir trabajo agotado (una memoria negativa). Otro, para aprender de los fracasos (la biblioteca). El primero saltaba la fase donde el segundo aprendía. Cada uno es correcto por separado; juntos forman un deadlock donde uno mata de hambre al otro.

¿Por qué 'un valor con dos significados son dos campos'?

Había dos clases de fracaso —uno procesado (el sistema lo ejecutó y falló) y uno bloqueado (nunca llegó a procesarse)— colapsadas en un solo criterio. Separarlas en dos contadores dejó guardar ambas sin confundir su calidad, y ordenarlas al reciclar.

¿La lección general?

Cuando añades un mecanismo para contener lo que hace otro y el sistema se comporta raro, sospecha del diseño antes que del código. Y una tabla en cero no siempre es un bug: a veces es un camino que nunca se recorre, y confundirlos cuesta días.

← Ver más artículosCotizar un proyecto