Jeśli wywołasz file_exists($path), a $path wskazuje na bucket Amazon S3, nie odwołujesz się do lokalnego dysku — wykonujesz zapytanie sieciowe. Jedno-liniowe sprawdzenie staje się pełnym cyklem komunikacyjnym (round-trip) do S3, co może dodać dziesiątki milisekund do każdego żądania.

PHP ukrywa zdalny magazyn za pomocą wrapperów strumieni (stream wrappers). Funkcje takie jak fopen(), file_exists(), is_dir(), unlink() i file_put_contents() są kierowane przez te wrappery, które tłumaczą wywołania na operacje S3 API. Mapowanie jest proste:

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

Każde wywołanie wiąże się z takim samym opóźnieniem, jak odpowiadające mu zapytanie S3 API. Gdy skrypt sprawdza dziesiątki plików, tworzy wzorzec typu N+1: jeden skok sieciowy dla każdego pojedynczego sprawdzenia.

Dlaczego to ma teraz znaczenie

W typowym środowisku programistycznym system plików znajduje się na tej samej maszynie, więc sprawdzenie zwraca wynik niemal natychmiastowo. W środowisku produkcyjnym, gdzie zasoby znajdują się na S3, ten sam kod powoduje gwałtowny spadek wydajności. AWS SDK buforuje niektóre wyniki w pamięci, dzięki czemu wielokrotne sprawdzenia w ramach jednego procesu PHP wydają się szybkie. Większość wdrożeń PHP tworzy jednak nowy proces dla każdego żądania HTTP, czyszcząc pamięć podręczną za każdym razem. Rezultat: pełny cykl komunikacyjny przez sieć dla każdej unikalnej ścieżki przy każdym żądaniu.

Strona WordPress zajmowała kiedyś dziesięć sekund na wyrenderowanie strony głównej. Zapytania do bazy danych były szybkie, ale strona wywoływała 73 indywidualne żądania do S3 tylko po to, aby złożyć treść. Każde wywołanie było „zimnym” zapytaniem, zmieniając pozornie nieszkodliwe file_exists() w zauważalne opóźnienie.

Strategie łagodzenia skutków

  • Przechowywanie pamięci podręcznej między żądaniami – Przenieś pamięć podręczną z procesu do wspólnego magazynu, takiego jak Redis. Gdy jedno żądanie wykryje, że klucz istnieje w S3, kolejne żądania odczytają wynik z pamięci podręcznej zamiast ponownie kontaktować się z S3.
  • Przyjęcie modelu „local-first” – Zapisuj pliki na lokalnym dysku, a następnie synchronizuj je z S3 asynchronicznie. Główna ścieżka żądania operuje tylko na lokalnym systemie plików; koszt sieciowy zostaje przeniesiony do zadania działającego w tle.
  • Audyt bazy kodu – Systematycznie przeszukuj kod w poszukiwaniu funkcji systemu plików, które mogą odwoływać się do zdalnego wrappera. Oznaczaj te, które pojawiają się wewnątrz pętli lub na krytycznych ścieżkach żądania.
  • Analiza danych APM – Jeśli czas odpowiedzi gwałtownie rośnie, podczas gdy metryki bazy danych pozostają stabilne, sprawdź czasy operacji SDK magazynu danych. Nagły wzrost opóźnienia SDK często wskazuje na ukryte wywołania S3.

Kluczowy wniosek: funkcja, która wygląda na mikrosekundowe lokalne sprawdzenie, może ukrywać dziesiątki milisekund opóźnienia sieciowego. Traktuj file_exists() na S3 jak wywołanie zewnętrzne, buforuj jego wynik i przenieś ciężkie operacje poza ścieżkę żądania. W przeciwnym razie ukryte opóźnienia będą nadal spowalniać Twoją stronę, jedno niewidoczne zapytanie sieciowe po drugim.