Если вы вызываете file_exists($path) и $path указывает на корзину Amazon S3, вы обращаетесь не к локальному диску, а выполняете сетевой запрос. Простая проверка в одну строку превращается в сетевой цикл (round-trip) к S3, что может добавить десятки миллисекунд к каждому запросу.

PHP скрывает удаленное хранилище за потоковыми обертками (stream wrappers). Такие функции, как fopen(), file_exists(), is_dir(), unlink() и file_put_contents(), проходят через эти обертки, которые преобразуют вызовы в операции S3 API. Соответствие выглядит следующим образом:

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

Каждый вызов влечет за собой ту же задержку (latency), что и соответствующий запрос к S3 API. Когда скрипт проверяет десятки файлов, возникает паттерн типа N+1: один сетевой переход (network hop) на каждую проверку.

Почему это важно именно сейчас

В типичной среде разработки файловая система находится на той же машине, поэтому проверка выполняется почти мгновенно. В продакшене, где ресурсы (assets) находятся в S3, тот же самый код приводит к резкому падению производительности. AWS SDK кэширует некоторые результаты в памяти, благодаря чему повторные проверки в рамках одного PHP-процесса кажутся быстрыми. Однако большинство PHP-развертываний запускают новый процесс для каждого веб-запроса, очищая кэш каждый раз. Результат: полный сетевой цикл (round-trip) для каждого уникального пути при каждом запросе.

Один сайт на WordPress когда-то тратил десять секунд на рендеринг главной страницы. Запросы к базе данных выполнялись быстро, но для сборки контента страница инициировала 73 отдельных вызова S3. Каждый вызов был «холодным» запросом, превращавшим, казалось бы, безобидный file_exists() в заметную задержку.

Стратегии минимизации последствий

  • Сохраняйте кэш между запросами — Перенесите внутрипроцессный кэш в общее хранилище, такое как Redis. Когда один запрос обнаруживает, что ключ существует в S3, последующие запросы будут считывать кэшированный результат вместо повторного обращения к S3.
  • Используйте подход «сначала локально» (local-first) — Записывайте файлы на локальный диск, а затем асинхронно синхронизируйте их с S3. Основной путь запроса будет взаимодействовать только с локальной файловой системой; сетевые затраты переносятся на фоновую задачу.
  • Проведите аудит кодовой базы — Систематически ищите функции файловой системы, которые могут использовать удаленную обертку. Отмечайте те, которые встречаются внутри циклов или на критических путях выполнения запроса.
  • Анализируйте данные APM — Если время ответа резко возрастает, в то время как метрики базы данных остаются стабильными, изучите время выполнения операций SDK хранилища. Внезапный рост задержки SDK часто указывает на скрытые вызовы S3.

Главный вывод: функция, которая выглядит как микросекундная локальная проверка, может скрывать десятки миллисекунд сетевой задержки. Относитесь к file_exists() в S3 как к внешнему вызову, кэшируйте его результат и выносите тяжелые операции за пределы основного пути запроса. В противном случае скрытая задержка продолжит замедлять ваш сайт — по одному невидимому сетевому запросу за раз.