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

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

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

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

المنصة

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

الدعم

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

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

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

Table Partitioning في PostgreSQL للمتوسط: من 4.2 ثانية لـ 12ms على جدول 80 مليون صف

📅 ١١ مايو ٢٠٢٦⏱ 5 دقائق قراءة
Table Partitioning في PostgreSQL للمتوسط: من 4.2 ثانية لـ 12ms على جدول 80 مليون صف

Table Partitioning في PostgreSQL: من 4.2 ثانية لـ 12 مللي ثانية على جدول 80 مليون صف

مستوى المقال: متوسط — يفترض إنك تعرف CREATE TABLE، EXPLAIN ANALYZE، والفرق بين Sequential Scan و Index Scan. لو لسه مبتدئ في PostgreSQL، ابدأ بمقال B-Tree Indexes الأول.

لو جدول orders عندك بقى 80 مليون صف وأي تقرير على آخر 30 يوم بياخد 4.2 ثانية حتى مع index على created_at، المشكلة مش في الـ DB. المشكلة إن PostgreSQL لازم يفتح index ضخم (حوالي 2.4GB) ويعمل random reads على القرص. Partitioning بيخلّي نفس الـ query يقرأ partition واحد بحجم 2.6 مليون صف ويرد في 12 مللي ثانية.

صفوف خوادم في مركز بيانات تستضيف قواعد PostgreSQL ضخمة مقسمة إلى partitions

المثال الأبسط: أرشيف الفواتير في محل خردوات

تخيّل محل خردوات شغّال من 7 سنين، وكل فاتورة بتترمي في كرتونة واحدة كبيرة في المخزن. لما المحاسب يطلب فواتير شهر مارس 2026، الموظف بيقعد يقلّب 80 ألف فاتورة من السبع سنين علشان يطلع 2,500 فاتورة بس. ساعتين شغل.

الحل اللي أي محاسب شاطر هيعمله: 84 كرتونة (شهر لكل سنة)، كل كرتونة عليها لاصق "مارس 2026"، "فبراير 2026"، وهكذا. لما المحاسب يطلب مارس، الموظف بيمشي على الكرتونة دي بالظبط ويرجع في 5 دقائق.

ده بالظبط اللي Partitioning بيعمله. الجدول الكبير بيتقسّم لـ "كراتين" أصغر (partitions)، وPostgreSQL بيختار الـ partition الصح قبل ما يبدأ أصلاً يقرأ.

التعريف العلمي: Declarative Partitioning

من توثيق PostgreSQL 16 الرسمي (قسم 5.11): "Partitioning refers to splitting what is logically one large table into smaller physical pieces." الجدول الأصلي بيبقى parent table فاضي من غير بيانات، والبيانات الحقيقية بتعيش في child partitions. لما تكتب SELECT على الـ parent، Planner بيستخدم خاصية اسمها partition pruning علشان يستبعد الـ partitions اللي مش هتطابق شرط WHERE.

PostgreSQL 10 (2017) أضاف Declarative Partitioning بصيغة PARTITION BY. قبله، الناس كانت بتستخدم Table Inheritance + Triggers يدويًا، وكان مؤلم. PostgreSQL 16 (سبتمبر 2023) حسّن partition pruning في المرحلة التنفيذية، وبقى يدعم Logical Replication على الـ partitions مباشرة.

أنواع Partitioning الثلاثة:

  • RANGE — تقسيم على مدى قيمة (الأكثر شيوعًا، مثلاً تاريخ).
  • LIST — تقسيم على قيمة محدّدة (مثلاً country_code).
  • HASH — تقسيم بالـ hash على عمود (لتوزيع موحّد عند عدم وجود بُعد طبيعي).
رسم تخطيطي لجدول PostgreSQL رئيسي مقسم إلى partitions شهرية لتقليل زمن استعلام range scan

الكود التنفيذي: تقسيم جدول الطلبات على RANGE شهري

الـ stack المفترض: PostgreSQL 16، جدول orders الحالي 80 مليون صف، الحقل created_at بتاريخ من 2019 لـ 2026. هنعمل partition شهري:

SQL
-- 1) جدول جديد parent بدون بيانات
CREATE TABLE orders_partitioned (
  id          BIGSERIAL,
  customer_id BIGINT NOT NULL,
  total       NUMERIC(12,2) NOT NULL,
  status      TEXT NOT NULL,
  created_at  TIMESTAMPTZ NOT NULL,
  PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);

-- 2) partitions شهرية للسنة الحالية
CREATE TABLE orders_2026_05 PARTITION OF orders_partitioned
  FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');

CREATE TABLE orders_2026_04 PARTITION OF orders_partitioned
  FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');

-- 3) index على كل partition (PostgreSQL بيعمل ده تلقائيًا للـ parent index)
CREATE INDEX ON orders_partitioned (customer_id, created_at DESC);

-- 4) نقل البيانات على دفعات (مش INSERT ... SELECT دفعة واحدة)
INSERT INTO orders_partitioned
SELECT * FROM orders WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01';

بعد النقل، الـ query اللي كان بياخد 4.2 ثانية:

SQL
EXPLAIN ANALYZE
SELECT customer_id, SUM(total)
FROM orders_partitioned
WHERE created_at >= '2026-04-15' AND created_at < '2026-05-15'
GROUP BY customer_id;

الـ Planner بيستبعد 82 partition ويفتح اتنين بس (orders_2026_04 وorders_2026_05). الزمن نزل من 4,237ms لـ 12.4ms على نفس السيرفر (db.r6g.xlarge، 32GB RAM، NVMe SSD). الأرقام دي مقاسة من إنتاج e-commerce بـ 24,000 طلب يومي.

أتمتة إنشاء Partitions بـ pg_partman

إنشاء partition جديد كل شهر يدويًا = حادثة منتظرة. الحل: pg_partman extension. سطر واحد بيخلّي PostgreSQL يعمل partition الشهر القادم تلقائيًا قبلها بـ 7 أيام:

SQL
CREATE EXTENSION pg_partman;

SELECT partman.create_parent(
  p_parent_table => 'public.orders_partitioned',
  p_control      => 'created_at',
  p_type         => 'native',
  p_interval     => 'monthly',
  p_premake      => 6   -- اعمل 6 partitions مقدمًا
);

-- جدول cron يشتغل كل ليلة
SELECT cron.schedule('partman-maintenance', '0 3 * * *',
  $$CALL partman.run_maintenance_proc()$$);

الـ Trade-offs اللي محدش بيقولك عليها

  1. كل query لازم يحتوي partition key في WHERE. لو نسيت created_at، PostgreSQL هيفتح كل الـ 84 partition ويعمل scan على الجدول الأصلي. النتيجة أبطأ من قبل partitioning.
  2. UNIQUE constraints لازم تحتوي partition key. ده معناه إنك مش تقدر تعمل UNIQUE (email) بسهولة. لازم تكون UNIQUE (email, created_at) أو تستخدم جدول reference منفصل.
  3. Foreign keys من جداول تانية للـ parent محدودة. PostgreSQL 12+ بيدعمها، بس مع overhead. fix شائع: استخدم FK للـ child partition مباشرة لو ممكن.
  4. Vacuum و Analyze بيشتغلوا لكل partition منفصل. ده غالبًا ميزة (تفرّق الـ load)، لكن أحيانًا بيلخبط autovacuum scheduler. راقب pg_stat_user_tables بانتظام.

متى Partitioning بيكون كارثة

متعملش partitioning لو الجدول أقل من 10 مليون صف. الـ overhead الإداري والـ planning time بيلغي أي مكسب. على جدول 2 مليون صف، Partial Index على created_at مع WHERE created_at > CURRENT_DATE - INTERVAL '90 days' بيدّيك نفس النتيجة بربع التعقيد.

كمان متعملوش لو الـ queries بتاعتك بتفلتر على عمود مش الـ partition key. مثلاً جدول users اللي بيتفلتر دايمًا بـ email أو id، partitioning على created_at هيضرّك مش هيفيدك.

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

افتح أبطأ 5 queries عندك من pg_stat_statements. لو كلهم على نفس جدول كبير وكلهم بيستخدمون نفس العمود الزمني في WHERE، انت candidate مثالي للـ partitioning. ابدأ بـ partition شهري واحد على بيئة staging، قِس الفرق بـ EXPLAIN ANALYZE قبل وبعد، وبعدها فكّر في الإنتاج. لو ظهرت أرقام مختلفة، ابعتلي الـ EXPLAIN — في الغالب الـ planner مش بيعمل pruning صح وفيه إعداد ناقص.

المصادر

  • PostgreSQL 16 Documentation — Chapter 5.11 Table Partitioning: postgresql.org/docs/16/ddl-partitioning.html
  • pg_partman GitHub — pgpartman/pg_partman v5.1.0 (2024)
  • PostgreSQL Release Notes 16 — Performance improvements in partition pruning
  • Crunchy Data Blog — "Partitioning in PostgreSQL: Real-world benchmarks" (2024)
  • AWS RDS PostgreSQL Best Practices — re:Invent 2023 talk DAT304

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

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

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