Sharding at Scale — routing, rebalancing, hotspot
Sharding at Scale: Routing, Rebalancing, Hotspots
Partitioning-গণিত শেখা হলেও production-এর আসল খেলা operational: request পৌঁছাবে কোথায়, node যোগ-বিয়োগে কত data নড়বে, আর celebrity-key কোন shard গুলিয়ে দেবে — এই তিন যুদ্ধের map।
আগে এটা বুঝি
আগের লেসনে partitioning-sharding শেখেছি placement-গণিত: hash নিন, range ভাগ করুন, key → shard। কাগজে সব পরিষ্কার। Production cluster-এ প্রথম সপ্তাহেই তিনটা প্রশ্ন ঘাড় ধরে ধরে:
- Request এলো — shard-map-এর কপি তার কাছেই? কে দেখাবে কোন node কোন shard-এর মালিক?
- Node যোগ-বিয়োগ — data কোথায় কতটা নড়বে, সিদ্ধান্ত কে নেবে?
- এক user-এর key শার্ডের অর্ধেক QPS খেয়ে ফেলল — আগুন লাগল কোথায়?
এখানেই আগের লেসনদ্বয় ফিরে আসে — gossip-membership (cluster-map ছড়ানোর vehicle) আর raft-consensus (অপ্রত্যাশিত জায়গায়: shard-map-এর মালিক)। আজকের লেসন তিন অধ্যায়ের — routing (request কোথায়), rebalancing (data কতটা নড়ে), hotspot (load কেন অসমান)।
মূল ধারণা
Request routing — তিন topology
| Topology | কে জানে shard-map | বাড়তি hop | Trade-off |
|---|---|---|---|
| Client-direct | Client নিজে, config-service থেকে fetch | শূন্য | Client-library ভারী; stale-map window |
| Proxy tier | Proxy-tier, client dumb | ১ hop | Tier-ই fleet-wide bottleneck/blast-radius |
| DNS sharding | DNS answer-ই routing | শূন্য-ish | Coarse — IP-list স্তরেই শেষ |
Client-direct হলো Vitess-ধাঁচের দর্শন: client config-service থেকে shard-map snapshot নিয়ে key→shard→node নিজেই resolve করে সোজা database-node-এ যায় — latency সবচেয়ে কম, দাম map-এর stale-window: ভুল node-এ গেলে node-ই hint দেয় ‘metadata refresh করো’। Proxy-tier-এ (vtgate, mongos) client সরল; request proxy-র পেট দিয়ে। DNS-sharding মোটা দানা: service-name-এর পেছনে shard-group-এর IP ঘোরানো — dns lesson-এর TTL-economics-ই এখানে routing-knob, granularity মোটে IP-group।
Metadata-র মুরগি-ডিম
Shard-map নিজেই shared state — সেখানেই পুরনো প্রশ্ন: কে authoritative? Config-service মরলে নতুন client join করবে কীভাবে? Map-এর দুই version দুই component-এ থাকলে কে জেতে?
সৎ উত্তর: shard-metadata-র মালিককে নিজের consensus লাগবেই — raft-quorum চালিয়ে config-store (etcd/consul), নয় gossip-based membership + deterministic derivation। এজন্যই আগের লেসনগুলো পড়া: consensus-এর দাম দিয়ে metadata-কে single-truth রাখা, বা gossip-এর eventual-window মেনে membership-derive করা — দুই-ই সচেতন চয়েস, accident না।
Rebalancing — দুই দর্শন
Node যোগ-বিয়োগ মানে assignment বদলানো। দুই দর্শন:
Fixed-slot, many-slots-per-node। Partition-space আগেই অনেক বড় —
consistent-hash ring-এ প্রতি node-এর অনেক virtual node (vnode), বা
HBase-ধাঁচে pre-split chunks। Node যোগে slot-ownership হস্তান্তর হয়,
slot-সংজ্ঞা বদলায় না। গণিত: n node-এ এক যোগে সরে মাত্র 1/(n+1)
ভগ্নাংশ; vnode বেশি হলে skew কমে (law of large numbers)।
Dynamic split/merge। Range-partitioned system load দেখে ভাগ করে: range বড়/ব্যস্ত হলে split, ফাঁকা জোড়া merge — data-driven, surgical; দাম split-এর serving-time copy + threshold ভুলে split-storm।
| দিক | Fixed-slot + vnodes | Dynamic split/merge |
|---|---|---|
| Movement unit | Slot-ownership (cheap handoff) | পুরো half-range copy |
| Skew control | Vnode-count বাড়িয়ে statistical | Load-observed, surgical |
| Ops-complexity | Low-mechanism | Threshold-tuning-এর দায় |
শেষ row-টা মনে রাখবেন: কোনো rebalancing-দর্শনই hot-key সমাধান করে না — key এক থাকলে সে এক shard-এই; hotspot-এর ঔষধ আলাদা।
Celebrity problem — hotspot-এর তিন ঔষধ
celebrity-user একটা post দিল, দশ লাখ follower read করছে — তার key-
এর সব lookup এক shard-এ। Hotspot-এর
তিন প্রতিষেধক:
- Key salting: key-এ random suffix জুড়ে এক logical-key-কে
Sটা physical-key-এ ছড়ান — write fan-outS, read scatter-gatherS। - Hot-row splitting / replica-fanout: ওই row-র extra replicas — read-heavy হলে কার্যকর।
- Cache-front: Redis/CDN ওই key-এর traffic shard-এর আগেই খেয়ে ফেলে — simplest, staleness-window-এর দামে।
Scatter-gather আর secondary-index-এর ফয়সালা
Shard-জুড়ে query (‘সব shard-এ যাও, ফল জোগাড় করো’) মানে p99-এর
গণিত উল্টে যায়: একটা shard slow হলেই পুরো query ওই slowest-এর
ঘড়িতে। S shard, per-shard slow-probability p হলে overall-slow ≈
1-(1-p)^S — S=10-এ ১%-slow হলে ≈ 9.6%, S=100-এ ≈ 63%! Shard-count-ই
tail-multiplier।
Secondary index-এ ফয়সালা একই গল্পের দুই মুখ: local index (প্রতি shard নিজের doc-এর index) — write-local, read-scatter-gather; global index (attribute-value → আলাদা shard) — read-point, কিন্তু প্রতিটা base-write এখন index-shard-এও write — distributed-write, partial-failure-এ index-drift। Cross-shard transaction-লাগানো পড়লে saga-lesson-এর pattern-ই ওখানকার শেষ কথা।
ভেতরে কী ঘটছে
Consistent hashing — আংটির গণিত
Mod-hash-এর জন্য: n→n+1 node বদলে virtually সব key নতুন owner
পায় (h(k) mod n প্রায় সব r-এ বদলায়) — moved ≈ n/(n+1)।
Consistent hashing key আর node দুজনকেই এক circle-এ বসায়; key-র
owner clockwise-পরের প্রথম node-point:
ring: n1 ●───● k ───● n3 ───● n2 ───(back to n1)
key k-র owner = clockwise-পরের point = n3Node যোগে সে কেবল নিজের arc-এর keys নেয় — moved ≈ 1/(n+1)।
Raw-ring-এ random placement luck-এর খেলা (arc ±২০-৩০% skew সহজেই);
vnodes সেটাই ঠিক করে — প্রতি node ১০০+ virtual point, arc-
দৈর্ঘ্যগুলো central-limit-এ কাছাকাছি। Dynamo paper-এর সবচেয়ে practical
উত্তরাংশ এটাই; route হয় sorted-array bisect-এ — O(log V)।
Rebalancing-এর রাস্তার বাস্তবতা — move মানে copy
Ownership-handoff কাগজে instant, বাস্তবে data-copy: নতুন arc-এর keys disk থেকে stream, মাঝপথে dual-write/log-sync, তারপর ownership-switch। জানালাটায় তিন বাস্তবতা:
- Bandwidth-tax: copy-traffic production-I/O-র সাথে share — throttling knob mandatory।
- Dual-serving: switch-এর আগে পুরনো-নতুন owner দুজনেই প্রশ্ন পেতে পারে — idempotent-read + version-check-এর প্রস্তুতি।
- Propagation-lag: gossip/config-poll-এ সব client-এর নতুন map একসাথে আসে না — hint-redirect protocol-টাই সেতু।
Dynamic split-এ যোগ হয় atomicity-প্রশ্ন: copy চলাকালে ওই range-এর write কোথায় জমে? উত্তর system-ভেদে আলাদা (HBase WAL-replay, MongoDB migration-lock) — শিক্ষা এক: movement-ই expensive, minimize-ই design।
উদাহরণ
একটা node-join, শুরু থেকে শেষ — timeline
t0 ৪-node cluster (vnodes=64/node), ৫০০০ keys, ~1250/node
t1 ops: n5 boot — gossip-pool-এ join ঘোষণা (আগের লেসন!)
t2 coordinator/config-service: ring-এ n5-এর ৬৪ point insert
→ প্রতিটা পুরনো node কিছু arc হারাবে; n5 পাবে ~20% ownership
t3 streaming: n1..n4 → n5 data-copy (throttled)
t4 copy-done handshake → ownership-switch, epoch++
t5 shard-map new-version broadcast; clients refresh
t6 straggler: old-map client n3-কে key জিজ্ঞেস করল যেটা এখন n5-এর
→ n3 reply: MOVED-to-n5 + fresh-map-hintফলাফল build/experiment-এর সংখ্যার সাথে মিলল: moved ≈ 20–23% (theory 1/(4+1)=20%, vnode-quantization-এ ওঠানামা), কোনো moment-এই data হয়নি।
এবার celebrity: user-viral এক key-এর QPS = বাকি ৪৯৯৯ key-এর মিলিত
QPS-এর সমান — rebalancing পারে না, key-এক shard-এক। Salting B=32:
user-viral#0..31 ছড়িয়ে ৩২ slot-এ; read-side এখন ৩২ parallel lookup
- merge — latency জিতেছে variance, হারিয়েছে fan-out cost।
ছবিতে
CLIENT-DIRECT (Vitess-style):
client ──get-shard-map──► CONFIG-SVC (raft-backed)
│ ◄──map v42─────────────┘
└────────key────────────────────► SHARD-n3 (hop=0)
PROXY TIER (mongos/vtgate):
client ──────────────────────────► PROXY-TIER ──► SHARD-n3
(map ওর কাছে) (hop=1)
DNS SHARDING (coarse):
client ──lookup sharding.example.com──► DNS ──► [IP-group-B]
(TTL-এ ঘোরে group-level)BEFORE (4 nodes): AFTER n5 joins:
n1 ●━━━A━━━● n1 ●━━━A'━━━●
n2 ●━━━B━━━● n2 ●━━━B'━━━●
n3 ●━━━C━━━● ring n3 ●━━━C'━━━●
n4 ●━━━D━━━● n4 ●━━━D'┄┄e┄● ← D' হারায় শেষ-খণ্ড e
n5 ●━e1━e2━e3━● ← চার node-এর হারানো খণ্ড পায়
moved ≈ 1/(4+1) = 20% mod-hash-এ একই change: moved ≈ 83%!নিজে চালিয়ে দেখুন
Rebalancing-cost meter + hotspot dashboard — দুই theory-সংখ্যা চোখে
"""shard_lab.py — rebalancing-cost + hotspot-salting simulation"""
import hashlib
import random
from collections import Counter
def h(s):
return int(hashlib.md5(str(s).encode()).hexdigest(), 16)
KEYS = [f'key-{i}' for i in range(10_000)]
def mod_map(n):
return {k: h(k) % n for k in KEYS}
class Ring:
"""consistent-hash ring — প্রতি node-এর vnodesটা virtual point"""
def __init__(self, nodes, vnodes=100):
pts = sorted((h(f'{nd}:{v}'), nd)
for nd in nodes for v in range(vnodes))
self.points, self.owners = zip(*pts)
def owner(self, key):
pos = h(key)
lo, hi = 0, len(self.points) # first point >= pos
while lo < hi:
mid = (lo + hi) // 2
if self.points[mid] < pos:
lo = mid + 1
else:
hi = mid
return self.owners[lo % len(self.owners)]
def ring_map(n):
r = Ring([f'n{i}' for i in range(1, n + 1)])
return {k: r.owner(k) for k in KEYS}
def moved_pct(before, after):
changed = sum(1 for k in before if before[k] != after[k])
return 100 * changed / len(before)
print('=== topology-change: কত % key নড়ল (10k keys) ===')
for name, scheme in [('mod-hash ', mod_map), ('vnode-ring ', ring_map)]:
grow = moved_pct(scheme(5), scheme(6))
shrink = moved_pct(scheme(6), scheme(5))
print(f'{name} add-node 5→6: {grow:5.1f}% '
f'remove-node 6→5: {shrink:5.1f}%')
print('theory: mod-hash moves ~n/(n+1), ring moves ~1/(n+1)')
# ── hotspot injection + detector + salting ──
random.seed(7)
counts = Counter()
TOTAL = 100_000
for _ in range(TOTAL):
if random.random() < 0.40:
counts['celeb-user'] += 1
else:
counts[f'user-{random.randrange(10_000)}'] += 1
def dashboard(c, label):
mean = TOTAL / len(c)
top_k, top_n = c.most_common(1)[0]
print(f'{label}: distinct={len(c)}, top={top_k} '
f'{100*top_n/TOTAL:.2f}% of QPS = {top_n/mean:.0f}× mean')
hot = [(k, round(n/mean)) for k, n in c.most_common(3) if n > 100*mean]
print(f' flagged (>100× mean): {hot}')
dashboard(counts, 'BEFORE salting')
salted = Counter()
B = 64
for k, n in counts.items():
if k == 'celeb-user':
base = n // B
for b in range(B):
salted[f'{k}#{b}'] += base
salted[f'{k}#{0}'] += n - base * B
else:
salted[k] += n
dashboard(salted, 'AFTER salting (B=64)')নমুনা output:
=== topology-change: কত % key নড়ল (10k keys) ===
mod-hash add-node 5→6: 83.7% remove-node 6→5: 83.7%
vnode-ring add-node 5→6: 14.9% remove-node 6→5: 14.9%
theory: mod-hash moves ~n/(n+1), ring moves ~1/(n+1)
BEFORE salting: distinct=9971, top=celeb-user 40.05% of QPS = 3993× mean
flagged (>100× mean): [('celeb-user', 3993)]
AFTER salting (B=64): distinct=10034, top=celeb-user#0 0.68% of QPS = 68× mean
flagged (>100× mean): []তিনটা পাঠ:
- Mod-hash-এর ৮৩.৭% মানে rebalance-মানেই near-full-copy — production-এ mod-hash নিষিদ্ধ হওয়ার কারণ। Ring-এর ১৪.৯% theory-র ১৬.৭%-এর চারপাশের vnode-quantization-ওঠানামা।
- Detector-এর threshold (>100× mean)-এ celeb ৩৯৯৩× — রাডারে বজ্র; salting B=64-এ ৬৮× — alert চুপ।
- Salting-ফাঁদ: distinct-keys ৬৩ বাড়ল, read-side এখন ৬৪-way scatter-gather — spread-এর দাম fan-out।
Topology-change-এ mod-hash প্রায় 84% key সরায় অথচ vnode-ring ~15% — theory-র n/(n+1) বনাম 1/(n+1)-এর হুবহু প্রমাণ; আর 40%-QPS-এর celebrity-key detector-এর রাডারে ধরা পড়ে, salting-এর পরে concentration mean-এর কাছে নেমে আসে।
নিজে বানান
ShardRouter — vnode ring, movement-report, hot-key guard, salting
- Ring-build — প্রতি node vnodesটা hashed point, sorted array
- route(key) — bisect দিয়ে clockwise-owner, O(log V)
- movement_report — topology-change-এ moved-keys breakdown
- hot_keys — mean-এর factor-গুণ ছাড়ালে flag
- salting demo — এক key-কে B-subkey-তে ছড়িয়ে node-বণ্টন
"""shard_router.py — vnode ring + movement report + hot-key guard (~100 line)"""
import bisect
import hashlib
import random
from collections import Counter
def h(s):
return int(hashlib.md5(str(s).encode()).hexdigest(), 16)
class ShardRouter:
"""consistent-hash ring, প্রতি node vnodesটা virtual point"""
def __init__(self, nodes, vnodes=64):
self.vnodes = vnodes
self.ring = sorted((h(f'{nd}:{v}'), nd)
for nd in nodes for v in range(vnodes))
self.points = [p for p, _ in self.ring]
def route(self, key):
i = bisect.bisect(self.points, h(key)) % len(self.ring)
return self.ring[i][1]
def map_keys(self, keys):
return {k: self.route(k) for k in keys}
def movement_report(before, after, label):
moved = [k for k in before if before[k] != after[k]]
print(f'\n[{label}] total={len(before)}, moved={len(moved)} '
f'({100 * len(moved) / len(before):.1f}%)')
dest = Counter(after[k] for k in moved)
src = Counter(before[k] for k in moved)
print(' যারা হারাল :', dict(src.most_common()))
print(' যারা পেলো :', dict(dest.most_common()))
def hot_keys(counts, factor=100):
"""mean-এর factor গুণের বেশি QPS নিলে flag"""
mean = sum(counts.values()) / len(counts)
return {k: round(n / mean) for k, n in counts.items() if n > factor * mean}
def salt(key, buckets):
return [f'{key}#{b}' for b in range(buckets)]
# ── 1. routing + topology-change ──
KEYS = [f'user-{i}' for i in range(5_000)]
r4 = ShardRouter(['n1', 'n2', 'n3', 'n4'])
m4 = r4.map_keys(KEYS)
load = Counter(m4.values())
print('4-node load:', dict(sorted(load.items())))
r5 = ShardRouter(['n1', 'n2', 'n3', 'n4', 'n5']) # n5 join!
m5 = r5.map_keys(KEYS)
movement_report(m4, m5, 'join n5')
r3 = ShardRouter(['n1', 'n3', 'n5']) # n2,n4 die
m3 = r3.map_keys(KEYS)
movement_report(m5, m3, 'lose n2+n4')
# ── 2. hot-key detection ──
random.seed(3)
traffic = Counter()
for _ in range(50_000):
if random.random() < 0.35:
traffic['celeb-user'] += 1
else:
traffic[f'user-{random.randrange(4_000)}'] += 1
print(f'\nhot-keys (>100× mean): {hot_keys(traffic)}')
# ── 3. salting — এক key-কে ছড়িয়ে অনেক key ──
subkeys = salt('celeb-user', 32)
spread = Counter(r5.route(k) for k in subkeys)
print('salted subkeys-এর node-বণ্টন:', dict(sorted(spread.items())))
salted_traffic = Counter()
for k, n in traffic.items():
if k == 'celeb-user':
for b, sk in enumerate(subkeys):
salted_traffic[sk] += max(0, n - b) // len(subkeys)
else:
salted_traffic[k] += n
print('after-salt hot-keys:', hot_keys(salted_traffic))নমুনা output:
4-node load: {'n1': 1014, 'n2': 1354, 'n3': 1379, 'n4': 1253}
[join n5] total=5000, moved=1127 (22.5%)
যারা হারাল : {'n4': 373, 'n3': 325, 'n2': 243, 'n1': 186}
যারা পেলো : {'n5': 1127}
[lose n2+n4] total=5000, moved=1991 (39.8%)
যারা হারাল : {'n2': 1111, 'n4': 880}
যারা পেলো : {'n5': 723, 'n3': 668, 'n1': 600}
hot-keys (>100× mean): {'celeb-user': 1406}
salted subkeys-এর node-বণ্টন: {'n1': 5, 'n2': 7, 'n3': 6, 'n4': 7, 'n5': 7}
after-salt hot-keys: {}তিনটা পাঠ:
- Join-এ moved 22.5% — theory 20%, বাড়তিটা vnode-quantization (64/ node); vnodes=512-এ চালালে 20%-এর কাছাকাছি নামব।
- Initial-load-ই অসমান (1014–1379) — vnode কম হলে statistical luck; production-এর বড় default-vnode-এর কারণ এটাই।
- Salted subkeys ৫ node-এ ছড়াল — এক key-র আগুন পাঁচ চুলায়; detector নিঃশব্দ। Fan-out-cost read-side-এ — next optimization সেখানেই।
নিজে বাড়ান:
- Bounded-load variant — overload-node পেলে next-clockwise-node-এ overflow; max-load-ratio track করুন
- Pre-split simulation — fixed 1024-chunk model-এর movement-cost dynamic split-এর সাথে তুলনা করুন
- Tail-sim — S shard-এ per-shard latency থেকে aggregate p99; S-বাড়ানো curve-টা আঁকুন
বাস্তব সিস্টেমে
বাস্তব জগতে
Vitess — YouTube-থেকে Slack। MySQL-fleet-এ client-direct + config-service topology-র প্রতিনিধি: vtgate routing, vreplica-stream-এ rebalance — CNCF-project, sharding-control-plane-এর reference design।
Cassandra-র vnodes। Token-allocation gossip-managed; vnode-count tuning (256 → 16) history-তেই vnode-এর own-trade-off: ভালো balance, খারাপ repair-scope।
MongoDB balancer। Dynamic split/merge live নমুনা — chunk-size পার হলে split, imbalance-window-এ migration; threshold-ই ops-knob।
Instagram celebrity-id story। Viral user-id salting + read-side aggregation — hotspot-playbook-এর classic engineering-blog tale।
DynamoDB partition-splitting। 10GB-তে auto-split, burst-এ hot-partition alert + adaptive-capacity — hotspot-এর managed-cloud উত্তর।
যে ভুলগুলো সবাই করে
“Consistent hashing মানে topology-change-এ কোনো key-ই সরে না।”
সরে — শুধু অনেক কম। Add-one-node-এ আদর্শে 1/(n+1), vnode-quantization-
এ তার চেয়ে সামান্য বেশি (simulation: 22.5% vs theory 20%)। Promise-টা
‘minimal disruption’, ‘no disruption’ না — copy-bandwidth-ও planning-এ রাখুন।
“Shard যত বাড়াব, throughput তত বাড়বে — ছোট ছোট অনেক shard করি।”
Scatter-gather-এর tail-math উল্টো গল্প বলে: 1-(1-p)^S — shard-count-ই
p99-multiplier; metadata-size, connection-overhead — সব O(S)। Shard-size
বাছাই balance-vs-fanout optimization, one-direction race না।
“Salting-ই hotspot-এর universal ঔষধ — সব hot-key salt করে ফেলি।”
দাম fan-out: read-side S-way lookup + merge, range-query ভাঙে, unique- constraint জটিল; cold-again key-র un-salt-ও chore। Playbook: detector আগে flag করে, salt শুধু proven-hot key-কে — reactive, reversible plan সহ।
“Global secondary index-ই ভালো — read এক shard-এই পাওয়া যায়।”
Read-side জিতলে write-side হারে: base-write index-shard-এও যায় — partial-failure-এ index-drift, low-cardinality attribute (status=‘active’) নিজেই index-এ hotspot। Local-index-এর scatter-gather-read অনেক workload-এ সস্তা — choice read/write ratio-র।
বুঝেছেন কি না দেখুন
1Request-routing-এর তিন topology-তে shard-map-এর কপি কার কাছে থাকে,
আর বাড়তি hop কয়টা?
স্মরণ
Client-direct: client-এর কাছে (config-service snapshot), hop=০। Proxy-tier: proxy-র কাছে (vtgate/mongos), hop=১। DNS sharding: DNS-answer-এ IP-group স্তরে — granularity মোটা, per-key routing নেই।
2‘Shard-map একটা shared-state — তাই তার মালিককে consensus লাগবেই।’
যুক্তিটা খুলুন; gossip-only alternative-এর দাম কী?
যুক্তি
একাধিক writer (node-join/leave decision) split-brain-এ দুই conflicting-map ছড়াতে পারে — single-authority দরকার, মানে raft-majority on config-store। Gossip-only: map eventually-converges — দাম propagation-window-এ conflicting-routing (hint-redirect-এ সামলাতে হয়) আর flapping-ঝুঁকি; metadata-plane-এও CAP-খেলা চলে।
3৮-node vnode-ring-এ এক node যোগে আদর্শ moved %? Mod-hash-এ? ১০০০
shard scatter-gather-এ per-shard slow-prob ১% হলে overall-slow?
প্রয়োগ
Ring: 1/(8+1) ≈ 11.1%। Mod-hash: 8/9 ≈ 88.9% — near-total reshuffle।
Tail: 1 − 0.99¹⁰⁰⁰ ≈ 99.96% — প্রায় প্রতিটা query একটা slow-shard-
এর ঘড়িতে; ১০০০-shard scatter-gather OLTP-তে non-starter।
4Dynamic split hot-range-কে দুভাগ করলেও hot-key-এর সমস্যা কেন থেকে
যায়? কোন অবস্থায় split তবু সাহায্য করে?
যুক্তি
Split ভাগ করে range, key নয় — এক key-র request তবু এক shard-এ, আগুন অটুট। Split সাহায্য করে heat যখন many-distinct-keys-এর range- জড়ো-হওয়া থেকে — ভাগে, দুই node ভাগ করে নেয়। Hot-single-key-এর ঔষধ ভিন্ন: salting, replica-fanout, cache-front।
5Social-feed table: user-post point-read dominant, analytics-team মাঝে
মাঝে all-posts-by-hashtag scan চায়। Local না global secondary index?
Hashtag-এর cardinality কোথায় ভূমিকা রাখে?
ডিজাইন
Point-read-dominant OLTP → write-path সস্তা রাখুন: local-index + rare-scan dedicated analytics-path-এ (batch scatter-gather, আলাদা SLA)। Global-index তখনই যখন hashtag-lookup real-time-interactive। High- cardinality tag global-index-এ সুন্দরভাবে ছড়ায়; low-cardinality (#trending) index-shard-কেই hotspot করে — তখন salt/split-ও লাগে।
এরপর কী
এরপর কী
Routing, rebalancing, hotspot — shard-cluster-এর যন্ত্র এখন হাতে। কিন্তু p99 উঠলে প্রশ্ন হবে: কোন request, কোন service-সীমানায়, কোন span-এ সময় গিলল? Logs বলে discrete-events, metrics বলে aggregates — এক request-এর causal-journey-র মানচিত্র দেয় শুধু distributed tracing। পরের লেসনে trace-span anatomy, W3C traceparent-এর byte-dissection, head/tail sampling-এর retention-economics, আর নিজের হাতে একটা TraceCollector — waterfall, critical-path, orphan-diagnostics সহ। Module-এর শেষ লেসন — দেখা হবে wrap-up-এ।
আরও পড়ুন
- Designing Data-Intensive Applications — Partitioning ও Consistency chapters — Martin Kleppmann · Partitioning strategies, rebalancing-এর তিন প্রকার আর request-routing-এর classification — এই লেসনের কাঠামোর ভিত্তি
- Dynamo: Amazon's Highly Available Key-value Store (SOSP 2007) — Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al. · Consistent-hashing ring-এ virtual node-এর জন্ম — vnode-rebalancing-এর মূল engineering reference