İçeriğe geç
Muhammet Şafak
en
Soran: Arda Cevaplandı:

Attribute tabanlı bir mapping katmanında reflection maliyetini production'da nasıl kontrol altında tutarım?


Soru

PHP attribute'larını okuyup DTO'lara/array'lere dönüştüren kendi serializer'ımı yazdım. Sorun şu ki, her istekte ilgili sınıf için `ReflectionClass` açıp attribute'ları yeniden okuyorum. Ölçek büyüdükçe (yüksek RPS, derin nesne grafikleri) bu per-request reflection maliyetinden endişeleniyorum. FPM altında her istek soğuk başladığı için bu iş her seferinde tekrarlanıyor gibi. Production'da bu maliyeti nasıl kontrol altında tutarım?

Cevap

Kısa cevap: Reflection’ın kendisi düşmanınız değil — onu her istekte çalıştırmak düşmanınız.

Kısa cevap

Attribute’ları bir kez okuyun, mapping’i cachelenebilir bir “plan”a (ideal olarak üretilmiş PHP koduna) derleyin; o zaman reflection maliyeti sıcak yoldan pratikte silinir.

Neden

  1. Worker cache’i mi, runtime cache’i mi? Uzun ömürlü bir worker’da (Octane/RoadRunner/FrankenPHP) statik array cache istekler arası yaşar — reflection worker ömrü başına bir kez çalışır. Klasik FPM’de her istek soğuktur; orada APCu ya da derlenmiş bir cache dosyası şart.
  2. Reflection ile OPcache ilişkisine dikkat. OPcache reflection sonuçlarını cachelemez; yalnızca derlenmiş kodu cacheler. Yani kazanç, işi reflection’dan alıp üretilmiş/derlenmiş koda ya da kalıcı bir cache’e taşımaktan gelir.

Ne yapmalı

  1. Önce ölçün, sonra optimize edin. Blackfire/XHProf ile profil alın. İstek başına birkaç sınıf üzerinde reflection çoğu zaman mikrosaniyeler mertebesindedir; profilde görünmüyorsa fazla mühendislik yapmayın. Ama yüksek RPS ve derin grafiklerde toplam hızla birikir.
  2. Ayrıştırılmış metadata’yı cacheleyin. Pahalı kısım mapping’in kendisi değil, ReflectionClass + attribute okumadır. Her sınıf için bir kez ayrıştırın; ortaya çıkan düz “mapping plan”ını sınıf adına göre bir statik cache’te (APCu/PSR-16) tutun.
    final class MappingPlanCache
    {
        /** @var array<class-string, MappingPlan> */
        private static array $plans = [];
    
        public function planFor(string $class): MappingPlan
        {
            // İstek/worker içinde sınıf başına yalnızca bir kez derlenir.
            return self::$plans[$class] ??= $this->compile($class);
        }
    
        private function compile(string $class): MappingPlan
        {
            $ref = new \ReflectionClass($class);
            // attribute'lar burada bir kez okunur, düz bir plana çevrilir
            return MappingPlan::fromReflection($ref);
        }
    }
  3. En hızlı yol için üretilmiş PHP’ye derleyin. Symfony Serializer’ın varsayılan ObjectNormalizer’ı ve Doctrine’in hydration adımı da hâlâ reflection üzerinden çalışır; reflection’sız, doğrudan property erişimiyle mapping yapan bir PHP sınıfı üretmek istiyorsanız bunu kendi derleme adımınızla siz kurmalısınız. OPcache bunu bytecode olarak servis eder. “Production modu” tam olarak budur.
  4. Cache invalidation’ı deploy’a bağlayın. Derlenmiş cache’i bir deploy hash’iyle anahtarlayın ya da deploy’da temizleyin. İstek başına invalidate etmeyin (amacı boşa çıkar); ama şema değişince de bayat bırakmayın.

Sonuç: Ben olsam işe per-worker statik cache ile başlardım (tek satır: ??=), ölçer ve ancak profil hâlâ reflection’ı sıcak yolda gösteriyorsa üretilmiş-PHP derlemesine geçerdim. Octane altında tek başına statik cache sorunu çoğu zaman yok eder; FPM altında üstüne APCu koyarsınız. İhtiyacınız olmayan bir derleyiciyi elle yazmayın — önce ölçün, katmanı ölçüm zorlayınca ekleyin.

İlgili Yazılar

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi