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
- 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.
- 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ı
- Ö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.
- 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); } } - 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. - 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.