CGI-bin ile günde 200 milyon isteği işlemek
(simonwillison.net)- Jake Gold, 16 iş parçacıklı AMD 3700X üzerinde bir Go + SQLite CGI programı çalıştırarak 1990'lar tarzı CGI'ın modern donanımda ne kadar dayanabildiğini test etti
- Sonuçlar, sıradan donanımlarda bile saniyede 2400'den fazla, günde 200 milyondan fazla isteğin işlenebildiğini gösteriyor
- CGI, her istek için bir süreci başlatıp çalıştırdığı ve sonra sonlandırdığı için geçmişte yüksek ek yük oluşturuyordu; bunu azaltmak için PHP ve FastCGI gibi yaklaşımlar ortaya çıktı
- Simon Willison, 2020'de datasette-ripgrep içinde Rust tabanlı ripgrep CLI'ını çağırarak aramaları işledi ve web isteği sırasında süreç çalıştırmaktan kaçınmak gerektiğine dair eski inancını değiştirdi
- Go ve Rust gibi hızlı başlayan diller ile çoklu CPU ortamlarında CGI tarzı işleme, eskisine kıyasla daha gerçekçi bir seçenek olabilir; ancak genel olarak önerilen varsayılan yaklaşım değildir
Modern donanımda CGI performansına yeniden bakış
- Serving 200 million requests per day with a cgi-bin, Jake Gold'un Go + SQLite CGI programı ile 1990'lar tarzı CGI'ın performansını test ettiği bir yazı
- Test ortamı, 16 iş parçacıklı AMD 3700X tabanlı bir sistem
- Temel sonuç, CGI ile bile sıradan donanımda saniyede 2400'den fazla, günde 200 milyondan fazla isteğin işlenebilmesi
- CGI, gelen her istek için ayrı bir süreç başlatıp çalıştıran ve ardından sonlandıran bir yöntem
- İlk dönem web topluluğu bu ek yükten kaçınmak için kodu bellekte sürekli tutup ilave maliyeti azaltan PHP ve FastCGI çözümlerini geliştirdi
CGI'ın eskisi kadar kötü olmayabileceğinin nedenleri
- Geçmişte CGI'ın darboğazlarından biri, web betiklerinin Perl, Python, Java gibi çok hızlı başlatılma hedefiyle tasarlanmamış dillerle yazılmasıydı
- Bugün Go ve Rust kullanıldığında CGI tarzı istek işleme çok daha verimli çalışabilir
- Simon Willison, 2020'de datasette-ripgrep geliştirirken Rust ile yazılmış ripgrep CLI aracını shell üzerinden çağırarak arama yaptı ve iyi sonuçlar elde etti
- CGI programları ayrı süreçler olarak çalıştığı için birden fazla CPU kullanan yapılarla da iyi uyum sağlayabilir
- Modern sunucular 384 CPU iş parçacığına sahip olabilir
- Küçük bir VM bile 16 CPU barındırabilir
- CPU ve bellek performansı da geçmişe göre büyük ölçüde hızlandı
- 1998 tarzı web uygulaması yazma yaklaşımı, Go ve Rust ile birleştiğinde ilginç bir deney konusu olabilir; ancak çoğu servisin izlemesi gereken varsayılan yol değildir
1 yorum
Hacker News yorumları
Python gibi bir şey kullansanız bile günümüzde CGI oldukça hızlı
Bir CGI betiğinin başlaması CPU’da 400 ms sürüyorsa ve sunucu 64 çekirdekliyse, saniyede 160 istek; sunucu başına günde 14 milyon hit işleyebilir
Statik varlıklar hariç günde birkaç milyon istekte bile zorlanan bir web servisi için darboğaz CGI sürecinin başlama süresi değildir
Eskiden buna “Python standart kütüphanesinde uzun süredir desteklenen sıkıcı bir teknoloji” derdim; ama kalan Python bakımcıları kararlılığı ve geriye dönük uyumluluğu zararlı görüyor gibi ve fazlasıyla sıkıcı, kararlı modülleri standart kütüphaneden kaldırıp durdular
cgi modülü Python 3.13’te kaldırıldı
Son 25 yılın çoğunda her gün kullandığım için prototiplemede hâlâ Python kullanma alışkanlığım var; ama artık buna üzülüyorum ve JS ile Lua arasında kararsızım
İlginç şekilde, orada 14 Temmuz 2000 tarihli, yani 25 yıl önceki https://peps.python.org/pep-0206/ bağlantısı veriliyor; o zaman da cgi paketi “kötü tasarlanmış ve artık düzeltilmesi neredeyse imkânsız” diye tanımlanmıştı
https://github.com/jackrosenthal/legacy-cgi paketi, standart kütüphane modülünün drop-in alternatifini sağlıyor gibi
Bu destek http.server modülündeki CGIHTTPRequestHandler’da hâlâ duruyor
cgi modülünde olanlar yalnızca HTML form verilerini ayrıştıran birkaç fonksiyondan ibaretti
Ama ardından alternatif olarak standart kütüphanesi bile olmayan JS’yi düşünmeyi pek anlayamıyorum
Lua’nın standart kütüphanesinde de CGI modülü yok
Şimdi ise daha çok, hızlıca yayına çıkmak için eklenmiş akıl almaz derecede savurgan parçaları bulup onları durdurmaya benziyor
Düzgün denerseniz uygulama, depolama erişimi dışında neredeyse hiç gecikme eklemeyebilir
Python’ı 1.6’dan beri öğrendim ama çoğunlukla işletim sistemi betikleme işleri için kullandım
1999–2003 arasında Apache ve IIS modüllerinde Tcl kullanıp modülleri sürekli C ile yeniden yazarak çok fazla dersi zor yoldan öğrendim
Yakın zamanda 350 dolarlık bir mini sunucuda Golang binary’si, RabbitMQ, Redis ve MySQL’i aynı makineye kurup test ettim; saniyede 5.000 isteği sürekli işledi
Bunu 24 saate vurunca 400 milyon istek ediyor
Günümüzde ücretsiz araçların ne kadar harika olduğuna şaşıyorum; buna rağmen bulut sağlayıcılarına bu kadar çok para ödememiz de şaşırtıcı
Tamamen birebir bir karşılaştırma değil ama bodrumdaki makinede her şeyi geliştirip ayarlama süreci gerçekten güzeldi
“Google böyle yapıyor” dışında bir gerekçe olmadan bu ek yükü taşımaya devam etmek garip
İyi kullandığımız modüler monolit mimarisi hakkında mutlaka bir yazı yazmalıyım
Bir hosting şirketinden özel sunucu kiralayıp dilediğiniz gibi kullanabilirsiniz. Yalnız bant genişliği veya aktarım kotası sınırlarına ciddi biçimde takılırsınız
Bulutun kullanılmasının nedeni, işin içinde çok sayıda çıkar ilişkisinin olması. Örneğin VC’ler ve yatırımcılar bulut şirketlerinde de hisse sahibi ve gerçekte hiç gelmeyecek hayali trafik patlamaları yüzünden yatırımlarının batmasından korkuyorlar
Bulut satış ekipleri, yatırımcıların kaygılarına dokunmakta ustadır
Seyahatteyken disk bozulursa yapacak bir şey yok; yanlışlıkla kendinizi dışarıda bırakırsanız fallback için seri terminal de yok
r/homelab insanları gibi kurulum yapılabilir, ama o zaman özellikle kendi zamanımı da hesaba katınca gerçekten para tasarrufu sağlayıp sağlamadığı belirsizleşiyor
Sonuç olarak bulut sağlayıcılarının ölçek ekonomisi sayesinde oldukça iyi bir anlaşma olduğunu düşünüyorum
Tuhaf şekilde, bunlardan yalnızca birini ayrı olarak alamıyorsunuz
Dört adet 20 TB disk ve makul bir CPU ile ne kadar büyük bir iş veya servis çalıştırabileceğinizi hayal edin
Bulut sağlayıcısından böyle bir yapılandırma almak kolay değil
“Erken dönem web topluluğu bunun kötü bir fikir olduğunu çabuk öğrendi ve PHP gibi teknolojiler icat etti” kısmında, teknik olarak çekirdek teknoloji mod_php idi
PHP’nin kendisi çalıştırılma biçimi açısından Perl’den farklı değildi; ama mod_perl ile karşılaştırıldığında mod_php’nin tasarım tercihleri sayesinde PHP betikleri sunucuya öylece atıldığında hızlı çalışıyordu, mod_perl’i çalıştırmak içinse biraz düşünce ve sihir gerekiyordu
İçerik yönetimi, ödev yükleme, etkinlik takvimi, not yönetimi, gerçek zamanlı sohbet ve forumlar vardı; hepsi CGI üzerinde saf C idi ve üzerinde çalışmak cehennem gibiydi
PHP’yi öğrendiğim gün neredeyse ağlayacaktım; çünkü RFC okuyarak veya HTTP’yi tersine mühendislik ederek sıfırdan zorlukla yazdığımız her şey PHP’de basit bir fonksiyon çağrısıydı
Artık aksak urlencode uygulamalarını debug etmek ya da HTTP başlığındaki tuhaf bir carriage return yüzünden bir günü çöpe atmak zorunda değildik
Apache modülünün ilk sürümünün 1996’da PHP/FI Version 2.0 için olduğunu sanıyorum: https://www.php.net/manual/phpfi2.php#module
cgi-bin'in veritabanına erişmesi gerekiyorsa, süreç her başladığında bağlantı açması gerekir
FastCGI gibi kodu bellekte tutmak yalnızca başlatma süresi maliyetinden kaçınmak için değil, veritabanı bağlantı havuzu ya da en azından iş parçacığı başına kalıcı bağlantılar bulundurabilmek içindir
“Python tek iş parçacıklı, o yüzden çok sayıda çalıştıralım” ile “Python yavaş, o yüzden çok sayıda çalıştıralım” aynı anda yapıldığında böyle olmuştu
Ölçek büyüdükçe sonunda pgbouncer gibi Python dışındaki paylaşımlı bağlantı havuzu ve bolca ayarla, veritabanını öldürmeden yükü kaldırır hale getirmek gerekir
Elbette ardından, belli ölçüde performans veren çok iş parçacıklı bir dille yeniden implemente edince her şey tekrar oldukça basitleşti
Fiilen proxy rolü gören ayrı bir daemon çalıştırmak şeklindeydi
TCP/IP yerine Unix soketi kullanınca bağlantı maliyeti görece düşer
Bu donanımda bir hello world uygulamasının saniyede 2.400 istek işlemesi biraz kötü değil mi
Performanstan vazgeçip tam olarak ne kazanıldığını da bilmiyorum. Kodun daha basit hale geldiği de yok
Böyle siteler toplamın yalnızca bir kısmıdır
HN trafik bombardımanına da dayanabilir gibi
Dün de tartışılmıştı: https://news.ycombinator.com/item?id=44464272
Şirkette bazen hızlıca yapılan dahili web uygulamaları için hâlâ cgi-bin dizini sunuyoruz
Basit tutulursa kullanışlı oluyor
CGI demek, HTTP/1.0'ı doğrudan stdout'a print etmek zorundasınız demek değil
Örneğin Python'ın yerleşik wsgiref.handlers.CGIHandler'ını kullanırsanız herhangi bir WSGI uygulamasını CGI betiği olarak çalıştırabilirsiniz
import wsgiref.handlers, flask
app = flask.Flask(name)
wsgiref.handlers.CGIHandler().run(app)
Betikleri uwsgi ve onun CGI eklentisi[1] ile çalıştırıyoruz
mod_cgi yüzünden Apache ya da lighttpd çalıştırmaktan daha basit ve esnek olduğunu düşünüyoruz
uwsgi bir systemd birimi olarak çalıştığı için systemd'nin sertleştirme ve sandbox özelliklerinin tamamından da yararlanabiliyoruz
uwsgi'nin CGI işlemede mod_cgi'de olmayan kullanışlı yanı, belirli dosya türleri için kullanılacak yorumlayıcıyı belirtebilmeniz
cgi = /cgi-bin=/webapps/cgi-bin/src
cgi-allowed-ext = .py
cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
İlk bayta kadar geçen süre 250~350 ms ve bizim kullanımımız için kabul edilebilir
[1]: https://uwsgi-docs.readthedocs.io/en/latest/CGI.html
Yakın zamanda bir yan projede Apache kullandım; nedenlerden biri .htaccess özelliğiydi ve benzer bir konuşma yaptık
.htaccess dosyasını herhangi bir yere koyduğunuzda Apache bunu her istekte ek sunucu yapılandırması olarak okuyabilir: https://httpd.apache.org/docs/2.4/howto/htaccess.html
Bundan kaçınmanın büyük nedeni performanstı. Her istekte ek disk erişimi gerekiyordu ve mümkünse bunu ana yapılandırma dosyasına koymak her zaman daha iyiydi
Ama artık çoğu sunucuda SSD var ve Linux'un dosya sistemi önbelleği için kullanabileceği boş RAM'e sahip olma olasılığı da yüksek
Elbette Apache yapılandırmayı bir kez değil, her istekte ayrıştırmak zorunda olduğundan performans hâlâ biraz kötüleşir
Yine de günümüzde çoğu sunucu CPU'sunun daha güçlü olduğu düşünülürse, birçok kullanım senaryosunda bu göze alınabilir
Yan proje çok erken bir sürümde ama şimdiden kullanılıyor: https://github.com/StaticPatch/StaticPatch/tree/main
“Ben gerçek bir programcı değilim. Çalışana kadar bir şeyleri birbirine ekler, sonra devam ederim. Gerçek programcılar ‘Evet, çalışıyor ama orada burada bellek sızıntıları var. Düzeltmemiz gerekmez mi?’ derdi. Ben ise Apache'yi her 10 istekte bir yeniden başlatırım.”
PHP o zamandan bu yana çok yol kat etti, ama bunun büyük bir kısmı ilk hataları düzeltme süreciydi
“PHP 8'in çok daha iyi olmasının nedeni, içinde benim kodumun çok daha az olması.”
Bu seçim yüzünden HTTP isteklerinin %99,99'u gereksiz disk okumalarıyla yavaşlıyor gibi geliyor
Hızlı prototipleme iş akışının bir parçası olarak bunu daha fazla düşündüm
Modern JIT dillerinin, FastCGI modeline geçilmediği sürece başlangıç süresinin büyük ölçüde import’lar tarafından belirleneceğini düşünüyorum
Yerel betikler için h2o web sunucusunu kullanmaya başlayınca bu aklıma geldi; mruby ve FastCGI handler ile yapılandırma dosyaları temiz ve hızlı yazılabiliyor, performansı da çok yüksek: https://h2o.examp1e.net/configure/fastcgi_directives.html
Bir başka işe yaradığı yer de müşterilerin yerel yazılımı kendi kodlarıyla genişletmesini sağladığınız durumlar
Örneğin bir yapay zeka aracını genişletmek için MCP kullandırmak yerine, belirli bir istek yapısını CGI olarak uygulamalarını isteyebilirsiniz
MCP hizmetinin kendisi de CGI ile uygulanabilir mi diye merak ediyorum
Bir MCP framework’ü, işlevleri her iki çalışma modunu da destekleyen bir program biçiminde de sunabilir
Spesifikasyonu biraz kurcalamam gerekecek
Benim için
inetddoğrudan CGI demektiBu sayede internet çok daha eğlenceli hale geldi
Doğrudan inetd ile birkaç shell betiği barındırdım; tamamen Bash ile yazılmış bir HTTP sunucum da vardı
O VPS uzun zaman önce kayboldu, o dönemde sürüm kontrolü de kullanmıyordum
Onu yazdığım dizüstü bilgisayar da yok oldu
Yine de gerçekten çok eğlenceliydi
Dağıtım Makefile + scp kadar kolaydı; test ise birkaç
netcatve grep’i bir araya getiren başka bir Bash betiğiydiNe güzel günlerdi