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.