Merhaba e-bergi okuyucuları. Sevgili İlke'nin (Demir) ısrarlı takiplerinden sonra e-bergi'deki ilk yazımı yazıyorum, umarım bundan sonra her ay birlikte olabilir, birşeyler paylaşabiliriz.

Bu ayki konumuz web'de proje geliştirirken, ölçeklenebilir olması için dikkat edilmesi gerekenler üzerine.

Bir web sitesi yapmak aslında çok kolay, bir http sunucusu ve bir veritabanı; bir de alan adınız oldu mu, değmeyin keyfimize. Ancak işler ciddiye binince, bu yaklaşımla başarılı olmak, İzmir'den New York'a yüzmek kadar imkansız. İyi bir proje yapmak, günlük milyonlarca kullanıcıya ulaşmak istiyorsanız, yola ilk çıkarken, sistemi tutarlı ve ölçeklenebilir yapmak için neler yapmanız gerektiğini hesaba katmalı, bunları ilk etapta uygulamazsanız da, işin ucunun nereye varacağını kestirebiliyor olmanız gerekir.

Basit bir örnekle başlayalım, Facebook benzeri bir arkadaşlık sitesi yapıyor olalım. Ayda 10$'lık bir sunucu ve yıllık 10$'lık bir alan adı bizim işimizi görecektir. Kolay olduğu için LAMP (linux & apache & mysql & php) ayarlarıyla işe başladığımızı düşünelim. Veritabanları dersinde de sınıfın en iyisiydik, ilişkisel veritabanı kavramlarının hepsini uyguladık, verimiz %99 tutarlı. Bir süre sonra, sistem yayıldığında, yavaş yavaş sunucu yükümüz artacak, daha iyi bir sunucuya taşınacağız. Bir süre sonra o da yetmeyecek ve veritabanı sunucusunu başka bir makinaya taşıyacağız. Ancak daha fazlasını düşünmediysek, artık yapacak bir şey yok; çünkü herşeyi iç içe çalışacak şekilde tasarladık ve teknik imkanlar yetmediği an sonumuz geldi.

Peki bu kabusu yaşamamak için ne yapmalıydık? Bu sorunun cevabı basit, sistemi ölçeklenebilir tasarlamalıydık. Ancak bunu uygulaması ne yazık ki bu kadar basit değil, genel geçer bazı yöntemler olsa da kendi projenize özel çözümleri bulup birleştirmeniz gerekiyor.

Yazının kalan kısmında, dağınık çalışabilecek bir sistem tasarlamak için nelere dikkat edebilirsiniz, neler kullanabilirsiniz onlardan bahsedeceğim. Tabi ki bahsi geçecek şeyler genelin ufak bir alt kümesi; ancak başlangıç ve fikir edinmek için faydalı olacaklarını düşünüyorum.

İyi Bir Çerçeveyle Yola Çıkın

Küçükten başlayıp zamanla büyüyen bir proje yapmanın en sıkıntılı yanı, kodun bir süre sonra içinden çıkılamaz hale gelip arap saçına dönmesi. Bunun için size ilk tavsiyem iyi bir iskelet yapı (framework) seçip onun üzerine kodunuzu geliştirmeniz. Yeni bir iskelet öğrenmek projeye giriş bariyerinizi yükseltse de, zamanla size sağlayacağı kolaylıklar sizden aldığı o vakti fazlasıyla iade edecektir. Çok gerekmediği sürece kendi iskeletinizi, kod çatısını geliştirmenizi tavsiye etmem; çünkü en basitinden takıma kattığınız her insana bunu anlatmanız gerekecek, döküman haline getirmiş olmanız gerekecek vs vs. Kendi çerçevenizi yazıyorsanız, tekerleği yeniden icat etmediğinizden emin olun :) Php için yazılmış bir çok iskelet var, bunlardan Zend ve Symfony'i önerebilirim.

Yükü İstemciye Atın

Geçen yaz çalıştığım şirkette bir sayfayı yaparken, şirketin müdendislik direktörüyle nasıl yapmamız gerektiğini tartışırken söylediği önemli bir laf vardı. 'Sana gelen her kullanıcı yeni bir sunucu getirmiyor ancak yeni bir istemci getiriyor'. Dolayısıyla işin ne kadarını istemcide hallederseniz sunucu maliyetiniz o kadar az olur. Projeye başlarken bunu göz önünde bulundurup, istemci tarafında da kaliteli bir kütüphane seçerek (prototype, jquery) uzun dönemde hem kullanıcı tecrübesi hem de sunucu maliyeti açısından kayda değer avantajlar sağlayabilirsiniz. Unutmayın, sayfanın tamamını sunucuda üretmektense, gerekli veriyi istemciye gönderip değişen kısmı istemcide üretmek çoğu zaman daha hızlı olacaktır.

Oturum (Session) Verilerini Dağınık Tutun

Vaka'mıza geri dönersek, takıldığımız nokta bir http sunucusunun yeterli olmadığı noktaydı. Çünkü öntanımlı php ayarlarında oturum (session) bilgileri lokal makinede tutuluyor. Halbuki bunun yerine dağıtık ve açık kaynak kodlu bir önbellek sistemi kullanabiliirdik, mesela memcached. Bunun için yapmanız gereken tek şey, sunucularınıza memcached kurmak ve php ayarlarında aşağıdaki değişiklikleri yapmak:

session.save_handler = memcache
session.save_path = "tcp://MEMCACHED_HOST:MEMCACHED_PORT?persistent=1"

Böylece tek bir sunucuya bağlı kalma derdimizden kurtulduk. Kullanıcının arka arkaya gelecek iki istemi farklı sunucularda değerlendirilse bile, oturumlar ortak bir alanda tutulduğu için davranışta bir bozukluk olmayacak.

Önbellek Kullanın

Http sunucu derdimizi hallettiğimizi düşünelim. Ancak kullanıcı sayımız arttıkça, benzer verileri defalarca kez veritabanından çekmeye başlayacağız. Basit bir örnekle, kullanıcılar ilk geldiklerinde ortak gördükleri bir etkinlik sayfası olsun. Her anasayfa isteminde haberlerin veritabanından geldiğini varsayalım. Veritabanının kendi önbelleği olsa bile, bu azımsanmayacak bir yük yaratacaktır. Oysa herkes aynı haberleri gördüğüne göre, bu haberleri memcached kullanarak bir kere veritabanından çekip, yeni bir haber gelinceye kadar memcached'den okuyabilirdik. Veya giriş yapan bir kullanıcı için de aynı şey geçerli. Her defasında kişinin bilgisini veritabanından çekmektense, bir kere çekip önbelleğe yazıp daha sonra oradan okumak ciddi bir performans kazancı sağlayacaktır.

Veritabanının İlişkisel Özelliklerinden Uzak Durun

Önbellek kullanarak veritabanı yükünü ciddi miktarda azaltsak da, bir süre sonra tek bir veritabanı sunucusu bize yetmiyor olacak. Bu durumda yapılacak ilk şey veritabanındaki birbirinden uzak tabloları farklı sunuculara koymak; ancak bu da çok uzun vadeli bir çözüm olmayacaktır. Bir süre sonra büyük tablolarınızı yatay veya dikey bölümlemeniz gerekecek (http://en.wikipedia.org/wiki/Partition_(database) ). Ücretli bir veritabanı kullanmadığımız için bu iş başa düşecek ve veritabanı şemasına gömdüğümüz her ilişkisel durum bizim için bir engel oluşturacak.

Örnek vermek gerekirse aşağıdaki iki tabloya sahip olalım.

Kullanıcı (kid, ad, soyad, adres, ...)
Arkadaş (kid1, kid2)

Arkadaş tablosundaki kid1 ve kid2 değerleri Kullanıcı tablosuna dış anahtar (foreign key) olsunlar. Yani veritabanı, Arkadas tablosuna bir veri eklemek istediğimizde bu kid1 ve kid2 nin, Kullanıcı tablosunda var olduğunu kontrol edecek böylece verimiz hep tutarlı kalacak. Ancak gün gelip Arkadas tablosu çok büyüdüğünde bu tabloyu yatay bölmek istersek, bu kuralı artık veritabanına otomatik olarak kontrol ettirmenin bir yolu olmayacak (tabi oracle kullanacak paramız yoksa :) ). Bu tarz sıkıntılı durumlara düşmemek için, ilk baştan itibaren bu tarz mantıksal kontrolleri veritabanında değil kendi kodlarımızda yapmakta fayda var. Bırakın veritabanı bundan bihaber olsun.

Sorgulamayacağınız Veriyi Birleştirin

MySQL gibi satır tabanlı veritabanlarının bir çok kolaylığı var. Mesela tabloları birleştiren, bir veya birkaç satır üzerinde sıralama, gruplama yapan oldukça karmaşık sorguları sadece SQL yazarak çalıştırmamızı sağlıyor. Bu birçok işi hızlı yapmamızı sağlasa da, veritabanının yükü arttıkça kullanılamaz hale geliyor. Bu özelliklerden maksimum verim almak için, üzerinde sorgu yapmayacağınız sütünları tek bir satır haline getirmekte fayda var. Mesela yukarıdaki Kullanıcı tablosunda kullanıcının yaklaşık 20-30 özelliği tutuluyor olacak. Oysaki bunlardan en fazla 5-6 tanesini sorgularken kullanıyor olacağız; ancak MySql bundan haberdar olmadığı için, o sütünları düzenli tutmak için de bir ton enerji harcayacak. Bunun yerine kullanmadığımız sütünları tek bir sütünda, seri hale getirilmiş bir şekilde tutabilir, hatta onları ayrı bir tabloya (kid, birleşik_veri) şeklinde yazabilirdik. Böylece veritabanından ciddi bir yükü de almış olurduk. İşin güzel yanı, reflection sunan her dilde bunu hiç uğraşmadan yapabilirsiniz. (php için: http://www.php.net/serialize, http://www.php.net/manual/en/function.unserialize.php)

Anahtar - Veri Çifti Veritabanları Kullanın

Üstteki bölümde veritabanı üzerinde sorgulamayacağımız verileri birleştirdik, MySql'den ciddi bir yük aldık. Peki madem bu sütünları yapısız tutuyoruz, neden MySql gibi nispeten yavaş bir veritabanında tutalım? Daha doğrusu neden MySql, o kadar işi varken, bu veriye ulaşmak için gönderdiğimiz SQL'leri anlamlandırmaya, optimize etmeye vs. uğraşsın, hepsinden öte, neden SQL kullanalım? Bu tarz durumlar için geliştirilmiş, çoğu Berkeley Db'yi baz alan birçok anahtar - veri çifti veritabanı mevcut. Bunlardan bir tanesi, memcached protokolünü uygulayan Memcachedb. Burada memcachedb'nin dağıtık olmasına dikkatinizi çekmek isterim. Yani sonsuz ölçeklenebilir bir sistem. Bunun dışında Redis gibi veri güvenliğini garanti etmeyen ancak size çok çok daha hızlı hizmet sunabilen teknolojilere de göz atabilirsiniz.

Mesaj Kuyruğu Kullanın

Web projelerinde aslında birçok kullanıcı sorgusu asenkron ele alınabilcekken, http'nin istek-cevap mimarisi yüzünden olsa gerek, birçok işlemi bitirmeden kullanıcıya dönmeyiz. Tabi ki bir istek sırasında boş yere açık kalan bağlantı, diğer sunucularak yapılan bağlantılar (önbellek sunucusu, veritabanı sunucusu vs.vs.) işletim sistemine ekstra maliyetler getirir, kullanıcı da gereksiz yere beklemiş olur. Bunun yerine mümkün olan yerlerde kullanıcıdan isteği alıp, istekle ilgili veriyi kaydedip kullanıcıya dönüp, işi de sunucular müsait olduğu bir zamanda yapmakta fayda var. Çok basit bir örnek verelim, arkadaşlık sitemiz üzerinden insanlar birbirlerine e-posta atabiliyor olsunlar. Normalde yapılması gereken işlemler şu şekilde olacak:

  1. İstekteki verilerin doğruluğunu kontrol et
  2. Alıcıya e-postayı gönder
  3. İstemciye geri dön

Halbuki burada istemcinin mailin gönderilme süresini (karşı taraftaki sunucuya ulaşılması, e-posta verisinin aktarılması vs) beklemesine hiç gerek yoktu. 1. basamaktan sonra isteği bir kuyruğa atıp kullanıcıya geri dönebilirdik. Daha sonra posta göndermekle görevli kuyruk çalışanlarından biri bu bilgiyi alır ve gerekli işlemi yapardı. Bu sayede hem sistemde aynı anda 100 kişi e-posta atmak isterse bir tıkanmak olmaz, hem kullanıcılar gereksiz yere bekletilmez, hem de 2. basamakta oluşacak geçici bir hata yüzünden işlem iptal edilmez. Ayrıca sistemin farklı işlem yapan parçalarını birbirinden ayırdığımız için daha iyi bir mimari kurmuş oluruz. Mesaj kuyruğu olarak Facebook'taki Causes uygulamasını yazan gurubun geliştirip daha sonra açık kaynak kodlu yaptığı Beanstalkd kullandığım ve önerebileceğim bir çözüm. Amazon SQS veri güvenilirliği sağladığı için daha farklı işlemlerde de kullanılabilir. MySQL üzerine veritabanı motoru olarak yazılmış seçenekler bulmak da mümkün.

Bulutlara Çıkın!

Basit bir hosting firmasından sunucu hesabı aldığınızda size bir kontrol paneli ve bir ftp hesabı veriliyor. Eğer şanslıysanız bir de limitli kabuk hesabınız oluyor. Ancak projenize özel yazılımlar yüklemek, MySQL'i ihtiyaçlarınıza göre optimize etmek, php ayarlarını değiştirmek vs. istediğinizde bu hesaplar yetersiz kalıyor. Eğer kendi sunucularınızı barındırmak isterseniz, bu da beraberinde ciddi bir maliyet ve yük getiriyor. Sunucu dünyasında yakın zamandaki en büyük gelişmelerden biri sanallaştırma. Aynı fiziksel makina üzerinde birbirinden habersiz bir çok sanal makina çalıştırabiliyorsunuz. Bu sanal makinalar sunucu tarlalarında çalışıyor, hatta dünya üzerinde farklı sunucu tarlalarına yayılmış bile olabiliyorlar. Böylece servis sağlayıcı hem size istediğiniz gibi oynayabileceğiniz bir işletim sistemi sunmuş oluyor, hem de bir çok kişiyi aynı makinada barındırarak maliyetlerden kısmış oluyor. Daha da ötesinde, yeni bir makinaya ihtiyacınız olduğunda 15 saniye kadar kısa bir sürede elinizdeki disk resminin bir kopyasını başlatabiliyorsunuz. Bunun önüne bir yük düzenleyici kurup sitenizin yoğun saatlerde 100 sunucuda, rahat saatlerde 10 sunucuda çalışıp bunun da tamamen otomatik olduğunu düşünmek bir hayal olmaktan çıkıyor. Düşen maliyetler de sistemin tuzu balı. Amazon AWS ve Racspace cloud hosting konusunda başarılı 2 firma.

Mesela Amazon ec2 (elastic cloud)'da sunucularınızı barındırıp, Amazon Ebs (elastic block store)'da kaybolmayacak verilerinizi tutup, içeriğinizi Amazon S3 (simple strorage service)'ten sunup, Amazon SQS(simple queue service) ile asenkron işlerinizi güvenilir ve dağıtık bir şekilde halledip hatta gün gelip Amazon Elastic MapReduce kullanıp, teknik açıdan muhteşem bir projeye imza atabilirsiniz. (burasi biraz Amazon reklamı koktu farkındayım, ancak bu kadar güzel teknolojileri bir arada sunduğu için Amazon bunu hak ediyor kanımca :) )

The Final Countdown: Hadoop Project & Google App Engine!!!

Hadoop Projesi

Aslında amacım bu yazıyı Hadoop Projesi üzerine yazmaktı, ancak yazıyı yazarken 3-4 paragraf ön bilgi yazdığımı farkedince, önce üstteki yazıyı yazmam gerektiğine karar verdim. Ama bu yazının sonunda Hadoop'tan bahsetmemek olmaz.

Üstte sırf ölçeklenebilirlik ve performans açılarından bakarak bir çok yöntem ve teknolojiden bahsettik. Kaldı ki daha bunlar gibi birçok farklı çözüm mevcut. Belki çözüme ulaşıyoruz ancak bayağı bir debelenmek gerekiyor. İşte Hadoop burada fark yaratabilecek bir bakış açısı, bu nedenle de bir çok kişi tarafından 'Sıradaki Büyük Şey' (The Next Big Thing) olarak adlandırılıyor.

Hadoop Projesi, Apache Foundation bünyesinde başlatılan, üzerinde tutarlı, ölçeklenebilir ve dağıtık çalışan projeler geliştirmeye imkan sağlayacak bir framework geliştirme projesi. Proje, Google'ın Map Reduce ve GFS (google file system) makalelerinden esinlenmiş ve bir çok dev firma tarafından kullanılıyor. (http://wiki.apache.org/hadoop/PoweredBy) Çok kısa bahsetmek gerekirse map reduce, lisp'teki map ve reduce fonksiyonlarından esinlenmiş bir isim. Büyük miktarda bir veriyi kısa zamanda işlemek istediğinizi düşünelim (sıralama, indexleme vs). Veriyi ufak parçalara (örn: 64mb) bölüyorsunuz ve bunları Mapper çalışanlarına dağıtıyorsunuz. Bu Mapperlar ellerindeki veriyi işleyip anahtar-veri formatında yayıyorlar. Yayılan bu verilerı Reducer çalışanları toplayıp aynı anahtardaki verileri grupluyorlar. Böylece tamamen dağıtık bir şekilde elinizdeki veriyi işlemiş oluyorsunuz.

Google'dan Jeffrey Dean ve Sanjay Ghemawat'ın OSDI'04 e gönderdikleri makaladen aldığımız basit bir map reduce örneğiyle bu bölüme son verelim. Makalenin tamamını http://labs.google.com/papers/mapreduce.html adresinde bulabilirsiniz.

Büyük bir doküman kümesinde, hangi kelimeden kaç tane olduğunu saymak istediğinizi düşünelim. Bunun için yazmamız gereken mapper, eline gelen kümedeki her kelime için bir (kelime, 1) ikilisi yayacak. Daha sonra bu yayılan ikililer kelimelerine göre gruplanarak Recuder'a gelecek. Reducer'ımızın yapması gereken tek şey bunları birleştirip toplam değeri yaymak. Bu kadar basit!

map(String key, String value):
// key: döküman adı
// value: döküman içeriği
for each word w in value:
EmitIntermediate(w, "1");
reduce(String key, Iterator values):
// key: bir kelime
// values: map'de EmitIntermediate'ta verilen değerlerin listesi
int result = 0;
for each v in values:
result += ParseInt(v);
Emit(AsString(result));

Google App Engine

Google, Hadoop projesinin esin kaynağı olmakla birlikte aynı zamanda başka süper bir projenin de arkasında. 'Google Uygulama Motoru', sizi sunucu yönetme, ölçekleme gibi dertlerinizden kurtarabilecek bir proje. Başka bir deyişle, projenizi geliştirirken Google'ın sağladığı SDK'yı kullanıyorsunuz ve uygulamanızı Google sunucularına yüklüyorsunuz. Sayfa görüntülenmeleriniz veya veri boyutlarınız arttıkça, bunu ölçeklemek Google'a kalıyor. Zaten projeyi onların altyapısında geliştirdiğiniz için otomatikman ölçeklemeye uygun oluyor. İşin daha güzel bir yanı da, başlangıç için sizden bir ücret talep etmiyor. Projeniz büyüdükten sonra, kullandığınız kadar kaynağa ödeme yapıyorsunuz. Şimdilik sadece Java ve Phyton destekliyor, hatta Java desteğinin henüz ciddi eksiklikleri var, ancak bunlar kısa sürede kapanacaktır. Google Uygulama Motoru'nu kullanmanın belki de tek dez avantajı kendinizi Google'a bağlıyor olmanız. Aslında belirli standartları kullandığınız için kısa sürede farklı bir sunucuya geçebilirsiniz ancak Google'ın rahatına alıştıktan sonra bunu yapacağınızı pek sanmıyorum :).

Bu yazı burada biterken, siz de buradan devam edebilirsiniz.