Large-scale sistemlərin building block-ları
Large-scale sistemlərin building block-ları
System design diaqramlarının çoxu eyni görünür: load balancer, API gateway, cache, queue, CDN. Bu oxşarlıq təsadüfi deyil, çünki həmin komponentlər böyük sistemlərin təkrarlanan problemlərini həll edir. Amma diaqramı başqasından köçürmək asandır, hər qutunun orada nə üçün olduğunu demək isə çətin.
Bu fəsil hər komponentə ch02-dəki gözlüklə baxır: struktur bir quality attribute üçün tactic-dir. Hər biri üçün dörd sual verəcəyik: nəyi həll edir, hansı attribute-ə xidmət edir, hansı riski gətirir və nə vaxt lazım deyil.
Request-in yolu
Tipik bir web request-i təxminən belə yol keçir:
- Client domain adını DNS vasitəsilə IP ünvanına çevirir.
- Statik content və cache-lənə bilən cavablar CDN-in edge server-indən gəlir.
- Qalan request-lər load balancer-ə çatır və o, request-i sağlam instance-lardan birinə yönləndirir.
- API gateway authentication, rate limiting və routing edir.
- Service biznes məntiqini icra edir.
- Service əvvəlcə cache-ə, cache-də olmayanı isə database-dən oxuyur.
- Dərhal cavab tələb etməyən iş (email, hesabat, indexing) message broker-ə yazılır və arxa planda emal olunur.
Bu siyahıdakı hər addım bir hop-dur. Hər hop latency əlavə edir, ayrıca xarab ola bilir və kimsə onu operate etməlidir. Ona görə də hər addım öz yerini qazanmalıdır.
Load balancer
Load balancer traffic-i bir neçə instance arasında paylayır. O, iki attribute-ə xidmət edir: scalability, çünki yeni instance əlavə etməklə horizontal böyümək mümkün olur, və availability, çünki xarab instance traffic almağı dayandırır.
İki səviyyədə işləyə bilər. L4 load balancer TCP/UDP səviyyəsində işləyir: yalnız IP və port görür, çox sürətlidir və protokoldan asılı deyil. L7 load balancer HTTP-ni anlayır: path-ə, header-ə və ya cookie-yə görə routing edə bilir, amma bunun üçün request-i parse etməli olur və bu da baha başa gəlir.
Paylama alqoritmləri:
- Round robin request-ləri növbə ilə paylayır. Request-lər təxminən eyni çəkidə olanda yaxşı işləyir.
- Least connections request-i ən az aktiv connection-u olan instance-a göndərir. Request-lərin müddəti çox fərqlənəndə daha ədalətlidir.
- Consistent hashing eyni key-i həmişə eyni node-a göndərir və node dəyişəndə key-lərin yalnız kiçik hissəsini yerindən tərpədir. Cache və sharding üçün vacibdir, ona data at scale fəslində ətraflı baxırıq.
Load balancer-in instance-ın sağlam olub-olmadığını bilməsi health check-lər vasitəsilə olur. Sadə "port açıqdır" yoxlaması azdır, çünki process işləyə, amma database-ə qoşula bilməyə bilər. Əks istiqamətdə də risk var: health check bütün dependency-ləri yoxlasa, ortaq database-in qısa müddətli problemi bütün instance-ları eyni anda "xəstə" elan edər və load balancer traffic göndərəcək yer tapmaz.
Session affinity (sticky session) eyni istifadəçini həmişə eyni instance-a yönləndirir. Session state yaddaşdadırsa, bu, onu "işlək" saxlamağın yoludur. Amma instance yıxılanda session-lar itir və yük qeyri-bərabər paylanır. Daha yaxşı həll ch07-dəki tactic-dir: service-ləri stateless etmək, session-u isə distributed cache-ə və ya token-ə çıxarmaq. Bundan sonra sticky session-a ehtiyac qalmır.
Və nəhayət: load balancer-in özü də single point of failure ola bilər. Managed cloud load balancer-lər bunu adətən özü həll edir. Öz load balancer-inizi qurursunuzsa, ch07-dəki redundancy və failover qaydaları ona da aiddir.
API gateway
API gateway client-lər ilə service-lər arasında tək giriş nöqtəsidir. Tipik vəzifələri: routing, authentication, rate limiting, TLS termination, request aggregation (bir client request-i üçün bir neçə service-i çağırıb cavabları birləşdirmək). Xidmət etdiyi attribute-lər security və maintainability-dir: cross-cutting məsələlər hər service-də ayrıca deyil, bir yerdə həll olunur.
Reverse proxy ilə fərqi nədədir? Reverse proxy (məsələn, nginx) request-i backend-ə ötürür, TLS-i bitirir, bəzən cache edir. API gateway bunun üzərində API səviyyəsində anlayış əlavə edir: API key-lər, quota-lar, versiyalar, consumer-lər. Hər API gateway reverse proxy-dir, amma hər reverse proxy API gateway deyil.
Əsas risk gateway-in tədricən yeni monolitə çevrilməsidir. Əvvəlcə bir az transformation əlavə olunur, sonra "sadəcə bu if-i", sonra da bütöv biznes qaydası. Nəticədə hər dəyişiklik gateway komandasından keçir və ch04-dəki boundary-lər pozulur. Qayda sadədir: gateway request-in necə gəldiyini bilir, nə demək olduğunu yox.
Fərqli client-lərin (mobile, web) ehtiyacları çox fərqlənəndə BFF (backend for frontend) istifadə olunur: hər client növü üçün ayrıca, nazik gateway, ona sahib olan da həmin frontend komandasıdır. Bu, ümumi gateway-in hamı üçün kompromisə çevrilməsinin qarşısını alır.
Bir monolit və bir client-iniz varsa, API gateway çox güman ki, lazım deyil. Load balancer və framework middleware-i kifayətdir.
Caching
Caching bahalı nəticəni ucuz yerdə saxlamaqdır. O, performance (latency) və scalability üçün tactic-dir: hər cache hit backend-dən götürülmüş bir yükdür.
Cache bir neçə səviyyədə yaşaya bilər: client-də (browser, mobile app), CDN-də, service-in öz yaddaşında (in-memory) və ayrıca distributed cache-də (Redis, Memcached). In-memory cache ən sürətlisidir, amma hər instance-ın öz nüsxəsi olur və onlar bir-birindən fərqlənə bilər.
Əsas strategiyalar:
- Cache-aside: application əvvəlcə cache-ə baxır, miss olanda database-dən oxuyub cache-ə yazır. Ən geniş yayılmış və ən sadə strategiyadır.
- Read-through: eyni məntiq, amma miss-i application deyil, cache layer-in özü idarə edir.
- Write-through: yazı eyni anda cache-ə və database-ə gedir. Cache təzə qalır, amma hər yazı yavaşlayır.
- Write-behind: yazı əvvəlcə cache-ə gedir, database-ə isə sonra asynchronous yazılır. Çox sürətlidir, amma cache node-u yıxılarsa, data itə bilər.
Phil Karlton-a aid edilən məşhur zarafata görə, computer science-də iki çətin şey var: cache invalidation və adlandırma. Ən sadə yol TTL-dir: data müəyyən müddətdən sonra köhnəlmiş sayılır. TTL qısa olanda hit ratio düşür, uzun olanda istifadəçi köhnə data görür. Event-lə invalidation daha dəqiqdir, amma ch05-dəki outbox problemi burada da yaranır: database yeniləndi, invalidation mesajı isə itdi.
Ən təhlükəli problem cache stampede-dir (thundering herd). Populyar key-in TTL-i bitir və min request eyni anda miss alıb database-ə hücum edir. Bunun qarşısını almağın yolları: key başına lock (yalnız bir request yenidən hesablayır), TTL-lərə təsadüfi jitter əlavə etmək və ya dəyəri vaxtı bitməmişdən əvvəl arxa planda yeniləmək.
Cache-in qiyməti consistency-dir. Cache əlavə etdiyiniz andan sisteminiz eventual consistency ilə yaşayır. Bunu bilərəkdən qəbul etmək lazımdır. Hit ratio-nu da mütləq ölçün: 30% hit ratio olan cache tez-tez yalnız mürəkkəblik və əlavə hop gətirir. Data hər dəfə fərqlidirsə və ya database onsuz da yükü rahat daşıyırsa, cache lazım deyil.
CDN
CDN content-i istifadəçiyə coğrafi cəhətdən yaxın edge server-lərdə saxlayan paylanmış cache şəbəkəsidir. O, latency-yə (Bakıdakı istifadəçi Frankfurt-dakı origin-ə deyil, yaxın edge-ə gedir) və origin-in scalability-sinə xidmət edir. Yaxşı konfiqurasiya olunmuş CDN statik traffic-in 90%-dən çoxunu origin-ə çatmadan cavablandırır.
Statik content (şəkil, JS, CSS, video) CDN üçün ideal namizəddir. Dinamik content də mümkündür: qısa TTL ilə cache-lənən API cavabları, edge-də TLS termination və hətta edge-də işləyən kod. Amma istifadəçiyə xas cavabları cache-ləmək təhlükəlidir. Səhv konfiqurasiya olunmuş cache key bir istifadəçinin səhifəsini başqasına göstərə bilər.
Burada da invalidation əsas problemdir. Purge API var, amma bütün edge-lərə yayılması vaxt aparır. Daha etibarlı yol versioned URL-lərdir: app.3f9a2c.js kimi fayl adları dəyişmir, yeni versiya yeni URL deməkdir. Belə faylları çox uzun TTL ilə cache-ləmək olar və purge-a ehtiyac qalmır.
İstifadəçiləriniz bir regiondadırsa və statik content azdırsa, CDN-in faydası kiçikdir. Amma qiyməti də aşağıdır, ona görə statik asset-lər üçün CDN nadir hallarda pis qərar olur.
Message broker
Message broker producer ilə consumer arasında asynchronous vasitəçidir. Ch02-dəki ifadə ilə: queue producer-ləri consumer failure-dan ayırmaq üçün tactic-dir. Xidmət etdiyi attribute-lər availability (consumer yıxılanda producer işləməyə davam edir) və scalability-dir (load leveling: qəfil traffic artımı queue-da yığılır və consumer-lər onu öz tempində emal edir).
İki əsas model var:
- Queue: hər mesajı bir consumer emal edir. İş paylamaq üçündür, məsələn email göndərmək və ya şəkil emal etmək.
- Pub/sub: hər mesaj bütün subscriber-lərə çatır. Bir event bir neçə müstəqil reaksiya doğuranda istifadə olunur: "sifariş yaradıldı" event-i həm anbara, həm analitikaya, həm də bildiriş service-inə lazımdır.
Ch03-dəki event-driven architecture bu building block üzərində qurulur. Pub/sub-ın ən böyük faydası decoupling-dir: yeni subscriber əlavə etmək üçün producer-ə toxunmaq lazım deyil.
Risk tərəfi az deyil. Broker-lər adətən at-least-once delivery verir, yəni eyni mesaj iki dəfə gələ bilər, və consumer idempotent olmalıdır. Request-in yolu asynchronous addımlara bölünür və debug çətinləşir. Queue-nun dərinliyi yeni bir metric-ə çevrilir: consumer-lər geri qalanda problem dərhal görünmür, saatlar sonra üzə çıxır. Uğursuz mesajlarla nə etməli olduğunuza reliability pattern-ləri fəslində qayıdacağıq.
İstifadəçi nəticəni dərhal görməlidirsə (məsələn, ödəniş təsdiqi), asynchronous emal UX-i pisləşdirir. Yük də azdırsa və hər iki tərəf eyni deploy vahidindədirsə, sadə funksiya çağırışı daha yaxşıdır.
Komponenti nə vaxt əlavə etməməli
Hər komponentin öz qiyməti var. Ch07-dəki riyaziyyatı xatırlayın: critical path-dəki komponentlərin availability-ləri bir-birinə vurulur. 99.95%-lik beş ardıcıl hop artıq 99.75% deməkdir. Hər hop həm də network round-trip, əlavə config, əlavə dashboard və gecə saat üçdə oyanmaq üçün əlavə səbəbdir.
| Komponent | Xidmət etdiyi attribute | Gətirdiyi risk | Nə vaxt lazım deyil |
|---|---|---|---|
| Load balancer | Scalability, availability | Özü SPOF ola bilər; səhv health check bütün instance-ları çıxarır | Tək instance və downtime-a dözümlü daxili alət |
| API gateway | Security, maintainability | Yeni monolit, biznes məntiqinin toplandığı yer, əlavə hop | Bir client və bir backend |
| Cache | Performance, scalability | Köhnə data, stampede, invalidation bug-ları | Aşağı hit ratio; database yükü rahat daşıyır |
| CDN | Latency, origin scalability | Səhv cache key ilə data sızması; purge gecikməsi | Bir region, az statik content |
| Message broker | Availability, scalability (decoupling) | Duplicate-lər, gizli lag, çətin debug | İstifadəçi nəticəni dərhal gözləyir; yük azdır |
Cədvəlin sağ sütunu ən çox unudulandır. Komponenti problem ortaya çıxanda əlavə edin, "gələcəkdə lazım olacaq" deyə yox. Load balancer-i sonradan əlavə etmək bir günlük işdir. Lazımsız message broker-i çıxarmaq isə aylar çəkə bilər.
Hazırkı sisteminiz haqqında suallar
- Ən vacib request-inizin yolunu client-dən database-ə qədər yazın. Hər hop hansı quality attribute-ə xidmət edir? Cavabı olmayan hop varmı?
- Cache-lərinizin hit ratio-sunu bilirsinizmi? Populyar key-in TTL-i bitəndə database-ə nə olur?
- API gateway-inizdə və ya proxy config-inizdə biznes qaydası varmı? Onun sahibi kimdir?
- Load balancer-inizin health check-i nəyi yoxlayır? Ortaq dependency-nin qısa problemi bütün instance-ları eyni anda traffic-dən çıxara bilərmi?