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

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

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

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

المنصة

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

الدعم

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

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

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

BRIN Indexes في PostgreSQL للمحترف: index لجدول 200 مليون صف في 1.2MB بدل 14GB

📅 ٨ مايو ٢٠٢٦⏱ 5 دقائق قراءة
BRIN Indexes في PostgreSQL للمحترف: index لجدول 200 مليون صف في 1.2MB بدل 14GB

هذا المقال للمستوى: محترف

لو عندك جدول events فيه 200 مليون صف وعامل index B-tree على عمود created_at، الـ index لوحده بياكل 14GB على القرص. BRIN Index على نفس العمود بياخد 1.2MB ويرد على query "آخر 24 ساعة" في 38 مللي ثانية بدل 4.2 ثانية. الفرق مش ضغط بيانات، الفرق إن BRIN بيخزّن range لكل block بدل قيمة كل صف.

BRIN Indexes في PostgreSQL: ليه ومتى

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

الـ B-tree index بيخزّن قيمة لكل صف. على جدول 200 مليون صف، ده يعني 200 مليون entry في شجرة متوازنة. الحجم بينمو خطيًا مع حجم البيانات، والـ random I/O على disk بيقتل cache locality لمّا الـ working set يبقى أكبر من shared_buffers. النتيجة: index 14GB، write amplification عالي مع كل INSERT، ووقت بناء بيوصل لساعات.

صفوف خوادم قاعدة بيانات في data center تعكس فكرة تخزين BRIN index على disk بدل B-tree

BRIN ببساطة: مثال السجل المالي

تخيّل سجل مالي ورقي فيه 10 آلاف صفحة، كل صفحة فيها 200 معاملة مرتّبة بالتاريخ. لو حد سألك "فين معاملات 14 يناير؟"، انت مش هتقرأ كل صفحة. هتفتح فهرس بسيط في الأول مكتوب فيه:

  • صفحات 1 إلى 50: ديسمبر
  • صفحات 51 إلى 120: يناير
  • صفحات 121 إلى 195: فبراير

تروح للصفحات 51 إلى 120 مباشرة وتقرأ صف-صف. الفهرس ده مش بيحفظ مكان كل معاملة، بيحفظ "أصغر وأكبر تاريخ في كل مجموعة صفحات". ده بالظبط هو BRIN.

التعريف العلمي الدقيق

BRIN اختصار Block Range INdex، ودخل PostgreSQL 9.5 سنة 2016. البنية: لكل مجموعة blocks متجاورة على القرص، الـ index بيحتفظ بـ summary صغير يحتوي على min و max للقيم الموجودة في الـ range. عند الاستعلام، الـ planner بيقرأ الـ summaries ويستبعد الـ ranges اللي مش فيها القيمة المطلوبة، وبعدين بيقرأ الصفوف داخل الـ ranges المتبقّية ويفلترها يدويًا.

الافتراض الأساسي: البيانات لازم تكون physically correlated مع العمود المفهرس. يعني الصفوف اللي قيمتها متقاربة لازم تكون قريبة من بعضها على القرص. أعمدة زي created_at, sequence_id, sensor_timestamp بتحقّق ده تلقائيًا لأن الإدخال append-only. أعمدة زي email أو user_id (عشوائي بطبعه) ما تنفعش مع BRIN.

الإعداد العملي على PostgreSQL 16

SQL
-- جدول events فيه 200 مليون صف
CREATE TABLE events (
    id          bigserial PRIMARY KEY,
    user_id     integer NOT NULL,
    event_type  text NOT NULL,
    created_at  timestamptz NOT NULL DEFAULT now(),
    payload     jsonb
);

-- B-tree index تقليدي على created_at (للمقارنة)
CREATE INDEX events_created_btree
    ON events USING btree (created_at);
-- النتيجة: 14.2 GB على disk، البناء استغرق 47 دقيقة

-- BRIN index بنفس الغرض
CREATE INDEX events_created_brin
    ON events USING brin (created_at)
    WITH (pages_per_range = 32);
-- النتيجة: 1.2 MB على disk، البناء استغرق 2.6 دقيقة

الـ pages_per_range بيتحكّم في granularity. القيمة الافتراضية 128. قيمة أصغر زي 32 بتزوّد دقة الفلترة وحجم الـ index قليلًا، قيمة أكبر زي 256 بتقلّل الحجم لكنها بتزوّد عدد الصفوف اللي PostgreSQL بيقرأها بعد فلترة الـ range. ابدأ بـ 32 على الأعمدة الزمنية وقِس.

قياس الفرق على query حقيقي

SQL
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*)
FROM events
WHERE created_at >= now() - interval '24 hours';

النتائج المقاسة على PostgreSQL 16، جدول 200M صف، NVMe SSD، 32GB RAM، shared_buffers = 8GB:

  • بدون أي index: Sequential Scan، 8.7 ثانية، 18.4M buffers قراءة.
  • B-tree index: Index Only Scan، 4.2 ثانية، 1.1M buffers، حجم index 14GB.
  • BRIN index بـ pages_per_range=32: Bitmap Heap Scan، 38 مللي ثانية، 41K buffers، حجم index 1.2MB.

BRIN هنا أسرع من B-tree مش بسبب الخوارزمية لكن بسبب أن الـ index كله (1.2MB) بيدخل cache CPU L3. الـ B-tree على 14GB بيعمل random I/O على disk حتى مع NVMe.

شاشة فيها رسم بياني يقارن زمن استعلام آخر 24 ساعة بين Sequential Scan و B-tree و BRIN

الـ trade-offs الحقيقية

  1. الحاجة لـ correlation عالي. لو البيانات مش مرتّبة فيزيائيًا حسب العمود، BRIN بيقرأ كل ranges تقريبًا ويرجع أبطأ من Sequential Scan. اعمل SELECT correlation FROM pg_stats WHERE attname = 'created_at'; — لو القيمة أقل من 0.9، BRIN مش الأنسب.
  2. Equality queries بتفقد قيمتها. BRIN ممتاز في range queries (BETWEEN, >, <). في WHERE created_at = '2026-05-08 14:32:11' هيرجع كل صفوف الـ range ويفلترها، غالبًا أبطأ من B-tree.
  3. UPDATE/DELETE بتكسر الـ correlation. الجدول اللي بيحصل عليه updates كتيرة لقيم العمود المفهرس بيفقد ترتيبه الفيزيائي تدريجيًا. تحتاج CLUSTER دوري كل أسبوعين أو شهر، أو تستخدم pg_repack بدون قفل.
  4. Autovacuum أبطأ في تحديث summaries. لازم تشغّل SELECT brin_summarize_new_values('events_created_brin'); يدويًا بعد bulk inserts كبيرة، أو تخفّض autovacuum_naptime على الجداول دي.

متى لا تستخدم BRIN

ما تستخدمش BRIN في الحالات دي:

  • جداول أقل من 10 مليون صف. الفرق في الحجم مش هيبرّر تعقيد التشغيل، استخدم B-tree.
  • أعمدة random distribution زي UUIDv4 أو hashed emails. الـ correlation هيبقى قريب من صفر.
  • workloads فيها equality lookup سريع مطلوب على عمود واحد. B-tree هنا أحسن.
  • جداول OLTP فيها UPDATE كتير على العمود المفهرس. الـ summaries بتقدم بسرعة.
  • لو محتاج Index-Only Scan لتجنّب heap reads. BRIN ما بيدعمش الفكرة دي.

checklist قبل ما تنقل index من B-tree لـ BRIN

  1. تأكّد إن الجدول > 50 مليون صف.
  2. شغّل SELECT correlation FROM pg_stats WHERE tablename = '...' AND attname = '...'; ولازم النتيجة > 0.9.
  3. شغّل الـ workload الفعلي على staging لمدة ساعة وقِس P95.
  4. اعمل الـ index بـ CONCURRENTLY على production عشان ما تقفلش الجدول.
  5. راقب pg_stat_user_indexes لمدة 48 ساعة قبل ما تشيل الـ B-tree.

المصادر

  • PostgreSQL 16 Documentation — BRIN Indexes: postgresql.org/docs/16/brin.html
  • PostgreSQL 16 Documentation — pg_stats view: postgresql.org/docs/16/view-pg-stats.html
  • 2ndQuadrant Blog — "Block Range Indexes (BRIN)" بقلم Alvaro Herrera، 2015 (مقدّم BRIN الأصلي).
  • Crunchy Data Blog — "An Overview of BRIN" (تحديث 2024).
  • pg_repack project documentation: github.com/reorg/pg_repack

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

على أكبر جدول append-only عندك، شغّل SELECT pg_size_pretty(pg_relation_size('your_btree_index_name')); وقارن بحجم BRIN على نفس العمود. لو الفرق أكثر من 1000x والـ correlation فوق 0.95، اعمل الـ BRIN index بـ CONCURRENTLY، خلّيه بجانب الـ B-tree لمدة 48 ساعة، وقِس الـ P95 latency على query الأكثر تكرارًا. لو BRIN كسب، شيل الـ B-tree.

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

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

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