[P1] 36 cartas traducidas en 500 tras el cutover: url_to_postid() se traía las 32.311 entradas #191

Open
opened 2026-08-04 02:09:54 +00:00 by rafa · 1 comment
Owner

Estado: código arreglado y desplegado. Queda una acción manual para Inma: purgar la caché de Cloudflare.

Salió al repasar los logs del día siguiente al cutover (#180), no en la verificación en caliente.

El síntoma

36 URLs devolvían 500: las mismas 9 «cartas de otras semanas» en los 4 idiomas traducidos. En español las 9 iban bien.

Era una regresión nuestra, no algo heredado: las mismas URLs contra CDMON respondían 200. Comprobado una a una contra las dos IPs.

Impacto real bajo para las personas —el 3-ago solo hubo 3 visitas a esas URLs, las tres de bingbot— pero Bing y Google están recorriendo el sitio y se llevaban 36 páginas en 500.

La causa

fea-homepage.php reescribe los enlaces internos al idioma activo, y para eso llamaba a url_to_postid() una vez por enlace. Estas cartas traen unos 40.

url_to_postid() acaba construyendo una WP_Query a partir de las reglas de reescritura. Cuando la URL no encaja en ninguna, la query se queda sin cláusula que la acote:

SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' ORDER BY post_date DESC

Las 32.311 entradas con su contenido entero, dos veces por petición. Los 256 MB de PHP se agotaban en class-wpdb.php:2322 y Apache devolvía el 500.

Encaja con el patrón: el filtro se salta el español (if (!$lang || $lang === 'es') return), que es justo el idioma que no fallaba, y solo actúa en is_singular().

El arreglo

Commit 78879ae en feat/mu-plugins-no-versionados. Documento: docs/cutover/500-cartas-traducidas-url-to-postid.md.

Se sustituye url_to_postid() por fea_href_a_post_id(): la estructura es /%postname%/, así que basta el último segmento del path y una consulta con LIMIT 1, que no puede degenerar por rara que sea la URL. Cacheado por petición.

El ORDER BY no es cosmético, reproduce a quién sirve WordPress esa misma URL:

caso cuántos criterio
página de primer nivel con el mismo slug que una entrada 4 gana la página (reglas verbosas de reescritura)
dos entradas con el mismo slug (duplicados del import de Joomla) 58 gana la de post_date más reciente

El primer intento ordenaba por ID ASC y en esos 58 casos habría traducido el enlace equivocado, sin dar ningún error.

Verificación

Las 116 cartas traducidas (29 × 4 idiomas) 116/116 en 200
Muestra de 200 entradas al azar (40 por idioma) todas en 200
fea_href_a_post_id() vs url_to_postid() — 61 slugs repetidos + 400 al azar 461/461 idénticos
La consulta gorda desaparecida del performance_schema
Suite E2E 13/13 en 200
Tráfico real posterior (15 min) 1.041×200, 74 IPs, 0 errores PHP, 0 5xx

Los enlaces se siguen reescribiendo, y algunos más que antes: url_to_postid() fallaba en URLs que sí resuelven bien por slug. Verificado contra CDMON que los pares nuevos son de verdad la misma entrada (mismo grupo de traducción de Polylang): como-bendecir-la-mesacomment-benir-la-table, felices-6heureux, 3-temarioprogramme, y tres más.

Centinelas carta-trad-it y carta-trad-fr añadidas a tools/e2e/sites/www.json.

🔴 Pendiente: purgar la caché de Cloudflare (Inma)

No lo puedo hacer yo: el token FEA_CF_API_TOKEN solo tiene permiso de DNS. POST /zones/{id}/purge_cache devuelve 10000 Authentication error. (El token además caduca el 15-ago-2026.)

Tampoco puedo comprobar si de verdad hace falta: Cloudflare devuelve 403 a nuestras IPs para cualquier hostname de feadulta, así que no podemos leer la cabecera cf-cache-status desde aquí. Lo que sí sabemos es que el plan es Free, donde Cloudflare no cachea HTML por defecto y no cachea respuestas 5xx, así que lo más probable es que no haya nada cacheado. Purgar es inocuo de todos modos y quita la duda.

Cómo hacerlo: panel de Cloudflare → zona feadulta.comCachingConfigurationPurge CacheCustom PurgeURL. Cabe un máximo de 30 URLs por operación, así que van en dos tandas.

Las 36 URLs
https://www.feadulta.com/en/light-and-beacon/
https://www.feadulta.com/en/living-is-not-the-same-as-life/
https://www.feadulta.com/en/life-that-transcends-death/
https://www.feadulta.com/en/celebrating-the-good-easter/
https://www.feadulta.com/en/jesus-makes-himself-present/
https://www.feadulta.com/en/powerful-and-stimulating/
https://www.feadulta.com/en/shepherds-and-little-shepherds/
https://www.feadulta.com/en/identifying-ourselves-with-its-values/
https://www.feadulta.com/en/relationship-of-living-love/
https://www.feadulta.com/fr/lumiere-et-phare/
https://www.feadulta.com/fr/ce-nest-pas-la-meme-chose-de-vivre-que-de-vivre/
https://www.feadulta.com/fr/vie-qui-transcende-la-mort/
https://www.feadulta.com/fr/celebrer-la-bonne-paques/
https://www.feadulta.com/fr/jesus-se-fait-present/
https://www.feadulta.com/fr/puissante-et-stimulante/
https://www.feadulta.com/fr/pasteurs-et-petits-bergers/
https://www.feadulta.com/fr/sidentifier-a-ses-valeurs/
https://www.feadulta.com/fr/relation-damour-vivant/
https://www.feadulta.com/it/luce-e-faro/
https://www.feadulta.com/it/vivere-non-e-lo-stesso-che-vivere/
https://www.feadulta.com/it/vita-che-trascende-la-morte/
https://www.feadulta.com/it/celebrare-la-buona-pasqua/
https://www.feadulta.com/it/gesu-si-fa-presente/
https://www.feadulta.com/it/potente-e-stimolante/
https://www.feadulta.com/it/pastori-e-pastorellette/
https://www.feadulta.com/it/identificarci-con-i-suoi-valori/
https://www.feadulta.com/it/relazione-damore-vivo/
https://www.feadulta.com/pt/luz-e-farol/
https://www.feadulta.com/pt/nao-e-o-mesmo-viver-que-viver/
https://www.feadulta.com/pt/vida-que-transcende-a-morte/
https://www.feadulta.com/pt/celebrar-a-boa-pascoa/
https://www.feadulta.com/pt/jesus-se-faz-presente/
https://www.feadulta.com/pt/poderosa-e-estimulante/
https://www.feadulta.com/pt/pastores-e-pastorinhos/
https://www.feadulta.com/pt/identificar-nos-com-os-seus-valores/
https://www.feadulta.com/pt/relacao-de-amor-vivo/

Si te resulta más cómodo, «Purge Everything» también vale: en este sitio la caché son estáticos y se rehace sola.

⚠️ Para la próxima

url_to_postid() no es seguro con URLs arbitrarias en un sitio grande. El comentario que ya había en el fichero decía que en los listados agotaba memoria, y por eso se había acotado a is_singular(). La cura se quedó corta: el problema no era el listado, era la función.

Y una trampa de método: esto no se reproduce con wp-cli. Polylang no instancia su clase de frontend en CLI, así que el filtro sale antes de tiempo y no pasa nada. Se cazó mirando performance_schema.events_statements_history_long en MySQL mientras se pedía la página por HTTP, que da la secuencia real de sentencias de la petición.

**Estado: código arreglado y desplegado. Queda una acción manual para Inma: purgar la caché de Cloudflare.** Salió al repasar los logs del día siguiente al cutover (#180), no en la verificación en caliente. ## El síntoma 36 URLs devolvían **500**: las mismas **9 «cartas de otras semanas» en los 4 idiomas traducidos**. En español las 9 iban bien. Era **una regresión nuestra, no algo heredado**: las mismas URLs contra CDMON respondían 200. Comprobado una a una contra las dos IPs. Impacto real bajo para las personas —el 3-ago solo hubo 3 visitas a esas URLs, las tres de bingbot— pero Bing y Google están recorriendo el sitio y se llevaban 36 páginas en 500. ## La causa `fea-homepage.php` reescribe los enlaces internos al idioma activo, y para eso llamaba a **`url_to_postid()` una vez por enlace**. Estas cartas traen unos 40. `url_to_postid()` acaba construyendo una `WP_Query` a partir de las reglas de reescritura. Cuando la URL no encaja en ninguna, la query **se queda sin cláusula que la acote**: ```sql SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' ORDER BY post_date DESC ``` **Las 32.311 entradas con su contenido entero**, dos veces por petición. Los 256 MB de PHP se agotaban en `class-wpdb.php:2322` y Apache devolvía el 500. Encaja con el patrón: el filtro se salta el español (`if (!$lang || $lang === 'es') return`), que es justo el idioma que no fallaba, y solo actúa en `is_singular()`. ## El arreglo Commit `78879ae` en `feat/mu-plugins-no-versionados`. Documento: `docs/cutover/500-cartas-traducidas-url-to-postid.md`. Se sustituye `url_to_postid()` por `fea_href_a_post_id()`: la estructura es `/%postname%/`, así que basta el último segmento del path y **una consulta con `LIMIT 1`**, que no puede degenerar por rara que sea la URL. Cacheado por petición. El `ORDER BY` no es cosmético, reproduce a quién sirve WordPress esa misma URL: | caso | cuántos | criterio | |---|---|---| | página de primer nivel con el mismo slug que una entrada | 4 | gana la **página** (reglas verbosas de reescritura) | | dos entradas con el mismo slug (duplicados del import de Joomla) | 58 | gana la de **`post_date` más reciente** | El primer intento ordenaba por `ID ASC` y en esos 58 casos habría traducido el enlace equivocado, sin dar ningún error. ## Verificación | | | |---|---| | Las 116 cartas traducidas (29 × 4 idiomas) | **116/116 en 200** | | Muestra de 200 entradas al azar (40 por idioma) | todas en 200 | | `fea_href_a_post_id()` vs `url_to_postid()` — 61 slugs repetidos + 400 al azar | **461/461 idénticos** | | La consulta gorda | desaparecida del `performance_schema` | | Suite E2E | **13/13 en 200** | | Tráfico real posterior (15 min) | 1.041×200, 74 IPs, **0 errores PHP, 0 5xx** | Los enlaces se siguen reescribiendo, y **algunos más que antes**: `url_to_postid()` fallaba en URLs que sí resuelven bien por slug. Verificado contra CDMON que los pares nuevos son de verdad la misma entrada (mismo grupo de traducción de Polylang): `como-bendecir-la-mesa` → `comment-benir-la-table`, `felices-6` → `heureux`, `3-temario` → `programme`, y tres más. Centinelas `carta-trad-it` y `carta-trad-fr` añadidas a `tools/e2e/sites/www.json`. ## 🔴 Pendiente: purgar la caché de Cloudflare (Inma) **No lo puedo hacer yo**: el token `FEA_CF_API_TOKEN` solo tiene permiso de DNS. `POST /zones/{id}/purge_cache` devuelve `10000 Authentication error`. (El token además **caduca el 15-ago-2026**.) Tampoco puedo comprobar si de verdad hace falta: **Cloudflare devuelve 403 a nuestras IPs** para cualquier hostname de feadulta, así que no podemos leer la cabecera `cf-cache-status` desde aquí. Lo que sí sabemos es que el plan es **Free**, donde Cloudflare **no cachea HTML por defecto** y **no cachea respuestas 5xx**, así que lo más probable es que no haya nada cacheado. Purgar es inocuo de todos modos y quita la duda. **Cómo hacerlo:** panel de Cloudflare → zona `feadulta.com` → *Caching* → *Configuration* → *Purge Cache* → **Custom Purge** → *URL*. Cabe un máximo de 30 URLs por operación, así que van en dos tandas. <details> <summary>Las 36 URLs</summary> ``` https://www.feadulta.com/en/light-and-beacon/ https://www.feadulta.com/en/living-is-not-the-same-as-life/ https://www.feadulta.com/en/life-that-transcends-death/ https://www.feadulta.com/en/celebrating-the-good-easter/ https://www.feadulta.com/en/jesus-makes-himself-present/ https://www.feadulta.com/en/powerful-and-stimulating/ https://www.feadulta.com/en/shepherds-and-little-shepherds/ https://www.feadulta.com/en/identifying-ourselves-with-its-values/ https://www.feadulta.com/en/relationship-of-living-love/ https://www.feadulta.com/fr/lumiere-et-phare/ https://www.feadulta.com/fr/ce-nest-pas-la-meme-chose-de-vivre-que-de-vivre/ https://www.feadulta.com/fr/vie-qui-transcende-la-mort/ https://www.feadulta.com/fr/celebrer-la-bonne-paques/ https://www.feadulta.com/fr/jesus-se-fait-present/ https://www.feadulta.com/fr/puissante-et-stimulante/ https://www.feadulta.com/fr/pasteurs-et-petits-bergers/ https://www.feadulta.com/fr/sidentifier-a-ses-valeurs/ https://www.feadulta.com/fr/relation-damour-vivant/ https://www.feadulta.com/it/luce-e-faro/ https://www.feadulta.com/it/vivere-non-e-lo-stesso-che-vivere/ https://www.feadulta.com/it/vita-che-trascende-la-morte/ https://www.feadulta.com/it/celebrare-la-buona-pasqua/ https://www.feadulta.com/it/gesu-si-fa-presente/ https://www.feadulta.com/it/potente-e-stimolante/ https://www.feadulta.com/it/pastori-e-pastorellette/ https://www.feadulta.com/it/identificarci-con-i-suoi-valori/ https://www.feadulta.com/it/relazione-damore-vivo/ https://www.feadulta.com/pt/luz-e-farol/ https://www.feadulta.com/pt/nao-e-o-mesmo-viver-que-viver/ https://www.feadulta.com/pt/vida-que-transcende-a-morte/ https://www.feadulta.com/pt/celebrar-a-boa-pascoa/ https://www.feadulta.com/pt/jesus-se-faz-presente/ https://www.feadulta.com/pt/poderosa-e-estimulante/ https://www.feadulta.com/pt/pastores-e-pastorinhos/ https://www.feadulta.com/pt/identificar-nos-com-os-seus-valores/ https://www.feadulta.com/pt/relacao-de-amor-vivo/ ``` </details> Si te resulta más cómodo, **«Purge Everything» también vale**: en este sitio la caché son estáticos y se rehace sola. ## ⚠️ Para la próxima **`url_to_postid()` no es seguro con URLs arbitrarias en un sitio grande.** El comentario que ya había en el fichero decía que en los listados agotaba memoria, y por eso se había acotado a `is_singular()`. La cura se quedó corta: el problema no era el listado, era la función. Y una trampa de método: **esto no se reproduce con wp-cli**. Polylang no instancia su clase de frontend en CLI, así que el filtro sale antes de tiempo y no pasa nada. Se cazó mirando `performance_schema.events_statements_history_long` en MySQL mientras se pedía la página por HTTP, que da la secuencia real de sentencias de la petición.
inma was assigned by rafa 2026-08-04 02:09:54 +00:00
Collaborator

Caché de Cloudflare purgada — verificado desde fuera

Hecha la acción manual que quedaba pendiente (Inma, 11-ago). Purga completa de la zona feadulta.com desde el panel.

Verificación de la purga: el mismo asset pedido dos veces devuelve cf-cache-status: MISS y luego HIT — caché vaciada y repoblándose desde el origen.

Las URLs del incidente ya responden 200, comprobadas con sus enlaces reales siguiendo redirecciones:

post URL código
54979 https://www.feadulta.com/en/toward-the-heart/ 200
54980 https://www.feadulta.com/fr/vers-le-coeur/ 200
54981 https://www.feadulta.com/it/verso-il-cuore/ 200
54982 https://www.feadulta.com/pt/rumo-ao-coracao/ 200

También 200 en el cluster de «El tesoro está ya en nosotros» (en/fr/it/pt). Ni un 500.

De paso, sanidad general tras la purga: portada, la carta en curso («Vemos fantasmas y son personas») y /numeros/ responden 200.

Por nuestra parte se puede cerrar.

## Caché de Cloudflare purgada — verificado desde fuera Hecha la acción manual que quedaba pendiente (Inma, 11-ago). Purga completa de la zona `feadulta.com` desde el panel. **Verificación de la purga:** el mismo asset pedido dos veces devuelve `cf-cache-status: MISS` y luego `HIT` — caché vaciada y repoblándose desde el origen. **Las URLs del incidente ya responden 200**, comprobadas con sus enlaces reales siguiendo redirecciones: | post | URL | código | |---:|---|---:| | 54979 | https://www.feadulta.com/en/toward-the-heart/ | 200 | | 54980 | https://www.feadulta.com/fr/vers-le-coeur/ | 200 | | 54981 | https://www.feadulta.com/it/verso-il-cuore/ | 200 | | 54982 | https://www.feadulta.com/pt/rumo-ao-coracao/ | 200 | También 200 en el cluster de «El tesoro está ya en nosotros» (en/fr/it/pt). Ni un 500. De paso, sanidad general tras la purga: portada, la carta en curso («Vemos fantasmas y son personas») y `/numeros/` responden 200. Por nuestra parte se puede cerrar.
Sign in to join this conversation.
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rafa/feadulta#191