اگر تابع file_exists($path) را فراخوانی کنید و $path به یک Amazon S3 bucket اشاره کند، شما در حال مراجعه به دیسک محلی نیستید، بلکه در حال ارسال یک درخواست شبکه هستید. این بررسی تک‌خطی به یک رفت و برگشت (round-trip) به S3 تبدیل می‌شود که می‌تواند ده‌ها میلی‌ثانیه به هر درخواست اضافه کند.

PHP فضای ذخیره‌سازی از راه دور را پشت stream wrapperها پنهان می‌کند. توابعی مانند fopen()، file_exists()، is_dir()، unlink() و file_put_contents() از طریق این wrapperها هدایت می‌شوند که فراخوانی‌ها را به عملیات S3 API ترجمه می‌کنند. این نگاشت (mapping) بسیار ساده است:

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

هر فراخوانی همان تأخیر (latency) مربوط به درخواست S3 API متناظر را تحمیل می‌کند. وقتی یک اسکریپت ده‌ها فایل را بررسی می‌کند، الگویی مشابه N+1 ایجاد می‌شود: یک پرش شبکه (network hop) برای هر بررسی واحد.

چرا این موضوع اکنون اهمیت دارد

در یک محیط توسعه معمولی، سیستم فایل روی همان ماشین قرار دارد، بنابراین بررسی تقریباً آنی انجام می‌شود. اما در محیط عملیاتی (production)، جایی که دارایی‌ها (assets) روی S3 قرار دارند، همین کد باعث افت شدید عملکرد (performance cliff) می‌شود. AWS SDK برخی از نتایج را در حافظه کش می‌کند که باعث می‌شود بررسی‌های مکرر در یک فرآیند PHP واحد، سریع به نظر برسند. با این حال، اکثر استقرار‌های PHP برای هر درخواست وب یک فرآیند تازه ایجاد می‌کنند و هر بار کش را پاک می‌کنند. نتیجه: یک رفت و برگشت کامل شبکه برای هر مسیر مجزا در هر درخواست.

یک سایت وردپرسی زمانی ۱۰ ثانیه طول می‌کشید تا صفحه اصلی خود را رندر کند. پرس‌وجوهای پایگاه داده سریع بودند، اما صفحه تنها برای چیدمان محتوا، ۷۳ فراخوانی مجزای S3 را اجرا می‌کرد. هر فراخوانی یک درخواست سرد (cold request) بود که یک file_exists() به‌ظاهر بی‌خطر را به یک تأخیر محسوس تبدیل می‌کرد.

استراتژی‌های کاهش اثر

  • ماندگاری کش در طول درخواست‌ها – کشِ درون-فرآیندی را به یک ذخیره‌ساز مشترک مانند Redis منتقل کنید. وقتی یک درخواست متوجه می‌شود که کلیدی در S3 وجود دارد، درخواست‌های بعدی به جای تماس مجدد با S3، نتیجه کش‌شده را می‌خوانند.
  • اتخاذ گردش‌کار اولویت‌محور بر پایه سیستم محلی (local-first) – فایل‌ها را روی دیسک محلی بنویسید و سپس آن‌ها را به‌صورت ناهمگام (asynchronously) با S3 همگام‌سازی کنید. مسیر اصلی درخواست فقط با سیستم فایل محلی درگیر می‌شود؛ هزینه شبکه به یک کار پس‌زمینه (background job) منتقل می‌شود.
  • بازبینی کد (Audit the codebase) – به‌طور سیستماتیک به دنبال توابعی در سیستم فایل بگردید که ممکن است به یک wrapper از راه دور ختم شوند. هر موردی را که در حلقه‌های فشرده (tight loops) یا مسیرهای حیاتی درخواست ظاهر می‌شود، علامت‌گذاری کنید.
  • بررسی داده‌های APM – اگر زمان پاسخ‌گویی ناگهان افزایش می‌یابد در حالی که معیارهای پایگاه داده ثابت هستند، زمان‌بندی‌های SDK ذخیره‌سازی را بررسی کنید. افزایش ناگهانی تأخیر SDK اغلب به فراخوانی‌های پنهان S3 اشاره دارد.

نکته کلیدی: تابعی که شبیه به یک بررسی محلی در حد میکروثانیه به نظر می‌رسد، می‌تواند ده‌ها میلی‌ثانیه تأخیر شبکه را پنهان کند. با file_exists() روی S3 مانند یک فراخوانی خارجی رفتار کنید، نتیجه آن را کش کنید و کارهای سنگین را از مسیر درخواست خارج کنید. در غیر این صورت، تأخیر پنهان با هر درخواست شبکه نامرئی، به کند کردن سایت شما ادامه خواهد داد.