Saltar al contenido
crawlforgeEnglish
Se rompió26 de agosto de 20268 min de lecturaRead in English

El enlace roto que caía entre dos condiciones

La 0.9.0 empezó a comprobar el destino de las redirecciones externas. La vista que responde a «qué enlaces tengo rotos» seguía sin encontrarlos, porque un 301 no está roto y al 404 del final no lo enlaza nadie. Registrar el dato y enseñarlo son dos trabajos distintos, y este es el segundo.

Ayer publicamos la 0.9.0 y conté el fallo del planificador que apareció al escribirla. Una de las cosas que traía era que el destino de una redirección externa por fin deja fila y se le comprueba el estado. Hoy sale la 0.9.1 porque ese trabajo no se veía donde hacía falta.

Dónde busca la gente los enlaces rotos

En una vista que se llama v_broken_links, y en la hoja del Excel que sale de ella. Su definición original era corta y parecía completa:

SELECT ... FROM links l
JOIN urls uf ON uf.id = l.from_url_id
JOIN urls ut ON ut.id = l.to_url_id
WHERE ut.status_code >= 400;

Los enlaces cuyo destino responde 400 o más. Ahora coge el caso de ayer: una página tuya enlaza /go/producto, esa URL responde 301 hacia una tienda que no es tuya, y la tienda devuelve 404.

El enlace apunta a /go/producto, cuyo estado es 301. No pasa el filtro. El 404 sí está en la base de datos desde la 0.9.0, con su fila y su estado comprobado, pero no hay ningún enlace que lo apunte, porque nadie enlaza el destino final: se llega por redirección. La fila caía entre las dos condiciones y no aparecía en ninguna parte.

Es incómodo de admitir porque el dato estaba ahí. Lo habíamos ido a buscar a propósito, con su petición HEAD y su cortesía de una a la vez por host ajeno, y luego la pregunta más obvia que se le hace a la herramienta no lo encontraba.

Seguir la cadena, y hasta dónde

La vista nueva sigue las redirecciones hasta donde acaban. Si el final está roto, el enlace sale, con dos columnas más para no perder información por el camino: via es la URL que se enlazó de verdad, que es la que hay que reescribir en tu HTML, y hops cuántos saltos hicieron falta. En una fila directa las dos van vacías, y esa diferencia importa: no es lo mismo «esto está roto» que «esto lleva a algo roto».

La cadena se recorre en SQL, con una consulta recursiva dentro de la propia vista. Podríamos haberlo hecho en Rust, que es donde ya lo hacen las reglas de cadena y de bucle, pero entonces la respuesta solo existiría dentro de la herramienta. Un fichero de rastreo es un SQLite normal y corriente, y parte de la promesa es que puedas abrirlo con sqlite3, escribir SELECT * FROM v_broken_links y tener la respuesta. Las dos formas conviven porque resuelven preguntas distintas.

Hay un tope de diez saltos, y no es adorno. Una redirección puede volver sobre sí misma, A → B → A, y sin tope la recursión genera filas hasta que alguien mate el proceso. Con el tope, la consulta responde. Hay un test que monta un bucle de verdad con dos URLs que se apuntan la una a la otra y comprueba dos cosas: que la vista contesta en lugar de colgarse, y que el bucle lo sigue reportando HTTP-REDIRECT-LOOP, que es la regla a la que le toca.

Por qué esto es un 0.9.1 y no un 0.10.0

El criterio de versiones de este proyecto está escrito en el changelog y dice que una regla nueva, o un arreglo que cambia lo que una regla reporta, es un salto de versión menor, porque alguien puede tener un control de calidad automático que dependa de que esa regla dispare o deje de disparar.

Aquí no pasa ninguna de las dos cosas. Ninguna regla lee v_broken_links: solo la lee la exportación. Ningún identificador de regla cambia de significado. Lo que cambia es que se listan enlaces que llevaban rotos desde el principio y no se listaban, así que si cuentas filas en broken_links.csv ese número puede subir. Está dicho en el changelog, porque un número que sube sin avisar es la clase de cosa que hace desconfiar de una herramienta.

El despiste, que también cuenta

Añadí la migración, escribí la vista, ejecuté los tests y se cayeron todos con esto:

rastrear: WriterGone

El escritor se muere y no dice por qué. La causa era tonta: el motor lleva una constante con la versión de esquema que sabe escribir, y se me quedó en 8 con la migración 9 puesta. El fichero declaraba una versión más nueva que el código y el código lo rechazaba, con razón.

Lo interesante es cuántas cosas lo cazaron. Primero el propio fallo. Después, al subir la constante, dos guardianes que leen el directorio de migraciones en tiempo de ejecución y comparan con la lista que aplican los helpers de test, uno en cada crate, y que fallan diciendo exactamente qué falta. Y por último el test de compatibilidad, que abre un fichero de cada versión anterior y comprueba que se migra sin perder nada. Cuatro sitios que hay que tocar juntos, y los cuatro avisan.

Lo que costó, medido

Sobre un rastreo real de 5.865 URLs, 145.191 enlaces y 486 redirecciones, contar la vista pasa de 8 a 21 milisegundos. La recursión solo recorre las URLs que son redirecciones, que en un sitio normal son un puñado frente al total.

Y un dato que no favorece al titular pero es el que hay: en ese rastreo la vista nueva no encuentra ni una fila más, porque ninguna de sus 486 redirecciones acaba en un 4xx. El caso que arregla es real y le pasa a cualquiera que use enlaces de afiliado o acortadores propios; en ese sitio concreto, ese día, no lo había. Publicamos el número igual.

El resto sigue donde estaba: 1.033 pruebas en verde, el análisis estático sin quejas y la regresión de rendimiento compilada con optimizaciones en 106.992 elementos por segundo con 30,4 MB de memoria máxima.

Se construye en público

Cada dos semanas: mediciones, fallos y ejemplos de uso. Nada más.

← Volver a la bitácora