هذا المقال للمستوى: محترف
لو عندك جدول 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، ووقت بناء بيوصل لساعات.
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
-- جدول 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 دقيقة