Bir planlama toplantısında “bu monolit artık çok büyüdü, mikroservislere bölelim” cümlesini duyduğumda ilk sorum hep aynı oluyor: hangi ölçüm bunu söylüyor?

Cevap çoğu zaman bir ölçüm değil. Bir his — “kod tabanı kocaman” —, bir blog yazısı ya da bir özgeçmiş kaygısı. Üçü de gerçek duygular, ama hiçbiri mimari bir gerekçe değil.

Modüler monolit yazısında sınırları erken çizip dağıtımı geç yapmayı savunmuştum. Bu yazı o “geç”in ne zaman geldiğini — ve gelmediğini nasıl anlayacağınızı — anlatıyor.

Mikroservis bir çözüm değil, bir takas

Mikroservis mimarisi özünde tek bir şeyi değiştirir: modüller arası bir method çağrısını bir network çağrısına dönüştürür. Aldığınız şey bağımsız deploy ve bağımsız ölçekleme. Karşılığında ödediğiniz şey uzun:

  • In-process’te ücretsiz olan her çağrı artık serializasyon, latency ve partial failure demek.
  • Tek bir DB transaction’ı yerine saga, outbox, eventual consistency.
  • Tek bir log akışı yerine, distributed tracing olmadan takip edilemeyen bir istek yolu.
  • Tek bir deploy yerine, versiyonlanması ve geriye uyumlu tutulması gereken servis kontratları.
  • Compiler’ın doğruladığı bir refactor yerine, kimsenin doğrulamadığı bir ağ kontratı.

Bunların hiçbiri “kötü” değil; hepsi gerçek sistemlerde gerçekten ödenen bedeller. Asıl soru şu: bu bedeli bir şeyin karşılığında mı ödüyorsunuz, yoksa peşin mi?

Geçişi haklı çıkarmayan gerekçeler

Mikroservis kararını en sık tetikleyen sebepler, ne yazık ki en zayıf olanlar.

“Kod tabanı çok büyük.” Büyük kod tabanının çözümü modül sınırı, dürüst dosya organizasyonu ve ölü kod temizliğidir — ayrı deploy değil. 200 bin satırlık bir monolit ile 20 servise bölünmüş 200 bin satır aynı miktarda koddur; ikincisi sadece araya network koymuş hâlidir.

“Deploy yavaş ve korkutucu.” Yavaş pipeline’ın çözümü pipeline’ı hızlandırmaktır. Korkutucu deploy’un çözümü test kapsamı, aşamalı yayın ve hızlı rollback’tir. Bunların hiçbiri sistemi bölmeyi gerektirmez; bölmek aksine deploy sayısını artırır.

“Bağımsız ölçeklemem lazım.” Monolit’in kendisi de yatayda ölçeklenir: aynı uygulamayı bir load balancer arkasında N kopya çalıştırmak mikroservis istemez. Bağımsız ölçekleme ancak modüllerin kaynak profili gerçekten ayrıştığında anlam kazanır — ki bu ayrı bir madde, aşağıda.

“Modern mimari bu.” Modernlik bir mimari gerekçe değil. Bir sistemin iyi olup olmadığını, kaç parçaya bölündüğü değil, bir hatayı gece 2’de izlenebilir kılıp kılmadığı söyler.

“Netflix böyle yapıyor.” Netflix’in mikroservisi finanse eden bir ölçeği ve yüzlerce mühendisi var. Üç kişilik bir ekip bu mimariyi kopyaladığında Netflix’in çözdüğü problemi değil, yalnızca operasyon yükünü kopyalar. Bu, boring architecture yazısındaki innovation token mantığının ta kendisi: bütçeyi yanlış yere harcamak.

Ortak hata kalıbı: bunların hepsi gerçek rahatsızlıklar, ama hiçbiri mikroservisle iyileşmiyor. Doğru hastalığa yanlış ilaç.

Geçişi haklı çıkaran ölçülmüş sinyaller

Modüler monolit yazısında bir modülü servise çıkarmak için dört sinyal saymıştım. Sistem ölçeğinde de eşik aynı — şu sinyallerden en az biri ölçülmüş olmalı:

  1. Ölçeklenme profili ayrışıyor. Bir parça kaynakları geri kalanından bambaşka tüketiyor: sürekli CPU’yu doyuran bir rapor/PDF motoru, ya da dakikada on binlerce hafif çağrı alan bir bildirim modülü. İkisini tek deploy biriminde tutmak, birini diğerinin kapasite planına mahkûm eder. Bunu metrikle gösterebiliyorsanız ayırmak yatay ölçeklemeden ucuza gelir.

  2. Sahiplik organizasyonel olarak ayrıştı. Ekip sayısı büyüdü ve aynı deploy hattındaki release çakışması artık ölçülebilir bir gecikme. Üç ekip her gün birbirinin deploy’unu beklemeye başladıysa, sınır artık teknik değil organizasyoneldir — Conway yasası faturayı kesiyordur.

  3. Farklı bir runtime gerçekten gerekiyor. Bir iş yükü PHP’de pahalı, Go ya da Rust’ta belirgin biçimde ucuz — ve bu “daha hızlı olur” hipotezi değil, ölçülmüş bir darboğaz. O parçayı ayrı bir serviste farklı bir dille çalıştırmak gerekçeli.

  4. Hata izolasyonu zorunlu ve process içinde garanti edilemiyor. Bir modülün çökmesi gerçekten geri kalan sistemi düşürmemeli ve bunu tek process içinde sağlayamıyorsunuz.

Dördünün ortak noktası: hepsi ölçülür. “Sanırım”, “ileride”, “olabilir” ile başlayan hiçbir cümle bu listede yok. Sinyal bir grafikte görünmüyorsa, henüz sinyal değildir.

Hepsini değil, bir modülü

Sinyal geldiğinde bile doğru hamle “monoliti mikroservislere bölmek” değildir. Doğru hamle, sinyali veren tek modülü servise çıkarmaktır.

Modüler monolitle başladıysanız bu zaten mekanik bir iş: sınır net, public yüzey tek bir sınıf. O modülü strangler kalıbıyla dışarı alırsınız — yeni servis ayağa kalkar, çağrılar kademeli ona yönlenir, eski kod silinir. Geri kalan sistem mutlu mesut monolit olarak kalır.

Big-bang rewrite — her şeyi aynı anda servislere bölmek — bilinen en pahalı ve en sık başarısız olan göç biçimidir. Bir sistemi tek seferde değil, sinyal verdikçe parça parça ayırın.

Monolit ne zaman yanlış cevaba dönüşür?

Monolit, yukarıdaki sinyallerden hiçbiri ölçülmediği sürece doğru cevaptır. Yanlış cevaba dönüştüğü an da bellidir: birden çok ekip aynı deploy biriminde sıkıştığında, bir parçanın kaynak profili sistemin geri kalanını rehin aldığında, ya da bir bileşenin çöküşü düzenli olarak tüm sistemi düşürdüğünde.

O gün geldiğinde mikroservise geçmek bir yenilgi değil — planlanmış bir adımdır. Sınırları baştan çizmiş olmanın bütün değeri de buradadır: geçiş, korkutucu bir göç değil, sırası gelmiş bir karar olur.


Mikroservis bir hedef değil, bir faturadır. O faturayı ödemek için bir sebebiniz olmalı — ve o sebep bir grafikte görünmeli, bir blog yazısında değil.

Sinyali bekleyin; kararı moda değil, ölçüm versin.