İçeriğe geç

Dağıtık Hesaplamanın 8 Yanılgısı

Dağıtık hesaplamanın yanılgıları, ağ üzerinden çalışan sistemlerle yeni tanışan mühendislerin yapmaya meyilli olduğu ve production’da er ya da geç çöken sekiz varsayımdır: ağ güvenilirdir, gecikme sıfırdır, bant genişliği sonsuzdur, ağ güvenlidir, topoloji değişmez, tek bir yönetici vardır, taşıma maliyeti sıfırdır ve ağ homojendir. L. Peter Deutsch bunların yedisini 1994’te Sun Microsystems’te bir araya getirdi; Bill Joy ve Sun’daki bir çalışma arkadaşının daha önce “ağ üzerinde hesaplamanın yanılgıları” olarak dolaştırdığı dört maddeyi temel aldı. Sekizincisini James Gosling 1997 civarında ekledi. Liste bir teori ya da protokol değildir. Tek bir process içindeki fonksiyon çağrısının kimse istemeden sağladığı ve ağ üzerinden yapılan bir çağrının sessizce geri aldığı özelliklerin kontrol listesidir.

Bu bakış, yanılgıların otuz yıl sonra hâlâ neden tekrar ortaya çıktığını açıklar. Kimse ağın kusursuz olduğuna inanarak kod yazmaz; varsayımlar, uzak bir çağrıyı yerel gibi gösteren soyutlamalar üzerinden içeri sızar: yerel bir metodun imzasını taşıyan, otomatik üretilmiş bir client stub; bir alan okunduğunda ilişkili satırı ağ üzerinden getiren bir object-relational mapper (ORM); varsayılan timeout’u sonsuz olan bir HTTP client; bir DNS yanıtını process’in ömrü boyunca önbellekte tutan bir runtime. Her soyutlama ağı gizlediği için faydalıdır ve aynı nedenle tehlikelidir.

Asıl denge, şeffaflık ile kontrol arasındadır. Ağı gizlemek, dağıtık kodu yerel kod kadar kolay yazılır kılar; ancak hata altındaki davranışı belirleyen kararları da gizler: ne kadar beklenecek, yeniden denenecek mi, ne kadar veri taşınacak, kime güvenilecek ve çağrılan taraf şu an nerede yaşıyor. Ağı görünür kılmak ise her çağrı noktasını bu kararları vermeye zorlar; bu da geliştirme süresine mal olur ve arayüzleri daha kaba taneli hâle getirir. Bu sayfadaki her düzeltme, bu kararlardan birini gizli bir varsayılandan çıkarıp açık ve gözden geçirilmiş bir seçime dönüştürür. Paket kaybı, bant genişliği sınırları ve topoloji değişikliğinin ayrıntılı ele alınışı Ağ Güvenilirliği, Bant Genişliği Sınırları ve Topoloji Değişiklikleri sayfasına aittir; bu sayfa varsayımların kendisini ve bunların nasıl hataya dönüştüğünü konu alır.

Yerel Bir Çağrının İstenmeden Verdiği Garantiler

Section titled “Yerel Bir Çağrının İstenmeden Verdiği Garantiler”

Jim Waldo ve Sun’daki çalışma arkadaşları temel argümanı yine 1994’te A Note on Distributed Computing makalesinde ortaya koydu: yerel ve uzak hesaplama gecikme, bellek erişimi, kısmi hata (partial failure) ve eşzamanlılık bakımından farklıdır ve bu farkları gizleyen bir arayüz iki ortamda birden doğru olamaz. Yerel bir çağrının, hiç bozulmadığı için kimsenin yazıya dökmediği bir sözleşmesi vardır: çağrılan taraf tam olarak bir kez çalışır ya da process onunla birlikte ölür, çağrı nanosaniyeler sürer, büyük bir argüman pointer olarak geçer, aradaki hiç kimse onu okuyamaz ya da değiştiremez, çağrılan tarafın adresi link aşamasında sabitlenir, yolun tamamı tek bir ekibe aittir, çağrının hiçbir maliyeti yoktur ve iki taraf da aynı bellek düzenini ve her tipin aynı sürümünü paylaşır.

Uzak bir çağrı bu garantilerin her birini, kendi başına davranabilen bir bileşenle değiştirir. Tek bir isteği çağıranın kodundan çağrılan tarafa kadar izlemek, her yanılgının nerede yaşadığını gösterir.

Callee host

Network owned by others

Caller host

serialize: fallacies 7, 8

capacity: fallacy 3

other owners: fallacy 6

loss, delay: fallacies 1, 2

stale answers: fallacy 5

reads or alters: fallacy 4

Caller code

Client stub and pool

Kernel TCP stack

NIC and host limits

Switches and links

Firewall or NAT

Load balancer

Server stub

Callee code

DNS and discovery

Anyone on the path

Callee host

Network owned by others

Caller host

serialize: fallacies 7, 8

capacity: fallacy 3

other owners: fallacy 6

loss, delay: fallacies 1, 2

stale answers: fallacy 5

reads or alters: fallacy 4

Caller code

Client stub and pool

Kernel TCP stack

NIC and host limits

Switches and links

Firewall or NAT

Load balancer

Server stub

Callee code

DNS and discovery

Anyone on the path

Tek bir uzak çağrının izlediği yol ve her yanılgının bu yola girdiği yer. Yerel bir çağrıda yalnızca ilk ve son kutu bulunur.

Caller code ile Callee code arasındaki her kutunun kendi durumu, kapasitesi, sahibi ve hata davranışı vardır. Firewall’un bağlantı tablosunun idle timeout’unu başka bir ekip belirler, load balancer’ın yapılandırması kendi takvimiyle yayına çıkar, DNS ise önbelleğe alındığı anda doğru olan bir yanıt döndürür. NIC (network interface card) ve altındaki hypervisor, Maliyet-Performans Eğrileri ve Donanım Sınırları sayfasında anlatıldığı gibi bant genişliği ve paket hızı kotaları uygular. Bunların hiçbiri durumunu çağırana bildirmez; çağıran onları yalnızca belirtiler üzerinden öğrenir: yavaş bir çağrı, bir reset, bir askıda kalma. Her yanılgı, bu kutulardan birinin orada olmadığını varsaymaktır.

Üçüncü Sonuç: Kısmi Hata Neden Her Şeyi Değiştirir

Section titled “Üçüncü Sonuç: Kısmi Hata Neden Her Şeyi Değiştirir”

İlk iki yanılgı en büyük hasarı verir, çünkü birlikte yerel kodun dayandığı bir özelliği ortadan kaldırırlar: her çağrı biter ve sonucu bilinir. Uzak bir çağrının iki değil üç sonucu vardır. Başarılı olabilir, çağıranın aldığı bir hatayla başarısız olabilir ya da çağıranın, çağrılan tarafın işlemi yürütüp yürütmediğini bilmediği bir durumda sona erebilir.

response received

error response or connection refused

timeout or reset after send

retry, callee deduplicates

retry, callee not idempotent

no retry, operation never ran

retry is safe

Sent

Succeeded

FailedKnown

Unknown

Duplicated

Lost

response received

error response or connection refused

timeout or reset after send

retry, callee deduplicates

retry, callee not idempotent

no retry, operation never ran

retry is safe

Sent

Succeeded

FailedKnown

Unknown

Duplicated

Lost

Tek bir uzak çağrının çağıranın gözünden olası sonuçları. Unknown durumunun yerel çağrıda karşılığı yoktur.

Timeout, isteğin başarısız olduğu anlamına gelmez; çağıranın beklemeyi bıraktığı anlamına gelir. İstek gidişte kaybolmuş, yürütülüp yanıtı dönüşte kaybolmuş ya da çağıran vazgeçtikten sonra onu yürütecek bir sunucuda hâlâ kuyrukta bekliyor olabilir. Çağıran bu durumları birbirinden ayırt edemez; diyagramda üç çıkışı olan tek bir Unknown durumunun bulunmasının nedeni budur. İstek yazılmadan önce reddedilen bir bağlantı bilinen bir hatadır ve yeniden denenmesi güvenlidir; istek yazıldıktan sonra gelen bir reset ise bilinmeyen sonuçtur.

İnsan müdahalesi olmadan Unknown durumundan çıkmanın tek genel yolu, yeniden denemeleri zararsız kılmaktır. Çağrılan taraf idempotent ise ya da her istek kimliğini kaydedip tekrarlanan bir kimlik için saklanan sonucu döndürüyorsa, yanıt gelene kadar yeniden denemek at-least-once teslimatı exactly-once etkiye dönüştürür. Ağ isteği istediği kadar teslim edebilir; ama durum yalnızca ilk teslimatta değişir ve sonraki her teslimat aynı yanıtı alır. Bu yalnızca tekrar engelleme (deduplication) kaydı, client’ın yeniden deneyebileceği en uzun pencereden daha uzun yaşıyorsa ve kimlik her denemede değil, çağıran tarafından bir kez üretiliyorsa geçerlidir. Mekanizmanın ayrıntıları Idempotency ve Güvenli HTTP Metotları ile En Az Bir Kez, En Fazla Bir Kez ve Tam Olarak Bir Kez Semantiği sayfalarında anlatılır. Mesajları sınırsız geciktirebilen bir ağın biçimsel ele alınışı Ağ Modelleri sayfasındadır.

Yanılgılar, yerel çağrının hangi garantisini ortadan kaldırdıklarına göre gruplanır ve her grubun tek bir düzeltme türü vardır.

“Ağ güvenilirdir” varsayımı, timeout’u ve Unknown sonucu için planı olmayan her çağrıda koda girer. Pek çok client varsayılan olarak sonsuza kadar bekler: örneğin gRPC, varsayılan olarak deadline koymadığını belgeler; yani çağıran bir deadline belirlemedikçe bir RPC (remote procedure call) süresiz bekler. Takılan bir çağrılan taraf ya da yol, çağıranın thread’ini veya bağlantısını tutar ve tek bir yavaş bağımlılık bütün bir pool’u tüketebilir. Düzeltme, her çağrıya çağıranın gecikme bütçesinden türetilmiş ve aşağı akışa yayılan bir deadline koymaktır; böylece çağrılan taraf, çağıranın çoktan vazgeçtiği işi durdurur. Bu konu Zaman Aşımları sayfasında ele alınır. Bailis ve Kingsbury’nin The Network is Reliable derlemesi, partition’ların, bağlantı arızalarının ve cihaz hatalarının tek bir veri merkezi dahil her ortamda yaşandığına dair yayımlanmış kanıtları bir araya getirir.

“Gecikme sıfırdır” varsayımı, çağrılar bedavaymış gibi tasarlanmış arayüzler üzerinden girer: her eleman için uzak bir metot çağıran bir döngü, lazy-load edilen bir ORM ilişkisi, bir isteği doğrulamak için sırayla üç başka servisi çağıran bir servis. Gecikmenin fiziksel bir tabanı vardır. Işık optik fiberde saniyede yaklaşık 200.000 km, yani kilometre başına yaklaşık 5 mikrosaniye ilerler; dolayısıyla 100 km fiber, hiçbir switch, kuyruk ya da yazılım devreye girmeden yaklaşık bir milisaniyelik round-trip time (RTT) ekler. Tek bir veri merkezi içinde bir RPC, serileştirme, kernel işi ve kuyruklama ile birlikte tipik olarak yüzlerce mikrosaniye sürer; kıtalar arasında ise onlarca ila yüzlerce milisaniye. Geveze (chatty) bir arayüz bu maliyeti sıralı çağrı sayısıyla, bir fan-out ise yavaş bir çağrıya denk gelme olasılığını çarpar; Dean ve Barroso’nun tarif ettiği bu kuyruk (tail) etkisi Ölçekleme İkilemi sayfasında sayısal olarak incelenir.

“Bant genişliği sonsuzdur” varsayımı, bir tasarım birkaç alana ihtiyaç duyduğu yerde bütün nesneleri taşıdığında, delta’lar yeterken tüm durumu replike ettiğinde ya da kullanıcı trafiğine hizmet eden bağlantılar üzerinden bir backfill çalıştırdığında girer. Bant genişliği host, akış (flow), bağlantı ve sağlayıcı kotası başına sınırlıdır ve bağlayıcı olan sınır çoğu zaman NIC’in etiket hızı değildir. Bir bağlantı doyduğunda paketler switch buffer’larında kuyruğa girer ve herhangi bir kayıp görünmeden önce gecikme artar; bu yüzden bir bant genişliği problemi genellikle önce bir gecikme problemi olarak ortaya çıkar. Akış başına sınırlar, incast ve aşırı abone edilmiş (oversubscribed) topolojiler Ağ Güvenilirliği, Bant Genişliği Sınırları ve Topoloji Değişiklikleri sayfasında ele alınır.

“Taşıma maliyeti sıfırdır” varsayımının iki boyutu vardır. Birincisi hesaplamadır: her çağrı payload’unu serileştirir, şifreler, kernel üzerinden kopyalar, şifresini çözer ve deserileştirir; küçük çağrılar etrafında kurulmuş servislerde bu, CPU’nun büyük bir kısmını tüketebilir. İkincisi paradır: bulut sağlayıcıları availability zone’lar arası, bölgeler arası ve internete giden veri transferini faturalar; yerleşime bakılmadan kurulmuş bir çağrı grafiğinin, taşıdığı byte’larla orantılı, tekrarlayan bir maliyeti olur. Bu maliyetlerin hiçbiri kutu ve oklardan oluşan bir diyagramda görünmez. Ölçülmeleri ve dağıtılmaları Maliyet Optimizasyonu sayfasında anlatılır.

“Ağ güvenlidir” varsayımı, bir servisin bir isteğe nereden geldiği için güvendiği her yerde girer: “firewall’un arkasında” olduğu için kimlik doğrulaması olmayan dahili bir endpoint, bağlantılar özel olduğu için servisler arasında şifresiz trafik, kimliğin yerine geçen bir IP adresi allowlist’i. Bu durumda çevre (perimeter) içindeki ele geçirilmiş herhangi bir iş yükü, meşru bir iş yüküyle aynı erişime sahip olur; container platformları IP adreslerini saniyeler içinde yeniden atadığı için de bir adres uzun süre hiçbir şeyi tanımlamaz. Düzeltme, her çağrıda iş yükü kimliğine (workload identity) göre kimlik doğrulama ve yetkilendirme yapmak ve aktarım sırasında şifrelemektir; bu, mTLS (mutual Transport Layer Security) ve Asla Güvenme, Her Zaman Doğrula sayfalarının konusudur.

“Topoloji değişmez” varsayımı, bir adresi bir kez çözümleyip saklayan her şey üzerinden girer. Java’da JDK, başarılı DNS sorgularını varsayılan olarak 30 saniye, bir security manager kuruluysa ise networkaddress.cache.ttl security property’si ayarlanmadıkça sonsuza kadar önbellekte tutar; bu yüzden uzun süre çalışan bir process, saatler önce devreden çıkarılmış bir adresi çağırmaya devam edebilir. Uzun ömürlü HTTP/2 ve gRPC bağlantıları bir katman yukarıda aynı etkiyi yaratır: üç backend’e bağlanmış bir client, servis 10 instance’a ölçeklendikten sonra da yalnızca o üçünü kullanmaya devam eder. Düzeltmeler; kısa ve gerçekten uygulanan DNS TTL’leri (time to live), üyelik değişikliklerini push eden ve DNS Tabanlı Keşif sayfasında anlatılan keşif mekanizmaları ve client’ları periyodik olarak yeniden bağlanıp yük dengelemeye zorlayan, sunucu tarafında bir maksimum bağlantı ömrüdür.

“Tek bir yönetici vardır” varsayımı, bir tasarım başkasına ait ayarlara bağımlı olduğunda girer. Firewall’un idle timeout’u, NAT (network address translation) gateway’in bağlantı tablosu, sağlayıcının bakım penceresi, platform ekibinin sertifika rotasyonu ve başka bir ekibin security group değişiklikleri; hepsi bir çağrının davranışını kodunda hiçbir değişiklik olmadan değiştirir. Örneğin AWS, NAT gateway’inin 350 saniye boşta kalan bağlantıları zaman aşımına uğrattığını ve böyle bir bağlantıdan gelen sonraki pakete TCP reset ile yanıt verdiğini belgeler; bağlantıları bundan daha uzun boşta tutan bir pool, her sessiz dönemin ardından reset görür. Düzeltme, bu tür sınırları keşfetmek, uygulamayı bu sınırların içinde kalacak şekilde yapılandırmak ve diğer ekiplerin yapılandırmasını kendi değişiklik süreci olan harici bir bağımlılık olarak ele almaktır.

“Ağ homojendir” varsayımı, bir tasarım her hop’un, client’ın ve karşı tarafın aynı şekilde davrandığını varsaydığında girer. Paket düzeyinde, maximum transmission unit (MTU) yoldan yola değişir: VXLAN encapsulation kullanan bir overlay ağı 50 byte başlık ekler, dolayısıyla bir host’tan çıkan 1.500 byte’lık bir paket overlay’e sığmayabilir. Path MTU discovery, bir paketin fazla büyük olduğunu bildiren ICMP (Internet Control Message Protocol) mesajlarına dayanır; bir firewall bunları filtrelediğinde küçük istekler başarılı olurken büyük yanıtlar kaybolur ve bağlantılar yalnızca belirli payload’larda askıda kalır. Uygulama düzeyinde ise karşı taraf, rolling deploy sırasında başka bir şema sürümü, başka bir TLS kütüphanesi ya da farklı varsayılanlara sahip başka bir runtime çalıştırıyor olabilir. Düzeltmeler; Şema Evrimi ve Schema Registry sayfasında anlatılan açık wire-format uyumluluk kuralları ve production’ın gerçekte çalıştırdığı karışıma karşı yapılan testlerdir.

Çalışılmış Örnek: N+1 Döngüsünün Arkasındaki Kataloğu Ayırmak

Section titled “Çalışılmış Örnek: N+1 Döngüsünün Arkasındaki Kataloğu Ayırmak”

Bir monolitin sipariş detay sayfasını, önce siparişi yükleyip ardından 40 satır kaleminin her biri için getProduct(id) ve getInventory(id) çağırarak oluşturduğunu varsayalım. Process içinde 81 çağrı yerel bir önbelleğe gider ve toplamda bir milisaniyenin çok altında kalır; bu yüzden kimse döngüyü fark etmemiştir. Ekip kataloğu ve stok bilgisini, üretilen stub’ları aynı imzaları koruyan servislere ayırır; döngü değişmeden yaşamaya devam eder. Aşağıdaki tüm sayılar örnek amaçlıdır.

Önce gecikme. Tek bir availability zone (AZ) içinde bir RPC uçtan uca 0,5 ms sürüyorsa, 81 sıralı çağrı 40,5 ms sürer. Instance’lar zone’lara dağıtılmışsa ve zone’lar arası bir çağrı 1,5 ms sürüyorsa, sayfa yaklaşık 122 ms’ye çıkar. Sonradan yapılan bir felaket kurtarma (disaster recovery) değişikliği katalog çağrılarını 70 ms uzaktaki başka bir bölgeye yönlendirirse, sayfa 81 kere 70 ms, yani yaklaşık 5,7 saniye sürer. Değişen yalnızca yerleşimdir.

Sonra güvenilirlik. Her çağrı bağımsız olarak %99,95 oranında başarılıysa, sayfanın başarılı olma olasılığı 0.9995^81, yani yaklaşık %96,0’dır: her servis %99,95 hedefini tutturduğu hâlde her yüz sayfa yüklemesinden dördü başarısız olur. Kuyruk daha da kötüdür. Her çağrının p99 gecikmesi 5 ms ise, 81 çağrıdan en az birinin en yavaş %1’lik dilime düşme olasılığı 1 - 0.99^81, yaklaşık %55,7’dir. Sayfa yüklemelerinin yarısından fazlası artık bağımlılıkların medyanında değil, kuyruğunda çalışır.

Per-request risk vs. number of remote calls (illustrative)1510204081160Remote calls per request1009080706050403020100Percent of requests
Per-request risk vs. number of remote calls (illustrative)1510204081160Remote calls per request1009080706050403020100Percent of requests

Üst çizgi: en az bir çağrının kendi p99 değerinden yavaş olma olasılığı. Alt çizgi: her çağrı %99,95 oranında başarılıyken en az bir çağrının başarısız olma olasılığı. Çağrıların bağımsız olduğu varsayılmıştır.

Mühendislik kararlarını değiştiren üst eğridir: her bağımlılığın sahipleri kendi p99 değerini savunur, ama düzinelerce çağrı yapan bir istek, hiçbirinin raporladığından çok daha kötü bir yüzdelik dilim yaşar. Bağımsızlık varsayımı iyimserdir; aşırı yüklenmiş tek bir bağımlılığa yapılan çağrılar birbiriyle ilişkilidir, dolayısıyla olay anlarında eğri daha hızlı yükselir.

Son olarak bant genişliği ve maliyet. Her ürün yanıtı 8 KB JSON ise, sayfa katalog ve stok servislerinden 640 KB taşır. Saniyede 500 sayfa yüklemesinde bu, sürekli olarak 320 MB/s, yani yaklaşık 2,6 Gbps eder. 30 günlük bir ayda bu yaklaşık 829.000 GB’tır. Instance’lar üç zone’a eşit dağıtılmış ve çağrılar zone’a bakılmadan yük dengeleniyorsa, çağrıların yaklaşık üçte ikisi bir zone sınırını geçer. Zone’lar arası transferin her iki tarafında GB başına 0,01 dolar ücret alındığını varsayalım; bu, AWS’nin çoğu bölge için uzun süredir yayımladığı bir fiyattır (güncel fiyatı kontrol edin). 829.000 GB’ın üçte ikisi, GB başına 0,02 dolardan ayda yaklaşık 11.000 dolar eder; üstelik monolitin içinde oluşturulması hiçbir şeye mal olmayan bir sayfa için.

Çözüm ağı değil, arayüzü değiştirmektir. Toplu bir getProducts(ids) metodu ve stok tarafındaki karşılığı sayfayı üç çağrıya indirir; iki toplu çağrı sipariş yüklendikten sonra paralel çalışabildiği için sayfa iki sıralı round trip’e ihtiyaç duyar: zone içinde yaklaşık 1 ms, bölgeler arasında bile yaklaşık 140 ms. Başarı olasılığı 0.9995^3, yaklaşık %99,85’e yükselir. Toplu yanıt ayrıca yalnızca sayfanın gösterdiği 1,5 KB’lık alanları döndürürse, transfer sayfa başına 120 KB’a ve zone’lar arası fatura ayda kabaca 2.000 dolara düşer. Arayüz gecikmenin, bant genişliğinin ve taşımanın bedava olduğunu varsaymayı bıraktığında her sayı iyileşti.

Her düzeltmenin bir bedeli vardır ve aralarından seçim yapmak asıl mühendislik kararıdır.

YanılgıOrtadan kalkan yerel garantiTipik hataYanıtYanıtın bedeli
Ağ güvenilirdirÇağrı, sonucu bilinen bir şekilde biterAskıda kalma, kaybolan ya da çiftlenen yazmalarDeadline’lar, idempotency anahtarları, sınırlı retryHer yazma yolu bir tekrar engelleme kaydı gerektirir
Gecikme sıfırdırÇağrı nanosaniyeler sürerGeveze arayüzler, sıralı fan-outKaba taneli ve toplu API’ler, paralel çağrılarDaha az yeniden kullanılabilir arayüzler, daha büyük payload’lar
Bant genişliği sonsuzdurArgümanlar referansla geçerDoymuş bağlantılar, kuyruklanmadan gelen gecikmeProjeksiyonlar, sıkıştırma, ayrı toplu veri yollarıDaha fazla API yüzeyi, sıkıştırma için CPU
Ağ güvenlidirÇağıranla çağrılan arasında kimse yokturTek bir ele geçirmeden sonra yanal hareketmTLS, çağrı başına yetkilendirmeSertifika yaşam döngüsü, handshake gecikmesi
Topoloji değişmezÇağrılan tarafın adresi link aşamasında sabittirBayat ya da kaldırılmış instance’lara trafikKısa TTL’ler, push tabanlı keşif, maksimum bağlantı ömrüDaha fazla yeniden bağlanma ve sorgu
Tek bir yönetici vardırYolun tamamının tek bir sahibi vardırKod değişmeden davranışın değişmesiAçık sınırlar, yol timeout’larının altında keepaliveEkipler arası koordinasyon ve dokümantasyon
Taşıma maliyeti sıfırdırÇağrılar bedavadırCPU ve transfer faturalarının trafikle büyümesiZone farkındalıklı yönlendirme, birlikte yerleşim, toplu çağrıZone’lar arasında dengesiz yük, yerleşim kısıtları
Ağ homojendirTek bellek düzeni ve tek sürümSürüm uyumsuzluğu, MTU kara delikleriUyumluluk kuralları, contract testleri, MTU kontrolleriDaha yavaş şema evrimi, test matrisi

Bir ekibin seçtiği arayüz stili, ağın ne kadarını görünür kılacağını belirler.

Arayüz stiliAğın görünürlüğüAna riskNe zaman tercih edilmeli
İnce taneli senkron RPCGizli; yerel çağrı gibi görünürN+1 döngüleri, fan-out kuyrukları, zincirleme askıda kalmalarİstek başına az çağrı, gecikmeye toleranslı dahili araçlar
Kaba taneli ya da toplu senkron RPCAPI’nin şeklinde görünürDaha büyük payload’lar, daha geniş sözleşmelerÇok sayıda ilişkili sorgu içeren kullanıcıya dönük yollar
Asenkron mesajlaşmaAçık; çağıran beklemezSıralama, tekrarlar, eventual consistencyKullanıcının isteği içinde tamamlanması gerekmeyen işler
Uzak verinin yerel önbelleği ya da replikasıOkumalar için ortadan kalkarBayatlık ve invalidationBayatlığın tolere edilebildiği, okuma ağırlıklı veriler
Servisleri birleştirmekTamamen ortadan kalkarDaha büyük deploy birimi, ortak yayın temposuBirbiri olmadan işe yarar bir şey yapamayan servisler

Son satır ağırlığı hak eder: neredeyse her istekte birbirini çağıran ve bağımsız deploy edilemeyen iki servis, her yanılgının bedelini öder ve dağıtıklıktan hiçbir şey kazanmaz; bu desen Dağıtık Monolit sayfasında anlatılır. Geveze etkileşimleri tek bir process içinde tutan sınırları seçmek Servis Granülaritesi sayfasının konusudur.

Yanılgıları Kodlayan Varsayılan Değerler

Section titled “Yanılgıları Kodlayan Varsayılan Değerler”

Pek çok yanılgı, kimsenin seçmediği varsayılan değerlerde yaşar. Linux’ta onaylanmamış veri taşıyan bir TCP bağlantısından ancak tcp_retries2 kadar yeniden iletimden sonra vazgeçilir; kernel dokümantasyonu varsayılan 15 değerinin yaklaşık 924,6 saniyelik varsayımsal bir zaman aşımına karşılık geldiğini belirtir. Boştaki bağlantılarda ölü karşı tarafları tespit eden TCP keepalive, uygulama socket üzerinde etkinleştirmedikçe kapalıdır ve varsayılan ilk probe iki saatlik boşluktan sonra gelir; bu, çoğu firewall ve NAT cihazının idle timeout’undan çok daha uzundur.

Terminal window
# Linux defaults on most distributions; inspect before relying on them
sysctl net.ipv4.tcp_retries2 # 15: data on a dead path waits about 15 min
sysctl net.ipv4.tcp_keepalive_time # 7200 s idle before the first keepalive probe
sysctl net.ipv4.tcp_keepalive_intvl # 75 s between probes
sysctl net.ipv4.tcp_keepalive_probes # 9 failed probes before the connection is dropped
# JDK DNS cache: 30 s by default, forever under a security manager.
# Set explicitly in $JAVA_HOME/conf/security/java.security:
# networkaddress.cache.ttl=30

Amaç bu değerleri global olarak değiştirmek değil, onlara güvenmeyi bırakmaktır: client tarafındaki bir deadline çağrıyı saniyelerle sınırlar, yoldaki en kısa ara kutu (middlebox) idle timeout’unun altında tutulan pool idle eviction bayat bağlantıları önler ve keepalive’lar uzun ömürlü boştaki bağlantılar için yedek görevi görür. Bu ayarlar yerindeyken kernel varsayılanları nadiren önemlidir; yokken kesintiyi varsayılanlar tanımlar.

Aşağıdaki her hata, en kolay imzasından tanınır.

Bir Ara Kutunun Arkasındaki Bayat Bağlantılar

Section titled “Bir Ara Kutunun Arkasındaki Bayat Bağlantılar”

Tetikleyici: pool’daki bağlantılar, yoldaki bir firewall ya da NAT cihazının idle timeout’undan daha uzun süre boşta kalır ve cihaz bu bağlantıların durumunu siler. Belirtiler: sessiz bir dönemin ardından gelen ilk istekler başarısız olur ya da askıda kalır, sonra servis toparlanır; bu her gece ya da hafta sonlarından sonra tekrarlanır. Reset gönderen bir cihaz anında connection reset hataları üretir; paketleri sessizce düşüren bir cihaz ise yalnızca client’ın deadline’ıyla ya da deadline yoksa TCP yeniden iletimiyle sınırlanan askıda kalmalara yol açar.

BackendStateful firewallConnection poolApp threadBackendStateful firewallConnection poolApp threadIdle timeout 10 min, state already evictedloop[TCP retransmissions with backoff]No request deadline set: thread blocked for minutesUpstream callers time out, health checks failBorrow connection idle for 20 min1Send request on pooled connection2No state for flow, packet dropped3Retransmit request4Dropped again5Pool and worker threads exhausted by other blocked calls6
BackendStateful firewallConnection poolApp threadBackendStateful firewallConnection poolApp threadIdle timeout 10 min, state already evictedloop[TCP retransmissions with backoff]No request deadline set: thread blocked for minutesUpstream callers time out, health checks failBorrow connection idle for 20 min1Send request on pooled connection2No state for flow, packet dropped3Retransmit request4Dropped again5Pool and worker threads exhausted by other blocked calls6

Paketleri sessizce düşüren bir firewall ve deadline’ı olmayan bir client. Timeout değerleri örnek amaçlıdır.

  1. ve 3. adımlarda bağlantı client’a sağlıklı görünür, çünkü ona aksini söyleyen hiçbir şey olmamıştır. 4. ve 5. adımlar, TCP’nin üstel geri çekilme (exponential backoff) ile yeniden iletim yapmasıdır; uygulama düzeyinde bir deadline olmadığında thread, yeniden iletim bütçesinin tamamını bekler. 6. adım bayat bağlantıları bir kesintiye dönüştürür, çünkü bunlardan birini ödünç alan her istek bir worker’ı bloke eder. Tespit: boşta kalma dönemlerinin sonuyla ilişkili hata ya da gecikme sıçramaları, client loglarındaki connection reset sayıları, yeniden iletim sayaçları (netstat -s, nstat) ve AWS NAT gateway’lerinde IdleTimeoutCount CloudWatch metriği. Önlem: istek deadline’ları, yoldaki en kısa idle timeout’un altında pool idle eviction ve uzun ömürlü bağlantılarda keepalive ya da uygulama düzeyinde ping.

Tetikleyici: bir bağımlılık yavaşlar ve çağrı grafiğinin her katmanı bağımsız olarak timeout’a düşüp yeniden dener. Belirtiler: bağımlılığın sağlığı bozuldukça üzerindeki yük artar; her biri üç deneme yapan üç katman, istek başına 27 denemeye kadar çıkabilir. Tespit: farklı istek başına deneme sayısı, katman bazında retry sayaçları. Önlem: tek bir katmanda, backoff, jitter ve retry bütçesiyle, bir circuit breaker arkasında yeniden denemek. Dinamikler Retry Fırtınaları ve Metastabil Hatalar ile Üstel Geri Çekilme ve Jitter ile Yeniden Deneme sayfalarında ele alınır.

Saniyeler süren bir partition, uzun arızalar için tasarlanmış otomasyonu tetikleyebilir. 21 Ekim 2018’de optik ekipman bakımı, GitHub’ın ABD Doğu Yakası ağ merkezini birincil Doğu Yakası veri merkezinden 43 saniyeliğine kopardı. Bu pencerede Orchestrator cluster yöneticisi veritabanı primary’lerini Batı Yakası’nda terfi ettirdi; bağlantı geri geldiğinde her site diğerinde olmayan yazmalara sahipti ve GitHub, veri kaybetmeden tutarlılığı geri kazanırken 24 saat 11 dakika boyunca kısıtlı hizmet verdi. Olay GitHub 2018 sayfasında incelenir.

Oct 21 2252 UTCOptical equipmentmaintenance cutsEast Coast hub link43 seconds withoutconnectivityDuring the partitionOrchestratorpromotes WestCoast primariesAfter reconnectWrites now spanboth coasts, failbackunsafeSite degraded toprotect dataFollowing dayRestore andreplication catch-upcomplete24 hours 11 minutesof degraded servicein totalGitHub, October 21-22, 2018 (from the public post-incident analysis)
Oct 21 2252 UTCOptical equipmentmaintenance cutsEast Coast hub link43 seconds withoutconnectivityDuring the partitionOrchestratorpromotes WestCoast primariesAfter reconnectWrites now spanboth coasts, failbackunsafeSite degraded toprotect dataFollowing dayRestore andreplication catch-upcomplete24 hours 11 minutesof degraded servicein totalGitHub, October 21-22, 2018 (from the public post-incident analysis)

Olayların sırası GitHub’ın yayımladığı analizden alınmıştır; ilki dışındaki zamanlar kesin değil, özetlenmiştir.

Zaman çizelgesi, güvenilirlik yanılgısının gizlediği asimetriyi gösterir: ağ arızası kısa sürdü ve kendiliğinden düzeldi, kurtarma ise manuel ve uzun oldu; çünkü uzun bir partition için doğru olan bir karar kısa bir partition’a uygulandı. Failover otomasyonunun yalnızca bir partition’ın başladığını fark eden bir dedektöre değil, partition’ların ne kadar sürdüğüne ve hangi yazmaların yolda olabileceğine dair bir modele de ihtiyacı vardır.

4 Ekim 2021’de Facebook’un backbone kapasitesini değerlendirmek için verilen bir bakım komutu tüm veri merkezlerinin bağlantısını kopardı ve denetim aracındaki bir hata komutu durduramadı. Veri merkezlerine ulaşamadıklarında BGP (Border Gateway Protocol) rotalarını geri çekecek şekilde tasarlanmış DNS sunucuları, hâlâ çalışırken internetten kayboldu; kurtarma araçları da aynı ağa bağımlı olduğu için mühendisler sahada çalışmak zorunda kaldı. Vaka Meta 2021 sayfasında incelenir: topolojiyi kendi yöneticileriniz de değiştirir ve onarım yolu, onarılan ağa bağımlı olmamalıdır.

Hataİlgili yanılgılarDışarıdan görünen belirtiTespit sinyaliÖnlem
Askıda kalan çağrılar, pool tükenmesi1, 6Bloke thread’ler, loglanan hata yokThread dump’ları, uçuştaki istek göstergeleriHer çağrıda deadline, bulkhead’ler
Çiftlenen ya da kaybolan yazmalar1Çift ücretlendirme, eksik kayıtlarMutabakat uyuşmazlıklarıIdempotency anahtarları, tekrar engelleme deposu
Kuyruğun belirlediği gecikme2p99, her bağımlılığın p99’unun çok üstündeİstek başına trace span sayısıToplu çağrı, paralel çağrılar, daha az hop
Bayat endpoint’ler5Ölçekleme ya da deploy sonrası hatalarKaldırılmış IP’lere bağlantılarKısa DNS TTL’leri, maksimum bağlantı ömrü
Boştaki bağlantı reset’leri5, 6Sessiz dönemlerden sonra hatalarReset sayıları, NAT idle timeout metrikleriPool eviction, keepalive’lar
Boyuta bağlı askıda kalmalar8Büyük yanıtlar askıda kalır, küçükler başarılı olurTam boyutlu segment’lerin yeniden iletildiğini gösteren paket yakalamalarıDoğru MTU, ICMP fragmentation-needed mesajlarına izin
Transfer maliyetinin büyümesi3, 7Fatura trafikten hızlı büyürServis başına zone’lar arası byteZone farkındalıklı yönlendirme, projeksiyonlar

Dağıtık izleme, ilk üç satır için en faydalı tespit aracıdır; çünkü kodun gizlediği yerde istek başına uzak çağrıların sayısını ve sırasını görünür kılar.

Yanılgıları Tasarım İncelemesinde Kullanmak

Section titled “Yanılgıları Tasarım İncelemesinde Kullanmak”

Liste en iyi bir inceleme prosedürü olarak işe yarar: bir tasarımdaki her ok için, okun sekiz garantiden hangisini varsaydığını sorun. Çağrının bir deadline’ı var mı ve Unknown sonucunda ne oluyor? Kullanıcı isteği başına kaç kez yapılıyor, sırayla mı paralel mi? Hangi sınırdan kaç byte geçiyor ve bedelini kim ödüyor? İki taraf birbirinin kimliğini nasıl doğruluyor? Çağıran, çağrılan tarafın taşındığını nasıl öğreniyor? Yolda kimin yapılandırması var? İki taraf farklı sürümler çalıştırdığında ne oluyor? Yanıtı olmayan bir ok, trafiği bekleyen bir hatadır.

İncelemeyi bir çağrı process sınırını her geçtiğinde, özellikle de ayrıştırma (decomposition) sırasında process içi bir çağrı bu sınırın arkasına taşındığında uygulayın; çünkü yerel çağrı varsayımları tam orada değişmeden hayatta kalır. DNS, keşif, yapılandırma servisleri ve sertifika otoriteleri de uzak çağrılardır ve çoğu zaman başlangıç yolundadır. Yanıtlara güvenmek yerine onları gecikme, kayıp ve partition’ların hata enjeksiyonu ile test edin.

Yanılgıları dağıtıklığa karşı bir argüman olarak kullanmayın; bilinçli olarak ödenecek maliyetleri tarif ederler ve kararın kendisi Ölçekleme İkilemi sayfasındaki gerekçelere dayanır. Her düzeltmeyi her çağrıya uygulamayın: tek bir zone içindeki gecelik bir batch işinin zone farkındalıklı yönlendirmeye ihtiyacı yoktur, kritik olmayan dahili bir okuma da bir timeout ile ve retry olmadan yaşayabilir. Listeyi bir hata modeli yerine koymayın; tehlikeleri adlandırır ama bir bileşenin hangi hatalara dayanması gerektiğini söylemez, bu Hata Modelleri sayfasının işidir. Ve güvenilirlik yanılgısını, bir partition sırasında hangi tutarlılık garantisinin korunabileceğine dair kesin bir önerme olan CAP Teoremi ile karıştırmayın; yanılgı, partition için hiç plan yapmamak gibi daha geniş bir hatadır.