Si llamas a file_exists($path) y $path apunta a un bucket de Amazon S3, no estás accediendo a un disco local; estás realizando una solicitud de red. Esa comprobación de una sola línea se convierte en un viaje de ida y vuelta (round-trip) a S3 que puede añadir decenas de milisegundos a cada solicitud.

PHP oculta el almacenamiento remoto tras wrappers de flujo (stream wrappers). Funciones como fopen(), file_exists(), is_dir(), unlink() y file_put_contents() se enrutan a través de estos wrappers, que traducen las llamadas en operaciones de la API de S3. El mapeo es directo:

  • file_exists()HeadObject
  • is_dir()ListObjects
  • unlink()DeleteObject
  • file_put_contents()PutObject

Cada llamada incurre en la misma latencia que la solicitud correspondiente a la API de S3. Cuando un script comprueba docenas de archivos, crea un patrón de estilo N+1: un salto de red por cada comprobación individual.

Por qué es importante ahora

En un entorno de desarrollo típico, el sistema de archivos reside en la misma máquina, por lo que la comprobación se devuelve casi instantáneamente. En producción, donde los activos se encuentran en S3, el mismo código crea un abismo de rendimiento. El AWS SDK almacena algunos resultados en memoria, lo que hace que las comprobaciones repetidas en un único proceso PHP parezcan rápidas. Sin embargo, la mayoría de los despliegues de PHP inician un proceso nuevo para cada solicitud web, borrando la caché cada vez. El resultado: un viaje de ida y vuelta completo por la red para cada ruta distinta en cada solicitud.

Un sitio de WordPress tardó una vez diez segundos en renderizar su página de inicio. Las consultas a la base de datos eran rápidas, pero la página activó 73 llamadas individuales a S3 solo para ensamblar el contenido. Cada llamada era una solicitud "en frío", convirtiendo un file_exists() aparentemente inofensivo en un retraso perceptible.

Estrategias de mitigación

  • Persistir la caché entre solicitudes – Mueve la caché del proceso a un almacén compartido como Redis. Cuando una solicitud descubre que una clave existe en S3, las solicitudes posteriores leen el resultado almacenado en caché en lugar de contactar con S3 de nuevo.
  • Adoptar un flujo de trabajo "local-first" – Escribe los archivos en un disco local y luego sincronízalos con S3 de forma asíncrona. La ruta principal de la solicitud solo toca el sistema de archivos local; el coste de red se traslada a un trabajo en segundo plano.
  • Auditar la base de código – Busca sistemáticamente funciones del sistema de archivos que puedan resolverse a un wrapper remoto. Marca cualquiera que aparezca dentro de bucles intensivos o rutas críticas para la solicitud.
  • Inspeccionar datos de APM – Si el tiempo de respuesta aumenta bruscamente mientras las métricas de la base de datos se mantienen estables, analiza los tiempos del SDK de almacenamiento. Un aumento repentino en la latencia del SDK suele indicar llamadas ocultas a S3.

La conclusión clave: una función que parece una comprobación local de microsegundos puede ocultar decenas de milisegundos de latencia de red. Trata file_exists() en S3 como una llamada externa, almacena su resultado en caché y traslada el trabajo pesado fuera de la ruta de la solicitud. De lo contrario, la latencia oculta seguirá ralentizando tu sitio, una solicitud de red invisible a la vez.