Statistics ও Cardinality Estimation — optimizer-এর চোখ
Statistics and Cardinality Estimation
Optimizer-এর সব জ্ঞানের উৎস ক'শো page-এর একটা sample — histogram, MCV, n_distinct, correlation কীভাবে সেই sample থেকে জন্ম নেয় আর কখন প্রতারণা করে।
আগে এটা বুঝি
গত লেসনে আমরা optimizer-এর search machinery দেখলাম — DP, cost model, geqo। কিন্তু ইঞ্জিনটা যত পরিশীলিতই হোক, তার প্রতিটা সিদ্ধান্তের ভেতরে একটা অনুমান: “এই filter-এ মোটামুটি কত row টিকবে?” সংখ্যাটার নাম cardinality estimate, আর পুরো optimizer-ই কার্যত ওই একটা সংখ্যার জিম্মাদার।
ভয়ের কথাটা এখানে: database আপনার data সম্পর্কে সব জানে না — জানে একটা ছোট sample, কয়েক হাজার row। ওই অভাবী sample থেকেই সে histogram আঁকে, MCV list বানায়, join-এর ফল অনুমান করে। বেশিরভাগ সময় অনুমান চমৎকার; মাঝে মাঝে চরম প্রতারণা — আর প্রতারিত optimizer হাজার-গুণ খারাপ plan বেছে নেয় পূর্ণ আত্মবিশ্বাসে।
আজকের ভ্রমণ:
ANALYZEঠিক কী কী জমায় — pg_stats-এর পাঁচ field খোলা- Equi-depth histogram আর MCV দিয়ে selectivity-র গণিত
- Correlation stat — physical clustering কীভাবে index-vs-seq ঘুরায়
- Error compounding: কেন ১০% ভুলও join-chain-এ বিপর্যয়
- Extended statistics — correlated column-এর ফাঁদ থেকে মুক্তি
মূল ধারণা
ANALYZE কী জমায়
ANALYZE প্রতিটা table-এর একটা random page-sample নেয় আর প্রতি column-এর জন্য এই তথ্য জমায় (দেখা যায় pg_stats view-তে):
| Field | মানে |
|---|---|
| null_frac | কত ভগ্নাংশ NULL |
| n_distinct | distinct value-র অনুমিত সংখ্যা (-1 = row-scaled ratio) |
| most_common_vals / freqs | সবচেয়ে frequent value আর তাদের ভগ্নাংশ (MCV list) |
| histogram_bounds | non-MCV value-গুলোর equi-depth bucket-সীমা |
| correlation | physical storage order আর logical column order-এর মিল |
Default statistics_target = 100 — sample size ≈ 300 × target = ৩০,০০০ row। Target বাড়ালে (per-column ALTER TABLE ... ALTER COLUMN ... SET STATISTICS) sample ও MCV/histogram precision বাড়ে — দাম: ANALYZE-র সময় আর pg_statistic-এর জায়গা।
Histogram — equi-depth বুনোট
MCV-তে না থাকা value-গুলোর জগতে planner রাখে equi-depth histogram: bucket-সীমাগুলো এমনভাবে কাটা যেন প্রতি bucket-এ sample-এর প্রায় সমান সংখ্যক row পড়ে — value-range সমান না, frequency সমান। Skewed data-তে এটাই বুদ্ধিমান: dense এলাকা সরু bucket-এ, sparse এলাকা চওড়া bucket-এ।
Range query-র selectivity: যেসব bucket overlap করে সেগুলোর ভগ্নাংশ-যোগফল, boundary bucket-এ linear interpolation:
histogram_bounds = [10, 20, 35, 80, 400] -- ৪টা bucket, প্রতিটায় ~25%
WHERE amount BETWEEN 30 AND 60:
bucket [20,35): ৫/১৫ অংশ = 0.167 × 25%
bucket [35,80): পুরোটা = 25%
bucket [80,400): ০ = 0
estimate = 0.167×0.25 + 0.25 ≈ 29% of non-MCV rowsMCV আর uniform-rest অনুমান
Equality (status = 'active'): value MCV list-এ থাকলে সরাসরি তার stored freq — সবচেয়ে নির্ভুল পথ। না থাকলে বাকি জগৎকে uniform ধরা হয়:
sel = (1 − Σ most_common_freqs) / (n_distinct − |MCV|)অর্থাৎ MCV-রা যা খেয়েছে বাকি ভগ্নাংশটা বাকি distinct value-দের মধ্যে সমান ভাগ। Skew যত তীব্র, MCV list তত বড় দরকার — নাহলে heavy tail uniform অনুমানে ডুবে যায়।
n_distinct — inflation-এর ফাঁদ
n_distinct অনুমান করা হয় sample-এর distinct count থেকে extrapolate করে। Correlated key-তে এটা ভয়ংকরভাবে বাড়িয়ে দেখাতে পারে: (city, street) জোড়া প্রায় unique, কিন্তু আলাদা আলাদা column-এর distribution সম্পূর্ণ আলাদা গল্প — planner জোড়া-জ্ঞান রাখেই না, যতক্ষণ না আপনি extended statistics দেন। Manual override-ও আছে: ALTER TABLE t ALTER COLUMN c SET (n_distinct = -0.4) (negative = ratio-of-rows)।
Correlation — physical clustering-এর ছাপ
Index scan-এর খরচ নির্ভর করে target rows কতটা ছড়ানো — একই page-এ পাশাপাশি থাকলে sequential-ish, সারা table-এ ছড়ালে প্রতি row-এ random page। correlation stat (-1..+1) ঠিক সেই মিল মাপে: insert-time-ordered timestamp column-এ ≈ 1 (index scan প্রায় free), random hash column-এ ≈ 0 (planner seq scan-ই পছন্দ করবে)। Table যদি কখনো physical order-এ reorganize করতে চান — সেটা CLUSTER কমান্ড, আর stat বদলাতে আবার ANALYZE।
Error compounding — ভুলের সুদ
Join chain-এ estimate-রা গুণ হয়ে চলে: প্রতিটা ধাপে ১০%-করে under-estimate হলে ৫ join পরে:
0.9⁵ ≈ 0.59 — অর্ধেকেরও কম!আর এই ভুল সংখ্যাই পরের join-এর algorithm বাছে — under-estimate মানে planner nested-loop ভাববে “ছোট, lookup-ই যথেষ্ট”, বাস্তবে লক্ষ row-এর ঢেউ। একটা bad input-ই পুরো plan poison করে — এজন্যই cardinality estimation database performance-এর সবচেয়ে গুরুত্বপূর্ণ অনুমান-বিজ্ঞান।
ভেতরে কী ঘটছে
Sampling internals — কেন page-ভিত্তিক
ANALYZE random tuple না, random pages নেয় — প্রতিটা sampled page-এর সব live tuple পড়ে। কারণ IO economics: আলাদা আলাদা tuple-এ jump করা মানেই random page read-এর ঝড়; page-granularity-তে ৩০,০০০ row-এর জন্য মোটামুটি ৩০,০০০/rows-per-page ≈ কয়েকশো page। এজন্যই ANALYZE production-এ সস্তা — কিন্তু page-clustered data-তে (একই block-এ একই value-র ঝাঁক) sample bias-এর সম্ভাবনা এখান থেকেই আসে।
Selectivity functions — operator ভেদে আলাদা বই
Planner-এর type system-এ প্রতিটা operator-এর নিজের selectivity function: scalar equality ওই uniform-rest formula, scalar range interpolation, LIKE-এর জন্য pattern-এর প্রথম অক্ষর-ভিত্তিক heuristic, join selectivity আবার দুই side-এর n_distinct/MCV মিলিয়ে। এই function-গুলোর গুচ্ছ-ই “optimizer-এর চোখ” — চোখ দুর্বল হলে শরীর (plan) কুঁকড়ে যায়।
Autovacuum আর stats lifecycle
Stats বাসি হয়: ১০% row বদলালেই autovacuum-এর analyze trigger। কিন্তু trigger-ও delayed, আর বড় bulk-load-এর পরে নিজে হাতে ANALYZE t না দিলে planner পুরনো ছবিতেই plan করবে — “load করার পরে সব query slow” complaint-এর এক-লাইনা diagnosis। দেখভালের tool: last_analyze, last_autoanalyze in pg_stat_user_tables।
উদাহরণ
একটা skewed column-এর পূর্ণ পাঠ
Table: payments(status text, amount numeric) — ৯৯% row active, বাকি ১% ছড়ানো আরও ৯টা value-তে। ANALYZE-র পরে pg_stats বলে দেবে:
most_common_vals = {active, failed, pending, refunded, ...}
most_common_freqs = {0.99, 0.004, 0.003, 0.002, ...} ← যোগফল ≈ 0.999
n_distinct = 10
histogram_bounds = {failed, pending, queued, refunded, stale, ...}এবার planner-এর হিসাব:
WHERE status = 'active' → MCV hit! sel = 0.99 ← প্রায় নিখুঁত
WHERE status = 'expired' → MCV miss: (1−0.999)/(10−5) = 0.0002
actual হয়তো 0.001 — ভুল, কিন্তু ছোট ভুল
WHERE status IS NOT NULL → sel = 1 − null_fracলক্ষণীয়: প্রথম estimate-টা প্রায় perfect কারণ MCV ঠিক skew-এর দিকেই তীর ধরে। তৃতীয়টা দেখায় null_frac-ও একটা first-class selectivity input। এই একই কলামে ORDER BY amount-এর index scan আলোচনায় ফিরবে correlation-এর অধ্যায়ে।
ছবিতে
Sample থেকে plan — পুরো নালী
value ──────────────────────────────────────────────▶
▂▃▅▇███▇▅▃▂▁ ▁▁▂▁▁ ← data density
├────┼──┼─┼┼──┼────────┼───────┼───┼──┤
bounds: 10 20 35 80 400 900 970 999
│ 25%│25%│ 25% │ 25% │ ← প্রতি bucket-এ সমান row
window [30,60): bucket২-এর ⅓ + bucket৩-এর পুরো = 8.3% + 25% ≈ 33%আর পুরো pipeline-টা এক নজরে:
- Random page sampleANALYZE — ৩০০×target rows, page-ভিত্তিক নমুনা
- Per-column statsMCV/freqs, equi-depth bounds, n_distinct, correlation
- Selectivity functionsoperator ভেদে estimate — equality/range/LIKE/join
- Cardinality propagationjoin/filter chain ধরে গুণ — এখানেই compounding
- Cost + plan choiceestimated rows → algorithm/access-path সিদ্ধান্ত
নিজে চালিয়ে দেখুন
skewed column-এ pg_stats পাঠ, optimizer-ফাঁদ, আর extended statistics-এর নিরাময়
ধাপ ১ — ইচ্ছাকৃত skewed dataset:
CREATE TABLE visits (
id bigint GENERATED ALWAYS AS IDENTITY,
city text NOT NULL,
zip text NOT NULL,
status text NOT NULL
);
-- zip = f(city) — পূর্ণ correlated! Dhaka↔1219 সর্বদা একসাথে
INSERT INTO visits (city, zip, status)
SELECT
(ARRAY['Dhaka','Chattogram','Khulna','Rajshahi','Sylhet'])[1 + (g % 5)],
CASE WHEN g % 5 = 0 THEN '1219' WHEN g % 5 = 1 THEN '4000'
WHEN g % 5 = 2 THEN '9100' WHEN g % 5 = 3 THEN '6000'
ELSE '3100' END,
CASE WHEN g % 100 > 2 THEN 'ok' ELSE 'flagged' END
FROM generate_series(1, 500000) g;
ANALYZE visits;
SELECT * FROM pg_stats WHERE tablename = 'visits'\x\x expanded mode-এ প্রতিটা field পড়ুন: most_common_freqs-এ ‘ok’-এর ভগ্নাংশ ≈ 0.97, histogram_bounds-এ city-গুলোর ক্রম, correlation ≈ ধনাত্মক কিছু (insert order-এর ছাপ)।
ধাপ ২ — optimizer-ফাঁদ পাতুন:
EXPLAIN ANALYZE SELECT count(*)
FROM visits WHERE city = 'Dhaka' AND zip = '1219';-> Seq Scan on visits (actual rows=100000 loops=1)
Filter: ((city = 'Dhaka') AND (zip = '1219'))
Rows Removed by Filter: 400000
Planning estimate: rows=20000 ← 0.2 × 0.2 = 0.04 × 500000
Actual: rows=100000 ← ৫ গুণ off — দুই column আসলে এক তথ্য!Planner দুই filter-কে independent ভেবে 0.2 × 0.2 = 0.04 ধরেছে; বাস্তবে দুটো এক মুদ্রার দুই পিঠ — 0.2-ই সত্যি। এখন scale-up ভাবুন: এই estimate-ই পরের join-এ hash-vs-nested ঠিক করবে।
ধাপ ৩ — extended statistics দিয়ে চোখ ফেরান:
CREATE STATISTICS visits_city_zip (dependencies)
ON city, zip FROM visits;
ANALYZE visits;
EXPLAIN ANALYZE SELECT count(*)
FROM visits WHERE city = 'Dhaka' AND zip = '1219';
-- Planning rows now ≈ 100000 — dependency-তে correlation ধরা পড়েছে!
SELECT * FROM pg_stats_ext WHERE statistics_name = 'visits_city_zip'\x
-- dependencies kind-এ functional-dependency coefficient দেখবেন:
-- "zip প্রায় সবসময় city থেকে derive হয়" — planner তখন দ্বিতীয় filter-এর
-- ভগ্নাংশ আর স্বাধীনভাবে গুণ করে না। ndistinct kind যোগ করতে চাইলে:
-- CREATE STATISTICS ... (dependencies, ndistinct)Correlated column-pair-এ planner selectivity গুণ করে ভয়ংকর under-estimate করে (rows=1 বনাম actual লক্ষাধিক), আর CREATE STATISTICS (dependencies) যোগ করলে estimate সংশোধিত হয় — statistics-ই optimizer-এর চোখ, অন্ধ হলে plan অন্ধ।
নিজে বানান
EquiDepthHistogram + MCV estimator — sampling-এর সূদ নিজের চোখে
- Power-law skewed numeric stream বানান (paretovariate)
- EquiDepthHistogram: sorted sample থেকে quantile edges, range-selectivity interpolation
- MCV estimator: top-k counter + uniform-rest fallback
- Brute-force exact answer full data-তে — ground truth
- Sample sizes {0.1%, 1%, 10%}-এ error% টেবিল — sampling law প্রমাণ
লক্ষ্য: ANALYZE যা করে তার হ্রাসপ্রাপ্ত প্রতিরূপ — ছোট sample থেকে estimator বানিয়ে full-data truth-র সাথে মেপে দেখা।
import random
from collections import Counter
random.seed(7)
# ---------- synthetic skewed data ----------
N = 200_000
def make_data(n):
# jittered Pareto: ঘন নিম্নমান, দীর্ঘ tail, কোনো duplicate-edge trap নেই
return [min(999_999,
int((random.paretovariate(1.15) - 1 + random.random()) * 30_000))
for _ in range(n)]
DATA = make_data(N)
# ---------- estimators ----------
class EquiDepthHistogram:
"""equi-depth: প্রতি bucket-এ sample-এর ~সমান সংখ্যক value"""
def __init__(self, sample, buckets=20):
s = sorted(sample)
self.buckets = buckets
self.edges = [s[round(k * (len(s) - 1) / buckets)]
for k in range(buckets + 1)]
def range_selectivity(self, lo, hi):
hits = 0.0
for k in range(self.buckets):
blo, bhi = self.edges[k], self.edges[k + 1]
width = max(bhi - blo, 1)
overlap = min(hi, bhi) - max(lo, blo)
if overlap > 0:
hits += min(overlap, width) / width
return hits / self.buckets
class MCVEstimator:
"""value-র বদলে stripe (v // 10000)-এ MCV — repeated discrete unit"""
def __init__(self, sample, k=10):
stripes = [v // 10_000 for v in sample]
self.total = len(stripes)
self.mcv = Counter(stripes).most_common(k)
self.freq = {s: c / self.total for s, c in self.mcv}
self.rest_mass = max(0.0, 1.0 - sum(self.freq.values()))
self.n_other = max(1, len(set(stripes)) - len(self.mcv))
def equality_selectivity(self, stripe):
return self.freq.get(stripe, self.rest_mass / self.n_other)
# ---------- ground truth (full data brute force) ----------
def exact_range(lo, hi):
return sum(1 for x in DATA if lo <= x <= hi) / N
def exact_eq(stripe):
return sum(1 for x in DATA if x // 10_000 == stripe) / N
# ---------- experiment: sample-size ladder ----------
QUERIES_R = [(2000, 6000), (0, 90000), (150000, 160000), (400000, 700000)]
QUERIES_E = [0, 1, 2]
def evaluate(frac):
m = max(int(N * frac), 50)
sample = random.sample(DATA, m)
hist = EquiDepthHistogram(sample)
mcv = MCVEstimator(sample)
errs_r = [abs(hist.range_selectivity(lo, hi) - exact_range(lo, hi))
/ max(exact_range(lo, hi), 1e-4) for lo, hi in QUERIES_R]
errs_e = [abs(mcv.equality_selectivity(s) - exact_eq(s))
/ max(exact_eq(s), 1e-4) for s in QUERIES_E]
return m, 100 * sum(errs_r) / len(errs_r), 100 * sum(errs_e) / len(errs_e)
print(f"{'sample':>7} | {'rows':>7} | {'range err%':>10} | {'equality err%':>13}")
print('-' * 47)
for frac in (0.001, 0.01, 0.10):
m, er, ee = evaluate(frac)
print(f'{frac:>6.1%} | {m:>7,} | {er:>9.1f}% | {ee:>12.1f}%')সাধারণ ফলাফল (seed=7 — আপনার সংখ্যা কাছাকাছি হবে):
sample | rows | range err% | equality err%
-----------------------------------------------
0.1% | 200 | 34.4% | 24.7%
1.0% | 2,000 | 19.6% | 7.8%
10.0% | 20,000 | 10.3% | 3.1%Reading: sample ১০০ গুণ বাড়ায় error উভয়ই ~৩× নেমেছে — sampling error ∝ 1/sqrt(n)-এর বাস্তব ছায়া। Range-এর error তুলনামূলক বেশি থাকে কারণ interpolation long-tail bucket-এ সবচেয়ে কাঁপে; equality দ্রুত ঠিক হয় কারণ ঘন stripe-গুলো তাড়াতাড়ি MCV list-এ ঢোকে। Production lesson: ANALYZE কোটি-row table-এও মাত্র ৩০,০০০ row দেখে — তবু যথেষ্ট, কারণ planner perfect estimate না, সঠিক order-এর estimate চায়।
নিজে বাড়ান:
- Buckets সংখ্যা sweep করুন 50 — ছোট sample-এ বেশি bucket কি সাহায্য করে না ক্ষতি করে?
- Uniform histogram (equi-width) variant লিখে skewed data-তে তুলনা করুন — equi-depth কেন standard তার প্রমাণ
- Join-selectivity mini-sim: দুই independent column-এর estimated sel গুণ করে exact join-size-এর সাথে error compounding দেখুন
বাস্তব সিস্টেমে
বাস্তব জগতে statistics engineering
Bulk-load discipline। ETL-এর পরে script-এ ANALYZE step থাকা একটা সংস্কৃতি — নইলে রাতের load-এর পরে সকালের dashboard পুরনো ছবি নিয়ে plan করে। Data warehouse migration checklist-এর অবিচ্ছেদ্য লাইন।
Oracle/SQL Server-এ এগিয়ে যাওয়া। SQL Server-এ automatic plan correction আর runtime feedback (CE feedback), Oracle-এ adaptive plans — classic PG-র absent-feedback philosophy-র industrial বিকল্প। Trade-off এখনো তর্কাস্পদ।
HyperLogLog আর sketches। Distinct-count estimation-এর modern sketch family (HLL) streaming systems-এ standard — ANALYZE-র n_distinct problem-এরই scalable উত্তর; approximate-but-bounded-error দর্শন।
Extended stats adoption। Multi-column filter-heavy schemas-এ (tenant_id + status জাতীয়) CREATE STATISTICS এখন performance review-র checklist item — ৫ লাইনের SQL, বহু ঘণ্টার firefighting বাঁচায়।
যে ভুলগুলো সবাই করে
“ANALYZE পুরো table scan করে — তাই বড় table-এ চালানো ব্যয়বহুল।”
ANALYZE random page-sample নেয় — ৩০,০০০ row-এর জন্য কয়েকশো page, কোটি-row table-এও সেকেন্ডের অঙ্কে। ব্যয়বহুল মনে হওয়ার আসল কারণ হলে সেটা lock contention বা ভুলly tuned maintenance window — scan cost না।
“Estimate ভুল হলে database bug — ঠিক করা উচিত সবসময়।”
Estimate মানেই probabilistic model-এর output — ছোট ভুল স্বাভাবিক, এমনকি সুস্থ লক্ষণ। সমস্যা তখনই যখন ভুল বড় এবং plan-choice বদলে দেয়। Rows=47 বনাম actual=61 — নিশ্চিন্তে উপেক্ষা করুন; rows=1 বনাম actual=100000 — তখনই তদন্ত।
“statistics_target বাড়ালেই slow query ঠিক হবে।”
Target বাড়ায় sample precision — কিন্তু সমস্যা যদি correlation/independence-অনুমানে হয় (correlated columns), বড় sample-ও সেই structural ভুল বহন করে। সেখানে ওষুধ extended statistics; target শুধু heavy-skew MCV truncation-এর ক্ষেত্রেই জাদু দেখায়।
“VACUUM আর ANALYZE একই জিনিস — autovacuum চললে statistics fresh।”
VACUUM dead-tuple space ফেরায়; ANALYZE statistics লেখে — দুই আলাদা কাজ, এক wrapper-এ (VACUUM ANALYZE)। Autovacuum দুটোই schedule করে কিন্তু আলাদা threshold-এ — তাই “autovacuum চলেছে” মানেই stats fresh না; last_analyze দেখুন, assumption না।
বুঝেছেন কি না দেখুন
1pg_stats-এর পাঁচটা প্রধান field আর প্রতিটার এক-লাইনা কাজ বলুন।
স্মরণ
null_frac — NULL-ভগ্নাংশ estimate; n_distinct — distinct-value অনুমান (join/group-by sizing); most_common_vals/freqs — skew-এর সরাসরি সাক্ষ্য, equality-র নিখুঁত path; histogram_bounds — non-MCV range-এর equi-depth map; correlation — physical clustering, index-cost model-এর ইনপুট।
2Σ most_common_freqs = 0.95, |MCV| = 8, n_distinct = 108। Non-MCV value-র equality selectivity কত?
প্রয়োগ
(1 − 0.95) / (108 − 8) = 0.05/100 = 0.0005। Uniform-rest অনুমান — বাকি ৫% mass ধরে ১০০টা remaining distinct value-এ সমান ভাগ। Actual skewed হলে এখানেই ভুল জন্মায়।
3Equi-width-এর বদলে equi-depth histogram কেন? Skewed data-তে equi-width ঠিক কোথায় অন্ধ হয়?
যুক্তি
Skewed data-র বেশিরভাগ row জমে ছোট value-window-এ — equi-width-এ ওই window একটাই mega-bucket হয়ে যায়, ভেতরের distribution completely invisible; বাকি শত bucket ফাঁপা। Equi-depth সমান-frequency কাটে — dense এলাকা zoom করে, sparse এলাকা coarse — প্রতি bucket-এর information content সমান থাকে।
4৪-join query-তে প্রতিটা intermediate estimate ২০%-করে over-estimate। Plan choice-এর ওপর প্রভাব কোন দিকে ঝুঁকে বেশি — আর কেন?
যুক্তি
Over-estimate মানে planner intermediate বড় ভাবে — hash/merge-এর দিকে ঝোঁকে, memory-budget ভয়ে batching ধরে, আর nested-loop-কে “too many iterations” ভেবে ফেলে দেয়। Under-estimate-এর বিপর্যয় (nested-loop explosion) তীব্র হলেও over-estimate-এর ব্যয় মূলত হারানো opportunity — hash-এর extra build খরচ। দুই দিকেই ক্ষতি, ভিন্ন চেহারায়।
5WHERE tenant_id = 42 AND status = 'archived' — প্রতিটা tenant-এ archived-ভাগ আসলে ৫%, কিন্তু planner rows=total×s₁×s₂ দিচ্ছে। কোন অনুমান ভাঙছে, remedy কী?
প্রয়োগ
WHERE tenant_id = 42 AND status = 'archived' — প্রতিটা tenant-এ archived-ভাগ আসলে ৫%, কিন্তু planner rows=total×s₁×s₂ দিচ্ছে। কোন অনুমান ভাঙছে, remedy কী?Independence অনুমান ভাঙছে — tenant আর status correlated (নির্দিষ্ট tenant-এর archived share গlobally আলাদা)। Remedy: CREATE STATISTICS (dependencies) ON tenant_id, status FROM t; ANALYZE; — functional dependency ধরলে planner দ্বিতীয় filter-কে স্বাধীন গুণন থেকে বাদ দেয়। Extreme case-এ partial index-ও ওষুধ।
6কোটি-row event table-এ নতুন rare event-type যোগ হলো (share 0.5%)। Planner তা এখনো দেখছেই না — estimate কেন ব্যর্থ, আর design-level তিনটা সমাধান?
ডিজাইন
ব্যর্থতার কারণ: ৩০k sample-এ 0.5%-এর প্রত্যাশিত নমুনা ≈ ১৫০ row — দেখার কথা; কিন্তু নতুন value MCV-তে জায়গা পায়নি হলে uniform-rest-এ ডুবে যায়, আর stats বাসি হলে কিছুই না। Solutions: (১) forced ANALYZE + statistics_target বাড়িয়ে MCV depth; (২) ওই value-র জন্য partial index + query hint-free rewrite; (৩) hot/cold partitioning — rare-type আলাদা table/partition, নিজের stats সহ। Design moral: rare-class analytics-এ statistics-কে contract হিসেবে সামলাতে হয়।
এরপর কী
এরপর কী
Storage engine, executor, optimizer, statistics — database-এর “মেধা” দেখা শেষ। এবার সেই প্রশ্ন, যেটা module-এর শুরু থেকে ঝুলছিল: বিদ্যুৎ চলে গেলে আমার committed transaction টিকে থাকে কীভাবে?
পরের লেসনে transactions আর ACID:
- Atomicity-র promise — all-or-nothing আসলে কীভাবে implement হয়
- Consistency, Isolation, Durability — চার letter-এর চার আলাদা প্রকৌশল-সমস্যা
- Commit মুহূর্তে crash হলে ঠিক কী থাকে disk-এ
- PostgreSQL-এর ভাষায়: commit sequence-এর প্রথম ঝলক
আজকের চোখ (statistics) এখন থেকে durability-র স্মৃতি (WAL) পর্যন্ত — পুরো engine একসাথে দেখা শুরু হবে।
আরও পড়ুন
- PostgreSQL Documentation — Statistics Used by the Planner — PostgreSQL Global Development Group · pg_stats-এর প্রতিটা field আর row estimation examples-এর প্রামাণ্য ব্যাখ্যা
- PostgreSQL Documentation — Extended Statistics — PostgreSQL Global Development Group · CREATE STATISTICS-এর dependencies/ndistinct kinds-এর official reference
- Query Optimization (ACM Computing Surveys, 1996) — Yannis Ioannidis · Selectivity estimation-এর সমস্যা আর histogram family-র survey — ক্লাসিক রেফারেন্স