الفكرة
كل record بيتبع partition واحد بالظبط، وكل partition كأنه داتابيز صغيرة. الأسماء بتختلف: shard (MongoDB, Elasticsearch)، region (HBase)، tablet (Bigtable)، vnode (Cassandra, Riak)، vBucket (Couchbase). الهدف الأساسي: Scalability — توزيع الداتا والكويريز على ماكينات كتير.
وعادة بيتعمل مع الـ replication: كل partition له نسخ على كذا نود، وكل نود ممكن تبقى leader لبعض الـ partitions و follower لغيرها.
تقسيم الـ Key-Value Data
أبسط طريقة: عشوائي — بيوزع كويس جدًا بس لما تدور على key مش هتعرف هو فين، فتسأل الكل. فيه طريقتين أحسن:
زي مجلدات الموسوعة: A–B، C–D… كل partition مدى متصل من المفاتيح. (حدود المدى مش لازم متساوية — بتتظبط حسب الداتا.)
✅ Range queries ممتازة (المفاتيح مترتبة جوه الـ partition).
❌ Hot spots: لو المفتاح timestamp، كل كتابات النهارده تروح لـ partition واحد! الحل: حط حاجة قبل الوقت (اسم السينسور + الوقت).
Bigtable, HBase, RethinkDB, MongoDB (قبل 2.4)
طبّق hash (مش لازم يكون قوي cryptographically، بس لازم ثابت — مش زي hashCode() في Java اللي ممكن يختلف بين processes)، وكل partition ياخد مدى من الـ hashes.
✅ توزيع عادل.
❌ ضاع الترتيب → range queries لازم تتبعت لكل الـ partitions.
Cassandra, Riak, Voldemort, MongoDB (hash mode)
Skew و Hot Spots
حتى الـ hash مش بيحل كل حاجة: لو key واحد عليه حمل خرافي (سيليبريتي عنده ملايين متابعين بيعلقوا على بوسته)، كل الطلبات هتروح لنفس المكان. أغلب الداتابيزات مش بتحلها أوتوماتيك. الحل اليدوي: ضيف رقم عشوائي من 2 خانات لآخر المفتاح الساخن → يتوزع على 100 مفتاح. بس القراءة بقت لازم تجمع من الـ 100، ولازم تعرف أنهي مفاتيح محتاجة ده (book-keeping).
Partitioning والـ Secondary Indexes
الـ primary key سهل. بس السؤال: “هات كل العربيات اللون الأحمر” — الـ index ده نقسمه إزاي؟
| 📄 Document-partitioned (Local index) | 🏷️ Term-partitioned (Global index) | |
|---|---|---|
| الفكرة | كل partition عنده index لداتاه هو بس. | index واحد global بس مقسوم بقيمة الـ term نفسه (color:red في partition، color:silver في partition تاني). |
| الكتابة | ✅ سهلة — partition واحد. | ❌ أبطأ — كتابة document واحد ممكن تعدل كذا partition في الـ index. |
| القراءة | ❌ Scatter/gather: تسأل كل الـ partitions → tail latency amplification. | ✅ partition واحد بس للـ term. |
| ملاحظات | MongoDB, Riak, Cassandra, Elasticsearch, SolrCloud, VoltDB. | التحديث غالبًا async (DynamoDB: كسور ثانية عادة) فممكن تقرا index مش محدث. |
Rebalancing — إعادة التوزيع
الحمل بيزيد، الداتا بتكبر، ماكينة بتموت… فلازم ننقل partitions بين النودز. المتطلبات: الحمل يفضل عادل، الداتابيز تفضل شغالة أثناء النقل، وماننقلش داتا أكتر من اللازم.
| الاستراتيجية | إزاي | ملاحظات |
|---|---|---|
| Fixed number of partitions | اعمل partitions أكتر بكتير من النودز (مثلًا 1000 لـ 10 نودز). نود جديدة “تسرق” partitions كاملة من الباقيين. الـ key → partition ثابت، اللي بيتغير بس partition → node. | Riak, Elasticsearch, Couchbase, Voldemort. لازم تختار العدد صح من الأول: كبير أوي = overhead، صغير أوي = partitions ضخمة صعب نقلها. |
| Dynamic partitioning | زي B-tree: partition كبر عن حد معين (HBase: 10GB) يتقسم نصين؛ صغر يتدمج مع جاره. | HBase, RethinkDB, MongoDB. عدد الـ partitions بيتكيف مع حجم الداتا. مشكلة: داتابيز فاضية = partition واحد → pre-splitting. ينفع للـ range والـ hash. |
| Proportional to nodes | عدد ثابت من الـ partitions لكل نود. نود جديدة تختار partitions عشوائي وتقسمهم وتاخد نصهم. | Cassandra (256 لكل نود افتراضيًا)، Ketama. محتاج hash partitioning. |
Request Routing — الطلب يروح فين؟
ده جزء من مشكلة أعم اسمها service discovery. تلات طرق:
الكلاينت يكلم أي نود (round-robin). لو مش عندها الـ partition تحوّل الطلب. (Cassandra/Riak: gossip protocol بين النودز.)
طبقة في النص partition-aware load balancer بتوجه الطلب.
الكلاينت نفسه عارف الـ partition على أنهي نود.
السؤال الصعب: إزاي اللي بيوجّه يعرف التغييرات؟ ده consensus. أنظمة كتير بتستخدم ZooKeeper: كل نود بتسجل نفسها، وZooKeeper يحتفظ بالـ mapping الموثوق، والـ routing tier يعمل subscribe على التغييرات. (HBase, SolrCloud, Kafka بتستخدمه؛ LinkedIn Espresso بيستخدم Helix فوقه؛ MongoDB عنده config servers و mongos.) وعمومًا الكلاينت بيعرف IPs الـ routing tier عن طريق DNS.
Parallel Query Execution
الـ NoSQL غالبًا بيدعم كويريز بسيطة على key واحد. لكن داتابيزات MPP (Massively Parallel Processing) — المستخدمة في الـ analytics — بتقسم الكويري المعقدة (joins, filters, aggregations) لمراحل بتتنفذ بالتوازي على نودز وpartitions مختلفة.
🧪 Lab
جرّب الـ declarative partitioning في Postgres:
CREATE TABLE events (id bigserial, user_id int, created_at timestamptz) PARTITION BY HASH (user_id);CREATE TABLE events_p0 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 0);CREATE TABLE events_p1 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 1);CREATE TABLE events_p2 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 2);CREATE TABLE events_p3 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 3);EXPLAIN SELECT * FROM events WHERE user_id = 7; -- partition واحدEXPLAIN SELECT * FROM events WHERE created_at > now() - interval '1 day'; -- كله
بعدين جرّبها PARTITION BY RANGE (created_at) وقارن.