Make love, not var_dump())

Captain's Log

Varnish uzun süredir kullandığımız bir "web hızlandırıcı". Mustafa Özgür Web Teknolojileri Günleri'nde yaptığı sunumda da bahsetmişti. Burada biraz daha açalım.

Varnish basitçe, yapılan istekleri, belli kurallar ile cache'leyip istemciye dönen, genelde web sunucuların önünde konumlandırılan bir araç. İstekler Varnish'e geliyor, Varnish önbelleğinde varsa isteği buradan dönüyor, eğer yoksa "backend"den fetch ediyor.

Peki nerelerde kullanılabilir?

En çok kullanılan senaryolardan birisi, "tüm kullanıcılar için aynı sunulan" içeriği Varnish üzerinden vermektir.

Örneğin, tüm kullanıcılara sunulan CSS ve JavaScript dosyaları aynıdır. Bu "static" dosyalar için istemci tarafında cache'leme yapılabilse de, bu dosyalar için üzerinde uygulamamızın çalıştığı "web sunucuya" inmek, eğer o sunucuda çok fazla iş varsa gereksiz olabilir.

Yine başka bir örnek ise, kullanıcıya özel içerik sunmayan, her kullanıcıya aynı içeriği sunan siteler için de geçerli. Örneğin, danışmanlığını vermekte olduğumuz Namshi "giriş yapmamış" tüm kullanıcılar için aynı içeriği basıyor. Siteye aynı anda 50 bin kullanıcı da gelse, hepsi istisnasız aynı içeriği görüyor.

Namshi'nin ayakkabıları listeledigi http://en-ae.namshi.com/shoes gibi bir siteyi açtığımızda olan işlemler kabaca şöyle;
  1. Üst taraftaki template'i render ediyor. Birkaç "cpu cycle" ile birkaç nano saniye ile halloluyor bu işlem.
  2. Sol taraftaki kategori ağacını almak için bir servise (veritabanı, memcached veya harici bir servis) istek yapıyor, dönen isteği yine template içinde render ediyor. Bu sefer işin içine veri girmeye başladığı için "cpu cycle" ve dolayısıyla işlem süresi arttı.
  3. Yine sol taraftaki filtre seçeneklerini almak için yine benzer bir servise istek yapıyor, template deriyor. Birkaç cpu cycle daha feda oluyor.
  4. İçerikte sağ üst taraftaki, "tanıtım" amaçlı ayakkabı resmini yine bir servise istek yapıp render ediyor.
  5. Daha sonra ise, o resmin altındaki 140 adet ürünü buluyor, her birinin detayını (resimlerini, içeriğini vs) bir veritabanından veya bir servisten çekiyor, bunları bir loop içinde (140 kere dönen bir for loop) template'leri render ederek, o ekranı çiziyor. Buradaki cpu cycle'lar yüzünden, gavurların dediği gibi "a kitten dies :(".
  6. Daha sonra aşağıdaki footer'ı derliyor.
  7. Bu elde ettiği tüm template'leri birleştirip, layout'u render ediyor ve istemciye sunuyor.

Tüm bu işlemler, çok optimize şekilde 100ms gibi bir sürede çözülüyor. Her şey çok güzel gidiyor. Ancak her 100ms, sunucu tarafında belli miktarda kaynak(işlemci, ram) tüketiyor. Ve alsında ürettiğimiz çıktı, bu sayfayı ziyaret edecek (giriş yapmamış) tüm kullanıcılar için aynı. Oluşan markup çıktısı her ne kadar ~20KB civarında olsa da, o 20KB'ı oluşturmak için, her istekte 5-10 MB RAM ve işlemci boşa harcanıyor. Ve siteye aynı anda 10.000 kullanıcı gelirse, 70-80 GB RAM'e ihtiyacınız oluyor! Toplamda ~20KB RAM işinizi görecekken 70-80 GB'lık bir sunucu bulmanız, yükü ona göre dağıtmanız ve bu sunucuları maintain etmeniz, en önemlisi bu sunuculara "para ödemeniz" gerekiyor. Varnish işte tam burada yardımınıza yetişiyor.

Varnish'e "arkadaş, sen, giriş yapmamış(coookie'sinde bir değer set edilmemiş???) tüm kullanıcılara aynı içeriği dön, boşu boşuna bizim web sunucumuzu, diğer web servislerimizi meşgul etme" diyebiliriz.

Ubuntu/Debian kullanıcıları
sudo apt-get install varnish
ile basitçe kurabilir. Varnish ontamınlı olarak 6081 portunu dinler. Bunun 80. portu dinlemesini isterseniz /etc/default/varnish dosyasındaki "DAEMON_OPTS" direktifini değiştirebilirsiniz.

Varnish'in öntanımlı ayar dosyası da /etc/varnish/default.vcl adresinde bulunur. Burada da öntanımlı olarak 8080 portunda çalışan "backend"den verileri alacak şekilde tasarlanmıştır. Siz web sunucunuzu 8080 portu üzerinden çalıştırıp, yukarıdaki DAEMON_OPTS'dan da varnish'i 80. porttan çalışması için ayarlarsanız artık sunucunza gelen istekler önce Varnish'e gelecek, orada eğer cache bulunamazsa web sunucunuza(backend) inecek.

Varnish ayarları, VCL(Varnish Configuration Language) ile tanımlanır. VLC ile, ayar dosyalarında, kullanılacak backend'ler, bu backend'lere gidecek yükü dağıtacak director'lar, gelen isteklerin hangi kurallara göre cache'leneceği, bu cache'in nasıl invalide edileceği gibi şeyleri belirleyen methodlar bulunuyor. Bizim senaryomuzda vlc_recv ve vlc_fetch methodları içinde tanımlamalarımızı yapacağız.
  1. # istekler, 8080. portta çalışan web sunucumuza gidecek
  2. backend default {
  3. .host = "127.0.0.1";
  4. .port = "8080";
  5. }
  6.  
  7. # istek baslangicinda cagirilir
  8. # burada, istek manipule edilebilir,
  9. # orn: istege bir header eklenebilir
  10. sub vcl_recv {
  11.  
  12. # AJAX istekleri cache'lenmesin,
  13. # cogu kutuphane boyle bir baslik ekler
  14. # eger bu baslik varsa, cache'leme
  15. if (req.http.X-Requested-With) {
  16. return(pass);
  17. }
  18.  
  19. # geri kalan tum istekler cache'lensin
  20. return(lookup);
  21. }
  22.  
  23. # istek, backend'den fetch edildiginde cagirilir
  24. sub vcl_fetch {
  25. # 200 donuyorsa, icerik 300 saniye boyunca cache'lensin
  26. if (beresp.status == 200) {
  27. set beresp.ttl = 300s;
  28. return (deliver);
  29. }
  30. }
Bu yapılandırmadan sonra varnish, "X-Requested-With" header'ı olmayan tüm istekleri 300s boyunca cache'lemeye başlaaycak.

Peki ne kadar hızlandırıyor?

Hemen test edelim :)
PHP ile basitçe 50ms'de dönen bir kod yazalım
  1. <?php
  2. //50ms bekle
  3. usleep(50000);
  4.  
  5. //ust tarafta 50ms suren bir islem oldugunu varsayin
  6. //daha sonra ekrana bugun basilacak
  7. //herkes ayni veriyi gorecek
  8. echo "Bugun : " . date("Y-m-d");
Şimdi apache üzerinde, mod_php ile çalışan bu adrese, anlık 100, toplam 5000 istek gönderelim.
$ ab -n 5000 -c 100 http://localhost:8080/varnishtest/ 
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 localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        Apache/2.2.22
Server Hostname:        localhost
Server Port:            8080

Document Path:          /varnishtest/
Document Length:        19 bytes

Concurrency Level:      100
Time taken for tests:   3.304 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      1145000 bytes
HTML transferred:       95000 bytes
Requests per second:    1513.34 [#/sec] (mean)
Time per request:       66.079 [ms] (mean)
Time per request:       0.661 [ms] (mean, across all concurrent requests)
Transfer rate:          338.43 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.4      0      12
Processing:    51   65   7.3     64     119
Waiting:       50   65   7.2     64     113
Total:         53   65   7.6     65     131

Percentage of the requests served within a certain time (ms)
  50%     65
  66%     68
  75%     70
  80%     71
  90%     75
  95%     78
  98%     83
  99%     88
 100%    131 (longest request)

5000 isteğin tamamlanması 3.304 saniye sürmüş. Bu da saniyede toplam 1513 isteğe cevap verebilmişiz demek. Bir de Varnish ile deneyelim.
$ ab -n 5000 -c 100 http://localhost/varnishtest/ 
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 localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        Apache/2.2.22
Server Hostname:        localhost
Server Port:            80

Document Path:          /varnishtest/
Document Length:        19 bytes

Concurrency Level:      100
Time taken for tests:   0.307 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      1334990 bytes
HTML transferred:       95000 bytes
Requests per second:    16309.86 [#/sec] (mean)
Time per request:       6.131 [ms] (mean)
Time per request:       0.061 [ms] (mean, across all concurrent requests)
Transfer rate:          4252.64 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.8      1       5
Processing:     1    5   7.5      4      61
Waiting:        1    5   7.4      4      58
Total:          2    6   8.0      5      66

Percentage of the requests served within a certain time (ms)
  50%      5
  66%      5
  75%      5
  80%      6
  90%      6
  95%      7
  98%     58
  99%     62
 100%     66 (longest request)

Bu sefer, 5000 istek, 0.307 saniyede döndü! Ve saniyede 16309.86 isteğe cevap verebilen bir sunucumuz oldu. Süper değil mi :) 10 kattan daha fazla kazancımız oldu. Bu da kabaca, 10 tane sunucunun işini tek sunucuda yapabiliyoruz demek.

Ben yurt dışında olduğumdan, Mustafa da Türkiye'deki işlerden dolayı kafasını kaldıramadığından dolayı blog'u biraz boş bıraktık, ama aralarda yine böyle şeyler yazacağız. Oya Başar'ın dediği gibi, "bizi özleyin anacım" ;)