Linux Admin

Tuesday, June 02, 2009

PHP Oturumlari Uzerine Kisa(!) Bir Hikaye

Son zamanlarda Zeitin'de gelistirdigimiz bir goruntu yayin aracinin arayuzunu PHP ile yazmayi tercih ettik. Bir canli yayin uygulamasi. Herhangi bir internet kamerasindan (webcam) aldigimiz goruntuyu canli olarak yayinlayabiliyoruz. Ornegi icin bizim sirketin sitesine bakabilirsiniz, ofisimiz 24 saat hizmetinizde.





Yayin yapacak olan kisi kamera takili bir bilgisayar ile yayin arayuzune giriyor ve bir flash uygulamasi araciligiyla yayin hemen basliyor. Biz bu is icin sirketteki eski dizustu bilgisayarlardan birisine bir kamera bagladik, yayin sayfasini actik, giris yaptik ve dizustu bilgisayari ofiste uygun dolabin uzerine tozlanmaya terkettik.


Kameranin surucusu daha sorunsuz calistigi icin dizustune Windows kurup oldugu yerden tekrar indirmemek icin de uzerine bir vnc sunucu kurduk. Oldu, calisti. Ne de olsa tum isi bir internet sayfasini acik tutmak, gunlerdir basariyla yapiyor bunu.

Ancak yayincida cok uzun sure (bir kac gun/hafta) acik kalan flash uygulamasi bir sure sonra tarayiciyi yoruyor. Bunun icin yayin sayfasi saatte bir kendisini yeniliyor (bildiginiz META tag'i ile). Yayincinin oturumunu unutmamasi ve her yenilenmesinde giris ekranina geri donmemesi icin de PHP oturumuna yayincinin bilgisini kaydettik.

Sistemi kurduk, her sey bir kac hafta gayet guzel calisti, gun gelip de uygulamaya yeni ozellikler eklememiz gerektiginde giris mekanizmasini da biraz degistirdik. Ancak bu degisiklikten sonra yayincinin oturumu her 4-5 saatte bir zaman asimina ugramaya basladi.

Hatanin nedenini anlamayinca git teslimleri uzerinden tum yamalari satir satir inceledik. Hatanin nedenini gene bulamadik. Haftalarca calisan kod durduk yerde calismaz olmustu ve ortada hataya neden olacak tek satir yoktu.



Artik kodda hata olmadigindan emindik (oysa onca zaman hatanin kodda oldugundan emin gibiydik), kod disinda bizi etkileyebilecek bilesenleri incelemeye basladik. Apache ayarlari, php.ini, yayincidaki cerezlerle (Cookie'ler) ilgili ayarlar...

Sorun uzerinde 2-3 kisi dusundugumuz halde gunlerce nedenini bulamadik. Sonunda PHP'nin oturumlari nasil yonettigini anlamak icin php.net'teki ilgili sayfalari okurken uc adet calisma zamani yapilandirmasi dikkatimi cekti.

Birisi session.cache_expire degiskeni idi. Bir oturumun ne kadar hayatta kalacagini belirtiyordu. Ontanimli degeri 3 saat idi ama bizim 30 dakikada bir kendini yenileyen sayfa oturumunu duzenli olarak 3 saat'te bir unutmuyordu, bazen 4-5 saati bile geciyordu. Hem yarim satte bir yeniden baslayan oturum neden zaman asimina ugrasindi ki.

Diger iki degiskeni pek anlamlandiramadim: session.gc_divisor ve session.gc_probability. Bu degiskenler oturumlari temizleme isleminin kac oturum baslatma (session_start()) basina yapilacagini belirtiyordu. Bu pek makul gelmedi.

PHP arka planda calisan ve surekli ayakta duran bir hizmete (daemon) sahip degil. Sonucta PHP, istekler geldikce (sayfalar acildikca) Apache tarafindan cagrilan bir uygulamadan ibaretti. Dolayisiyla zaman asimi gibi belli surelerde bir yerine getirmesi gereken gorevlerin zamanlamasi icin ince cozumler gerektiriyor.

PHP gelistiricileri oturumlarin zaman asimina ugratmak icin her session_start() isleminde %1 ihtimalle temizlik yapmayi uygun bulmuslar. Aslinda cok cakma bir cozum ama anlasilan pratikte ihtiyaci karsiliyor ki yillardir farkina bile varmamisim.

Bizim sistemimizde ise calisan tek site bahsettigim yayinci sayfasi ve oturum kullanmayan izleyici sayfalarindan ibaret diyebilirim. Bu durumda session_start() cagri sayimiz da her yarim saatte sadece "bir". Dolayisiyla 3 saat gectikten sonra oturumun kapanma olasiligi her yarim saatte %1.

Bunlari anlayinca aralarda gordugumuz 4-5 saat boyunca zaman asimina ugramama durumunu aciklayabildim. Kucuk bir hesapla oturumun 3 saatlik zaman asimi sonrasinda her yarim saat icin sadece 1/100 ihtimal ile temizlenecegini dusunursek, diyelim ki %1 ihtimal 50. denemede denk geldi. Bu durumda sistemin 30dk*50 = 25saat kadar dayanabilmesi gerekirdi. Ancak sistemin en cok 5-6 saat kadar dayandigini biliyordum.

PHP'nin oturum degiskenlerini /tmp altinda bir dosyada tuttugunu bilyordum, biraz inceleyince buldum ki benim kullandigim Debian Lenny sistemde aslinda /var/lib/php5 dizininde duruyordu. (PHP'yi hic elle derlemedigim icin kullandigi dizinlerin yerine pek asina degilim.)

Uzun bir sure /var/lib/php5'i nereden hatirladigimi cikartamadim. Sonra surekli olarak /var/log/messages'da bununla ilgili kayitlari gordugumu hatirlayabildim:
Jun 2 21:39:01 farmer /USR/SBIN/CRON[25973]: (root) CMD ( [ -x /usr/lib/php5/maxlifetime ] && [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -n 200 -r -0 rm)
Cron'dan yarim saatte bir calisan bu betik /var/lib/php5/ altinda son degisiklik tarihi belli sureden daha eski olan dosyalari temizliyordu.

/usr/lib/php5/maxlifetime da bir bash betigiydi ve yaptigi is de /etc/php5/*/php.ini dosyalarini okuyarak aralarinda en uzun zaman asimi suresini donmekti. PHP'den boyle cakma bir cozum beklemiyordum dogrusu. Gerci bu cozumu belki de Debian paketcileri yapmistir, pesine dusup incelemedim.

/usr/lib/php5/maxlifetime betigi de benim sistemimde 24 degeri donuyordu. Crondan da 30 dakikada bir calistigina gore en kotu durumda 24+30=54dk kadar gecikme ile tum oturumlari temizleyecekti.

Bu durum galiba bizim gibi session_start()'i seyrek araliklar ile cagiranlar icin dusunulmustu ve aslinda daha onceki hesabimin sonucunda bulabildigim gibi benim oturumunun asla bir gunu bulmamasini acikliyordu.

Oturumum zaman asimi suresi geldikten sonra en kotu ihtimal ile 54 dakika sonra silinecekti.

Geriye cozemedigim tek soru kaldi: yarim saatte bir session_start() cagiriyorsam zaman asimi suresi de devamli olarak yarim saat otelenecek ve asla zaman asimina ugramayacakti. Ancak kodlarda hata olmadigi halde 3 saat sonra sonra oturum kapaniyordu.

Neyse ki eldeki verilerle cozumu bulmam zor olmadi. PHP oturumlarini /var/lib/php5 dizininde sakliyordu ve syslog'daki saitirdan anladigim kadariyla oturumlari silmek icin de dosya sisteminin tuttugu degisiklik zamani bilgisinden faydalaniyordu. Bu durumda kurdugum oturumun degisiklik zamanini izleyerek sorunun nedenini bulabilirdim.

Oncelikle oturumu baslattim ve oturumumun oldugu dosyayi buldum. Allah'tan PHP oturumlari karmasik sekillerde saklamiyordu:
bekir@farmer:/var/lib/php5$ sudo cat /sess_76995dd9473835f06e03d0da59275eef
publisher|s:6:"kaplan";token|s:9:sts18683140";
Sonra stat komutu ile benim oturum dosyamin bilgilerine baktim, asagida gordugunuz "Change:" yazan kisim dosyada en son ne zaman degisiklik yapildigini gosteriyor.

bekir@farmer:/var/lib/php5$ sudo stat ./sess_76995dd9473835f06e03d0da59275eef
File: `./sess_76995dd9473835f06e03d0da59275eef'
Size: 45 Blocks: 8 IO Block: 4096 regular file
Device: 904h/2308d Inode: 15705843 Links: 1
Access: (0600/-rw-------) Uid: ( 33/www-data) Gid: ( 33/www-data)
Access: 2009-06-03 00:37:44.000000000 +0300
Modify: 2009-06-03 00:37:26.000000000 +0300
Change: 2009-06-03 00:37:26.000000000 +0300
PHP oturumlari temizlerken sadece degisiklik zamanina baktigi icin artik tum is sadece bu dosyanin son degistigi zaman ("Change:") degerinin ne zaman kuruldugunu kesfetmeye kalmisti.

Bir kac deneme yaptim. Oturum olustugunda dogal olarak yukaridaki gibi bir kayit olusuyordu ancak yarim saatte bir gelen sayfa guncellemelerinde bu ozellik guncellenmiyordu. Oysa ki her seferinde session_start()'i cagiriyordu ancak dosyayi takip ettigimde sadece erisim ("Access:") zamaninin degistigini farkettim.

Yazdigimiz kodlara dondum oturumla ilgili kisimlarini tekrar okudum. Eger kurulu bir oturum varsa sayfayi kullaniciya gosteriyor ve oturum uzerinde uzerinde hic islem yapmiyorduk. Cok guzel.

Ancak oturum uzerinde islem yapmadigimiz icin PHP oturum dosyasini asla guncellemiyor ve dolayisi ile dosya sisteminedeki degisiklik zamani ("Change Time") bilgisinin guncellenmesini tetiklemiyordu.

Belkedigimin aksine session_start() oturum dosyasinin degisiklik zamanini guncellemiyordu. Aslinda yapiyi anladiktan sonra cok normal bir davranis oldugunu anlayabiliyorum.

Tum bu incelemelerden sonra koda tek satir ekleyerek uyguladigim cozum her sayfa acilisinda oturum olsa da sadece bir oturum degiskenine atama yapmak oldu.

Gunlerdir aradigimiz sorunun cozumu soyle tek satir imis:
$_SESSION["reset_session_timeout_hack"] = 1;

Sistem su anda iki gundur sorunsuz calisiyor. Tum yazdiklarimi buldugum gece php.net sitesini gezip hatayi ayiklamak 2 saat kadar vaktimi almisti ama bu yaziyi hazirlamak icin 6 saat kadar caba harcadim, hala bir yerlerde ciddi yanlislik yapiyorum sanirim.

1 comment:

  1. Artık benim hatasız kod yazdığımı kabul etmişsindir umarım? :)

    ReplyDelete