Autovacuum في PostgreSQL هو نظام الصيانة الخلفي الذي يستعيد مساحة الصفوف الميتة لإعادة استخدامها، ويحدّث إحصاءات مخطط الاستعلام، ويحافظ على Visibility Map، ويمنع Transaction ID Wraparound. في الإنتاج يجب أن يبدأ Vacuum مبكرًا على كل جدول كثير التغيّر، وأن يحصل العمال على Throughput يكفي للانتهاء قبل تراكم دين جديد. الطريقة الآمنة هي القياس ثم الضبط لكل جدول، لا تعطيل Autovacuum أونسخ قيمة عامة إلى كل العنقود.
يعتمد هذا الدليل على وثائق PostgreSQL 18 الحالية التي تم التحقق منها في 22 أكتوبر 2026. القيم الافتراضية والسلوك المذكور حقائق موثقة، أما الحدود والتنبيهات وخطوات الإطلاق الموسومة كتوصيات فهي نقطة بداية هندسية يجب اختبارها على Workload والتخزين وإصدار PostgreSQL المدعوم لديك.
لماذا تصبح Dead Tuples مشكلة موثوقية؟
يستخدم PostgreSQL نموذج MVCC؛ لذلك تترك UPDATE وDELETE عادة نسخ الصفوف القديمة حتى لا تعود مرئية لأي Transaction. يزيل VACUUM العادي هذه النسخ من الجداول والفهارس ويجعل المساحة قابلة لإعادة الاستخدام. غالبًا لا يعيدها إلى نظام التشغيل، لكن تشغيله المتكرر يحافظ على حجم مستقر ويقلل الحاجة إلى إعادة كتابة مزعجة.
تحدد وثيقة Routine Vacuuming أربع مسؤوليات: إعادة استخدام مساحة الصفوف المتغيرة، وتحديث إحصاءات Planner، وتحديث Visibility Map لتسريع Index-only Scan، والحماية من Transaction وMultixact Wraparound. لذلك قد يظهر تأخر Autovacuum كتضخم جدول وفهرس أوخطط قديمة أوانخفاض Index-only Scans أوارتفاع Write Amplification أوVACUUM طارئ مضاد للـWraparound.
لا تساوِ تقدير n_dead_tup ببايتات يمكن استردادها من القرص. فهو تقدير، وVACUUM العادي يجعل المساحة قابلة لإعادة الاستخدام داخل Relation، وللفهرس سلوك مستقل. قس حجم Relation وزمن التطبيق مع الإحصاءات. جدول تضخم بعد حذف مرة واحدة قد يحتاج Rewrite مخططًا، بينما جدول دائم التغيّر يحتاج Vacuum أبكر.
افهم Trigger قبل تغييره
بالنسبة إلى UPDATE وDELETE يصبح الجدول مؤهلًا عندما تتجاوز الصفوف المتغيرة Threshold ثابتة مع نسبة من حجم الجدول:
vacuum trigger = autovacuum_vacuum_threshold
+ autovacuum_vacuum_scale_factor × estimated table rowsتوثق PostgreSQL 18 قيمًا افتراضية 50 و0.2، مع حد أقصى افتراضي للـTrigger يبلغ 100,000,000 صف. وفق القيم الافتراضية قد ينتظر جدول مقدر بمئة مليون صف نحو عشرين مليون تغيير. قد يكون ذلك متأخرًا جدًا لجدول ساخن، مع أن القيم نفسها مناسبة لجدول صغير. وتوجد Triggers للإدخال لأن الجداول Insert-only تحتاج Freezing وصيانة Visibility Map أيضًا.
الـTrigger لا يضمن الانتهاء. قد ينتظر العمل Worker متاحًا ثم يستغرق أكثر مما يسمح به معدل التغيير. تقول مرجعية إعدادات Vacuum إن الحد الافتراضي للعمال المتزامنين ثلاثة، وإن Worker Slots وMax Workers عنصران منفصلان في PostgreSQL 18. رفع العمال يزيد التوازي لكنه قد يزيد I/O والذاكرة إجمالًا.
فضّل Storage Parameters للجداول الاستثنائية:
ALTER TABLE public.orders SET (
autovacuum_vacuum_scale_factor = 0.02,
autovacuum_vacuum_threshold = 5000,
autovacuum_analyze_scale_factor = 0.01,
autovacuum_analyze_threshold = 2500
);هذه قيم توضيحية وليست هدفًا عامًا. احسب عدد الصفوف التي قد تتغير خلال مدة Vacuum، ثم اختر Trigger تترك وقتًا كافيًا للانتهاء. أعد التقييم مع تغير عدد الصفوف ومعدل التحديث والفهارس والتخزين.
ابنِ Baseline من إحصاءات PostgreSQL
ابدأ بقائمة مرتبة لا بتعديل إعداد. يعرض Cumulative Statistics تقديرات الصفوف الحية والميتة وعدد عمليات Vacuum وأوقاتها. قد تتأخر الإحصاءات أوتُخزن خلال Transaction الحالية، لذلك قارن نوافذ زمنية متسقة بدل اعتبار Query واحدة حقيقة دقيقة.
SELECT schemaname, relname,
n_live_tup, n_dead_tup,
last_autovacuum, autovacuum_count,
last_autoanalyze, autoanalyze_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 25;اربط النتائج بـpg_total_relation_size ومعدل التغيير وزمن المستخدم. راقب نسبة Dead إلى Live، والزمن منذ آخر Autovacuum ناجح، وحجم Relation وWAL وStorage Latency. الاتجاه المستمر أهم من Snapshot: ارتفاع Dead Tuples بعد كل دورة يعني أن العمال يبدأون متأخرين أوأبطأ من الحمل أوأن شيئًا يمنع الإزالة.
أثناء Vacuum تعرض pg_stat_progress_vacuum المرحلة وتقدم Blocks. ويمكن عرض Delay Time عند تفعيل Cost-delay Timing. استخدمها للتمييز بين “لم تتم الجدولة” و“ينتظر السعة” و“المسح بطيء” و“الإزالة محجوبة”. غياب صف يعني فقط أنه لا توجد VACUUM مدعومة تعرض تقدمها الآن، وليس أن الصيانة سليمة.
افحص Transactions الطويلة وIdle in Transaction لأن Snapshot قديمة قد تبقي النسخ مرئية. افحص Replication Slots والنسخ أيضًا؛ فقد يأتي الاحتفاظ من مستهلك لا يتقدم. أصلح سبب الاحتفاظ قبل تكرار Vacuum على الجدول نفسه.
احمِ النظام من Transaction ID Wraparound
منع Wraparound حد لصحة البيانات وليس تفضيل أداء. يتتبع PostgreSQL أقدم Transaction ID غير مجمدة لكل Relation في pg_class.relfrozenxid. عندما يقترب العمر من autovacuum_freeze_max_age يفرض النظام Vacuum مضادة للـWraparound. توثق PostgreSQL قيمة افتراضية 200 مليون Transaction، وتوضح أن عمال الحماية يعملون حتى لو عُطل Autovacuum العادي.
راقب age(datfrozenxid) لكل Database وage(relfrozenxid) لأقدم Relations. نبّه بحسب الهامش المتبقي وسرعة استهلاكه، لا بحسب عمر ثابت فقط. قد يستهلك عنقود مشغول ملايين IDs أسرع بكثير أثناء Batch أوحادث.
لا تلغِ عمال Anti-wraparound لتخفيف الحمل من دون معرفة الهامش. إذا لم يتقدم Freezing فسيحمي PostgreSQL البيانات برفض الكتابة في النهاية. احجز I/O واهتمامًا تشغيليًا قبل بلوغ الطوارئ. وحتى الجداول Insert-only الكبيرة تحتاج Freezing عندما يكون n_dead_tup منخفضًا.
اضبط Throughput من دون I/O Storm
استخدم تسلسلًا مضبوطًا: اخفض Trigger أولًا لعدد قليل من الجداول التي تنمو Dead Tuples فيها بين الدورات، ثم راقب هل تنتهي VACUUM ضمن النافذة. بعد ذلك فقط فكّر في عدد العمال العام أوCost-based Delay.
يؤخر Cost-based Vacuum العمل عمدًا بعد بلوغ حد من الكلفة. قد يقلل رفع Throughput مدة Vacuum لكنه ينافس القراءة والكتابة. وقد يحسن زيادة العمال تقدم جداول متعددة بينما تضاعف I/O. لذلك اربط التغيير بـGuardrails للتخزين والمعالج وWAL وزمن الاستعلام.
خطة إطلاق عملية:
- اجمع Baseline لسبعة أيام للجداول الأعلى تغيرًا ولهامش Transaction Age؛
- غيّر جدولًا واحدًا أومجموعة صغيرة عبر ALTER TABLE؛
- راقب عدة دورات Vacuum كاملة وفترة ذروة؛
- قارن مدة الانتهاء ومنحنى Dead Tuples والنمو وStorage Latency وp95 للتطبيق؛
- وسّع فقط إذا بقيت Guardrails سليمة، واحتفظ بالقيم السابقة للـRollback.
قد يقلل Partitioning نطاق الصيانة عندما يملك نموذج البيانات Lifecycle طبيعيًا، لكنه ليس علاجًا لضبط سيئ. كل Partition هي Relation مستقلة تحتاج المراقبة. حذف Partition منتهية فعال، لكن آلاف الأقسام الصغيرة ترفع كلفة التخطيط والتشغيل.
اختر عملية الصيانة الصحيحة
استخدم Autovacuum للحمل الدائم. استخدم VACUUM يدويًا للحاق موجّه أونافذة صيانة مضبوطة أوتحقق بخيارات صريحة. يجب أن يكمل العمل اليدوي الـDaemon لا أن ينشئ جدولين متنافسين بصمت.
استخدم VACUUM (ANALYZE) عندما تحتاج إعادة استخدام المساحة وتحديث Planner معًا، وANALYZE وحدها عندما تغير توزيع البيانات بلا مشكلة Dead Tuples. استخدم REINDEX أوالصيغة Concurrent فقط عندما يثبت الدليل مشكلة خاصة بالفهرس؛ Vacuum الجدول وإعادة بناء الفهرس يعالجان مشكلتين مختلفتين.
يعيد VACUUM FULL كتابة الجدول، ويحتاج مساحة إضافية وقفل ACCESS EXCLUSIVE. توصي الوثائق بـVACUUM عادي متكرر بدل الاعتماد عليه. احجز Rewrite لحالة استثنائية تبرر فيها إعادة المساحة إلى نظام التشغيل القفل والوقت والمساحة وخطة التراجع.
لا تعطل Autovacuum على العنقود كحل أداء. لا تضع Scale Factor منخفضة جدًا للجميع بلا حساب تكرار الاستيقاظ. لا تضبط من n_dead_tup وحدها، ولا تقتل العمال تلقائيًا، ولا تتجاهل Idle Transactions، ولا تعتبر نجاح VACUUM يدوية إثباتًا للحل.
قائمة إنتاج مختصرة
احصر أكبر Relations وأكثرها تغيرًا، وسجل Overrides الحالية. ضع Trigger أبكر للجداول التي يتجاوز معدل تغيرها المعادلة الافتراضية. تأكد من سعة العمال وI/O للانتهاء. راقب التقدم النشط والدورات السابقة والحجم وزمن التطبيق وSnapshots القديمة وReplication Retention وهامش Transaction Age.
الحالة المطلوبة ليست “صفر Dead Tuples”، بل حلقة صيانة محدودة: تتراكم النسخ ضمن Budget معروفة، ويبدأ Vacuum بهامش كافٍ، وينتهي أسرع من إنشاء الدين، وتبقى إحصاءات Planner وVisibility مفيدة، ويظل Wraparound بعيدًا عن الطوارئ. عندها يصبح Autovacuum عنصر موثوقية، لا عملية خلفية لا يلاحظها الفريق إلا وقت الحادث.
المراجع الرسمية
تدعم هذه المراجع سلوك الأدوات المذكورة. الأمثلة وقرارات التصميم توضيحية، ويجب تكييفها مع متطلبات المشروع وإصداراته.
إعداد: Noor Yasser
تعمل على تحدٍ تقني مشابه؟
أساعد الفرق على تحويل القرار المعماري إلى نطاق واضح وتنفيذ يمكن تشغيله ومراجعته بثقة.




