تقسيم الجداول في PostgreSQL يحل مشكلة حقيقية لكنه يخلق مشاكل مختلفة إذا طُبق في الوضع الخاطئ. قبل اللجوء إليه، افهم ما يفعله مخطط الاستعلامات فعلياً مع الأقسام.
تقسيم الجداول في PostgreSQL يحل مشكلة حقيقية، لكنه يخلق مشاكل مختلفة إذا طُبق في الوضع الخاطئ. في كل مرة تتجاوز فيها جداول السجلات والأحداث في نظام SaaS مئة مليون صف، يقترح أحدهم التقسيم. أحياناً يكون ذلك صحيحاً تماماً. وأحياناً يجعل كل شيء أسوأ. الفرق يكمن في فهم ما يفعله مخطط الاستعلامات فعلياً مع الأقسام.
ما الذي يفعله تقسيم الجداول في PostgreSQL فعلياً
يدعم PostgreSQL ثلاث استراتيجيات للتقسيم: النطاق (Range)، والقائمة (List)، والتجزئة (Hash).
تقسيم النطاق يقسم الصفوف حسب نطاق متواصل من القيم، وأكثرها شيوعاً هو الطابع الزمني. جدول audit_events مقسم حسب created_at قد يكون لديه قسم واحد لكل شهر.
تقسيم القائمة يقسم الصفوف حسب قيم منفصلة محددة. في نظام SaaS للشرق الأوسط قد تكون لديك أقسام مختلفة لكل دولة: لبنان والإمارات والسعودية.
الفائدة من وجهة نظر مخطط الاستعلامات هي تقليص الأقسام: عندما يتضمن الاستعلام تصفية على مفتاح القسم، يتجاهل المخطط الأقسام التي لا يمكن أن تحتوي على صفوف مطابقة.
لكن تقليص الأقسام يعمل فقط عندما يكون التصفية على مفتاح القسم نفسه. استعلام يفلتر حسب tenant_id وحده على جدول مقسم حسب التاريخ لا يستفيد على الإطلاق.
ما لا يساعد فيه التقسيم:
- الحمل الزائد على الاتصالات
- عمليات البحث بالمفاتيح غير مفاتيح التقسيم
- إنتاجية INSERT
- زمن الاستعلام عند امتداده عبر أقسام متعددة
حالة الاستخدام في SaaS متعدد المستأجرين
السيناريو الذي يساعد فيه تقسيم النطاق بالوقت حقاً هو جداول السلاسل الزمنية كثيفة الكتابة التي تنمو بلا حدود ويتم الاستعلام عنها حسب نطاقات زمنية حديثة.
سجلات المراجعة هي المثال النموذجي. كل إجراء مستأجر يكتب حدثاً. نظام SaaS متوسط الحجم بـ 500 مستأجر نشط يولد عشرات الملايين من الصفوف شهرياً. الاستعلامات على سجلات المراجعة تتضمن دائماً تصفية زمنية:
CREATE TABLE audit_events (
id BIGSERIAL,
tenant_id UUID NOT NULL,
action TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);
CREATE TABLE audit_events_2026_05
PARTITION OF audit_events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE INDEX ON audit_events_2026_05 (tenant_id, created_at);
المؤشر المركب على (tenant_id, created_at) داخل كل قسم هو ما يجعل الاستعلام لكل مستأجر بنطاق زمني سريعاً. التقسيم يقلل حجم المؤشر الذي يحتاج إلى المسح.
أين يضر التقسيم في أنظمة SaaS
الاستعلامات عبر الأقسام. أي استعلام لا يفلتر على مفتاح القسم يجب أن يمسح جميع الأقسام. في نظام متعدد المستأجرين، إذا احتجت إلى تشغيل عمليات عبر جميع المستأجرين (تسوية الفوترة، تصدير البيانات، تقارير الامتثال)، فإن الجدول المقسم بالتاريخ يفرض مسحاً كاملاً لكل الأقسام.
صيانة الأقسام. الجداول المقسمة تتطلب إنشاء أقسام مستقبلية مسبقاً. إذا كان لديك استراتيجية تقسيم شهرية ولم يُنشئ أحد قسم يوليو قبل الأول من يوليو، فإن عمليات الإدراج تفشل. تحتاج إلى cron job أو إعداد pg_partman.
قيود المفاتيح الخارجية لا تعبر حدود الأقسام. كثير من الفرق تتخلى عن قيود FK بين الجداول عند التقسيم، وتقبل أن تفرض سلامة المرجعية على طبقة التطبيق.
إعادة ملء البيانات مؤلمة. إذا قررت تقسيم جدول موجود بـ 200 مليون صف، لا يمكنك إضافة التقسيم بأمر ALTER TABLE بسيط. تحتاج إلى إنشاء جدول مقسم جديد، ونقل البيانات قسماً تلو الآخر، وتبديل اسم الجدول، وإعادة بناء جميع مراجع المفاتيح الخارجية. على نظام إنتاجي حي يخدم عملاء في لبنان والإمارات والسعودية، هذه عملية تستغرق أياماً.
البديل: التحسين الصحيح للمؤشرات قبل التقسيم
قبل اللجوء إلى التقسيم، استنفد خيارات المؤشرات. لنمط الاستعلام الشائع في نظام متعدد المستأجرين، مؤشر مصمم جيداً على جدول واحد يتفوق على مخطط تقسيم مصمم بشكل سيئ في كل مرة.
المؤشرات الجزئية على جدول غير مقسم يمكن أن تكون فعالة للغاية:
CREATE INDEX idx_audit_events_recent
ON audit_events (tenant_id, created_at)
WHERE created_at >= '2026-01-01';
هذا المؤشر صغير لأنه يستبعد البيانات التاريخية. الاستعلامات المفلترة للأحداث الحديثة تستخدم هذا المؤشر وتعمل بسرعة.
مؤشرات BRIN مصممة للجداول الكبيرة ذات الإلحاق فقط حيث يرتبط الترتيب الفيزيائي للصفوف بالعمود المفهرس. سجل مراجعة حيث تُدرج الصفوف دائماً بقيم created_at متصاعدة هو حالة استخدام BRIN المثالية:
CREATE INDEX idx_audit_brin ON audit_events USING BRIN (created_at);
مؤشر BRIN أصغر بمراتب من مؤشر B-tree على نفس العمود.
في المعايير التجريبية: على جدول بـ 10 مليون صف، مؤشر B-tree مركب على (tenant_id, created_at) يحقق باستمرار أوقات استعلام أقل من 10ms للحالة الشائعة.
متى يكون التقسيم الإجابة الصحيحة
سياسات الاحتفاظ بالبيانات. إذا احتجت إلى حذف سجلات المراجعة الأقدم من 12 شهراً، فإن DELETE FROM audit_events WHERE created_at < NOW() - INTERVAL '12 months' على جدول بمليارين من الصفوف يستغرق ساعات ويحمل أقفالاً تعطل العمليات العادية. DROP TABLE audit_events_2025_04 يكتمل في ثوانٍ. هذا هو السبب الأكثر إقناعاً للتقسيم.
سير عمل الأرشفة. نقل البيانات القديمة إلى طبقة تخزين أرخص سهل مع الأقسام: افصل القسم القديم وألحقه بجدول أصل جديد في مساحة جدول الأرشيف.
البيانات الزمنية الكثيفة فعلاً. إذا كان مستأجر واحد يولد ملايين الصفوف يومياً (بيانات أجهزة استشعار IoT، تدفقات المعاملات، سجلات POS من مئات المواقع)، وكل استعلام يفلتر بنافذة زمنية حديثة، فإن تقليص الأقسام يوفر تحسينات حقيقية.
دروس من الإنتاج
- تقليص الأقسام يعمل فقط على مفتاح القسم. نظام متعدد المستأجرين يستعلم بـ
tenant_idعلى جدول مقسم بالتاريخ لا يستفيد ما لم يفلتر أيضاً بالتاريخ. - أحسن أولاً توظيف المؤشرات. مؤشر مركب على
(tenant_id, created_at)يحل الحالة الشائعة بجزء بسيط من التعقيد التشغيلي. - أفضل حالة استخدام للتقسيم النطاقي هي الاحتفاظ:
DROP PARTITIONبدلاً منDELETE WHERE created_at <على جدول بمليارين من الصفوف. - لا تقسم جدولاً موجوداً كبيراً دون خطة ترحيل بدون توقف.
- أنشئ الأقسام المستقبلية مسبقاً. القسم المفقود يعني فشل الإدراج في الإنتاج.
هل جداول PostgreSQL الخاصة بك تنمو بسرعة وتريد معرفة ما إذا كان التقسيم أو التحسين بالمؤشرات هو الخيار الصحيح؟ تواصل مع فريق Voxire: https://voxire.com/get-a-quote/



