NoSQL — document, KV, wide-column, graph
NoSQL: Four Families
Key-value, document, wide-column, graph — চার পরিবারের NoSQL-এর জন্মকথা, ভেতরের যন্ত্র, লুকানো বিল; আর JSONB-যুগে সীমানা ঝাপসা হয়ে যাওয়ার গল্প।
আগে এটা বুঝি
২০০০-এর দশকের মাঝখানে web-এর scale হঠাৎ ভিন্ন গ্রহের হয়ে গেল — Google-এর ডেটা petabyte-ছুঁল, Amazon-এর cart পৃথিবী-জুড়ে, আর relational database-এর তখনকার সবচেয়ে বড় ধর্মগ্রন্থ vertical-scale- কে বলছিল “আরও বড় box কিনুন”। Bigtable (2006) আর Dynamo (2007) paper-দুটো ঘোষণা করল: horizontal scale আর schema-flexibility — এই দুটোর জন্য relational model-এর অংশবিশেষ ছেঁড়ে ফেলা যায়। Movement-টার নাম হলো NoSQL।
আজ, দুই দশক পরে, ঠান্ডা মাথায় ফেরা যায়: NoSQL কোনো religion নয় — চারটা আলাদা engineering-answer, চারটা আলাদা প্রশ্নের:
- “key দিলে value দাও, আর কিছু নয়” — key-value
- “nested document জমাই, structure পরে বুঝি” — document
- “প্রতিটা row-এর column আসলে আলাদা, sparse-ও” — wide-column
- “data-র চেয়ে relationship-ই প্রধান” — graph
আজকের লেসনে চার পরিবার ঘুরব — প্রতিটার জন্ম-কারণ, ভেতরের যন্ত্র, আর সেই বিজ্ঞাপনে না-লেখা বিলগুলো। শেষে দেখব SQL-জগৎ নিজেই কীভাবে JSONB নিয়ে সীমানা ঝাপসা করল — আর এটাই module-এর শেষ পাতা।
মূল ধারণা
চার পরিবার, চারটা চুক্তিপত্র
| Family | Model | Query-chুক্তি | Representative |
|---|---|---|---|
| Key-value | opaque blob ↔ key | get(key) / put(key,val) — O(1)-ই সব | Redis, DynamoDB |
| Document | nested JSON-ish doc | field-path lookup, doc-scoped aggregation | MongoDB, Couchbase |
| Wide-column | rows × dynamic column-families | partition-key-scoped read | Cassandra, Bigtable, HBase |
| Graph | nodes + typed edges | traversal — pointer-chase | Neo4j, Neptune |
Key-value — ইচ্ছাকৃত অন্ধত্বর গতি
সবচেয়ে কঠোর চুক্তি: server value-র ভেতরটা দেখে না। ফল: indexing-plan নেই, query-planner নেই, lock-scope একটাই (whole value) — O(1) hash-lookup ছাড়া আর কোনো path-ই exist করে না। Redis-এ সব data memory-তে, persistence optional (RDB snapshot / AOF-log — WAL-পরিচিত মুখ!); DynamoDB-তে LSM Tree-engine write-amplification-tuned disk-এ।
দুটো built-in ধারণা production-এর প্রাণ:
- TTL (
SETEX) — key-এর মৃত্যু-তারিখ আগেই লিখে রাখা; session, cache-entry, OTP — সব auto-expire - Atomic ops on value — INCR, HINCRBY — counter-logic server- side, race-free
Document — schema-on-read-এর স্বাধীনতা
Document-store value-র ভেতরটা বোঝে: nested object/array,
field-path query, doc-ভিত্তিক index। মূল promise: schema-on-read
— লেখার সময় structure enforce নেই, পড়ার সময় আপনি shape ঠিক
করবেন। Product-catalog-এর মতো heterogeneous data-র জন্ম: TV-র
doc-এ screen_size, book-এর doc-এ page_count — এক collection,
কোনো NULL-column-স্তূপ নেই।
চুক্তির অন্ধ-দিক: join নেই। Related-data হয় embed করবেন (denormalize — duplicate + update-anomaly-র ঝুঁকি), নয় client-side multi-query (N+1)। Integrity-ও app-এর দায় — database invariant enforce করে না। Schema-flexibility-র দাম এখানেই: constraint যেখানে লেখা হয় না, সেখানে ভাঙারও সুযোগ।
Wide-column — partition-key-ই আপনার query-design
Cassandra-র table দেখতে SQL-এর মতো, কিন্তু দর্শন উল্টো: আগে query ভাবুন, তারপর table। Partition-key (hash-sharded — sharding লেসনের ring!) ঠিক করে কোন data একসাথে বসবে; clustering-columns ঠিক করে ভেতরের sort-order। Query যদি partition-key include করে — single-node O(log n); না করে — full-cluster scan, production-এ নিষিদ্ধ-প্রায়। Ad-hoc analytics-এর জন্য এই জগৎ নয় — সেটা warehouse-দের কাজ।
Graph — relationship-first জগৎ
Property-graph model: node-এ property-map, edge-এ type+direction+ property। Social-feed, fraud-ring, recommendation — “friends-এর friends-এর liked pages” জাতীয় প্রশ্ন relational-এ multi-way join বা recursive CTE; graph-এ pointer-chase: node-এর adjacency-list memory-তে, প্রতিটা hop constant-cost — এটাই index-free adjacency (neighbor খুঁজতে global index নয়, direct reference)। Depth বাড়লে join-এর খরচ exponential-blowup-ঘেঁষে, traversal-এর linearly।
ভেতরে কী ঘটছে
LSM-connection — wide-column-এর engine
Sharding লেসনের consistent-hash ring Cassandra-র data-placement ঠিক করে; ভেতরের storage-engine আমাদের চেনা: LSM Tree — memtable, SSTable, compaction, bloom-filter। Write-optimized path-ই Cassandra-র write-heavy workload-claim-এর রহস্য। আর delete = Tombstone marker — compaction পর্যন্ত বাঁচে; tombstone-storm (bulk-delete) হলো read-latency-র ক্লাসিক শত্রু। Module-এর আগের লেসনগুলো এখানেই ফসল দিচ্ছে — engine-ভিন্ন হলেও সমস্যা-সেট universal।
Redis-এর persistence — পরিচিত দুই পথ
Snapshot (RDB): fork করে copy-on-write image dump — buffer-pool-
flush-এর আত্মীয়। AOF (append-only file): প্রতিটা write-command log
— হুবহু WAL-দর্শন, fsync-policy knob (always/everysec/no) —
synchronous_commit-ladder-এর ছোট ভাই। Crash-recovery = AOF-replay।
“NoSQL-এ durability নেই” myth-এর জবাব: আছে, আপনি টিউন করেন কতটুকু।
Consistency-knob — CAP-লেসনের ফসল
DynamoDB/Cassandra-র consistency-level per-operation: ONE,
QUORUM, ALL (write-side W, read-side R)। W+R > N হলে
read-write overlap-এ fresh-read guarantee (quorum-গণিত — Level 9-
এর পূর্ণ বিষয়)। LWW-conflict, hinted-handoff, read-repair —
eventual-consistency-র merge-machinery এখানে production-grade।
CAP-লেসনের “per-operation choice” এখানেই বাজারে।
উদাহরণ
একই feature — চার পরিবারে চার রূপ
Feature: e-commerce session + cart + recommendation-graph।
KEY-VALUE (Redis)
SETEX sess:{token} 1800 '{"uid":42,"cart":[...]}'
-> ৩০ মিনিটের জীবন, TTL-এ নিজেই মরে; GET O(1)
-> প্রশ্ন ছাড়া প্রশ্ন: token ছাড়া কিছুই খোঁজা যায় না
DOCUMENT (MongoDB)
db.carts.insertOne({_id:42, items:[
{sku:"A1", qty:2}, {sku:"B7", qty:1}],
coupon:{code:"EID25", pct:25}})
-> cart এক doc — read atomic, write এক round-trip
-> কিন্তু "সব user যারা EID25 নিয়েছে" — collection scan
বা app-side fan-out (coupon-index আলাদা বানানো ছাড়া)
WIDE-COLUMN (Cassandra)
CREATE TABLE cart_events(
user_id uuid, ts timeuuid, sku text, qty int,
PRIMARY KEY ((user_id), ts));
-> partition=user_id: এক user-এর timeline single-node,
time-ordered; retention = TTL-per-row
-> "সব cart যেখানে sku=B7" — partition-key নেই: forbidden
GRAPH (Neo4j)
(:User {id:42})-[:VIEWED]->(:Product {sku:"B7"})
-[:BOUGHT]->(:Product)
MATCH (u:User {id:42})-[:BOUGHT]->(p)<-[:BOUGHT]-(similar)
(similar)-[:BOUGHT]->(rec)
RETURN rec LIMIT 5
-> "যারা B7 কিনেছে তারা আর কী কিনেছে" — ৩-hop chase,
index-free adjacency-তে ms-scaleএকই domain, চারটা আলাদা প্রশ্ন-প্রকৃতি — আর বাস্তব product-গুলো তাই polyglot: Redis-session + PG-orders + Cassandra-events + graph-service। Pragmatism, fashion নয় — প্রতিটা store-যন্ত্র তার প্রশ্নের জন্য।
ছবিতে
চার মডেলের ভূগোল এক ছবিতে
KEY-VALUE DOCUMENT WIDE-COLUMN GRAPH
┌───────────┐ ┌────────────────┐ ┌───────────────┐ ┌──────────┐
│ k1 ─► ▓▓▓ │ │ {_id:1, │ │ partition-key │ │ (U42)───►│
│ k2 ─► ▒▒▒ │ │ user:{city: │ │ ┌───────────┐ │ │ VIEWED │
│ k3 ─► ░░░ │ │ "Dhaka"), │ │ │row: cols..│ │ │ ▼ │
│ (opaque) │ │ items:[…]} │ │ │sorted by │ │ │ (P"B7") │
│ │ │ (understood!) │ │ │clustering │ │ │ ▲ │ │
└───────────┘ └────────────────┘ │ └───────────┘ │ │ └──┘
└───────────────┘ (pointer-chase)
query-power: kv < document ≈ wide-column* graph(traversal-এ)
flexibility: kv > document > wide-column > graph(schema)
*: শর্তসাপেক্ষ — partition-key-scoped query-তেইনিজে চালিয়ে দেখুন
JSONB+GIN, TTL-অনুকরণ, আর recursive CTE বনাম N+1
ধাপ ১ — document-store pattern, PG-তেই:
CREATE TABLE events (id bigserial PRIMARY KEY, doc jsonb NOT NULL);
INSERT INTO events (doc)
SELECT jsonb_build_object(
'user', jsonb_build_object('id', g % 5000,
'city', (ARRAY['Dhaka','Ctg','Sylhet'])
[1 + g % 3]),
'action', (ARRAY['view','cart','buy'])[1 + g % 3])
FROM generate_series(1, 200000) g;
\timing on
EXPLAIN ANALYZE SELECT count(*) FROM events
WHERE doc @> '{"user": {"city": "Dhaka"}}';
-- Seq Scan — পুরো ২ লক্ষ row
CREATE INDEX events_gin ON events USING gin (doc);
EXPLAIN ANALYZE SELECT count(*) FROM events
WHERE doc @> '{"user": {"city": "Dhaka"}}';
-- Bitmap Index Scan on events_gin — containment-index!@> containment-operator নেস্টেড path-এও কাজ করে, GIN সেটাই
inverted-index করে — MongoDB-র field-index-এর সরাসরি প্রতিযোগী।
Timing-দুটো তুলনা করুন — document-pattern-এর দরকারে dedicated
document-store কখন দরকার, কখন PG-ই যথেষ্ট, সংখ্যা বলবে।
ধাপ ২ — Redis-style TTL, lazy-expiry মডেল:
import time
store = {}
def setex(key, val, ttl_s):
store[key] = (val, time.time() + ttl_s)
def get(key):
item = store.get(key)
if item is None:
return None
val, expires_at = item
if time.time() > expires_at:
del store[key]
return None # lazy expiry — পড়তে গিয়েই মৃত্যু
return val
setex("sess:abc", {"uid": 42}, 2)
print(get("sess:abc")) # {'uid': 42}
time.sleep(2.1)
print(get("sess:abc")) # None — এবং entry-ও মুছে গেল
print(len(store)) # 0Real Redis এর সাথে active-expiry-cycle-ও চালায় (background-এ random-key sample-check) — lazy-only হলে অপঠিত expired-key জমে memory-ফাঁদ। দুই strategy-র division-of-labor মনে রাখুন — housekeeping-লেসনের echo!
ধাপ ৩ — graph-traversal: CTE বনাম N+1:
CREATE TABLE person AS
SELECT g AS id FROM generate_series(1, 400) g;
CREATE TABLE friend (a int, b int);
INSERT INTO friend
SELECT 1 + floor(random()*400)::int,
1 + floor(random()*400)::int
FROM generate_series(1, 1200);
\timing on
-- এক query-তে ৪-depth reach:
WITH RECURSIVE reach AS (
SELECT a, b, 0 AS depth FROM friend WHERE a = 7
UNION
SELECT f.a, f.b, r.depth + 1
FROM friend f JOIN reach r ON f.a = r.b
WHERE r.depth < 4
)
SELECT count(DISTINCT b) AS reached FROM reach;import subprocess, time
def q(sql):
return subprocess.run(["psql", "-Atc", sql],
capture_output=True, text=True).stdout.strip()
t0 = time.perf_counter()
frontier, seen = [7], set()
for depth in range(4): # N+1: প্রতি hop-এ এক query
ph = ",".join(map(str, frontier))
rows = q(f"SELECT DISTINCT b FROM friend WHERE a IN ({ph});")
nxt = [int(x) for x in rows.splitlines() if x]
seen |= set(nxt)
frontier = nxt or []
print(f"N+1 ৪-hop: {(time.perf_counter()-t0)*1000:.0f}ms")
# CTE-র EXPLAIN-এর execution-time-এর সাথে তুলনা করুনCTE single-round-trip-এ set-based; N+1 প্রতি hop-এ process-spawn + network — gap বাড়বে hop-আর latency-বাড়লে। Graph-database-এর traversal-advantage এই gap-এরই বিশাল-সংস্করণ (plus adjacency-in-memory)।
PostgreSQL একাই document-pattern (containment-query + GIN) চালায়; TTL-expiry-র lazy-model হাতে দেখা যায়; আর graph-traversal-এ recursive CTE এক query-তে কাজ শেষ করে যেখানে N+1 নেটওয়ার্ক-রাউন্ডে ডুবে যায়।
নিজে বানান
TinyDocumentStore — append-only + field-path index
- Append-only JSONL log — put/delete record; restart-এ replay
- Nested field-path flattening — user.city জাতীয় path-এ index
- Secondary index — path → value → id-list; put/unput maintenance
- find({path: value}) API — index-hit হলে postings, না হলে scan
- Benchmark — indexed বনাম unindexed-field query, stats ছাপুন
লক্ষ্য: document-store-এর হৃৎপিণ্ড দুটো — durable-append-log আর secondary-index — নিজের হাতে গড়া; benchmark-এ index-এর অস্তিত্বের দাম সংখ্যায়।
import json, os, time
class TinyDocStore:
"""Append-only persisted doc-collection + field-path secondary index"""
def __init__(self, path="/tmp/tinydocs.jsonl"):
self.path = path
self.docs = {} # id -> doc
self.index = {} # path -> {value: [ids]}
self.stats = {"index_hit": 0, "scan": 0}
if os.path.exists(path):
with open(path) as f:
for line in f:
rec = json.loads(line)
self._apply(rec)
self.wal = open(path, "a") # append-only durability
# ---- log-replay == source of truth (WAL-লেসনের echo) ----
def _apply(self, rec):
if rec["op"] == "put":
old = self.docs.get(rec["id"])
if old is not None:
self._unindex(rec["id"], old)
self.docs[rec["id"]] = rec["doc"]
self._index(rec["id"], rec["doc"])
elif rec["op"] == "del":
old = self.docs.pop(rec["id"], None)
if old is not None:
self._unindex(rec["id"], old)
@staticmethod
def flatten(doc, prefix=""):
out = {}
for k, v in doc.items():
path = f"{prefix}.{k}" if prefix else k
if isinstance(v, dict):
out.update(TinyDocStore.flatten(v, path))
else:
out[path] = v
return out
def _index(self, did, doc):
for path, val in self.flatten(doc).items():
self.index.setdefault(path, {}) \
.setdefault(val, []).append(did)
def _unindex(self, did, doc):
for path, val in self.flatten(doc).items():
postings = self.index.get(path, {}).get(val, [])
if did in postings:
postings.remove(did)
def put(self, did, doc):
rec = {"op": "put", "id": did, "doc": doc}
self.wal.write(json.dumps(rec) + "\n")
self.wal.flush()
self._apply(rec)
def delete(self, did):
rec = {"op": "del", "id": did}
self.wal.write(json.dumps(rec) + "\n")
self.wal.flush()
self._apply(rec) # tombstone-marker-অভিনয়
def find(self, where):
((path, want),) = where.items() # single-predicate API
postings = self.index.get(path, {}).get(want)
if postings is not None:
self.stats["index_hit"] += 1
ids = list(postings) # O(postings) — সরাসরি
else:
self.stats["scan"] += 1
ids = [i for i, d in self.docs.items()
if self.flatten(d).get(path) == want]
return [self.docs[i] for i in ids]
# ---------------- benchmark ----------------
db = TinyDocStore("/tmp/tinydocs.jsonl")
if os.path.exists("/tmp/tinydocs.jsonl"):
os.remove("/tmp/tinydocs.jsonl"); db = TinyDocStore(
"/tmp/tinydocs.jsonl")
N = 50000
t0 = time.perf_counter()
for i in range(N):
city = ("Dhaka", "Ctg", "Sylhet", "Khulna")[i % 4]
db.put(i, {"name": f"u{i}", "age": 20 + i % 30,
"address": {"city": city}})
load_s = time.perf_counter() - t0
def bench(where, n=200):
t0 = time.perf_counter()
for _ in range(n):
hits = db.find(where)
ms = (time.perf_counter() - t0) / n * 1000
return len(hits), ms
h1, ms1 = bench({"address.city": "Dhaka"}) # indexed path
h2, ms2 = bench({"nonexistent.field": "x"}) # unindexed -> scan
print(f"loaded {N} docs in {load_s:.2f}s "
f"({N/load_s:,.0f} puts/s)")
print(f"indexed query : {h1} hits, {ms1:.2f} ms/query")
print(f"scan query : {h2} hits, {ms2:.2f} ms/query")
print(f"speedup : {ms2/ms1:.0f}×")
print(f"stats : {db.stats}")
db.delete(0)
assert all(d["name"] != "u0" for d in db.find({"age": 20}))
print("delete+tombstone replay: ok")প্রত্যাশিত output (hardware-ভেদে সংখ্যা):
loaded 50000 docs in 1.31s (38,168 puts/s)
indexed query : 12500 hits, 8.41 ms/query
scan query : 12500 hits, 61.72 ms/query
speedup : 7×
stats : {'index_hit': 200, 'scan': 200}
delete+tombstone replay: okতিনটা পাঠ: (১) index-hit postings-থেকে সরাসরি — hit-count বাড়লেও latency তেমন বাড়ে না, scan-এ linearly বাড়ে; (২) unindexed-field-এর query নীরবে scan-mode-এ ঢুকে যায় — stats-counter-ই একমাত্র সতর্কবার্তা (document-store-এ “missing index” silently slow); (৩) restart করে চালালে log-replay সব state ফিরিয়ে আনবে — durability append-log-ই।
নিজে বাড়ান:
- Multi-predicate AND — intersection-of-postings algorithm
- Log-compaction — পুরনো put-overwrite গুটিয়ে snapshot+tail (LSM-compaction-এর মিনি-রূপ)
- Range-predicate (
age >= 25) — sorted-postings + bisect
বাস্তব সিস্টেমে
বাস্তব জগতে চার পরিবার
Redis — session/cache/rate-limit এর universal sidekick। প্রায় সব web-stack-এর পাশে একটা Redis; TTL, INCR, pub/sub, sorted-set leaderboard — ছোট ছোট চুক্তি, বিশাল কাজ।
MongoDB — content/catalog/profile। Schema-on-read-এর আসল খোরাক heterogeneous-documents; Atlas-যুগে vector-search-সহ AI-era feature-rush। Aggregation-pipeline আজ যথেষ্ট SQL-ish — irony-টা লক্ষ্য করুন।
Cassandra/Bigtable-lineage — telemetry আর time-series-র রাজ্য। Apple, Netflix, Uber-scale write-volume; partition-per-device/user timeline; TTL-per-row retention — housekeeping-কে write-path-এই সরিয়ে দেওয়া।
Neo4j — relationship-ই product যেখানে। Fraud-detection (ring- pattern), knowledge-graph, recommendation; GraphQL-resolver-পেছনের fan-out-ও প্রায়ই graph-store।
Polyglot-এর বাস্তব দাম। প্রতিটা বাড়তি store = আরেকটা operational surface — backup, monitoring, security, team-skill। তাই mature-teams- এর প্রশ্ন প্রথমে: “PG দিয়ে কি আদৌ যায়?” — আর ২০২৬-এর উত্তর প্রায়ই “হ্যাঁ”: JSONB-document, pgvector-embedding, TimescaleDB-series, Apache AGE-graph — extension-ecosystem-ই one-database-many-models- এর যুক্তি। NoSQL-এর সবচেয়ে বড় উত্তরাধিকার হয়তো NoSQL-নিজেই নয় — SQL-জগৎকে ভাঙা-চৌচির comfort-zone থেকে তোলা।
যে ভুলগুলো সবাই করে
“NoSQL মানে 'no SQL' — আর SQL-মানে legacy।”
নামটাই historical accident — মূল hashtag ছিল “No Relational” / “Not Only SQL”। আজ MongoDB-র aggregation JSON-SQL-এর মতোই verbose; Cassandra-র CQL literally SQL-syntax; আর PostgreSQL JSONB+CTE document-ও traversal-ও গিলে ফেলে। Movement-এর আসল উত্তরাধিকার data-model-diversity, language-war নয়।
“Schema-less মানে schema নেই — বাগান-বাঁধাই নেই।”
Schema database-থেকে application-এ সরে গেল, হারায়নি — migration হলো deploy-time code-change, validation হলো hand-written layer। Field-name typo এখন silent-null; rename মানে সব doc-এ backfill-script। “Flexibility”-র আসল অর্থ: schema-র দায়িত্ব আপনার কাঁধে — escape নয়।
“Denormalization document-store-এ free — embed করেই ফেলি।”
Embed-করা data duplicate — আর duplicate-এর ক্লাসিক বিল: update- anomaly। User-এর name হাজার order-doc-এ embedded থাকলে rename মানে হাজার doc-rewrite (বা চিরকালের পুরনো-নাম inconsistency)। Embed করুন শুধু child-data যেটা parent-এর সাথে lifecycle-share করে (order-items order-এর সাথেই জন্ম-মৃত্যু) — reference-data নয়।
“Redis-এ data রাখলেই "cache তো" — হারালে ক্ষতি নেই।”
Session, cart, rate-limit-counter, distributed-lock — Redis-এ production-critical state-এর বিশাল ভাগ আজ। Persistence off বা failover-misconfigured হলে restart মানে login-storm সব-user-এ। Cache-vs-source-of-truth distinction architecture-decision, product-name নয়।
বুঝেছেন কি না দেখুন
1চার পরিবারের প্রতিটার এক-লাইনের core-contract কী?
স্মরণ
KV: key-দিলে opaque-value, আর কিছু নয় (O(1))। Document: nested-doc store + field-path query, schema-on-read। Wide-column: partition-key- scoped sorted-read, ad-hoc নয়। Graph: nodes+typed-edges, traversal first-class।
2Schema-on-read-এর সুবিধা আর দাম — একটা concrete scenario-তে দুটোই
দেখান (যেমন product-catalog)।
যুক্তি
সুবিধা: TV-doc-এ screen_size, book-doc-এ page_count — এক collection- এ heterogeneous-products, নতুন category-তে migration লাগে না, deploy- ই নতুন shape। দাম: “catalog-এর সব item-এ price আছে?” invariant-টা database enforce করে না — app-layer validation; typo-field silent; analytics-এ union-of-shapes-এর জটিলতা। Flexibility = responsibility-shift।
3Index-free adjacency কেন recursive-SQL-এর চেয়ে deep-traversal-এ জেত?
খরচ-বক্ররেখা দুটোর আকৃতি বর্ণনা করুন।
যুক্তি
Relational-এ প্রতিটা hop একটা join/index-probe — global structure-এ খোঁজা; depth d-তে cost বাড়ে branching-factor-এর মতো (recursive CTE set-based হলেও materialize+dedupe overhead)। Graph-এ neighbor-রা node-object-এর direct-reference — hop = pointer-dereference, memory- local; cost ≈ visited-nodes-এর linear। তাই d বাড়লে SQL-কার্ভ steep, traversal-কার্ভ gentle — crossover-এর পরে graph দূরে ছাড়ে।
4Cassandra-table-এ PRIMARY KEY ((user_id), event_time)। এই চার query-
এর কোনটা efficient, কোনটা forbidden-class? (a) user X-এর last-10
events; (b) event_time BETWEEN t1,t2 (global); (c) user X-এর
event_time-range; (d) সব user-এর count।
প্রয়োগ
(a) ✓ — partition-key + clustering-order reverse; (b) ✗ forbidden — partition-key নেই, full-cluster scatter; (c) ✓ — partition-scoped range, clustering-sort-এর মূল লাভ; (d) ✗ — global aggregate, OLTP- path-এ নয় (warehouse/stream-aggregate-এ যাবে)। Rule: partition-key query-তে থাকলেই efficient-path।
5Startup-এ একটা MVP: user-profile + activity-feed + real-time-presence +
analytics। Team-এর proposal: চারটা আলাদা best-of-breed store day-one-
এ। Review-position দিন — polyglot-এর কোন দাম তারা হিসাবেই নেইনি,
আর pragmatic-alternative কী?
ডিজাইন
হিসাবের বাইরে: ৪× operational-surface (backup/security/on-call/skill), cross-store consistency-ঝামেলা (profile-change feed-এ propagate?), early-stage-এ query-pattern এখনো অস্থির — premature specialization। Pragmatic: day-one একটা PG — profile=row, feed=table(+partition later), presence=UNLOGGED/TTL-ish table বা tiny-Redis, analytics=read-replica
- scheduled-rollup। Scale-signal আসলে প্রতিটা অংশ আলাদা হবে — তখন extract, evidence-ভিত্তিক। Polyglot হবে ভাগ্যের ভরসায় নয়, মাপা সিদ্ধান্তে।
এরপর কী
এরপর কী — module wrap-up
Databases module সম্পূর্ণ। আমরা যাত্রা করেছি disk-এর byte থেকে planet-scale architecture — storage-page-র tuple, buffer-pool-এর victim-selection, B-Tree-র split, MVCC-র xmin/xmax, isolation-এর anomaly-লড়াই, WAL-এর ordering-contract, ARIES-র তিন pass, checkpoint- vacuum-এর housekeeping, replication-এর lag-গণিত, sharding-এর ring, CAP-র বাইনারি মুহূর্ত, আর আজ চার পরিবারের model-diversity। মূল সূত্র একটাই ছিল সব লেসন জুড়ে: প্রতিটা feature-এর একটা লুকানো বিল আছে, আর production-ই সেই বিল-পাঠের পরীক্ষাকেন্দ্র।
এরপর Level 9 — distributed systems: এখানে যেগুলোর teaser দেখেছি, সেগুলোর পূর্ণ গণিত — consensus (Raft/Paxos), quorum-এর intersection-বীজগণিত, logical/vector clock, distributed transactions আর 2PC-র ভঙ্গুরতা, failure-detector আর FLP-impossibility। Database- module-এর প্রতিটা “এক machine-এ সত্য” সেখানে প্রশ্ন হয়ে ফিরবে: “ক’টা machine-এ সত্য বানাব কীভাবে?”
আরও পড়ুন
- Designing Data-Intensive Applications, Chapters 2–3 — Data Models and Storage Engines — Martin Kleppmann · Document/graph model-এর trade-off আর four-family taxonomy-র সেরা আলোচনা
- Bigtable: A Distributed Storage System for Structured Data — Fay Chang, Jeffrey Dean, Sanjay Ghemawat, et al. — OSDI 2006 · Wide-column model-টার মূল paper — Cassandra/HBase-এর পূর্বপুরুষ
- PostgreSQL Documentation — JSON Functions and Operators — PostgreSQL Global Development Group · JSONB containment, GIN indexing — SQL-renaissance অংশের উৎস