Partitioning ও Sharding — বড় table ভাঙার গণিত
Partitioning and Sharding
এক machine-এ table ভাঙা আর অনেক machine-এ data ছড়ানো — একই মনস্তত্ত্বের দুই scale; key-বাছাইয়ের ত্রিভুজ আর consistent-hashing-এর minimal-movement গণিত।
আগে এটা বুঝি
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 |
|---|---|---|
| RANGE | value-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 n | even-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-ই সিস্টেমের ভাগ্য লিখে ফেলে। তিনটা চাপ:
- Cardinalitydistinct value যথেষ্ট না হলে shards-এর সংখ্যাই সীমিত
- Skewvalue-দের অসম distribution মানে কিছু shard গরম, কিছু অলস
- 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 দখল করে, তাই:
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,000Partitioning স্বয়ংক্রিয়ভাবে 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।রিং-এ 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!নিজে চালিয়ে দেখুন
Declarative partitioning — pruning আর skew নিজের হাতে
ধাপ ১ — 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-ও আটকায় না।
নিজে বানান
ConsistentHashRing — 1/n movement প্রমাণিত
- Ring class — sorted positions + hash→owner map
- add/remove node = vnode-positions যোগ/বাদ
- assign(key) = bisect_right-এ ঘড়ির-কাঁটার পরের node
- 2→3 node resize-এ moved-fraction মাপুন
- 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-এ ০.৩% — বড়-সংখ্যার নিয়ম হাতে-কলমে।
নিজে বাড়ান:
remove("B")কেস যোগ করুন — 3→2 resize-এ moved fraction কত হওয়ার কথা (hint: B-এর ভাগটুকুই)? মিলবে কি না দেখুন- Weighted nodes — বড় machine দ্বিগুণ vnodes পাক; শেয়ার proportional কি না যাচাই করুন
- 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-প্যাটার্ন অনুমান করে বাছতে হয়।
বুঝেছেন কি না দেখুন
1Partition 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?
প্রয়োগ
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।
3Consistent 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 ২০%।
4Time-series table-কে PARTITION BY RANGE(created_at) monthly
ভাঙলে সদ্য-মাসের partition সব-write খায় — এটা কি সমস্যা? কখন
সমস্যা, কখন নয়?
যুক্তি
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 থাকলেই হলো।
5Global 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-এর পাঠ্যপুস্তক ভিত্তি