Partitioning
Partitioning
Fəsil 5-də eyni data-nın bir neçə surətini saxladıq. Amma data həcmi və ya sorğu yükü bir maşına sığmayanda surət çıxarmaq kömək etmir: hər replica eyni böyüklükdədir və eyni yazma yükünü daşıyır. Bu halda data-nı hissələrə bölmək lazımdır. Hər hissə partition adlanır, MongoDB və Elasticsearch-də shard, HBase-də region, Cassandra-da vnode kimi də tanınır. Hər qeyd tam olaraq bir partition-a aiddir və hər partition özlüyündə kiçik database-dir.
Partitioning-in məqsədi scalability-dir: böyük dataset çoxlu diskə, sorğu yükü isə çoxlu processor-a yayılır. Yaxşı bölünmüş sistemdə on node on dəfə çox data saxlayır və təxminən on dəfə çox throughput verir. Pis bölünmüş sistemdə isə bütün yük bir node-a düşür və qalan doqquzu boş dayanır. Bu fəsil bölgünü düzgün etməyin, index-ləri bölməyin, node əlavə olunanda yenidən balanslamağın və sorğunu düzgün partition-a yönləltməyin yollarını izah edir.
Partitioning və replication birlikdə
Partitioning adətən replication ilə birlikdə işləyir: hər partition-ın surəti bir neçə node-da saxlanılır. Leader-based modeldə hər partition-ın öz leader-i var və bir node bəzi partition-lar üçün leader, digərləri üçün follower ola bilər. Belə düzülüşdə bir node çökəndə onun leader olduğu partition-ların failover-i baş verir, qalan partition-lar isə təsirsiz qalır. Fəsil 5-də replication haqqında deyilənlər hər partition-a ayrıca tətbiq olunur və bu fəsildə bölgünün özünə baxırıq.
Key-value data-nı bölmək
Sadə haldan başlayaq: data key ilə əldə edilir. Məqsəd yükü node-lar arasında bərabər yaymaqdır. Bərabər olmayan bölgü skewed adlanır, bütün yükün düşdüyü partition isə hot spot-dur. Ən sadə yanaşma qeydləri təsadüfi node-a yazmaqdır, amma onda oxuyanda hansı node-a getməyi bilməzsiniz və hamısını soruşmalı olarsınız.
Key range üzrə partitioning
Hər partition-a key-lərin davamlı diapazonu verilir: kağız ensiklopediyanın cildləri kimi, birinci cild A–B, ikinci C–D. Sərhədlər bərabər məsafədə olmaq məcburiyyətində deyil, çünki data bərabər paylanmayıb: T–Z bir cildə sığır, A isə ikisini tutur. Sərhədlər əl ilə və ya avtomatik seçilir.
Hər partition daxilində key-lər sıralı saxlanılır, Fəsil 3-dəki SSTable-lar kimi. Bu, range scan-ləri ucuz edir: "mart ayının bütün ölçmələri" tək partition-da ardıcıl oxumadır.
Zəif tərəfi hot spot-lardır. Sensor data-sında key timestamp olsa, bütün bugünkü yazmalar tək partition-a gedir, digərləri boş qalır. Həll key-i dəyişməkdir: sensor adını timestamp-in qabağına qoymaq. Onda yazma sensor-lara görə yayılır, amma "bütün sensor-ların bu saatkı data-sı" sorğusu hər partition-a ayrı getməli olur.
Hash üzrə partitioning
Skew riskini azaltmaq üçün key-in özü deyil, hash-i partition seçir. Yaxşı hash funksiyası oxşar key-ləri bir-birindən uzağa atır: "2026-11-02T10:00" və "2026-11-02T10:01" fərqli partition-lara düşür. Kriptoqrafik güc lazım deyil, MD5 və ya Fowler–Noll–Vo kifayətdir. Diqqət edilməli məqam: bəzi dillərin built-in hash funksiyası proses hər başlayanda fərqli nəticə verir və partitioning üçün yararsızdır.
Hər partition-a hash dəyərlərinin diapazonu verilir. Bu, yükü bərabər yayır, amma range scan-ləri itirir: bir vaxtlar qonşu olan key-lər indi hər yerə səpələnib. MongoDB-də hash sharding açıqsa, range query bütün partition-lara gedir.
Cassandra bu iki yanaşmanı birləşdirir: compound primary key-in birinci hissəsi hash-lənir və partition-ı seçir, qalan sütunlar isə partition daxilində sıralama üçündür. Məsələn (user_id, update_timestamp) key-ində hər istifadəçinin yeniləmələri tək partition-dadır və vaxta görə range scan mümkündür, fərqli istifadəçilər isə fərqli partition-lara yayılıb. Bu, one-to-many əlaqələr üçün güclü pattern-dir.
Terminologiya haqqında qeyd: hash partitioning bəzən consistent hashing adlanır, amma bu termin əslində CDN-lərdə yükü təsadüfi seçilmiş sərhədlərlə yaymaq üçün istifadə olunan konkret üsula aiddir və database-lərdə nadir hallarda tətbiq edilir. Fərqi qarışdırmamaq üçün "hash partitioning" demək daha dəqiqdir.
Hash-in həll etmədiyi skew
Eyni key-ə həddindən çox müraciət olanda hash kömək etmir: milyonlarla follower-i olan məşhurun hər paylaşımı eyni key-ə düşür. Hazırda database-lər bunu avtomatik həll etmir, application özü etməlidir. Məlum üsul "qaynar" key-in sonuna təsadüfi iki rəqəmli suffix əlavə etməkdir: yazmalar yüz fərqli key-ə yayılır. Qiyməti oxumadadır, hər oxuma yüz key-i toplamalıdır, ona görə yalnız az sayda qaynar key üçün dəyər və hansı key-lərin qaynar olduğunu izləmək lazımdır.
Secondary index-ləri bölmək
İndiyə qədər qeydə yalnız primary key ilə çatırdıq. Secondary index-lər, məsələn "qırmızı rəngli bütün maşınlar", partition-a aydın şəkildə uyğun gəlmir, çünki qeydi unikal təyin etmir. İki yanaşma var.
Document-partitioned: lokal index
Hər partition öz qeydləri üçün ayrıca index saxlayır və digər partition-ların data-sı ilə maraqlanmır. Yazma sadədir: qeyd hansı partition-a gedirsə, index də orada yenilənir. Oxuma isə bahadır: qırmızı maşınlar bütün partition-larda ola bilər, ona görə sorğu hər partition-a göndərilib nəticələr birləşdirilir. Buna scatter/gather deyilir və Fəsil 1-dəki tail latency amplification-ın birbaşa nümunəsidir: ən yavaş partition cavabı müəyyən edir. Buna baxmayaraq MongoDB, Cassandra, Elasticsearch və SolrCloud bu yanaşmanı seçir.
Term-partitioned: qlobal index
Alternativ bütün data-nı əhatə edən tək index qurub onu da bölməkdir, amma primary key-dən fərqli şəkildə: məsələn color:a–color:r bir partition-da, qalanı digərində. Index-in bölündüyü açar term adlanır, ona görə bu yanaşma term-partitioned-dir. Term üzrə range partitioning diapazon sorğularına, hash isə bərabər yükə kömək edir.
Oxuma səmərəlidir: client yalnız lazımi term-in olduğu partition-a gedir. Yazma isə mürəkkəbləşir: bir qeydin sütunları fərqli index partition-larına düşə bilər və hər yazma bir neçə node-a toxunur. Bunu synchronous etmək Fəsil 7-dəki distributed transaction tələb edir, ona görə praktikada qlobal index-lər asinxron yenilənir: yazdıqdan dərhal sonra index-də görünməyə bilər. DynamoDB-nin global secondary index-ləri belə işləyir.
| Xüsusiyyət | Document-partitioned | Term-partitioned |
|---|---|---|
| Yazma | tək partition, synchronous | bir neçə partition, adətən asinxron |
| Oxuma | scatter/gather, tail latency | hədəfli, tək partition |
| Index-in təzəliyi | dərhal | gecikə bilər |
| Uyğun hal | yazma-ağır, nəticələri az sorğular | oxuma-ağır, seçici sorğular |
Rebalancing
Vaxt keçdikcə sorğu yükü artır, dataset böyüyür, maşınlar çökür. Hamısı data-nı bir node-dan digərinə köçürməyi tələb edir: rebalancing. Yaxşı rebalancing üç tələbə cavab verir: sonra yük bərabər paylanıb, köçürmə zamanı sistem oxumanı və yazmanı qəbul edir, şəbəkə və disk yükü azaltmaq üçün yalnız lazım olan qədər data köçür.
Niyə hash mod N olmaz
İlk instinkt key-in hash-ini node sayına bölüb qalığını götürməkdir. Problem N dəyişəndə ortaya çıxır: on node-dan on birə keçəndə key-lərin böyük əksəriyyəti yeni node-a düşür, çünki hash mod 10 və hash mod 11 demək olar ki, hər key üçün fərqlidir. Bütün data cluster-də yerini dəyişir, bu da ən bahalı rebalancing-dir.
Sabit sayda partition
Sadə həll node sayından qat-qat çox partition yaratmaqdır: on node-da min partition, hər node-da yüz. Yeni node gələndə hər mövcud node-dan bir neçə partition "oğurlayır" ki, bölgü yenidən bərabərləşsin. Partition-ların sayı və key-lərin partition-a təyini dəyişmir, yalnız partition-ların node-a təyini dəyişir. Bu, Riak, Elasticsearch, Couchbase və Voldemort-un yanaşmasıdır.
Partition sayı əvvəldən seçilir və sonradan dəyişmir. Çox böyük seçsəniz, hər partition-ın idarə overhead-i yığılır; çox kiçik seçsəniz, node sayı artanda hər node-a az partition düşür. Dataset-in həcmi dəyişkən olanda düzgün sayı tapmaq çətindir. Daha güclü maşınlara daha çox partition verməklə qeyri-bərabər hardware-i də nəzərə almaq olar.
Dinamik partitioning
Key range partitioning-də sabit sərhədlər dataset dəyişəndə skew yaradır. Dinamik yanaşmada partition müəyyən ölçünü keçəndə ikiyə bölünür, çox kiçiləndə isə qonşusu ilə birləşir: B-tree-nin yuxarı səviyyəsinə bənzəyir. Partition sayı data həcmi ilə böyüyür. HBase və MongoDB belə işləyir. Zəif tərəfi başlanğıcdır: boş database tək partition-dan ibarətdir və ilk bölünməyə qədər bütün yük bir node-dadır. Bəzi sistemlər bunu pre-splitting ilə həll edir: key paylanması məlum olanda başlanğıc sərhədləri əl ilə verilir.
Node sayına mütənasib
Üçüncü yanaşma hər node-a sabit sayda partition verməkdir, məsələn Cassandra-da 256. Dataset böyüdükcə partition-lar böyüyür, node əlavə olunanda isə yeni node mövcud partition-ların bəzilərini təsadüfi nöqtələrdə kəsib yarısını götürür. Təsadüfilik qeyri-bərabər bölgü yarada bilər, amma çoxlu partition üzərindən ortalama bu təsiri azaldır.
Avtomatik, yoxsa əl ilə
Tam avtomatik rebalancing rahatdır, amma riskli: Fəsil 5-dəki failover kimi, bu da gözlənilməz nəticə verə bilər. Yüklənmiş node cavab verməyəndə sistem onu çökük sayıb partition-larını başqalarına köçürməyə başlayır, bu da həmin node-u və şəbəkəni daha da yükləyir və cascading failure yaranır. Ona görə bir çox sistem avtomatik plan təklif edir, amma icra üçün operator-un təsdiqini gözləyir. Rebalancing bahalı əməliyyatdır və insanın nəzarətində olması çox vaxt daha təhlükəsizdir.
Sorğunu düzgün partition-a yönləltmək
Partition-lar node-lar arasında köçürüləndə client hansı node-a müraciət edəcəyini necə bilir? Bu, service discovery-nin xüsusi halıdır. Üç yanaşma var:
- Client istənilən node-a müraciət edir; node sorğunu özündə olmayan partition üçün sahibinə ötürüb cavabı qaytarır.
- Bütün sorğular partition-aware routing tier-dən keçir; o, load balancer rolunu oynayır və özü data saxlamır.
- Client partition-ların node-lara təyinini bilir və birbaşa düzgün node-a gedir.
Hər halda əsas sual eynidir: təyinat dəyişəndə yönləndirən tərəf bundan necə xəbər tutur? Bir çox sistem ZooKeeper kimi ayrıca coordination service-dən istifadə edir: hər node özünü orada qeyd edir, təyinatın nüfuzlu surəti orada saxlanılır və dəyişiklikdə routing tier bildiriş alır. Fəsil 9 bu cür service-lərin daxilində consensus-un necə işlədiyini açır. Cassandra və Riak isə ayrıca service olmadan gossip protocol ilə işləyir: node-lar bir-birinə öz bildiklərini ötürür və hər node təyinatı bilir, bu da birinci yanaşmaya uyğundur.
İndiyə qədər tək key ilə işləyən sorğulara baxdıq. Analitik massively parallel processing database-ləri isə bir sorğunu bir neçə partition-da paralel icra olunan mərhələlərə bölür: join, filter və aggregation hər partition-da hesablanıb birləşdirilir. Fəsil 10-dakı batch processing eyni ideyanın ümumiləşdirilmiş formasıdır.
Fəslin nəticəsi
Partitioning böyük data-nı çoxlu maşına yaymağın yeganə yoludur və bu yayılmanın hər detalında trade-off var: key range sorğunu asanlaşdırır, hash yükü bərabərləşdirir; lokal index yazmanı, qlobal index oxumanı ucuzlaşdırır; sabit partition sayı sadədir, dynamic partitioning uyğunlaşandır. Hər partition kiçik, müstəqil database olduğuna görə bir neçə partition-a toxunan yazma yeni bir problemi qaldırır: onların hamısı ya uğurlu olmalı, ya heç biri. Bu, növbəti fəslin, yəni transaction-ların mövzusudur.
Tapşırıqlar
- IoT platformasında hər sensor saniyədə bir ölçmə göndərir və tipik sorğu "verilən sensor-un son 24 saatı"dır, nadir sorğu isə "bütün sensor-ların son 5 dəqiqəsi". Key range, hash və compound key yanaşmalarının hər biri üçün yazma skew-unu və hər iki sorğunun toxunduğu partition sayını müqayisə edin, sonra seçiminizi əsaslandırın.
- Dörd node, hər birində 64 sabit partition olan cluster-ə beşinci node əlavə olunur. Hər node-dan neçə partition köçürülməlidir ki, bölgü bərabər qalsın? Eyni vəziyyəti
hash mod Nilə müqayisə edib köçürülən data payını qiymətləndirin. - Document-partitioned secondary index-li sistemdə "qiyməti 100-dən az məhsullar" sorğusu p99-da yavaşdır. Scatter/gather-in bu latency-ni niyə yaratdığını Fəsil 1-dəki tail latency amplification ilə izah edin və term-partitioned index-ə keçidin hansı yeni problemi gətirəcəyini göstərin.