Foundationপ্রথম নীতি থেকে
LEVEL 8লেসন ১৭/১৯অ্যাডভান্সড৫৫ মিনিট

Partitioning ও Sharding — বড় table ভাঙার গণিত

Partitioning and Sharding

এক machine-এ table ভাঙা আর অনেক machine-এ data ছড়ানো — একই মনস্তত্ত্বের দুই scale; key-বাছাইয়ের ত্রিভুজ আর consistent-hashing-এর minimal-movement গণিত।

এই লেসন শেষে আপনি পারবেন

  • Partitioning (এক machine) আর sharding (অনেক machine)-এর সীমানা টেনে দিতে পারবেন — PostgreSQL declarative RANGE/LIST/HASH partitioning-এর যন্ত্র আর pruning কীভাবে query-plan ছোট করে
  • Shard-key-র ত্রিভুজ — cardinality, skew, query-pattern — প্রতিটা দিক মেলাতে ব্যর্থ হলে ঠিক কোন দুর্যোগ (hotspot, scatter-gather) ঘটে — উদাহরণসহ বলতে পারবেন
  • Hash sharding hotspot এড়ায় কিন্তু range-scan মারে, range sharding scan-friendly কিন্তু monotonic key-তে front-hotspot — trade-off দুটো যুক্তি দিয়ে ব্যাখ্যা করতে পারবেন
  • Consistent hashing ring-এর গণিত — node resize-এ মাত্র `k/n` key সরে — আর virtual nodes কীভাবে load মসৃণ করে, naive mod-hash-এর সাথে তুলনা করে দেখাতে পারবেন
  • Cross-shard query-র scatter-gather খরচ, cross-shard transaction-এ two-phase commit-এর ঝলক, আর rebalancing-এর operational যন্ত্রণা — scenario ধরে হিসাব করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

Buffer pool, WAL, checkpoint, vacuum — সব শিখে ফেলেছি। কিন্তু একটা সীমা এখনো অতিক্রম হয়নি: একটা table-ই যদি হাজার কোটি row-এর, তখন কী? Index যত বড়, B-Tree- traversal তত গভীর; vacuum তত লম্বা; backup তত ভারী; আর এক machine- এর RAM-এ working set ধরে রাখাই সম্ভব না।

সমাধান-দর্শনটা চেনা চেনা — ভাগ করো: data-কে ছোট ছোট স্বাধীন chunk-এ ফেলে দাও, যাতে প্রতিটা প্রশ্ন পুরো পাহাড় না ছুঁয়ে শুধু দরকারি ঢিবিটা ছোঁয়। কিন্তু এই এক দর্শনের দুইটা scale:

  • Partitioning — একই machine-এ, একই PostgreSQL instance-এ, এক logical table ভেতরে অনেক physical piece
  • Sharding — pieces গিয়ে বসে ভিন্ন machine-এ, একটা cluster গড়ে

আজকে দুটোই: প্রথমে PG-র declarative partitioning আর pruning-এর হাতে-কলমে লাভ, তারপর sharding-এর আসল গণিত — key বাছাইয়ের ত্রিভুজ, আর consistent hashing-এর সেই জাদুকরী প্রতিশ্রুতি: node বাড়ালেও key-দের মাত্র 1/n অংশ সরবে।

মূল ধারণা

Partitioning — এক machine-এর ভেতরের ভাঙন

PostgreSQL-এর declarative syntax সোজা: parent table ঘোষণা করুন “আমি এই column-এর উপর ভাংব”, তারপর প্রতিটা piece আলাদা child:

পদ্ধতিনিয়মপ্রাকৃতিক workload
RANGEvalue-interval ভিত্তিক ('2026-07' থেকে '2026-08')time-series, log, archive-by-date
LISTনির্দিষ্ট value-তালিকা (region IN ('dhaka','ctg'))category/geo ভিত্তিক
HASH<Term id="hash-function">hash</Term>(key) mod neven-spread, কোনো natural range নেই

লাভগুলো কোথায়?

Pruning: query-তে partition-column-এর filter থাকলে planner আগেই বাদ দেয় কোন chunks ছোঁয়ার দরকারই নেই। এক বছরের data, এক দিনের প্রশ্ন — ৩৬৫-এর মধ্যে ১টা partition scan। Index এত ছোট হয়ে যায় যে hot path-এর পুরো tree memory-তে থাকে।

Per-piece housekeeping: vacuum, analyze, index-rebuild এখন piece-ভিত্তিক — এক মাসের partition-এ ছোট vacuum, পুরনো partition- এ একবার-ই freeze। আর archival হয়ে যায় metadata-operation: পুরনো মাস DETACH PARTITION — row-delete-এর লক্ষ lock-ও নেই, vacuum-ও নেই, মুহূর্তেই শেষ।

Partition-wise join: দুটো table যদি একই key-তে ভাঙা হয়, join-ও piece-ভিত্তিক হতে পারে — জুলাই-মাস orders × জুলাই-মাস customers, প্রতিটা pair আলাদা ছোট join। enable_partitionwise_join on করলে planner এই ভাঙা-join বানায় — বড় join-এর memory-footprint উল্লেখযোগ্য কমে।

Sharding — ভাঙা pieces ভিন্ন machine-এ

Partitioning-এর লাভ যখন এক machine-এর capacity-ও ছাড়িয়ে যায়, pieces-দের পাঠাতে হয় আলাদা server-এ। এখন দরকার একটা routing layer — client-এর query কোন shard যাবে সেই সিদ্ধান্ত: হয় application-code-এ, হয় middleware/proxy-তে (Vitess, Citus-জাতীয় extension-layer)। এই স্তরের জন্ম-দাগই sharding-এর সব জটিলতা — cross-shard query, cross-shard transaction, rebalancing।

Shard key-র ত্রিভুজ

কোন column-এ ভাংব — এই একটা decision-ই সিস্টেমের ভাগ্য লিখে ফেলে। তিনটা চাপ:

Shard key-র তিন চাপ
  1. Cardinalitydistinct value যথেষ্ট না হলে shards-এর সংখ্যাই সীমিত
  2. Skewvalue-দের অসম distribution মানে কিছু shard গরম, কিছু অলস
  3. Query-patternkey-তে নেই এমন filter মানে প্রতিটা প্রশ্নে সব shard

user_id-তে ভাঙলে সাধারণত ভালো — cardinality বিশাল, query বেশিরভাগ user-scoped। কিন্তু celebrity-user problem: এক user-এর activity অন্যদের হাজারগুণ — hash-ও তাকে ছড়াতে পারে না, ওর সব data এক shard-এই। country-তে ভাঙলে skew নিশ্চিত — ১৯০টা country-র অর্ধেক traffic পাঁচটা দেশের। আর created_at-এ ভাঙলে query-pattern ভাঙে — “user X-এর profile” মানে সব shard ঘুরে আসা। Perfect key বিরল — trade-off বাছাই-ই design।

ভেতরে কী ঘটছে

Hash বনাম Range — দুই দর্শনের দ্বন্দ্ব

Hash sharding: hash(user_id) mod n — distribution প্রায়- uniform, hotspot-এর ভয় কম। কিন্তু range-query ধ্বংস: “আজকের সব order” মানে সব shard-এ জিজ্ঞেস। আর n বদলালে (mod নতুন!) প্রায় সব key নতুন জায়গায় — rebalance মানে mass-migration (এই ব্যথারই সমাধান নিচে)।

Range sharding: interval ভিত্তিক — time-series-এর প্রাকৃতিক ভাই। Range-scan এক shard-এই শেষ। কিন্তু monotonic key-র বিষ: আজকের সব write পৌঁছায় সবচেয়ে শেষের shard-এ — front- hotspot, বাকি shards দর্শক। Timestamp, auto-increment id — সব এই ফাঁদে। চিকিৎসা: composite key (device_id + time), বা salting — key-তে random-prefix মিশিয়ে spread।

Consistent hashing — minimal movement-এর গণিত

Naive mod-হ্যাশের মূল রোগ: shard-count বদলালে mapping formula বদলায়, তাই প্রায় সব key reassign হয়। Consistent hashing-এর চালাকি: key আর node দুটোই একই বৃত্তে বসাও — দুটোকেই hash করে 0–2³² রিং-এ বসাও; প্রতিটা key-এর owner = তার position থেকে ঘড়ির-কাঁটায় পরের প্রথম node।

এখন একটা node যোগ হলে কী হয়? শুধু তার position-এর ঠিক আগের arc-এর key-রা ওর কাছে চলে যায় — বাকি সব key-র owner অপরিবর্তিত। Uniform hash-এ প্রতিটা node গড়ে বৃত্তের 1/n arc দখল করে, তাই:

moved fraction≈kn,k=added-node index\text{moved fraction} \approx \frac{k}{n}, \quad k = \text{added-node index}

Node যোগ/বাদ — সরে শুধু সেই ভগ্নাংশ, বাকি সব স্থির। Naive mod-এর (n−1)/n-এর সাথে এর তফাত আকাশ-পাতাল।

Virtual nodes — smoothing

সমস্যা: node ক’টা হলে arc-সমান না — ছোট cluster-এ এক node-এর arc দুর্ঘটনাক্রমে দ্বিগুণ হতে পারে। সমাধান: প্রতিটা physical node-কে রিং-এ শত শত ছোট clone-position-এ বসাও (A#v0, A#v1, …) — আইন-বৃত্তের বড়-সংখ্যা-নিয়ম কাজে দিলে প্রতিটার share গড়ের কাছাকাছি চলে আসে। Dynamo/Cassandra/Memcached-client — সবার implementation-এই vnodes।

Cross-shard-এর দুই ছায়া

Scatter-gather: partition-column-এ না-পড়া প্রশ্ন মানে সব shard-এ fan-out, সবার উত্তর merge। Latency = সবচেয়ে ধীর shard- এর latency (tail-রাজত্ব), throughput-খরচ shard-সংখ্যার গুণ। Design-rule: সবচেয়ে ঘন ঘন query-টা shard-key-scoped করাই shard-key বাছাইয়ের প্রধান মানদণ্ড।

Cross-shard transaction: user A (shard 2) থেকে user B (shard 5)-এ transfer? দুই shard-এ atomicity চাই — classic উত্তর two-phase commit: phase 1 সব shard prepare (“commit করতে প্রস্তুত?”), phase 2 সব commit। খরচ: coordinator-blocking, prepare-এর আটকে-থাকা locks, আর coordinator মরলে in-doubt transactions। এই ভঙ্গুরতাই distributed-systems module-এর consensus-লেসনের প্রবেশদ্বার। Rebalancing-এর যন্ত্রণা এর সাথে জোড়া: data-move চলাকালীন দুই shard-এ dual-write বা migration-log — production sharded system-এর সবচেয়ে ভয়ের রুটিন।

উদাহরণ

Pruning চোখে দেখা — plan-এর ভেতরে

Table: measurements, RANGE-partitioned by month — ২৪টা partition, মোট ১০ কোটি row। দুইটা একই-মনে-হওয়া প্রশ্ন:

Q1: WHERE taken_at >= '2026-08-01' AND taken_at < '2026-08-02'
    Plan: Seq Scan on measurements_2026_08   (cost=0..4218 rows=...)
          ->  এক partition! ২৪-এর মধ্যে ১। ~৪২ লক্ষ row-র মধ্যে
             প্রশ্নটা ১৭ লক্ষ row ছুঁয়েছে — planner prune করেছে
             বাকি ২৩টা partition আগেই, index-ও ছোট-তাই-গরম

Q2: WHERE sensor_id = 42 AND taken_at > now() - '1 day'
    Plan: Append
          -> Seq Scan on measurements_2026_06 ...
          -> Seq Scan on measurements_2026_07 ...
          -> Seq Scan on measurements_2026_08 ...
          -> ... ২৪ line — partition-key filter-এর অভাবে কোনো
             prune নেই; সব partition ঘুরে দেখা

আর skew-এর পাঠ — insert-গুলো ৯০% আগস্টে ফেলে দিলে:

SELECT tableoid::regclass, count(*) FROM measurements GROUP BY 1;

measurements_2026_08 | 90,000,000     <- এই partition এখন নিজেই
measurements_2026_07 |  5,000,000        "বড় table"-সমস্যায়!
measurements_2026_06 |  5,000,000

Partitioning স্বয়ংক্রিয়ভাবে skew solve করে না — key-বাছাই solve করে। Time-based partition-এ সদ্য-অতীত সবসময় গরম; সেটা সাধারণত ঠিকই আছে (পুরনো data ঠান্ডা, ছোট index), কিন্তু বোঝা টের পাবেন write-burst-এ।

শেষ ঝলক — archival হিসেবে metadata-op:

ALTER TABLE measurements DETACH PARTITION measurements_2025_01;
-> ৭.২ লক্ষ row চলে গেল... মুহূর্তে। কোনো DELETE-sweep নেই,
   কোনো vacuum-পরব নেই, কোনো বিশাল WAL-ও নেই।

ছবিতে

Ring-এর ভূগোল

                 0/2³²
                   │
        ┌──────[K9]────────┐
        │                  │
      [B]                [K2]
        │  ▲               │
        │  └── C যোগে সরল  │
      [K4]                 │
        │                [A]
       ...                │
        └───[K7]──[K1]───┘

C যোগের আগে: K2,K9 → B-র arc ; C-র position পড়ল K2 আর K9-এর
মাঝের জায়গায় -> শুধু ওই arc-এর key-রা B থেকে C-তে।
K1,K4,K7 — অপরিবর্তিত। moved ≈ 1/n।
Consistent hashing ring — node আর key একই বৃত্তে; key-এর owner তার ঘড়ির-কাঁটার পরের node; C যোগ করায় শুধু তার arc-এর key-ই সরে।
রিং-এ positions (vnodes=4 হলে সরলিত):

A#v0   B#v0   A#v1   C#v0   B#v1   A#v2   C#v1 ...
 └── A-এর মোট arc = চার clone-arc-এর যোগ ≈ মোটের ⅓
     (এক clone দুর্ভাগ্যবশত ছোট হলেও বাকিরা মিটমাট করে)

vnodes=1 হলে: তিনটা arc, একটা হতে পারে দ্বিগুণ —
             এক node দুর্ঘটনায় দ্বিগুণ load!
Virtual nodes — এক physical node শত clone-position-এ; arc-দের যোগফল গড়ের কাছে, ছোট cluster-এও।

নিজে চালিয়ে দেখুন

EXPERIMENT

Declarative partitioning — pruning আর skew নিজের হাতে

macOS / Linux (PostgreSQL)· ২০ মিনিট

ধাপ ১ — partitioned table আর দুই partition:

CREATE TABLE measurements (
  taken_at date NOT NULL,
  sensor_id int NOT NULL,
  reading numeric
) PARTITION BY RANGE (taken_at);

CREATE TABLE m_2607 PARTITION OF measurements
  FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
CREATE TABLE m_2608 PARTITION OF measurements
  FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');

ধাপ ২ — ভেতরে data, ইচ্ছে-করে skew-সহ:

INSERT INTO measurements
SELECT DATE '2026-08-10' + (g % 18),
       g % 40, random() * 100
FROM generate_series(1, 800000) g;

INSERT INTO measurements
SELECT DATE '2026-07-20', g % 40, random() * 100
FROM generate_series(1, 40000) g;
SELECT tableoid::regclass AS partition, count(*)
FROM measurements GROUP BY 1 ORDER BY 2 DESC;
-- m_2608 | 800,000      <- skew ইচ্ছাকৃত
-- m_2607 |  40,000

ধাপ ৩ — pruning-এর প্রমাণ:

EXPLAIN ANALYZE
SELECT avg(reading) FROM measurements
WHERE taken_at = DATE '2026-08-11';
-- -> Seq Scan on m_2608 (never m_2607!)

EXPLAIN ANALYZE
SELECT avg(reading) FROM measurements
WHERE sensor_id = 7;
-- -> Append over BOTH partitions — partition-key-হীন filter,
--    কোনো prune নেই

প্রথম plan-টায় শুধু এক partition-এর নাম — সেটাই pruning; execution time-ও তুলনা করুন। দ্বিতীয়টায় Append node — সব piece ঘুরেছে।

ধাপ ৪ — detach হিসেবে instant-archive:

\timing on
ALTER TABLE measurements DETACH PARTITION m_2607;
-- Time: কয়েক ms — ৪০ হাজার row "মুছল" metadata-op-এ
DROP TABLE m_2607;

DELETE-এ এত row মোছা মানে লক্ষ lock, vacuum-পরব, WAL-storm — detach-এ কিছুই না। এই asymmetry-ই partitioning-এর নীরব সবচেয়ে বড় লাভ।

Production-note: partition create/drop-এর রুটিনটা হাতে না করে pg_partman extension-ই করে — সময়-আগাম partition pre-create, retention-based auto-detach। Real-world PG time-series-এ de-facto standard; TimescaleDB-র hypertable-ও একই ভাবনার আরও স্বয়ংক্রিয় রূপ।

এটা কী প্রমাণ করে

Planner partition-metadata পড়েই অপ্রাসঙ্গিক partitions বাদ দেয় — EXPLAIN-এ pruning দৃশ্যমান; আর key-বাছাই ভুল হলে partitioning নিজে skew-ও আটকায় না।

নিজে বানান

BUILD IT

ConsistentHashRing — 1/n movement প্রমাণিত

Python · ●●○○○
  1. Ring class — sorted positions + hash→owner map
  2. add/remove node = vnode-positions যোগ/বাদ
  3. assign(key) = bisect_right-এ ঘড়ির-কাঁটার পরের node
  4. 2→3 node resize-এ moved-fraction মাপুন
  5. Naive mod-hash-এর movement-এর সাথে টেবিল-তুলনা — দাবি যাচাই

লক্ষ্য: “consistent hashing-এ মাত্র k/n সরে, mod-hash-এ প্রায় সব” — এই দাবিটা empirical-ভাবে প্রতিষ্ঠিত।

import hashlib
from bisect import bisect_right

def h(s: str) -> int:
    return int(hashlib.md5(s.encode()).hexdigest(), 16)

class Ring:
    def __init__(self, vnodes=150):
        self.vnodes = vnodes
        self.pos = []            # sorted hash-positions
        self.owner = {}          # position -> node

    def add(self, node):
        for i in range(self.vnodes):
            hv = h(f"{node}#{i}")
            self.pos.append(hv)
            self.owner[hv] = node
        self.pos.sort()

    def remove(self, node):
        dead = {p for p in self.pos if self.owner[p] == node}
        self.pos = [p for p in self.pos if p not in dead]
        for p in dead:
            del self.owner[p]

    def assign(self, key):
        i = bisect_right(self.pos, h(key)) % len(self.pos)
        return self.owner[self.pos[i]]

    def shares(self):
        tally = {}
        for o in self.owner.values():
            tally[o] = tally.get(o, 0) + 1
        total = len(self.owner)
        return {k: v / total for k, v in sorted(tally.items())}


KEYS = [f"user:{i}" for i in range(30000)]
ring = Ring(vnodes=150)
ring.add("A"), ring.add("B")

before = {k: ring.assign(k) for k in KEYS}
ring.add("C")                                   # resize 2 -> 3
moved_ring = sum(ring.assign(k) != before[k] for k in KEYS) / len(KEYS)

before_mod = {k: h(k) % 2 for k in KEYS}
after_mod = {k: h(k) % 3 for k in KEYS}
moved_mod = sum(before_mod[k] != after_mod[k] for k in KEYS) / len(KEYS)

print(f"{'strategy':<24} {'moved':>8} {'theory':>10}")
print(f"{'consistent hash':<24} {moved_ring:>7.1%} {'~1/n = 33.3%':>12}")
print(f"{'naive mod-hash':<24} {moved_mod:>7.1%} {'~(n-1)/n = 66.7%':>17}")

print("\nvnode-smoothing (share of keys per node):")
for vn in (1, 5, 150):
    r = Ring(vnodes=vn)
    for n in "ABC":
        r.add(n)
    s = r.shares()
    spread = max(s.values()) - min(s.values())
    print(f"  vnodes={vn:<3} shares="
          f"{ {k: round(v, 3) for k, v in s.items()} } "
          f"spread={spread:.1%}")

প্রত্যাশিত output (hash-এর ভাগ্যে সামান্য ওঠানামা):

strategy                     moved     theory
consistent hash              33.4%  ~1/n = 33.3%
naive mod-hash               66.8%  ~(n-1)/n = 66.7%

vnode-smoothing (share of keys per node):
  vnodes=1   shares={'A': 0.442, 'B': 0.291, 'C': 0.267} spread=17.5%
  vnodes=5   shares={'A': 0.371, 'B': 0.318, 'C': 0.311} spread=6.0%
  vnodes=150 shares={'A': 0.335, 'B': 0.333, 'C': 0.332} spread=0.3%

দুইটা প্রমাণ এক টেবিলে: resize-এ consistent-এর movement ঠিক তত্ত্বের 1/n-এ বসে, mod-এরটা (n−1)/n — দ্বিগুণের বেশি। আর vnode-row-গুলো দেখায় smoothing-এর কাজ: ১ clone-এ spread ১৭.৫%, ১৫০ clone-এ ০.৩% — বড়-সংখ্যার নিয়ম হাতে-কলমে।

নিজে বাড়ান:

  1. remove("B") কেস যোগ করুন — 3→2 resize-এ moved fraction কত হওয়ার কথা (hint: B-এর ভাগটুকুই)? মিলবে কি না দেখুন
  2. Weighted nodes — বড় machine দ্বিগুণ vnodes পাক; শেয়ার proportional কি না যাচাই করুন
  3. Lookup-latency model — positions list বড় হলে bisect O(log v·n); real systems-এ sorted-array না tree/hash-jump — কেন, ভাবুন

বাস্তব সিস্টেমে

বাস্তব জগতে ভাঙা data

PostgreSQL ecosystem। pg_partman + native declarative partitioning — time-series/log workload-এর standard stack; TimescaleDB hypertable-ও একই ধারণার automation-heavy রূপ। Citus extension sharding-কে PG-র ভেতরেই নিয়ে আসে — coordinator + worker nodes, shard-key-scoped query সরাসরি single-shard push-down।

The ring everywhere। Memcached-client (consistent hashing- এর মূল paper 1997), Amazon Dynamo, Cassandra token-ring, Discord- এর billions-of-messages storage, Cloudflare-এর edge-cache routing — আপনি যে সিস্টেম নাম ধরুন, ring+vnodes কোনো-না-কোনো রূপে আছে।

Range-sharding-এর বিশাল জয়। Google Bigtable থেকে HBase, Spanner-এর tablet-splitting — সবগুলো range-based, কারণ ordered data-র locality-র লাভ তাদের workload-এ hotspot-ঝুঁকির চেয়ে বড়। দুই দর্শনের দ্বন্দ্ব production-এ “কোন workload” প্রশ্নেই ঝেরে ওঠে।

Rebalancing horror-এর প্রতিষেধক। Pre-splitting: Cassandra/ HBase-এর প্র্যাকটিস — শুরুতেই অনেক ছোট token-range/tablet বানিয়ে রাখা, পরে শুধু move করা হয় split না করা। Instagram-এর বিখ্যাত PG-sharding writeup-এও একই পাঠ: shard-scheme-এর ভুল পরে ঠিক করা প্রায় অসম্ভব — migrate মানে মাসব্যাপী dual-write যুগ।

যে ভুলগুলো সবাই করে

“Partitioning আর sharding একই জিনিস — scale শুধু আলাদা।”

যন্ত্র ভিন্ন, সমস্যা-শ্রেণিও ভিন্ন। Partitioning-এ এক process, এক transaction-manager, এক buffer pool — planner cross-partition query নিজেই join করে, transaction স্বাভাবিক। Sharding-এ ভিন্ন machine — cross-shard join মানে scatter-gather, transaction মানে 2PC, consistency-র প্রশ্ন নতুন মহাদেশ। প্রথমটা performance-tool, দ্বিতীয়টা architecture-decision।

“Partition যত বেশি, pruning তত বেশি — তাই হাজার partition বানাই।”

Overhead-ও linearly বাড়ে: planner প্রতিটা partition-এর constraint parse/plan করে — হাজার partition মানে planning-time নিজেই seconds-scale; প্রতিটার relation-cache entry, statistics, lock objects। Sweet spot সাধারণত শত-সংখ্যা, workload-dependent। Partition “manageable-size-এ ভাঙা”, “যত ছোটা সম্ভব” নয়।

“Hash partitioning "better" — range-এ তো hotspot হয়।”

Hotspot-এর উৎস key-র জন্মগত বৈশিষ্ট্য, partition-method নয়। Monotonic key (timestamp) hash-এও ঠিক আছে, range-এ বিপজ্জনক; celebrity-user hash-এও এক shard-এই। আর hash যে দেয়ই না: range- scan, time-window archival, locality। Method-বাছাই query-pattern দিয়ে হয়, fear দিয়ে নয়।

“Shard-key পরে বদলানো যাবে — migration tool তো আছে।”

সবচেয়ে দামি ভুল। Shard-key বদল মানে প্রতিটা row-এর ঘর বদল — online mass-migration: dual-write যুগ, backfill, verification, cutover — মাসের প্রজেক্ট, প্রতিটা ধাপে data-correctness ঝুঁকি। এজন্যই key-বাছাই সিস্টেমের জীবনে একবার-ই-ঠিক-করার মতো decision — ভবিষ্যৎ query-প্যাটার্ন অনুমান করে বাছতে হয়।

বুঝেছেন কি না দেখুন

1

Partition pruning কখন কাজ করে, কখন নয় — planner-এর দৃষ্টিতে শর্তটা কী?

স্মরণ

Query-তে partition-key-এর উপর constant-comparable predicate থাকতে হয় — taken_at = X, taken_at BETWEEN a AND b। Key-তে না-পড়া filter (sensor_id=7) মানে planner কোনো partition বাদের প্রমাণ পায় না — Append দিয়ে সব ঘুরিয়ে দেয়। Function-wrapped key (date_trunc('day', taken_at)=X) প্রায়ই prune ভাঙে — expression-index/partition-expression-এর বিচার লাগে।

2

৮-shard cluster, hash-sharded by user_id, uniform users। এক API-endpoint: “এই user-এর সব friend-দের latest post” — friend-list গড়ে ৫০ জন, সবরা এলোমেলো shard-এ। Per-request shard-hit গড়ে কত? এই endpoint-কে fast করার দুইটা design-option?

প্রয়োগ

Hit: ৫০ distinct friends প্রায় সবাই ভিন্ন shard-এ — গড়ে ~min(50,8) = ৮ shard-এই পৌঁছাবে (৮ shard-এ ৫০ random user প্রায়-সব shard ঢাকে); scatter-gather latency = max of 8। Options: (১) denormalize — feed-cache per-user (friend-দের recent posts আগেই তার home-shard- এ materialize, write-time fan-out); (২) social-graph partitioning — friendship-edge দুই পাশের shard-এই store, read local। মূল পাঠ: shard-incompatible access-pattern মানে denormalize বা graph-redesign।

3

Consistent ring-এ 4 node থেকে 5-এ গেলে গড়ে কত ভগ্নাংশ key সরে? Naive mod-এ? দুটোর অনুপাত দিয়ে operational প্রভাবটা ব্যাখ্যা করুন।

যুক্তি

Ring: 1/n = 1/5 = ২০%। Mod: (n−1)/n = 4/5 = ৮০%। অনুপাত ৪×। Operational: cache-cluster-এ mod-resize মানে ৮০% cache-entry ভুল node-এ — effective cold-start, origin-load spike; ring-এ মাত্র ২০% miss। Storage-এ এটা আরও বড় — ৮০% data-copy network vs ২০%।

4

Time-series table-কে PARTITION BY RANGE(created_at) monthly ভাঙলে সদ্য-মাসের partition সব-write খায় — এটা কি সমস্যা? কখন সমস্যা, কখন নয়?

যুক্তি

প্রায়ই নয় — এটা intended: hot-data ছোট এক partition-এ, তার index সম্পূর্ণ memory-তে, পুরনোগুলো ঠান্ডা-অপ্রাসঙ্গিক; vacuum ও শুধু hot-piece-এ চলে। সমস্যা হয় extreme write-burst-এ এক partition = এক set-এর disk-spindle/WAL-bandwidth saturate করলে, বা retention- window এত ছোট যে partition-per-hour লাগে (planning-overhead)। Chill: hot-partition size manageable থাকলেই হলো।

5

Global chat-app: message-table কোটি-কোটি row। Query-প্যাটার্ন ৯৫% “conversation-id অনুযায়ী last-N messages”; ৪% “user-এর সব unread”; ১% admin analytics (full-scan ok)。 Shard-design propose করুন — key, method, আর প্রতিটা pattern-এর যাত্রাপথ।

ডিজাইন

Key: conversation_id — ৯৫%-pattern-কে single-shard করে; cardinality বিশাল; skew ঝুঁকি viral-conversation-এ (celebrity-problem) — mitigation: super-large conversation-এ fan-out room-sharding বা read-replica। Method: HASH (conversation-id-র কোনো natural range-নৈতিক অর্থ নেই; range-scan দরকারও নেই) + vnode-ring rebalance-এর জন্য। Unread-pattern: user↔conversation mapping separate store user_id-sharded, বা per-user inbox-index denormalize। Analytics: async ETL warehouse-এ — OLTP-shard-কে কখনো full-scan দিয়ে জ্বালানো যাবে না। মূল সূত্র: hottest pattern = shard-key; বাকিরা design দিয়ে মানানসই।

এরপর কী

এরপর কী

Replication-এ data-র নকল, sharding-এ data-র বিভাজন — দুটো মিলে distributed system। আর সেখানেই একটা theorem প্রতিটা design-meeting দখল করে বসে: CAP। কিন্তু বেশিরভাগ মানুষ এটা ভুল বোঝে — “P বাছাই করা যায়”, “NoSQL AP, SQL CP”… পরের লেসনে theorem-টা আসল রূপে, তারপর PACELC — আর দেখব consistency-র পুরো সিঁড়িটা, Level 9-এর distributed-systems journey-র প্রথম সিঁড়ি-কাঠি।

আরও পড়ুন

  • PostgreSQL Documentation — Table Partitioning — PostgreSQL Global Development Group · Declarative partitioning, pruning, partition-wise operations — লেসনের PG-half-এর উৎস
  • Designing Data-Intensive Applications, Chapter 6 — Partitioning — Martin Kleppmann · Key-skew, hotspot, consistent hashing, rebalancing — লেসনের sharding-half-এর প্রধান অনুপ্রেরণা
  • Database System Concepts — Distributed Databases chapters — A. Silberschatz, H. Korth, S. Sudarshan · Data fragmentation আর distributed query processing-এর পাঠ্যপুস্তক ভিত্তি