Join Algorithms — nested loop, hash, merge
Join Algorithms
একই join তিনভাবে করা যায় আর তিনভাবের খরচ দশগুণে দশগুণ আলাদা — nested loop, hash, merge-এর গণিত, আর PostgreSQL-এ প্রতিটাকে জোর করে চালিয়ে মাপার পদ্ধতি।
আগে এটা বুঝি
গত লেসনে আমরা দেখলাম SQL text কীভাবে plan tree হয়ে executor-এ পৌঁছায়। সেই tree-র node-গুলোর মধ্যে একটা জাত সবসময় সবচেয়ে বেশি সময় খায়: join। Production-এর প্রায় প্রতিটা ধীর query-র ভেতরে একটা mis-chosen join node থাকে।
সংজ্ঞা মৌলিক: relational algebra-র join definition matching tuples-এর pair — কিন্তু হিসাব করার পথ অনেক, আর পথের পার্থক্য সহজেই হাজার গুণ। একই ২-table join এক algorithm-এ ০.১ সেকেন্ড, আরেকটায় ৯০ সেকেন্ড — data এক, hardware এক, শুধু strategy আলাদা।
আজকের তিন নায়ক আর তাদের রাজত্ব:
- Nested loop family — brute force থেকে buffer-aware block version, তারপর index-এর জাদু
- Hash join — ছোট side দিয়ে hash table, বড় side stream; memory ফুরালে grace partitioning
- Sort-merge join — sorted input-এর রাজা, duplicate-এর ধৈর্যশীল handler
শেষ পর্যন্ত PostgreSQL-এ প্রতিটাকে জোর করে চালিয়ে নিজের হাতে timing মাপবেন — planner-এর decision আর বাস্তব clock-এর মিল নিজের চোখে।
মূল ধারণা
Nested loop — ভিত্তি থেকে পরিশীলন
Naive: বাইরের table-এর প্রতিটা tuple-এর জন্য ভেতরের table-এর সব tuple ঘেঁটে মেলান — <Term id="asymptotic-notation">asymptotic notation</Term>-এর ভাষায় O(T_R · T_S) tuple-pair। Page-স্তরে আরও খারাপ: প্রতিটা outer page-এ পুরো inner table আবার পড়া — disk বারবার ঘুরবে।
Block-nested loop: buffer pool-এর জ্ঞান কাজে লাগাও। B-টা buffer frame থাকলে B−2 টায় outer-এর block, ১-এ inner-এর এক page, ১-এ output। এবার inner scan হয় outer-এর প্রতি page-এ না, প্রতি chunk-এ একবার:
IO(R ⋈ S) = P_R + ⌈P_R / (B − 2)⌉ × P_SIndex-nested loop: ভেতরের column-এ index থাকলে ঘেঁটে দেখার দরকারই নেই — প্রতিটা outer tuple-এ শুধু index lookup (গড় C_index page):
IO ≈ P_R + T_R × C_indexছোট outer + indexed inner (orders.customer_id-র উপর index) — এই combination-এ অন্য কোনো algorithm প্রতিযোগিতাই করতে পারে না।
Hash join — build আর probe
Equality join (a.k = b.k) হলে pair-test-ই লাগে না — <Term id="hash-function">hash function</Term>-ই address বলে দেয়:
- Build: ছোট side-এর প্রতিটা tuple
<Term id="hash-table">hash table</Term>-এ key-ভিত্তিক bucket-এ ঢোকান — memory-তে পুরোটা আটকে গেলে best case - Probe: বড় side stream করুন — প্রতিটা tuple hash করে bucket lookup; match মিললে output
খরচ: দুই side একবারই পড়া + hash table-এর CPU — IO-তে সর্বনিম্ন সম্ভাব্য। শর্ত একটাই: build side memory-তে আটকাতে হবে।
Memory ফুরালে — grace hash: দুই side-কেই একই hash function দিয়ে k-টা partition-এ ভাগ করে disk-এ লিখুন। Key মিলতে হলে দুই tuple-এর একই partition-এ পড়া বাধ্যতামূলক — তাই partition i ↔ partition i join করলেই সব match। Partition-ও বড় হয়ে গেলে? Recursive hashing — সেই partition-কে আবার ভিন্ন seed-এ ভাঙা। PostgreSQL-এর ভাষায় EXPLAIN-এ Hash Batches: 8 — ঠিক এই ভাগটা।
Sort-merge join — সাজানো জগতের বিবাহ
দুই side-ই join key-তে sorted হলে একবার সমান্তরাল walk-ই যথেষ্ট: দুই pointer, ছোটটা advance। Sortedness আসে তিনভাবে — explicit sort node, অথবা index scan যা আগে থেকেই sorted order দেয় (তৃতীয়টাই merge-এর আসল জয়: sort খরচ শূন্য)।
Duplicate-এর সমস্যা: এক key-র left-run × right-run পুরোটা output — একে বলে equi-zone; right pointer zone শেষে ফিরে পড়তে হয়। Extreme skew-তে (সব tuple এক key) merge হারে, nested-loop-ও হারে — hash-ও এক bucket-এ ডুবে যায়; skew-ই সব algorithm-এর common enemy।
Decision table — কে কখন
| অবস্থা | Winner | কারণ |
|---|---|---|
| ছোট outer, indexed inner | Index NL | প্রতি outer tuple-এ সস্তা lookup |
| বড় দুই table, equality, memory ঠিক | Hash join | দুই side একবারই পড়া |
| Input already sorted (index/order) | Sort-merge | sort খরচ শূন্য, streaming |
| Memory ছোট, data বিশাল | Merge বা batched hash | দুটোই disk-friendly |
Inequality join (a.x range vs b.y) | শুধু nested loop family | hash/merge equality-only |
ভেতরে কী ঘটছে
PostgreSQL-এর হাতে-কলমে বিবরণ
enable_* switches নিষেধ না — ভয় দেখানো। SET enable_hashjoin = off planner-কে বলে দেয় ওই path-এর cost-এ বিশাল penalty জুড়ে নিতে; অন্য কোনো বৈধ path না থাকলে সে-ই আবার hash join-ই বেছে নেবে। Point query-তে (unique index + equality) সব switch off করলেও nested loop-ই ফেরে — alternative নেই। তবু tuning/experiment-এ এরাই steering wheel।
work_mem আর Hash Batches-এর গণিত। Hash node build table-এর জন্য বাজেট পায় work_mem — মনে রাখুন, per node, per operation। Build size বাজেট ছাড়ালে batches:
Batches ≈ ⌈ build_size / work_mem ⌉ (power-of-two-তে rounded)প্রতিটা batch temp file-এ; probe-এর tuple নিজের batch-এ route হয়। EXPLAIN (ANALYZE, BUFFERS)-এ temp written=/temp read= spill-এর সাক্ষ্য। Parallel query-তে subtlety: প্রতিটা worker নিজের work_mem — মোট memory ভেবে বসে থাকলে চমক অবধারিত।
Merge-এর শর্ত। PG merge join-এ দুই input-এই নির্দিষ্ট sort ordering লাগে; না থাকলে plan-এ Sort node ঢোকে — merge-এর সুবিধা তখন sort-এর ঋণে ডুবে যায়। B-tree index scan ordering free দেয় — indexed column-এর merge প্রায়ই হঠাৎ জেতে এজন্যই।
Parallel variants। Parallel Seq Scan-এর ওপর Parallel Hash Join (shared hash table — Parallel Hash node) আর parallel-aware nested loop; বড় analytics plan-এ এদের নাম দেখলে চমকাবেন না।
উদাহরণ
পূর্ণ worked example — সংখ্যা দিয়ে
R = customers: 10,000 tuple, 100 page। S = orders: 500,000 tuple, 5,000 page। Usable buffers B = 52। Inner column-এ index (lookup গড় 3 page)।
১. Naive NL (page-level): P_R + P_R×P_S = 100 + 100×5000 = 500,100 reads
২. Block-NL (chunk=50): P_R + ⌈100/50⌉×P_S = 100 + 2×5000 = 10,100 reads ← ৪৯× কম
৩. Index-NL: P_R + T_R×C_idx = 100 + 10000×3 = 30,100 reads
৪. Hash join: P_R + P_S = 100 + 5000 = 5,100 reads (+CPU)
৫. Sort-merge: sort(R)+sort(S)+P_R+P_S — sorting-ই dominant termReading: বড় unsorted join-এ hash champion। কিন্তু outer 50-tuple হলে? Index-NL: 100 + 50×3 = 250 — প্রায় শূন্য। Outer-এর size formula ঘুরিয়ে দেয়। আর orders যদি (customer_id) index থেকে আসে (sorted free), merge হঠাৎ competitive। একটা worked example-ই সার: absolute সেরা algorithm নেই — parameter space-এ প্রতিটার নিজের রাজ্য।
ছবিতে
Hash join lifecycle + decision ladder
BUILD PHASE PROBE PHASE
──────────── ────────────
smaller side larger side (streamed)
─────────────┐ │
▼ ▼
┌───────────┐ h(k) ┌────────────┐
│hash table │◀───buckets──────│ per-tuple │
│(memory) │ ┌────────┐ │ h(k)+lookup│
└───────────┘ │ match? ├───┴─────┬──────┘
│ yes→out│ │ no→skip
└────────┘
memory-তে ধরা না গেলে (grace):
both sides ──▶ h₁(k) mod k ──▶ P₀..P₄ (disk)
│
প্রতি partition-pair-এ আবার build+probe
(partition-ও বড় হলে recursive: h₂ seed)planner-এর মাথার ভেতরের সিঁড়িটা ঠিক এরকম:
- equality predicate আছে?না হলে সরাসরি nested loop family — আর প্রশ্ন নেই
- outer ছোট + inner indexed?হ্যাঁ হলে index-NL প্রায় অপ্রতিরোধ্য
- input sorted (index/ORDER BY)?হ্যাঁ হলে merge candidate — sort খরচ শূন্য
- build side memory-তে?হ্যাঁ → in-memory hash; না → batches/grace partitioning
- cost model final comparesurvivors-দের estimated cost মিলিয়ে winner — পরের লেসনের বিষয়
নিজে চালিয়ে দেখুন
planner-কে জোর করে তিন join চালান, work_mem কমিয়ে spill দেখুন
ধাপ ১ — dataset:
CREATE TABLE cust_small (id int PRIMARY KEY, tier text);
INSERT INTO cust_small
SELECT g, CASE WHEN g % 9 = 0 THEN 'gold' ELSE 'basic' END
FROM generate_series(1, 2000) g;
CREATE TABLE orders_big (id int, cust_id int, amount numeric);
INSERT INTO orders_big
SELECT g, (random() * 1999)::int + 1, round((random() * 500)::numeric, 2)
FROM generate_series(1, 300000) g;
ANALYZE cust_small; ANALYZE orders_big;ধাপ ২ — তিনবার, প্রতিবার একটা করে force করে:
-- A) nested loop:
SET enable_hashjoin = off; SET enable_mergejoin = off;
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*) FROM cust_small c JOIN orders_big o ON o.cust_id = c.id;
-- B) hash join:
SET enable_nestloop = on; SET enable_mergejoin = off;
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*) FROM cust_small c JOIN orders_big o ON o.cust_id = c.id;
-- C) merge join:
SET enable_mergejoin = on; SET enable_hashjoin = off;
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*) FROM cust_small c JOIN orders_big o ON o.cust_id = c.id;প্রতিটা plan-এর root-এর কাছের join node-এর নাম verify করুন (Nested Loop / Hash Join / Merge Join)। C-তে দুই side-এ Sort node এসেছে কি না দেখুন — cust_small-এর PK index হয়তো এক side-কে free ordering দিয়ে দিয়েছে। টেবিলটা আপনার মেশিনের সংখ্যায় ভরুন:
| algorithm | execution ms | notes |
|-------------|--------------|--------------------------------|
| nested loop | ___ | loops=? rows-per-loop=? |
| hash join | ___ | Batches=1 |
| merge join | ___ | Sort nodes present? extra mem? |ধাপ ৩ — work_mem কমিয়ে spill:
RESET enable_hashjoin; RESET enable_mergejoin;
SET work_mem = '64kB'; -- ইচ্ছাকৃত দরিদ্র বাজেট
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*) FROM cust_small c JOIN orders_big o ON o.cust_id = c.id;-> Hash Join ...
Hash Cond: (o.cust_id = c.id)
-> Hash
Buckets: 2048 Batches: 16 Memory Usage: 40kB ← আগে Batches: 1!
Buffers: ... temp read=210 temp written=208 ← disk-এ গেছে!Batches: 16, temp read/written non-zero — grace partitioning live। এবার SET work_mem = '4MB' — Batches নামবে, temp blocks শূন্যের দিকে, time কমবে। তিনটা work_mem value-র সংখ্যা পাশাপাশি রাখুন — optimizer লেসনের cost model ঠিক এই behavior predict করবে।
একই data-তে তিন join algorithm-এর execution time স্পষ্টভাবে আলাদা হয়, আর work_mem কমালে Hash Batches 1 থেকে বেড়ে disk spill (temp blocks) দেখায় — memory constraint-ই batching-এর কারণ, theory আর EXPLAIN হুবহু মেলে।
নিজে বানান
তিন join engine + benchmark matrix — নিজের হাতে ছোট query processor
- lists-of-dicts input-এ nested_loop, hash_join, sort_merge_join তিনটা function
- Duplicate key সব engine-এ ঠিক handle হয় — merge-এ equi-zone logic
- dict-based ground truth বানিয়ে তিনটার output cross-check
- Benchmark sizes {100×100k, 10k×10k} — runtime matrix প্রিন্ট
- Naive nested loop-এ pair-test cap — ১০⁸ টানার আগে skip
লক্ষ্য: theory-র তিন algorithm production-grade না হলেও semantics-complete হোক — duplicate, empty side, সব edge ঠিক। Benchmark-এ তাদের পার্থক্য নিজের চোখে।
import random
import time
random.seed(42)
# ---------- engines ----------
def nested_loop_join(left, right, lk, rk):
out = []
for l in left: # প্রতিটা left tuple-এ
for r in right: # পুরো right scan
if l[lk] == r[rk]:
out.append((l, r))
return out
def hash_join(left, right, lk, rk):
ht = {} # build: right side
for r in right:
ht.setdefault(r[rk], []).append(r)
out = [] # probe: stream left
for l in left:
out.extend((l, m) for m in ht.get(l[lk], ()))
return out
def sort_merge_join(left, right, lk, rk):
L = sorted(left, key=lambda t: t[lk])
R = sorted(right, key=lambda t: t[rk])
out, i, j = [], 0, 0
while i < len(L) and j < len(R):
lv, rv = L[i][lk], R[j][rk]
if lv < rv:
i += 1
elif lv > rv:
j += 1
else: # equi-zone: পুরো run × পুরো run
i2 = i
while i2 < len(L) and L[i2][lk] == lv:
i2 += 1
j2 = j
while j2 < len(R) and R[j2][rk] == lv:
j2 += 1
out.extend((a, b) for a in L[i:i2] for b in R[j:j2])
i, j = i2, j2
return out
def ground_truth(left, right, lk, rk):
idx = {}
for r in right:
idx.setdefault(r[rk], []).append(r)
return [(l, m) for l in left for m in idx.get(l[lk], ())]
# ---------- data ----------
KEY_POOL = 5000 # ইচ্ছাকৃত ছোট → duplicates guaranteed
def make_rows(n):
return [{'k': random.randrange(KEY_POOL), 'p': f'r{i}'}
for i in range(n)]
# ---------- correctness ----------
L, R = make_rows(300), make_rows(400)
def sig(rows):
return sorted((a['p'], b['p']) for a, b in rows)
gt = sig(ground_truth(L, R, 'k', 'k'))
for name, fn in [('nested', nested_loop_join),
('hash', hash_join),
('merge', sort_merge_join)]:
assert sig(fn(L, R, 'k', 'k')) == gt, f'{name} mismatch!'
print('correctness: ৩টা engine-ই ground truth multiset-এর সাথে মিলেছে')
# ---------- benchmark matrix ----------
ALGOS = [('nested', nested_loop_join), ('hash', hash_join), ('merge', sort_merge_join)]
MAX_PAIR_TESTS = 2 * 10 ** 7 # naive-এর জন্য safety cap
print(f"\n{'size':>13} | {'nested':>26} | {'hash':>10} | {'merge':>10}")
print('-' * 68)
for nl, nr in [(100, 100_000), (10_000, 10_000)]:
left, right = make_rows(nl), make_rows(nr)
cells = []
for name, fn in ALGOS:
if name == 'nested' and nl * nr > MAX_PAIR_TESTS:
cells.append(f'skip (est {nl * nr:,} tests)')
continue
t0 = time.perf_counter()
got = fn(left, right, 'k', 'k')
dt = time.perf_counter() - t0
cells.append(f'{dt * 1000:>9.1f} ms')
print(f'{f"{nl}x{nr}":>13} | {cells[0]:>26} | {cells[1]:>10} | {cells[2]:>10}')সাধারণ ফলাফল (laptop, Python 3.12 — সংখ্যা machine-ভেদে বদলাবে, pattern দেখুন):
correctness: ৩টা engine-ই ground truth multiset-এর সাথে মিলেছে
size | nested | hash | merge
--------------------------------------------------------------------
100x100000 | 790.0 ms | 30.4 ms | 108.2 ms
10000x10000 | skip (est 100,000,000 tests) | 16.6 ms | 31.9 msতিনটা reading: (১) hash-এর build cost amortize হয় — বড় probe-এ flat। (২) merge-এর খরচ মূলত sorting; input pre-sorted হলে সে সবাইকে ছাড়ায়। (৩) nested-এর quadratic curve ১০⁸ pair-test-এ minutes-এ ডুবত — planner বড় unindexed join-এ তাকে কেন বাছে না, benchmark-ই উত্তর।
নিজে বাড়ান:
- Pre-sorted input দিয়ে merge মাপুন —
sorted()call বাদ দিয়ে timing তুলনা - Grace-hash simulation: hash_join-এ max-memory-budget + partition-recurse যোগ করুন
- Skew test: KEY_POOL = 10 করে equi-zone explosion দেখুন — merge-এর zone আর hash-এর bucket দুটোই ফুলে যাবে
বাস্তব সিস্টেমে
বাস্তব জগতে join algorithms
MySQL-এর ইতিহাস-পাঠ। বহু বছর MySQL-এ শুধু nested-loop variants — hash join 8.0.18-তে (2019) এলো। এক algorithm-এর একচ্ছত্রতা optimizer-কে কতটা অন্ধ করে, decade-long সেই গল্প।
Spark আর distributed world। Spark SQL-এ shuffle hash vs sort-merge choice ঠিক আজকের decision table; shuffle = grace partitioning-এর network রূপ। Skew hint (SKEW JOIN) আসলে equi-zone explosion-এর প্রতিষেধ।
OLTP বনাম OLAP split। Point-lookup-heavy workload-এ (auth, session store) index-nested loop দিনে লক্ষবার চলে; analytics-এ উল্টো — hash king, nested loop প্রায় নিষিদ্ধ। এক database, দুই জগৎ, দুই বিজয়ী।
DuckDB-র single-machine বিপ্লব। Vectorized hash join + perfect-hash specialization laptop-এ billion-row join — algorithm engineering-এর ceiling কোথায়, তার প্রমাণ।
যে ভুলগুলো সবাই করে
“enable_hashjoin = off দিলে hash join আর কখনো চলবে না।”
Off মানে অত্যন্ত অপছন্দের — cost-এ বিশাল penalty, কিন্তু path বৈধই থাকে। Alternative না থাকলে planner ফিরে এসেই hash বেছে নেবে। Switches steering wheel, brake না।
“Hash join সবসময় fastest — nested loop legacy technology।”
ছোট outer + selective index-এ index-NL milliseconds-এ শেষ, যেখানে hash-এর build phase নিজেই বড় side-এর পূর্ণ scan চায়। OLTP point query-তে nested loop রাজা, full analytics-এ hash। Absolute champion নেই — parameter-ই রাজা।
“Merge join করতে SQL-এ ORDER BY লিখতেই হয়।”
ORDER BY result-এর ব্যাপার, join-input-এর না। Planner চায় input physically sorted — B-tree index scan সেটা free দেয়, না পেলে নিজেই Sort node বসায়। Query text-এ ORDER BY ছাড়াও merge join সম্পূর্ণ বৈধ ও common।
“work_mem বড় করে রাখলে সব hash join দ্রুত হবে।”
work_mem per hash node, per operation, parallel-এ per worker — allocate হয়। ১০০ concurrent connection × ক’টা join node × 256MB: মোট RAM সহজেই শেষ, swap-এ গেলে পুরো DB ঝুলে যায়। Batching-এর disk spill ধীর কিন্তু নিরাপদ; OOM দ্রুত কিন্তু মারাত্মক।
বুঝেছেন কি না দেখুন
1Block-nested loop-এর IO সূত্রটা লিখুন — B বাড়লে ঠিক কোন ভাগ কমে?
স্মরণ
IO = P_R + ceil(P_R / (B−2)) × P_S। B বাড়লে chunk বড় হয় → ceil(P_R/(B−2)) ভাগটা (inner re-scan সংখ্যা) কমে। B যথেষ্ট (outer পুরোটা ধরার মতো) হলে scan মাত্র ১ — নিম্নসীমা।
2Outer 80 page, inner 2000 page, buffers B = 22। Block-NL-এ inner কতবার scan, মোট IO কত?
প্রয়োগ
Chunk = B−2 = 20 page → scans = ceil(80/20) = 4। IO = 80 + 4×2000 = 8,080 reads। Outer-এর page count scan সংখ্যা ঠিক করে; inner-এর size শুধু গুণফলে ঢোকে।
3Hash join-এ build side কেন ছোট side? বড় side build করলে ঠিক কী খারাপ হয়?
যুক্তি
Hash table পুরোটা memory-তে থাকতে হয় — ছোট side বেছে footprint ও spill probability কমে। বড় side build করলে table ফুলে batches/temp বাড়ে, অথচ probe (ছোট side) তবু পুরো পড়তে হয় — দুই দিকেই ক্ষতি, লাভ শূন্য।
4EXPLAIN-এ Hash Join-এর ভেতরে Batches: 64, Memory Usage: 30MB — মানে কী? Batches কমানোর তিনটা পথ?
যুক্তি
Batches: 64, Memory Usage: 30MB — মানে কী? Batches কমানোর তিনটা পথ?Build side work_mem (30MB) ছাড়িয়েছে — ৬৪ partition-এ ভাগ হয়ে disk-এ spill করছে (temp files)। পথ: (১) work_mem বাড়ানো, (২) build side ছোট করা — filter/pushdown early reduce, (৩) statistics ঠিক করা — ভুল estimate-এ planner build-side মাপও ভুল ধরে (ANALYZE)।
5employees e JOIN contracts s ON s.end_date greater-than e.hire_date — planner কোন algorithm-গুলোর মধ্যে আটকে যাবে, কেন?
প্রয়োগ
employees e JOIN contracts s ON s.end_date greater-than e.hire_date — planner কোন algorithm-গুলোর মধ্যে আটকে যাবে, কেন?শুধু nested loop family। Hash bucket এক specific key-র ঠিকানা — range predicate বহু bucket জুড়ে থাকে, O(1) lookup অর্থহীন। Merge sorted-equality pairing ধরে চলে। Range-range matching-এর কোনো linear-time trick নেই — block/index-NL-ই একমাত্র বৈধ পথ।
6Report query-তে Merge Join + দুইটা Sort node, Sort Method: external merge Disk: 1.2GB, runtime ৪০s। Diagnosis আর তিন remedy?
ডিজাইন
Merge Join + দুইটা Sort node, Sort Method: external merge Disk: 1.2GB, runtime ৪০s। Diagnosis আর তিন remedy?Diagnosis: কোনো input physically sorted না — planner sort করে merge করছে, আর sort দুটো memory-র বাইরে disk-এ। Remedy ১: এক side-এ join-key-র B-tree index → free ordering, Sort node গায়েব। Remedy ২: work_mem বাড়িয়ে in-memory sort (per-op budget মনে রেখে)। Remedy ৩: merge-এর সুবিধা এখানে নেই — enable_mergejoin=off দিয়ে hash-path test। মূল শিক্ষা: external sort silent disk tax।
এরপর কী
এরপর কী
আজ দেখলাম তিন algorithm কীভাবে চলে — বড় প্রশ্নটা খোলাই রইল: planner কীভাবে বাছে? আর multi-way join-এ (৫-table!) কোন জোড়া আগে — order-এর সংখ্যা কোটি ছাড়ায়।
পরের লেসনে query optimizer-এর মস্তিষ্ক:
- Optimization = search problem — plan space কত বড়, equivalence rules কীভাবে সেটা কাটে
- Cost model-এর ভেতরে: seq_page_cost, random_page_cost, cpu_tuple_cost — startup-vs-total আর LIMIT-এর connection
- System-R dynamic programming bottom-up enumeration — wide join-এ greedy + genetic algorithm (geqo)
- random_page_cost flip করে plan উল্টে দেখা — নিজের হাতে
আজকের timing টেবিল সংরক্ষণ করুন — পরের লেসনের cost formulas সেই সংখ্যাগুলোই predict করতে শেখাবে।
আরও পড়ুন
- Database Systems: The Complete Book, Chapter 15 — Query Execution Algorithms — Hector Garcia-Molina, Jeffrey Ullman, Jennifer Widom · Nested-loop, hash আর sort-based join-এর IO সূত্র — এই লেসনের B-buffer গণিতের উৎস
- PostgreSQL Documentation — Query Planning (Planner Cost Constants) — PostgreSQL Global Development Group · work_mem ও enable_* GUC-দের official semantics
- Database Internals, Part 1 — Query Execution: Join Algorithms — Alex Petrov · Grace hash আর hybrid hash join-এর modern treatment