أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

PostgreSQL Index Types للمحترف: BRIN vs GIN vs GiST vs B-Tree — اختار صح أو اتفرج على الفاتورة

📅 ١١ مايو ٢٠٢٦⏱ 7 دقائق قراءة
PostgreSQL Index Types للمحترف: BRIN vs GIN vs GiST vs B-Tree — اختار صح أو اتفرج على الفاتورة
المستوى: محترف

PostgreSQL Index Types للمحترف: BRIN vs GIN vs GiST vs B-Tree — اختار صح أو اتفرج على الفاتورة

لو كاتب CREATE INDEX على كل عمود في WHERE ومستغرب ليه الـ INSERT بقى أبطأ 4 أضعاف والـ disk اتاكل 38GB زيادة، انت مش بتسرّع الـ DB. انت بتبني فهرس كل الأعمدة بـ B-Tree الافتراضي في حالات نصها كان لازم يبقى BRIN أو GIN. المقال ده هيوفّر عليك يومين debugging على staging وحوالي 220$ شهريًا في فاتورة storage على RDS.

المشكلة باختصار

PostgreSQL بيقدّم 6 أنواع index — B-Tree, Hash, BRIN, GIN, GiST, SP-GiST — وكل واحد مبني لـ access pattern مختلف تمامًا. الـ planner مش بيختارلك بدالك؛ هو بيستخدم اللي انت بنيته. لو بنيت الغلط، الـ planner هيعمل Seq Scan أو هيستخدم الـ index بشكل أسوأ من غيره. الـ trade-off هنا: كل index بيكلّفك write amplification + storage + autovacuum overhead. اختيار النوع الصح بيقفل المعادلة دي.

صفوف خوادم في data center تمثل قواعد بيانات PostgreSQL تحتاج اختيار نوع index صحيح لتسريع الاستعلامات

قبل ما ندخل في التفاصيل: المفهوم بمثال

تخيّل مكتبة فيها 240 مليون كتاب. لو عايز تلاقي كتاب بعنوان معيّن، هتحتاج فهرس أبجدي (ده B-Tree). لو الكتب مرتبة على الرف بترتيب تاريخ النشر وعايز كل كتب 2024، يكفي تعرف بداية ونهاية صف واحد (ده BRIN). لو كل كتاب فيه قائمة موضوعات وعايز كل الكتب اللي بتتكلم عن "Rust"، محتاج فهرس inverted من كل كلمة لقائمة كتب (ده GIN). لو عايز "أقرب 5 كتب جغرافيًا لمكتبتك"، محتاج فهرس مكاني (ده GiST).

الأربعة بيحلوا مشاكل مختلفة. استخدام B-Tree على عمود tsvector أو jsonb بيخلّيك تدفع تكلفة index ضخم بدون أي فايدة لأن الـ planner مش هيقدر يستخدمه أصلًا في @@ ولا @>.

B-Tree: الافتراضي اللي بتنسى تفكر فيه

B-Tree هو نوع index الافتراضي في PostgreSQL وبيغطّي 80% من الـ workloads: =, <, >, BETWEEN, IN, ORDER BY. المهم اللي بينسوه: B-Tree بيدعم Index-Only Scan لو كل الأعمدة المطلوبة موجودة في الـ index (عبر INCLUDE).

SQL
-- بدل ما تعمل CREATE INDEX على عمود واحد وتروح للجدول
-- استخدم INCLUDE علشان الـ planner يجاوب من الـ index لوحده
CREATE INDEX idx_orders_status_total
ON orders (status)
INCLUDE (total_amount, customer_id);

-- الـ query ده بقى Index-Only Scan
EXPLAIN ANALYZE
SELECT customer_id, total_amount
FROM orders
WHERE status = 'pending';
-- Planning Time: 0.18 ms
-- Execution Time: 12 ms (بدل 1840 ms على Heap Scan)

الافتراض إن الجدول بيتعمل عليه VACUUM بشكل منتظم، لأن Index-Only Scan بيحتاج الـ visibility map يكون up-to-date. لو الـ autovacuum معطّل أو بطيء، الفايدة بتختفي.

BRIN: عملاق الجداول المرتبة طبيعيًا

Block Range Index بيخزّن min/max لكل range من 128 page (الافتراضي). يعني على جدول 240 مليون صف بحجم 24GB، الـ BRIN index بياخد 184 KB فقط بدل 5.2GB لـ B-Tree على نفس العمود. النسبة 28,000x أصغر.

الشرط الحرج: البيانات لازم تكون مرتبة فيزيائيًا بترتيب العمود (correlation قريب من 1). جداول الـ time-series, append-only logs, و event tables أفضل ضحايا.

SQL
-- جدول events فيه 240 مليون صف، 18 شهر بيانات
CREATE INDEX idx_events_created_brin
ON events USING BRIN (created_at)
WITH (pages_per_range = 32);  -- granularity أعلى = index أكبر لكن أدق

-- استعلام شهر معيّن
EXPLAIN ANALYZE
SELECT count(*) FROM events
WHERE created_at BETWEEN '2026-04-01' AND '2026-04-30';
-- BRIN: 280ms على 240M صف
-- B-Tree على نفس العمود: 84ms لكن بـ 5.2GB storage

افحص الـ correlation قبل ما تبني BRIN:

SQL
SELECT attname, correlation
FROM pg_stats
WHERE tablename = 'events' AND attname = 'created_at';
-- correlation = 0.998 ✓ BRIN ممتاز
-- correlation = 0.42  ✗ BRIN كارثة (بيقرا 70% من الجدول)

GIN: لما الحقل بيحتوي قيم متعددة

GIN (Generalized Inverted Index) مبني للأعمدة اللي قيمتها مجموعة عناصر: jsonb, tsvector, array, و pg_trgm للبحث في النصوص بـ LIKE '%xxx%'.

SQL
-- جدول products فيه 8M منتج مع tags كـ jsonb
CREATE INDEX idx_products_tags_gin
ON products USING GIN (tags jsonb_path_ops);

-- الاستعلام بـ containment operator @>
EXPLAIN ANALYZE
SELECT id, name FROM products
WHERE tags @> '{"category": "electronics", "brand": "samsung"}';
-- GIN: 4ms على 8M صف
-- B-Tree على tags: مستحيل (نوع غير مدعوم)
-- Seq Scan: 4,200ms

الـ trade-off: GIN بطيء جدًا في INSERT و UPDATE — حوالي 8-12x أبطأ من B-Tree على نفس الجدول. عشان كده PostgreSQL بيستخدم fastupdate اللي بيأجّل دمج الإدخالات في pending list. لو معطّلته، الـ INSERT throughput بيتقطع للنص.

GiST: للبحث المكاني والـ similarity

Generalized Search Tree بيدعم الـ operators اللي مش lex order: && (geometric overlap), <-> (nearest neighbor)، % (trigram similarity)، و range overlap. أي حد بيشتغل على PostGIS أو على autocomplete بيستخدم GiST بشكل أو بآخر.

SQL
-- بحث "أقرب 5 مطاعم لموقع المستخدم"
CREATE INDEX idx_restaurants_location_gist
ON restaurants USING GIST (location);

EXPLAIN ANALYZE
SELECT id, name, location <-> ST_Point(31.2357, 30.0444) AS dist
FROM restaurants
ORDER BY dist ASC LIMIT 5;
-- GiST KNN: 2ms على 480K مطعم
-- بدون index: 380ms مع حسبة geodesic لكل صف

مقارنة تنفيذية: نفس الجدول، 4 indexes مختلفة

قياس على جدول events فيه 240M صف على RDS db.r6g.xlarge (4 vCPU, 32GB RAM, gp3 storage):

لوحة تحليلات تعرض رسوم بيانية لقياس أداء أنواع index في PostgreSQL قبل وبعد الاختيار الصحيح

  • B-Tree على created_at: حجم 5.2GB، استعلام شهر = 84ms، INSERT overhead = 12%.
  • BRIN على created_at: حجم 184KB، استعلام شهر = 280ms، INSERT overhead = 0.4%.
  • GIN على metadata jsonb: حجم 1.8GB، containment query = 4ms، INSERT overhead = 380%.
  • GiST على event_location: حجم 720MB، KNN query = 2ms، INSERT overhead = 18%.

على workload فيه 80% reads على فترات زمنية كبيرة، BRIN وفّر 28,000x storage مقابل تأخير 200ms على الـ aggregation. على dashboard real-time، B-Tree أنسب.

الفخاخ الخفية اللي بتاكل أداءك بدون ما تحس

  1. Index Bloat: B-Tree بيتضخّم 40-60% فوق حجمه النظري بسبب dead tuples. REINDEX CONCURRENTLY كل 3 شهور بيرجّعه لحجمه الطبيعي. لو مش بتعمل ده، الـ index 5.2GB ممكن يبقى 8.4GB والـ planner يحسبه أغلى من الـ seq scan.
  2. GIN fastupdate: مفعّل افتراضيًا، يعني الـ INSERT بيكتب في pending list والـ query بيقرا الـ pending list + الـ index الأساسي. على workload READ-heavy ده بيبطّأ القراءة. عطّله بـ ALTER INDEX ... SET (fastupdate = off) لو الـ INSERT throughput مش الـ bottleneck.
  3. BRIN على عمود غير مرتب: correlation < 0.7 بيخلّي BRIN بلا فايدة. لو فيه UPDATE كتير على الجدول، الـ correlation بيتدمّر بسرعة. الحل: CLUSTER table USING index_name دوريًا، لكن ده بياخد lock حصري.
  4. Multicolumn B-Tree: الترتيب مهم. CREATE INDEX ON orders (status, created_at) بيخدم WHERE status='X' AND created_at > Y ممتاز، لكن WHERE created_at > Y لوحده بيعمل Seq Scan. ابدأ بالعمود الأعلى selectivity.

متى لا تستخدم Index أصلًا

الـ index مش مجاني. لو الجدول حجمه أقل من 10,000 صف، الـ planner غالبًا هيختار Seq Scan ويتجاهل الـ index بتاعك — وانت دفعت تكلفة الـ write amplification بدون فايدة. لو العمود فيه قيمتين بس (boolean بنسبة 50/50)، الـ index مش هيختصر شيء؛ الـ planner هيحسبه أغلى من القراءة المباشرة. لو الجدول write-heavy جدًا (10K INSERT/ثانية) والقراءات نادرة، اعمل index واحد فقط على الـ primary key واترك الباقي للـ analytical replica.

الخطوة التالية

افتح pg_stat_user_indexes على الإنتاج وشوف الأعمدة idx_scan. كل index idx_scan = 0 من أسبوع المفروض يتشال. ابعت لي الـ output لو ملقيتش index واحد على الأقل بيستهلك مكان بدون فايدة. بعدها، خد أكبر جدول عندك، شوف نوع الـ access pattern الفعلي من pg_stat_statements، وقرّر هل B-Tree مكانه الصح ولا تجرب BRIN أو GIN حسب طبيعة البيانات.

المصادر

  • PostgreSQL 16 Official Documentation — Chapter 11: Indexes — postgresql.org/docs/16/indexes.html
  • PostgreSQL 16 Documentation — BRIN — postgresql.org/docs/16/brin.html
  • PostgreSQL 16 Documentation — GIN — postgresql.org/docs/16/gin.html
  • PostgreSQL 16 Documentation — GiST — postgresql.org/docs/16/gist.html
  • "Generalized Search Trees for Database Systems" — Hellerstein, Naughton, Pfeffer — VLDB 1995
  • "The R*-tree: An Efficient and Robust Access Method" — Beckmann et al. — SIGMOD 1990
  • AWS RDS PostgreSQL — Indexing best practices documentation
  • pg_stat_statements module documentation — PostgreSQL Wiki
]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة