PHP uygulamalarında performans açısından en büyük sıkıntı, PHP kodunun her kullanıcı isteğinde yorumlanmak (interpretation) zorunda olmasıdır. Java gibi bir dilde örneğin kod bir sefere mahsus derlenir ve bu derlenen kod koşturulur. PHP, Perl, Python gibi dillerde kod derlenmez ve her defasında yorumlanır. Yorumlama işlemi zaman alıcı bir işlemdir ve bu genel olarak çok kullanıcı trafiğine maruz kalan PHP uygulamalarının yavaş çalışmasına sebep olur.
Bu yazıyı okudunuz PratikProgramci.com sayfası da bir PHP uygulaması. PratikProgramci.com için yaptığım hızlandırma ve performans iyileştirme işlemlerini sizinle paylaşmak istiyorum.
PHP yorumlanan bir dil demiştim. Peki kod nerede yorumlanmaktadır? PHP uygulamaları genel olarak Apache gibi bir web sunucu bünyesinde çalışır. Bunun için web sunucusunun PHP kodu yorumcusunu yüklemiş olması gerekmektedir. Bu genelde Apache konfigürasyonunda şu şekilde yapılır.
// Kod 1 LoadModule php5_module modules/libphp5.so
PHP kodunu yorumlamak için kullanılan program parçasının bir Apache modülü olarak derlenmesi ve LoadModule direktifi ile Apache tarafından yüklenmesi gerekmektedir. PHP uygulamalarında kötü performansın başlıca sebeplerinden bir tanesi de budur. Apache resim, javascript, html ve css gibi statik içerikleri kullanıcıya aktarmak için geliştirilmiş bir web sunucusudur. PHP modülü gibi modüller kullanıldığı taktirde dinamik içerik oluşturabilmektedir. Lakin hem dinamik içerik üretmeye çalışmak, hem de statik sayfaları kullanıcıya göndermek, bir koltuk altında iki karpuz taşımak gibi bir şeydir. Bu tek sorumluluk prensibine aykırı olan bir durumdur. Her modülün tek bir sorumluluk alanı olmalıdır.
Apache bünyesinde PHP kodunun yorumlanmasının neden uzun sürdüğünü açıklamaya çalışayım. Aşağıda Linux işletim sisteminde çalışan bir Apache web sunucusu görmektesiniz. Linux altında programlar process olarak isimlendirilir. Kod 2 de 4 Apache process çalışır durumdadır. Her bir process bir kullanıcıya cevap verir. Bu durumda aşağıda yer alan Apache web sunucusu eş zamanlı olarak 4 kişiye aynı anda hizmet verebilmektedir.
// Kod 2 4962 ? Sl 0:00 /usr/local/apache2/bin/httpd -k start 4963 ? Sl 0:00 /usr/local/apache2/bin/httpd -k start 4964 ? Sl 0:00 /usr/local/apache2/bin/httpd -k start 5046 ? Sl 0:00 /usr/local/apache2/bin/httpd -k start
Peki aynı zamanda 5. bir kullanıcı istekte bulunduğunda, bu isteğe Apache nasıl cevap vermektedir? Yeni bir httpd process oluşturarak. Yeni bir httpd process oluşturmak zaman alıcı bir işlemdir. Bunun üzerine bir de httpd PHP kodunu yorumlamakla meşgul olunca, kullanıcı on saniyeler boyunca yaptığı isteğin cevabını beklemek zorunda kalabilmektedir.
Performansı artırmak için web sunucusunu ve PHP yorumcusunu ayırmamız gerekiyor. Bir kullanıcı web sunucusuna eriştiğinde, web sunucusu bu isteği yine bir unix process olarak çalışan PHP yorumcusuna yönlendirerek, kendisi bir işlem yapmadan, kodun yorumlama işleminin başka bir process tarafından yapılmasını sağlayabilir. Bu şekilde devamlı bir process olarak hazır bekleyen PHP yorumcusu kodu çok hızlı bir şekilde yorumlayarak, neticeyi web sunucusuna aktarabilir. Bu şekilde genel olarak performansı artırabiliriz.
Bu ayrımı gerçekleştirmek için Apache bünyesinde mod-proxy-fcgi modülünü şu şekilde konfigüre etmemiz gerekiyor:
// Kod 3 LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/usr/local/apache/htdocs/pratikprogramci.com/$1
Bu konfigürasyon ile Apache .php dosyalarına yapılan tüm istekleri, aynı sunucusu bünyesinde ve 9000 numaralı portta çalışan PHP yorumcusuna yönlendirmektedir. Şimdi PHP yorumcusunun nasıl konfigüre edildiğini inceleyelim.
PHP yorumcusunu bir unix process i olarak çalıştırabilmek için PHP FastCGI Process Manager (php-fpm) ismini taşıyan programı kullanabiliriz. Bu program PHP 5.5 in bir parçasıdır ve PHP modülünü derlerken şu şekilde aktif hale getirilebilir:
// Kod 4 ./configure --enable-fpm
PHP 5.5 öncesi için programı bu sayfadan indirerek, derlemek mümkün.
Derleme işlemi tamamlandıktan sonra php-fpm isminde bir program oluşur.
// Kod 5 host:/usr/local/apache2/php/sbin# ls -l -rwxr-xr-x 1 root root 29818060 Mär 29 15:49 php-fpm
/usr/local/apache2/php/etc dizini altında php-fpm.conf isminde fpm programını konfigüre etmek için kullanılan bir dosya bulunmaktadır. Bu dosyayı değişiklik yapmadan kullanabiliriz. Bunun yanı sıra birde PHP yorumcusu havuzu oluşturmak için ikinci bir konfigürasyon dosyasına ihtiyaç duymaktayız. Bu dosyanın içeriği şu şekilde olabilir:
// Kod 6 [www] listen = 127.0.0.1:9000 pm = dynamic pm.max_children = 100 pm.start_servers = 25 pm.min_spare_servers = 25 pm.max_spare_servers = 50 pm.max_requests = 500
Kod 3 e baktığımızda, web sunucusunun ProxyPassMatch ile istekleri 9000 nolu porta yer alan uygulamaya yönlendirdiğini görmekteyiz. Bu port kod 6 da yer alan listen direktifi ile tanımlanmış olan port değeridir. Kod 6 da yer alan konfigürasyon dosyası ile php-fpm uygulaması 25 adet (pm.start_servers) PHP yorumcusunu çalışır hale getirmektedir. Apache bu yorumculara fast cgi protokolünü kullanarak 127.0.0.1:9000 adresinde erişmekte, kullanıcı isteğini bu havuzdan bir yorumcuya aktarmakta ve neticeyi alarak, kullanıcıya göndermektedir.
Kod 6.1 de aktif durumda olan php-fpm process lerini görmektesiniz.
// Kod 6.1 4816 ? S 0:54 php-fpm: pool www 4817 ? S 0:53 php-fpm: pool www 4818 ? S 0:54 php-fpm: pool www 4819 ? S 0:53 php-fpm: pool www 4820 ? S 0:54 php-fpm: pool www 4821 ? S 0:55 php-fpm: pool www 4822 ? S 0:54 php-fpm: pool www 4823 ? S 0:54 php-fpm: pool www 4824 ? S 0:54 php-fpm: pool www 4825 ? S 0:54 php-fpm: pool www 4826 ? S 0:54 php-fpm: pool www 4827 ? S 0:54 php-fpm: pool www 4828 ? S 0:54 php-fpm: pool www 4829 ? S 0:53 php-fpm: pool www 4830 ? S 0:52 php-fpm: pool www 4831 ? S 0:54 php-fpm: pool www 4832 ? S 0:53 php-fpm: pool www 4833 ? S 0:53 php-fpm: pool www 4834 ? S 0:53 php-fpm: pool www 4835 ? S 0:52 php-fpm: pool www 4836 ? S 0:53 php-fpm: pool www 4837 ? S 0:53 php-fpm: pool www 4838 ? S 0:53 php-fpm: pool www 4839 ? S 0:55 php-fpm: pool www 4840 ? S 0:53 php-fpm: pool www 4842 ? S 0:54 php-fpm: pool www 4845 ? S 0:52 php-fpm: pool www 4865 ? S 0:53 php-fpm: pool www 4866 ? S 0:52 php-fpm: pool www 4867 ? S 0:52 php-fpm: pool www 4868 ? S 0:52 php-fpm: pool www 4869 ? S 0:51 php-fpm: pool www 4870 ? S 0:52 php-fpm: pool www 4871 ? S 0:51 php-fpm: pool www 4872 ? S 0:51 php-fpm: pool www 4873 ? S 0:51 php-fpm: pool www 4877 ? S 0:39 php-fpm: pool www
Bu şekilde bünyesinde PHP yorumcusu da taşıyan sişkin bir Apache process yerine, sadece statik içeriği kullanıcıya aktaran ve PHP işlemlerini bir PHP FPM yorumcusuna devreden bir Apache web sunucusu oluşturmak mümkündür. Bu değişiklik performansı %50 oranında artırabilir.
Bu ayrım ile uygulama performansını artırdık. Performansı PHP 5.5 bünyesinde yer alan OpCache modülü ile daha da iyileştirebiliriz.
OpCache ile PHP kodunun her defasında yorumlanmasını engelleyebiliriz. OpCache yardımı ile PHP kodu derlenmekte ve bytecode olarak önbelleğe alınmaktadır. Bu şekilde aynı kod tekrar çalıştığında, yorumlanması gerekmediği için daha hızlı çalışmaktadır. Bu işlem Java’da HotSpot derleyicisi tarafından yapılmaktadır. Java bytecode da derlenmiş kod olsa da, JVM tarafından yorumlanan bir koddur. HotSpot derleyicisi çok kullanılan Java kodlarını Assembly koduna dönüştürmektedir.
PHP 5.5 öncesi OpCache modülünü aktif hale getirmek için php.ini dosyasına aşağıda yer alan konfigürasyonun eklenmesi gerekmektedir. PHP 5.5 ile OpCache aktif halde gelmektedir.
// Kod 7 zend_extension=/usr/local/apache2/php/lib/php/extensions/no-debug-zts-20121212/opcache.so opcache.memory_consumption=512 opcache.max_accelerated_files=50000 opcache.revalidate_freq=0 opcache.consistency_checks=1
Kod 7 de görüldüğü gibi opcache.so ismini taşıyan OpCache modülü PHP FPM tarafından yüklenmektedir.
Peki yaptığımız iyileştirmeleri nasıl ölçebiliriz?
Kod 8 ve 9 da Apache web sunucusunu test etmek için kullanılan ab ile yaptığım performans ölçümlerini görmektesiniz. Bu testleri aynı anda 10 kullanıcının her biri arka arkaya http://www.pratikprogramci.com/urun/profesyonel-java-sanal-kursu-1-bolum/ adresine 1000 adet istekte bulunacak şekilde yapılandırdım. Kod 8 de yer alan testi OpCache aktif olmadan yaptım. Kod 9 da yer alan test neticesi OpCache aktif iken oluştu.
// Kod 8 $>ab -k -c 10 -n 1000 http://www.pratikprogramci.com/urun/profesyonel-java-sanal-kursu-1-bolum/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking www.pratikprogramci.com (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Document Path: /urun/profesyonel-java-sanal-kursu-1-bolum/ Document Length: 78701 bytes Concurrency Level: 10 Time taken for tests: 140.626 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Keep-Alive requests: 0 Total transferred: 79679000 bytes HTML transferred: 78701000 bytes Requests per second: 7.11 [#/sec] (mean) Time per request: 1406.257 [ms] (mean) Time per request: 140.626 [ms] (mean, across all concurrent requests) Transfer rate: 553.32 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 1 Processing: 1070 1401 159.2 1380 2603 Waiting: 663 844 141.3 823 2045 Total: 1070 1401 159.2 1380 2604 Percentage of the requests served within a certain time (ms) 50% 1380 66% 1427 75% 1458 80% 1476 90% 1546 95% 1613 98% 1791 99% 2182 100% 2604 (longest request)
// Kod 9 $>ab -k -c 10 -n 1000 http://www.pratikprogramci.com/urun/profesyonel-java-sanal-kursu-1-bolum/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking www.pratikprogramci.com (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Document Path: /urun/profesyonel-java-sanal-kursu-1-bolum/ Document Length: 78701 bytes Concurrency Level: 10 Time taken for tests: 62.460 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Keep-Alive requests: 0 Total transferred: 79677000 bytes HTML transferred: 78701000 bytes Requests per second: 16.01 [#/sec] (mean) Time per request: 624.599 [ms] (mean) Time per request: 62.460 [ms] (mean, across all concurrent requests) Transfer rate: 1245.75 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 517 622 61.5 614 1064 Waiting: 288 361 37.6 355 587 Total: 517 622 61.6 614 1065 Percentage of the requests served within a certain time (ms) 50% 614 66% 637 75% 648 80% 656 90% 684 95% 719 98% 753 99% 940 100% 1065 (longest request)
Görüldüğü gibi OpCache aktif olan PHP yorumcusu %60 oranında daha hızlı çalışmaktadır. Aşagıda http://tools.pingdom.com/ ile yaptığım hız testi neticesini görmektesiniz. Sayfa yükleme zamanı şimdi 1.7 saniye iken PratikProgramcı.com ilk kuruldugu günlerde bu değer 6-7 saniyeyi bulabilmekteydi.
EOF (End Of Fun)
Özcan Acar
Yorumlar
“PHP Uygulamaları Nasıl Hızlandırılır? FastCGI, PHP-FPM ve OpCache Kullanımı” için bir yanıt
Güzel bir yazıydı, bu tarz kaliteli yazıları görmek güzel. Teşekkürler hocam.