Replication
Replication
Eyni data-nın bir neçə maşında surətini saxlamaq üç səbəbdən lazımdır: istifadəçiyə coğrafi cəhətdən yaxın olmaq və latency-ni azaltmaq, bir node çökəndə sistemin işləməyə davam etməsi, yəni availability, və oxuma yükünü bir neçə maşına yaymaq, yəni read throughput. Data dəyişməsəydi, replication trivial olardı: bir dəfə kopyalayıb unutmaq olardı. Bütün çətinlik dəyişikliklərdədir.
Təsəvvür edin ki, istifadəçi profilini yeniləyir və bir saniyə sonra səhifəni yeniləyəndə köhnə dəyəri görür. Yaxud iki data mərkəzi eyni sənədi fərqli cür dəyişib və hansının düz olduğunu heç kim demir. Bu fəsil dəyişiklikləri replica-lar arasında yaymağın üç əsas üsulunu, hər birinin zəif nöqtələrini və onlarla yaşamaq üçün qaydaları izah edir.
Leader-based replication
Ən geniş yayılan model leader-based və ya master-slave replication-dır. Replica-lardan biri leader seçilir; bütün yazmalar yalnız ona gedir. Leader dəyişikliyi lokal storage-ə yazır və replication log vasitəsilə follower-lərə göndərir. Follower-lər log-u eyni sırada tətbiq edir. Oxumalar istənilən replica-dan mümkündür, yazmalar isə yalnız leader-dən.
Synchronous və asynchronous
Leader yazmanı client-ə təsdiqləməzdən əvvəl follower-i gözləyirsə, replication synchronous-dur: follower-də data leader ilə eynidir, amma follower cavab verməyəndə yazma bloklanır. Hamısını synchronous etmək praktiki deyil, çünki bir node-un gecikməsi bütün sistemi dayandırır.
Gözləmirsə, asynchronous-dur: yazma sürətlidir və follower-in vəziyyəti leader-ə təsir etmir, amma leader çöksə, hələ replicate olunmamış yazmalar itir. Client "uğurlu" cavabı alıb, data isə yoxdur. Bu, durability-nin zəifləməsidir.
Praktik kompromis semi-synchronous-dur: bir follower synchronous, qalanları asynchronous. Synchronous follower yavaşlayanda asynchronous follower-lərdən biri onun yerinə keçir. Nəticə: ən azı iki node-da data-nın ən son surəti var.
Yeni follower qurmaq
Faylları sadəcə kopyalamaq olmaz, çünki data kopyalama zamanı dəyişir. Əvəzinə leader-in müəyyən anda snapshot-ı yeni node-a kopyalanır, sonra follower snapshot-dan sonrakı dəyişiklikləri replication log-dan tələb edir. Bunun üçün snapshot-ın log-dakı dəqiq mövqeyi lazımdır: PostgreSQL-də log sequence number, MySQL-də binlog coordinates.
Follower çökəndə
Follower leader-dən aldığı son transaction-ı diskində bilir və yenidən qalxanda həmin nöqtədən sonrakı dəyişiklikləri istəyir. Bu, catch-up recovery-dir.
Leader çökəndə: failover
Leader düşəndə follower-lərdən biri yeni leader olmalı, client-lər yazmaları ona yönləndirməli, qalan follower-lər isə onu izləməlidir. Bu proses failover adlanır və üç addımdan ibarətdir: çökməni aşkar etmək, adətən timeout ilə; yeni leader seçmək, ən son data-ya malik olan replica-nı; sistemi yenidən konfiqurasiya etmək. Köhnə leader qayıdanda follower olduğunu qəbul etməlidir.
Hər addımda nəsə səhv gedə bilər:
- Asynchronous replication-da yazmalar itir. Yeni leader köhnə leader-in bütün yazmalarını almayıbsa, həmin yazmalar adətən atılır. GitHub-ın məşhur insidenti bu qəbildəndir: köhnəlmiş MySQL follower leader olub, auto-increment primary key-ləri artıq istifadə olunmuş dəyərlərdən təkrar verdi. Redis-dəki cache həmin key-ləri köhnə qeydlərə bağlamışdı və bəzi istifadəçilər başqalarının şəxsi data-sını gördülər.
- Split brain. İki node eyni anda özünü leader sayır və hər ikisi yazma qəbul edir. Bəzi sistemlər bir node-u zorla söndürür, amma mexanizm diqqətsiz qurulsa, hər ikisi sönə bilər.
- Timeout seçimi. Qısa timeout lazımsız failover-ə səbəb olur, məsələn yük zamanı gecikən leader sağlam ola-ola əvəz edilir, bu da yükü daha da artırır. Uzun timeout isə bərpanı gecikdirir.
Bu səbəbdən bir çox operations komandası failover-i əl ilə edir. Fəsil 8 və 9 bu problemlərin kökünə, yəni distributed sistemlərdə qeyri-müəyyənliyə qayıdacaq.
Replication log-un formaları
Leader dəyişikliyi follower-ə hansı formada göndərir? Dörd yanaşma var:
| Üsul | Nə göndərilir | Problem |
|---|---|---|
| Statement-based | INSERT, UPDATE sətirləri olduğu kimi | NOW(), RAND() kimi nondeterministic funksiyalar, auto-increment, trigger-lər fərqli nəticə verir |
| WAL shipping | Fəsil 3-dəki write-ahead log-un byte-ları | Storage engine-in daxili formatına bağlıdır; leader və follower eyni versiyada olmalıdır, zero-downtime upgrade çətinləşir |
| Logical (row-based) | Sətir səviyyəsində dəyişiklik: insert-də bütün sütunlar, update-də key və yeni dəyərlər | Storage engine-dən ayrılıb, fərqli versiyalar və hətta fərqli sistemlər oxuya bilir |
| Trigger-based | Application səviyyəsində trigger ayrı cədvələ yazır, kənar proses oxuyur | Overhead və bug-lara meyl, amma ən çevikdir |
Logical log həm də change data capture üçün təməldir: dəyişikliklər data warehouse-a, cache-ə və search index-ə eyni log ilə axır. Fəsil 11 bunu ətraflı açır.
Replication lag-ın problemləri
Asynchronous follower-dən oxuyanda köhnəlmiş data görə bilərsiniz. Yazmanı dayandırsanız, bir müddət sonra bütün replica-lar eyniləşir: buna eventual consistency deyilir. "Eventual" nə qədər çəkdiyini demir: normal halda millisaniyələr, yük altında dəqiqələr. Bu gecikmə üç anomaliya yaradır.
Read-after-write consistency
İstifadəçi şərh yazır, leader-ə gedir; səhifəni yeniləyir, hələ yenilənməmiş follower-dən oxuyur və şərhi görmür. İstifadəçi data-nın itdiyini düşünür. Read-your-writes zəmanəti bu halı istisna edir: istifadəçi öz yazdığını həmişə görür, başqalarının yazdığı isə gecikə bilər. Üsullar:
- İstifadəçinin dəyişdirə biləcəyi data-nı leader-dən, qalanını follower-dən oxumaq: məsələn öz profilini leader-dən, başqalarınınkını follower-dən.
- Son yazmadan bir dəqiqə ərzində bütün oxumaları leader-ə yönləltmək və ya lag-ı bir dəqiqədən çox olan follower-dən oxumamaq.
- Client son yazmanın timestamp-ini saxlayır və replica ən azı o nöqtəyə çatmayıbsa, sorğunu başqasına ötürür.
Bir neçə cihaz olanda çətinləşir: timestamp cihazlar arasında paylaşılmalı, fərqli şəbəkələrdən gələn sorğular eyni data mərkəzinə yönləndirilməlidir.
Monotonic reads
İstifadəçi əvvəlcə az gecikən replica-dan oxuyur və şərhi görür, sonra çox gecikəndən oxuyur və şərh yoxa çıxır: zamanda geriyə gedir. Monotonic reads bunu qadağan edir: ardıcıl oxumalar heç vaxt daha köhnə vəziyyət qaytarmır. Sadə həll hər istifadəçinin həmişə eyni replica-dan oxumasıdır, məsələn user id-nin hash-i ilə.
Consistent prefix reads
Partition olunmuş sistemlərdə sual və cavab fərqli partition-larda ola bilər və üçüncü şəxs cavabı sualdan əvvəl görür. Consistent prefix reads yazmaların oxucu üçün eyni sırada görünməsini tələb edir. Həll səbəb-nəticə ilə bağlı yazmaları eyni partition-a yazmaqdır; Fəsil 9 buna causality adı ilə qayıdır.
Bu anomaliyalarla application kodunda mübarizə aparmaq çətindir; daha yaxşısı database-in transaction ilə zəmanət verməsidir. Distributed sistemlərə keçiddə bir çox vendor transaction-ları performans naminə atdı; Fəsil 7 və 9 bunun nə qədər əsaslı olduğunu müzakirə edir.
Multi-leader replication
Tək leader-in zəif tərəfi odur ki, hər yazma ondan keçməlidir. Bir neçə node-un yazma qəbul etdiyi modelə multi-leader deyilir: hər leader eyni zamanda digərlərinin follower-idir.
Tək data mərkəzi daxilində nadir hallarda dəyər. Mənalı olduğu üç hal:
- Bir neçə data mərkəzi. Hər data mərkəzinin öz leader-i var, yazmalar lokal təsdiqlənir və asinxron olaraq digər mərkəzlərə axır. Latency aşağı, data mərkəzi itkisi müstəqil idarə olunur.
- Offline client-lər. Telefondakı təqvim şəbəkəsiz işləyir, dəyişikliklər sonradan sinxronlaşır. Hər cihaz bir leader-dir, "data mərkəzləri arası" gecikmə isə saatlar və ya günlər ola bilər.
- Collaborative editing. Bir neçə istifadəçinin eyni sənədi redaktə etməsi eyni problemdir: hər istifadəçinin lokal replica-sı var.
Yazma konfliktləri
Multi-leader-in böyük problemi eyni qeydin iki leader-də eyni anda fərqli cür dəyişdirilməsidir. Tək leader ikinci yazmanı birincinin bitməsinə qədər gözlədir. Burada isə hər iki yazma lokal qəbul edilib və konflikt yalnız sonradan, asinxron aşkarlanır.
Ən yaxşı strategiya konflikti qarşısını almaqdır: hər qeyd üçün yalnız bir "ev" leader-i təyin etmək, məsələn istifadəçinin bütün yazmaları ən yaxın mərkəzə gedir. Qaçılmaz olanda isə konflikti converge etmək lazımdır, yəni bütün replica-lar eyni nəticəyə gəlməlidir:
- Last write wins: hər yazmaya id və ya timestamp verib ən böyüyü saxlamaq. Sadədir və data itirir.
- Birləşdirmə: dəyərləri qarışdırmaq, məsələn əlifba sırası ilə birləşdirilmiş string. Nadir hallarda məna kəsb edir.
- Konflikti saxlamaq: hər iki dəyəri qeyd edib sonradan application-a və ya istifadəçiyə həll etdirmək.
Application səviyyəsində həll yazmada, replication zamanı konflikt aşkarlananda handler çağırmaqla, və ya oxumada, növbəti oxuma hər iki versiyanı alıb birləşdirməklə ola bilər. Avtomatik həll üçün CRDT-lər, yəni concurrent dəyişikliklərə riyazi olaraq converge edən data strukturları, və Google Docs-un əsasındakı operational transformation var.
Topologiyalar
All-to-all topologiyada hər leader hər leader-ə göndərir. Circular və star topologiyalarda node-lar aralıq ötürücüdür və bir node çökəndə yol kəsilir. All-to-all-ın problemi sıradır: iki yazma fərqli yollarla fərqli sırada çata bilər, insert-dən əvvəl update gəlir. Düzgün həll üçün Fəsil 9-dakı version vector-lər lazımdır.
Leaderless replication
Üçüncü yanaşma leader anlayışını atır. Amazon-un Dynamo dizaynından sonra Cassandra, Riak və Voldemort bu modeli yayıb. Client yazmanı eyni anda bir neçə replica-ya göndərir, oxumanı da bir neçəsindən edir.
Quorum
n replica olsun. Yazma w replica təsdiqləyəndə uğurlu sayılır, oxuma r replica-dan cavab alır. Şərt belədir:
w + r > n
Bu halda oxuduğunuz r replica-dan ən azı biri ən son yazmanı görüb. Tipik seçim n = 3, w = 2, r = 2-dir: bir node çöksə də, oxuma və yazma davam edir. Çox oxunan sistemlərdə w = n, r = 1 seçilir: oxumalar sürətli, yazmalar isə bir node çökəndə dayanır.
Çökük node qayıdanda köhnəlmiş olur. Bunu iki mexanizm düzəldir: read repair, client oxuyanda fərqli versiyalar görüb köhnə replica-ya yenisini yazır, və anti-entropy, arxa plan prosesi replica-ları müqayisə edib fərqləri kopyalayır.
Quorum-un məhdudiyyətləri
w + r > n yazmanın görünməsinə zəmanət verir, amma incə hallar qalır: iki yazma concurrent olanda hansının qalib olduğu aydın deyil; oxuma yazma ilə eyni anda olanda nəticə qeyri-müəyyəndir; yazma w-ə çatmayanda rollback olunmur və uğurlu replica-lar yeni dəyəri saxlayır. Buna görə leaderless sistemlər yalnız eventual consistency vəd edir, read-your-writes kimi zəmanətlər isə yoxdur.
Sloppy quorum və hinted handoff
Şəbəkə kəsilməsində client w sayda "düzgün" replica-ya çata bilmir. Ya yazmanı rədd etmək, ya da müvəqqəti olaraq başqa node-lara yazmaq olar; ikincisi sloppy quorum-dur. Şəbəkə bərpa olunanda müvəqqəti node-lar yazmaları əsl replica-lara ötürür: hinted handoff. Availability artır, amma w + r > n şərti artıq oxumanın ən son yazmanı görməsinə zəmanət vermir, çünki yazma "səhv" node-lardadır.
Concurrent yazmaları aşkar etmək
Leaderless sistemdə konfliktlər normadır: iki client eyni key-ə eyni anda yazanda replica-lar fərqli sırada ala bilər. "Concurrent" sözünü dəqiq təyin edək: A əməliyyatı B-dən əvvəl baş verib, əgər B A-nın nəticəsini görüb və ya ona əsaslanıb. Nə A B-dən əvvəl, nə B A-dan əvvəldirsə, onlar concurrent-dir. Fiziki vaxt yox, bir-birindən xəbərdarlıq önəmlidir.
Bunun üçün server hər key üçün version nömrəsi saxlayır. Client yazmazdan əvvəl oxuyur, mövcud versiyaları birləşdirir və yazanda hansı versiyanı gördüyünü bildirir. Server həmin versiyadan köhnələri silir, görülməyənləri isə sibling kimi saxlayır. Bir neçə replica-da hər replica-nın öz sayğacı olur və onların toplusu version vector adlanır.
Sibling-ləri birləşdirmək application-ın işidir. Alış-veriş səbətində union əlverişlidir, amma silmə problemlidir: bir sibling-də məhsul silinib, digərində var, union onu qaytarır. Həll tombstone-dur: silməni açıq işarə ilə qeyd etmək. Bu məntiq mürəkkəb olduğuna görə CRDT-lər cəlbedicidir.
Üç yanaşmanın müqayisəsi
| Xüsusiyyət | Single-leader | Multi-leader | Leaderless |
|---|---|---|---|
| Yazma qəbul edən node | bir | bir neçə | istənilən |
| Konflikt | yoxdur | var, asinxron aşkarlanır | var, version vector ilə |
| Leader çökəndə | failover lazımdır | digər leader işləyir | failover yoxdur |
| Yazma latency-si çox mərkəzdə | yüksək | aşağı | aşağı |
| Consistency zəmanəti | ən güclü | zəif | zəif |
| Tipik istifadə | əksər OLTP | geo-distributed, offline | yüksək availability tələb edən key-value |
Single-leader sadəliyinə görə default-dur. Multi-leader və leaderless availability və latency qazandırır, qiyməti application-a sızan konfliktlərdir. Növbəti fəsil ikinci oxu əlavə edir: data-nı node-lar arasında bölmək.
Tapşırıqlar
- Bir leader və iki asynchronous follower olan sistemdə leader çökür. Hansı şərtlərdə client-in "uğurlu" cavab aldığı yazma itir? Semi-synchronous rejimə keçid bu riski necə dəyişir və yazma latency-sinə nə edir? Cavabınızı Fəsil 1-dəki percentile-lər baxımından əsaslandırın.
- Üç data mərkəzində multi-leader replication işləyir. İstifadəçi adı unikal olmalıdır. Hansı konflikt həlli strategiyası bu invariant-ı qoruya bilər, hansı qoruya bilməz? Konfliktin qarşısını tamamilə alan dizayn təklif edin və onun qiymətini göstərin.
n = 5replica-lı leaderless sistemdəwvərüçün üç fərqli kombinasiya seçin: oxuma-ağır, yazma-ağır və balanslı. Hər biri üçün neçə node çöksə sistemin işlədiyini və sloppy quorum açıq olandaw + r > nzəmanətinin niyə pozulduğunu izah edin.