بلاگز / Postgres انڈیکس کی اقسام: کونسا کب استعمال کریں

Postgres انڈیکس کی اقسام: کونسا کب استعمال کریں

شائع ہوا
2 ستمبر، 2026
مصنف
Faizan Nadeem
ٹیگز
PostgreSQL Database Design Backend Development
کتب خانہ کی شیلفوں کا قریب سے منظر جو کتابوں سے بھری ہوئی ہیں، قطار کے ساتھ زاویے سے لیا گیا شاٹ جس میں راہداری کا دور والا سرا دھندلا ہے
تصویر از Jamie Taylor بر Unsplash

زیادہ تر developers سے پوچھیں کہ Postgres index کیا ہوتا ہے تو جواب عموماً یہ ہوگا: “ایک B-tree جو WHERE clauses کو تیز بناتا ہے۔” عام schema میں شاید 80% indexes کے لیے یہ بات درست ہو، اور یہی مفروضہ پہلی بار ٹوٹتا ہے جب کوئی query میں WHERE tags @> ARRAY['urgent'] یا WHERE ST_DWithin(location, ..., 500) شامل کرتا ہے اور پھر حیران ہوتا ہے کہ اس column پر موجود B-tree index بالکل بھی مدد کیوں نہیں کر رہا۔ Postgres بلا وجہ چار واقعی مختلف index access methods کے ساتھ نہیں آتا — B-tree، GIN، GiST، اور BRIN میں سے ہر ایک “اس condition سے match کرنے والی rows جلدی تلاش کرو” کے الگ pattern کو حل کرتا ہے، اور عادتاً default والا index لگا دینا وہی راستہ ہے جس سے teams ایسے indexes بنا بیٹھتی ہیں جو writes کو مہنگا کرتے ہیں مگر reads میں کبھی استعمال ہی نہیں ہوتے۔

اصل مہارت چار acronyms یاد کرنے میں نہیں — بلکہ یہ سیکھنے میں ہے کہ query کے WHERE clause کو دیکھ کر پہچانا جائے کہ lookup کی اصل شکل کیا ہے: scalar values پر equality اور range، arrays یا JSON کے اندر containment، geometric یا range types میں overlap اور proximity، یا کسی بہت بڑی table میں physical row order کے ساتھ سادہ correlation۔ جب آپ query کی shape کو نام دے لیتے ہیں تو مناسب index type عموماً خود ہی واضح ہو جاتا ہے۔ اس post میں ہم چاروں کو دیکھیں گے، یہ بھی کہ writes پر ہر ایک کی کیا cost ہے، اور partial اور covering indexes کس طرح حساب کتاب کو مکمل طور پر بدل دیتے ہیں۔

آپ یہ سیکھیں گے:

  • چار بنیادی index types میں سے ہر ایک اصل میں کس قسم کے سوال کا جواب دینے کے لیے بنایا گیا ہے
  • کیوں B-tree درست default ہے اور کب یہ کافی نہیں رہتا
  • JSONB، array، اور full-text search columns کے لیے GIN index کب استعمال کرنا چاہیے
  • GiST کس کام کے لیے ہے، اور مشابہ نظر آنے والے use cases میں یہ GIN سے کیسے مختلف ہے
  • کیوں BRIN indexes بہت بڑی، قدرتی طور پر ordered tables پر تقریباً مفت ہوتے ہیں
  • کیسے partial indexes index کا size بھی کم کرتے ہیں اور write cost بھی، جب کسی column میں زیادہ تر ایک ہی value ہو
  • کیسے covering indexes Postgres کو index-only scans کے ذریعے table کو مکمل طور پر skip کرنے دیتے ہیں

فہرستِ مضامین

  1. بنیادی باتیں: ایک Index کی Cost کیا ہوتی ہے
  2. B-tree: Default، اور کیوں
  3. GIN: Containment اور Full-Text Search
  4. GiST: Overlap، Proximity، اور Exclusion
  5. BRIN: بہت بڑی، Ordered Tables پر تقریباً مفت
  6. Partial Indexes: صرف وہی Index کریں جو اہم ہے
  7. Covering Indexes اور Index-Only Scans
  8. Query Shape کے مطابق انتخاب
  9. عام غلطیاں

بنیادی باتیں: ایک Index کی Cost کیا ہوتی ہے

ہر index ایک tradeoff ہے، مفت فائدہ نہیں۔ reads میں ایک اچھی طرح match کیا گیا index full table scan کو targeted lookup میں بدل دیتا ہے۔ writes میں ہر INSERT، UPDATE، یا DELETE جو کسی indexed column کو touch کرے، اسے اس column کو cover کرنے والے ہر index کو بھی update کرنا ہوتا ہے — زیادہ indexes کا مطلب سست writes، زیادہ disk space، اور autovacuum کے لیے زیادہ کام۔ اسی لیے “بس ایک index add کر دو” ہر جگہ درست جواب نہیں؛ یہ اس بات پر شرط لگانے جیسا ہے کہ read savings، write cost سے زیادہ ہوں گی، اور یہ شرط ہر table میں اس کے read/write ratio کے مطابق مختلف ہوتی ہے۔

Postgres کی pg_indexes اور pg_stat_user_indexes views یہ جاننے کا سب سے دیانت دار ذریعہ ہیں کہ یہ شرط فائدے میں رہی یا نہیں:

SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE relname = 'orders'
ORDER BY idx_scan;

اگر کوئی index ہفتوں کی production traffic کے بعد بھی idx_scan = 0 پر پڑا ہے تو وہ reads میں کسی فائدے کے بغیر خالص write overhead ہے — اسے drop کرنے کے لیے مضبوط امیدوار۔ کسی نئے index type کی طرف جانے سے پہلے یہ سمجھنا ضروری ہے کہ plan کو اتنی توجہ سے پڑھا جائے کہ معلوم ہو اصل میں scan کس shape کا ہو رہا ہے؛ EXPLAIN ANALYZE guide یہ سمجھاتی ہے کہ index کے لیے پکارتی sequential scan کو اس scan سے کیسے الگ کریں جو واقعی سستا option ہے۔

B-tree: Default، اور کیوں

B-tree وہی structure ہے جو CREATE INDEX تب بناتا ہے جب آپ کچھ اور specify نہ کریں، اور یہ درست default ہے کیونکہ یہی واحد structure ہے جو scalar types جیسے integers، text، timestamps، اور UUIDs پر equality اور ordered range queries — =, <, >, BETWEEN, ORDER BY, اور IN — کو مؤثر انداز میں handle کرتا ہے۔

CREATE INDEX idx_orders_created_at ON orders (created_at);

-- Uses the index for both of these:
SELECT * FROM orders WHERE created_at > now() - interval '7 days';
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20;

Composite B-tree index — یعنی ایسا index جو کئی columns پر بنا ہو — پہلے اپنے leading column کے مطابق ordered ہوتا ہے، پھر اگلے column کے مطابق، اور اسی طرح آگے؛ اس کا مطلب ہے کہ CREATE INDEX statement میں column order صرف ظاہری چیز نہیں۔ (customer_id, status) پر index ایسی queries کو serve کرتا ہے جو صرف customer_id پر filter لگائیں یا customer_id AND status دونوں پر ساتھ filter کریں، لیکن وہ اس query کے لیے کچھ نہیں کرتا جو صرف status پر filter کرتی ہے، کیونکہ index اوپری سطح پر status کے مطابق sorted نہیں ہوتا۔ جو column سب سے زیادہ selective ہو اور سب سے زیادہ اکیلے filter کیا جاتا ہو، اسے پہلے رکھیں۔

جس لمحے condition “کیا یہ scalar value اس range میں آتی ہے” نہ رہے، B-tree اچھا انتخاب نہیں رہتا — containment (@>, ?)، full-text (@@)، geometric overlap، اور similarity range والے سوالات نہیں ہیں، اور B-tree انہیں full scan پر گرے بغیر اصل میں answer ہی نہیں کر سکتا۔

GIN index (Generalized Inverted Index)، B-tree کے برعکس سوال کے لیے بنایا گیا ہے: “یہ واحد value sorted order میں کہاں بیٹھتی ہے” کے بجائے یہ جواب دیتا ہے “کون سی rows اس composite value کے اندر یہ element رکھتی ہیں۔” یہی بالکل JSONB containment check، array membership test، یا full-text search کی shape ہے۔

-- JSONB containment
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';

-- Array containment
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);
SELECT * FROM articles WHERE tags @> ARRAY['postgres'];

-- Full-text search
CREATE INDEX idx_articles_search ON articles USING GIN (to_tsvector('english', body));
SELECT * FROM articles WHERE to_tsvector('english', body) @@ to_tsquery('index & performance');

اندرونی طور پر GIN index ہر individual element (ہر JSON key/value pair، ہر array element، ہر lexeme) سے ان rows کی mapping رکھتا ہے جو اسے contain کرتی ہیں — یعنی ایک inverted index، بالکل ویسا ہی structure جیسا search engine استعمال کرتا ہے۔ یہی structure containment کو تیز بناتا ہے، اور یہی وجہ بھی ہے کہ writes پر GIN indexes، B-tree کے مقابلے میں واضح طور پر زیادہ مہنگے ہوتے ہیں: ایک row insert کرنے کا مطلب یہ ہو سکتا ہے کہ درجنوں individual entries update ہوں، اگر اس row کے JSONB document یا array میں درجنوں elements ہوں۔

Postgres کا fastupdate mechanism (جو default سے on ہوتا ہے) ان updates کو ہر insert پر سیدھا index tree میں لکھنے کے بجائے pending list میں batch کر دیتا ہے، یوں background maintenance cost کے بدلے فی-write latency کم ہو جاتی ہے۔ اگر کسی write-heavy table پر GIN index ہو تو pg_stat_user_tables میں autovacuum frequency پر نظر رکھیں — GIN-heavy table کو اکثر default settings سے زیادہ aggressive autovacuum درکار ہوتا ہے۔

Postgres 9.4 کے بعد سے GIN multicolumn indexes بھی support کرتا ہے، جو ایک عام pattern میں اہم ہے: ایک ہی query میں JSONB column اور plain scalar column دونوں پر filter لگانا۔

CREATE EXTENSION IF NOT EXISTS btree_gin;

CREATE INDEX idx_products_multi ON products
USING GIN (tenant_id, attributes);

SELECT * FROM products
WHERE tenant_id = 42 AND attributes @> '{"in_stock": true}';

btree_gin extension ایک scalar column جیسے tenant_id کو jsonb یا array column کے ساتھ GIN index میں حصہ لینے دیتا ہے، تاکہ ایک ہی index combined filter کو serve کر سکے، بجائے اس کے کہ planner کو tenant_id پر B-tree اور attributes پر GIN index میں سے انتخاب کرنا پڑے اور پھر دونوں result sets کو bitmap-AND کے ذریعے جوڑنا پڑے۔ Combined index بہتر ہے یا دو الگ indexes، یہ selectivity پر منحصر ہے — اندازہ لگانے کے بجائے دونوں کو EXPLAIN (ANALYZE, BUFFERS) کے ساتھ ناپیں۔

GiST: Overlap، Proximity، اور Exclusion

GiST (Generalized Search Tree) سطحی طور پر GIN سے ملتا جلتا لگتا ہے — دونوں non-scalar data کو handle کرتے ہیں — لیکن یہ جن سوالات کا جواب دیتے ہیں وہ مختلف ہیں۔ GIN یہ جواب دیتا ہے: “کیا یہ row اس exact element کو contain کرتی ہے؟” GiST یہ جواب دیتا ہے: “کیا اس row کی value اس دوسری value سے overlap کرتی ہے، intersect کرتی ہے، یا اس کے قریب واقع ہے؟” یہ بنیادی طور پر مختلف اور کچھ حد تک lossy structure ہے جو exact element lookups کے بجائے bounding regions کے گرد بنایا گیا ہے۔

-- Geometric proximity (with the earthdistance/cube or PostGIS extension)
CREATE INDEX idx_venues_location ON venues USING GIST (location);
SELECT name FROM venues
WHERE ST_DWithin(location, ST_MakePoint(-122.42, 37.77), 5000);

-- Range overlap
CREATE INDEX idx_bookings_period ON bookings USING GIST (during);
SELECT * FROM bookings WHERE during && tsrange('2026-08-01', '2026-08-05');

-- Exclusion constraints (no overlapping bookings for the same room)
ALTER TABLE bookings ADD CONSTRAINT no_overlap
  EXCLUDE USING GIST (room_id WITH =, during WITH &&);

آخری مثال خاص طور پر اہم ہے — ان چار structures میں صرف GiST ہی EXCLUDE constraint کو back کرتا ہے، اور یہی وہ طریقہ ہے جس سے Postgres database level پر یہ enforce کرتا ہے کہ “ایک ہی key کے لیے دو rows کی ranges overlap نہیں کر سکتیں”، بجائے اس کے کہ یہ منطق application code میں لکھی جائے۔ صرف row-level locking اور application logic سے اس چیز کو درست سنبھالنا واقعی مشکل ہے، اور database-level constraint ان race conditions کو بند کر دیتا ہے جو concurrency میں application code اکثر miss کر دیتا ہے۔

کچھ types (جیسے jsonb) کے لیے GiST اور GIN دونوں ایک ہی operator classes کو support کرتے ہیں، اور جب دونوں قابلِ اطلاق ہوں تو عموماً GIN query کے وقت تیز ہوتا ہے مگر build میں سست اور disk پر بڑا، جبکہ GiST تیزی سے build ہوتا ہے اور نسبتاً چھوٹا رہتا ہے مگر query speed میں کچھ قیمت چکانی پڑتی ہے — اگر آپ boundary case پر ہیں اور فیصلہ EXCLUDE یا خاص geometric operators کی ضرورت سے طے نہیں ہوتا، تو دونوں کو چیک کریں۔

BRIN: بہت بڑی، Ordered Tables پر تقریباً مفت

BRIN (Block Range Index) مکمل طور پر مختلف طریقہ اپناتا ہے: individual rows کو index کرنے کے بجائے یہ ہر physical block range (default طور پر 128 pages) کے لیے minimum اور maximum value store کرتا ہے۔ اس سے یہ B-tree کے مقابلے میں ڈرامائی حد تک چھوٹا ہو جاتا ہے — اکثر دو سے تین orders of magnitude تک — مگر اس کی قیمت precision میں کمی کی صورت میں آتی ہے: BRIN index صرف یہ بتا سکتا ہے کہ کون سے block ranges شاید matching row رکھتی ہوں، یہ نہیں کہ کون سی exact rows match کرتی ہیں، اس لیے Postgres کو candidate rows کو اصل condition کے مقابلے میں دوبارہ check کرنا پڑتا ہے۔

CREATE INDEX idx_events_logged_at ON events USING BRIN (logged_at);

یہ خاص طور پر تب درست tool ہے جب کوئی column physical insertion order کے ساتھ مضبوط correlation رکھتا ہو — append-only table پر created_at یا logged_at timestamp اس کی canonical مثال ہے، کیونکہ ایک ہی وقت کے آس پاس insert ہونے والی rows واقعی disk پر ایک دوسرے کے قریب رہتی ہیں۔ Time-series یا event log table جس میں hundred million rows ہوں، وہاں BRIN index چند hundred kilobytes کا ہو سکتا ہے، جبکہ اسی column پر B-tree hundreds of megabytes تک جا سکتا ہے، اور query performance اتنی قریب رہتی ہے کہ range-scan-heavy workloads کے لیے یہ tradeoff بالکل مناسب بنتا ہے۔

جس لمحے indexed column کا physical row order کے ساتھ correlation ختم ہو جائے، BRIN غلط انتخاب بن جاتا ہے — مثال کے طور پر status column جو table میں بکھری ہوئی rows پر in place update ہوتا رہے، پوری بنیاد ہی توڑ دیتا ہے، کیونکہ ہر block range کا min/max پھر کسی کام کی چیز نہیں رہتا۔

Partial Indexes: صرف وہی Index کریں جو اہم ہے

Partial index خود CREATE INDEX میں ایک WHERE clause شامل کرتا ہے، تاکہ صرف وہی rows index ہوں جو اس condition سے match کرتی ہوں۔ اس فہرست میں یہ سب سے کم استعمال ہونے والا مگر نہایت طاقتور tool ہے، اور یہ براہِ راست اس سوال کا جواب دیتا ہے کہ مکمل index کے بجائے postgres partial index کب استعمال کرنا چاہیے: جب بھی کوئی column ایک value کی طرف بہت زیادہ skewed ہو اور آپ کی queries حقیقت میں اس غالب value کے لیے filter کبھی کرتی ہی نہ ہوں۔

CREATE INDEX idx_orders_pending ON orders (created_at)
WHERE status = 'pending';

اگر 95% orders completed یا cancelled ہوں اور application اس path پر صرف pending orders ہی query کرتی ہو، تو status پر full index اپنا زیادہ تر size ایسی rows کو cover کرنے میں ضائع کرتا ہے جنہیں کوئی اس انداز میں lookup ہی نہیں کرتا۔ اوپر والا partial index اسی column پر full index کے مقابلے میں بہت چھوٹا ہوتا ہے، ہر non-pending row پر write کے وقت maintain کرنے میں سستا ہوتا ہے (کیونکہ وہ writes اس index کو touch ہی نہیں کرتیں)، اور — چونکہ یہ چھوٹا ہے — cache میں resident رہنے کے امکانات بھی زیادہ ہوتے ہیں۔ یہی pattern soft-deletes (WHERE deleted_at IS NULL)، feature flags، اور ہر ایسے enum-like column پر لاگو ہوتا ہے جس میں ایک dominant مگر کم query ہونے والی value ہو۔

Covering Indexes اور Index-Only Scans

عام index scan کو پھر بھی underlying table (یعنی “heap”) پر جانا پڑتا ہے تاکہ وہ columns لائے جا سکیں جو خود index میں موجود نہیں۔ Covering index، جو INCLUDE کے ساتھ بنایا جاتا ہے، اضافی columns کو index leaf pages میں صرف اس لیے store کرتا ہے کہ query کا جواب صرف index سے ہی دیا جا سکے اور heap کو مکمل طور پر skip کیا جا سکے — یعنی index-only scan۔

CREATE INDEX idx_orders_customer_covering
ON orders (customer_id, status) INCLUDE (total, created_at);

EXPLAIN (ANALYZE, BUFFERS)
SELECT total, created_at FROM orders
WHERE customer_id = 4821 AND status = 'pending';
-- Index Only Scan using idx_orders_customer_covering

INCLUDE columns، index کی sort key کا حصہ نہیں ہوتے — یہ filtering یا ordering میں مدد نہیں کرتے، بس ساتھ شامل رہتے ہیں تاکہ ایسی queries کے لیے heap lookup غیر ضروری ہو جائے جنہیں صرف یہی columns واپس چاہییں۔ یہ index size کے بدلے read speed کا سودا ہے (کیونکہ included columns وہی data دوبارہ رکھتے ہیں جو پہلے ہی table میں موجود ہے)، اور اس کا پورا فائدہ تبھی ملتا ہے جب table کا visibility map autovacuum کے ذریعے اچھی طرح maintain ہو — index-only scan کو ان pages کے لیے heap visibility پھر بھی check کرنی پڑتی ہے جنہیں حال ہی میں vacuum نہیں کیا گیا، اس لیے heavy churn والی table جس میں autovacuum tuning مناسب نہ ہو، اسے مکمل benefit نہیں ملے گا۔

Query Shape کے مطابق انتخاب

  • Scalar column پر equality یا range، sorting، یا عمومی default: B-tree۔
  • “کیا یہ JSONB/array X کو contain کرتا ہے” یا full-text search: GIN۔
  • Geometric overlap، proximity، range overlap، یا EXCLUDE constraint: GiST۔
  • بہت بڑی، زیادہ تر append ہونے والی table جہاں column insertion order سے correlate کرتا ہو: BRIN۔
  • ایسا skewed column جہاں queries کو صرف نایاب value سے غرض ہو: partial index، اس base type کے ساتھ ملا کر جو اس column کے لیے موزوں ہو۔
  • ایسی hot query جسے صرف چند columns واپس چاہییں: INCLUDE کے ساتھ covering index، اس type کے اوپر جو خود filter کے لیے مناسب ہو۔

یہ ایک دوسرے سے mutually exclusive نہیں ہیں — partial GIN index، یا WHERE clause کے ساتھ covering B-tree، دونوں مکمل طور پر درست ہیں اور اکثر بہترین جواب بھی، جب آپ query shape اور data skew دونوں کی شناخت کر لیتے ہیں۔

Index typeجواب دیتا ہےbuild/write cost (نسبتی)نسبتی sizeعام استعمال
B-treeequality, range, sortlowmoderateprimary keys, foreign keys, ORDER BY columns
GINcontainment, full-texthighmoderate–largeJSONB, arrays, tsvector
GiSToverlap, proximity, exclusionmoderatemoderategeometry, ranges, EXCLUDE constraints
BRINcorrelated range narrowingvery lowtinyappend-only time-series, huge log tables

اس table کو آغاز سمجھیں، حتمی فیصلہ نہیں — یہ جاننے کا واحد طریقہ کہ کون سا index واقعی کسی خاص query کی مدد کرتا ہے، یہ ہے کہ اسے بنا کر EXPLAIN (ANALYZE, BUFFERS) پہلے اور بعد میں compare کیا جائے، کیونکہ آپ کے اصل data پر حقیقی selectivity tradeoff کو کسی بھی سمت موڑ سکتی ہے۔

ایک اور بات جسے جلدی عادت کا حصہ بنا لینا چاہیے: ان میں سے کوئی بھی index type ایسی table پر سادہ CREATE INDEX کے ساتھ نہیں بنانا چاہیے جو پہلے ہی production traffic serve کر رہی ہو۔ عام CREATE INDEX ایک lock لیتا ہے جو build کے پورے دورانیے میں table پر writes کو block کر دیتا ہے، اور بڑی table پر اس کا مطلب منٹوں تک blocked INSERT/UPDATE/DELETE statements ہو سکتے ہیں۔ CREATE INDEX CONCURRENTLY وہی index بناتا ہے — B-tree، GIN، GiST، یا BRIN، یہ option چاروں پر لاگو ہوتا ہے — مگر وہ lock hold کیے بغیر، اگرچہ build نسبتاً سست ہوتا ہے اور اگر یہ آدھے راستے میں interrupt ہو جائے تو دوبارہ کوشش کی معمولی سی ضرورت پڑ سکتی ہے۔

عام غلطیاں

غلطی: B-tree index شامل کرنا اور یہ توقع کرنا کہ وہ JSONB containment query کو تیز کر دے گا۔ jsonb column پر B-tree پورے document کی equality کو support کر سکتا ہے مگر @> containment کو نہیں۔ حل: containment، full-text، اور array membership کے لیے GIN استعمال کریں۔

غلطی: high-write table پر fastupdate GIN indexes کو monitor نہ کرنا۔ Pending list اتنی بڑی ہو سکتی ہے کہ autovacuum کے لیے ساتھ چلنا مشکل ہو جائے، اور نتیجتاً query latency آہستہ آہستہ بڑھنے لگے۔ حل: pg_stat_user_tables پر نظر رکھیں اور خاص طور پر GIN-heavy tables کے لیے autovacuum thresholds tune کریں۔

غلطی: ایسے column پر BRIN استعمال کرنا جس کا physical order سے کوئی correlation نہ ہو۔ Index بظاہر موجود لگتا ہے مگر تقریباً کچھ بھی narrow نہیں کرتا، اور queries بہرحال زیادہ تر candidate block ranges scan کرنے پر واپس آ جاتی ہیں۔ حل: BRIN منتخب کرنے سے پہلے column کے لیے pg_stats میں correlation چیک کریں۔

غلطی: composite B-tree index کو columns کی غلط ترتیب کے ساتھ بنانا۔ (status, customer_id) پر index، صرف customer_id پر filter کرنے والی query کو اتنی اچھی طرح serve نہیں کرتا جتنی (customer_id, status) کرتی۔ حل: اس column کو آگے رکھیں جو سب سے زیادہ اکیلے استعمال ہوتا ہو یا جس کی selectivity سب سے زیادہ ہو۔

غلطی: unused indexes کے لیے کبھی pg_stat_user_indexes چیک نہ کرنا۔ ہر وہ index جو حقیقی استعمال میں نہیں، خالص write overhead ہے۔ حل: وقتاً فوقتاً idx_scan کا جائزہ لیں اور مکمل traffic cycle کے بعد بھی صفر پر موجود indexes کو drop کریں۔

خلاصہ

کوئی ایک “درست” Postgres index نہیں ہوتا — درست index وہ ہے جو اس query کی مخصوص shape کے مطابق ہو جو آپ حقیقت میں چلا رہے ہیں، اور Postgres آپ کو چار ساختی طور پر مختلف tools اسی لیے دیتا ہے کیونکہ scalar range lookups، containment، overlap، اور huge-table correlation واقعی چار مختلف مسائل ہیں۔ B-tree عام schema کی زیادہ تر ضروریات پوری کرتا ہے؛ GIN اور GiST ان JSONB، array، full-text، اور geometric cases کو handle کرتے ہیں جنہیں B-tree چھو بھی نہیں سکتا؛ BRIN بہت بڑی، قدرتی طور پر ordered tables پر space میں زبردست بچت دیتا ہے؛ اور partial اور covering indexes ایسے modifiers ہیں جو آپ کے data کے اصل skew اور access pattern کو سمجھ لینے کے بعد اوپر کی کسی بھی قسم کو زیادہ سستا یا تیز بنا سکتے ہیں۔

اگلی بار جب آپ CREATE INDEX کی طرف ہاتھ بڑھائیں، کیا آپ اسے query کی اصل shape کے مطابق لگا رہے ہوں گے، یا صرف عادتاً B-tree کو default مان رہے ہوں گے؟

مزید مضامین