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.
[sourcecode language=”java”]
// Kod 1
LoadModule php5_module modules/libphp5.so
[/sourcecode]
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.
[sourcecode language=”java”]
// 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
[/sourcecode]
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:
[sourcecode language=”java”]
// 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
[/sourcecode]
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:
[sourcecode language=”java”]
// Kod 4
./configure –enable-fpm
[/sourcecode]
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.
[sourcecode language=”java”]
// Kod 5
host:/usr/local/apache2/php/sbin# ls -l
-rwxr-xr-x 1 root root 29818060 Mär 29 15:49 php-fpm
[/sourcecode]
/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:
[sourcecode language=”java”]
// 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
[/sourcecode]
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.
[sourcecode language=”java”]
// 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
[/sourcecode]
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.
[sourcecode language=”java”]
// 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
[/sourcecode]
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.
[sourcecode language=”java”]
// 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)
[/sourcecode]
[sourcecode language=”java”]
// 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)
[/sourcecode]
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.