دراسة حالة · منصة تحليلات (سرّية)، أمريكا الشمالية

زيادة سرعة الاستيعاب 10 أضعاف في خط تحليلات PostgreSQL باستخدام جداول UNLOGGED

كيف استخدمت UnlockLive جداول PostgreSQL من نوع UNLOGGED، واستيعاب COPY على دفعات، ونمطاً منضبطاً بجدولين لرفع خط أنابيب تحليلات عالي الحجم من 8 آلاف إلى 80 ألف حدث في الثانية — دون فقدان ضمانات سلامة البيانات التي يتطلبها المسار الخاضع للتدقيق.

  • القطاعالبيانات / التحليلات
  • السنة2024
  • البلدالولايات المتحدة
  • المدة3 أشهر
10x Ingest Throughput on a PostgreSQL Analytics Pipeline Using UNLOGGED Tables hero screenshot

النتائج في لمحة

  • 10xسعة استيعاب مستدامة (8K → 80K+ حدث/ثانية)
  • 60%تكلفة IOPS أقل في RDS على نفس فئة المثيل
  • <50msزمن إدراج المرحلة الأولى عند p95 تحت حمل مستمر
  • 0تراجعات في سلامة البيانات على مسار التقارير المدقَّق

التحدي

كانت منصة تحليلات تستوعب أحداث المنتج من قاعدة عملاء متنامية في جدول واحد باسم "raw_events". كان المخطط الأصلي صحيحاً لكنه بطيء: فكل حدث يصل إلى جدول مسجَّل بسبعة فهارس، وكان سجل WAL هو عنق الزجاجة، وبلغ معدل الاستيعاب حدّه الأقصى عند نحو 8,000 حدث في الثانية. وكانت عمليات الإدخال/الإخراج في RDS (IOPS) أكبر بند تكلفة، وكان التوسع المخطط له بثلاثة أضعاف في عدد العملاء سيعطل خط الأنابيب قبل توقيع العقد التالي.

قرأ الفريق النصائح المعتادة — «استخدموا طابور رسائل» و«قسّموا الجدول إلى شظايا» و«انتقلوا إلى قاعدة بيانات سلاسل زمنية متخصصة» — لكن كل هذه الإجابات كانت تعني ترحيلاً يمتد عدة أرباع سنة. أما السؤال الحقيقي فكان: هل يستطيع PostgreSQL مواكبة الحمل إذا توقفنا عن مقاومته؟ والشرط الصارم أن أي بيانات تصل إلى جداول التقارير الخاضعة للتدقيق يجب أن تكون دائمة ومتسقة ومفهرسة. ولم يكن بإمكاننا التضحية بالسلامة في جانب التحليلات لتوفير الاستيعاب.

حلّنا

قسّمنا خط الأنابيب إلى مرحلتين منفصلتين بوضوح بعقدين مختلفين للمتانة.

المرحلة 1 (الاستيعاب): جدول تجهيز من نوع UNLOGGED يستقبل التدفق الكثيف. تتخطى جداول UNLOGGED في PostgreSQL كتابات WAL عند الإدخال، وهو المقايضة الصحيحة تماماً لبيانات التجهيز قصيرة العمر — إذ نحصل على إنتاجية إدخال خام أعلى بمقدار 5-10 أضعاف مقابل فقدان الصفوف المجهَّزة إذا تعطل الخادم. وبالجمع مع COPY على دفعات (وليس INSERT) من خدمة استيعاب بـ Python وقاعدة مقصودة «بلا فهارس على جدول التجهيز»، قفز الاستيعاب الخام من 8 آلاف إلى أكثر من 80 ألف حدث في الثانية على مثيل RDS نفسه.

المرحلة 2 (المتانة): يفرّغ عامل Celery جدول التجهيز على دفعات صغيرة إلى جدول `events` الحقيقي المسجَّل بالكامل والمفهرس ضمن معاملة واحدة مع عمليات upsert غير مكررة الأثر. ولا يقرأ مسار التقارير الخاضع للتدقيق إلا من الجدول الدائم. وإذا تعطل الخادم أثناء الاستيعاب، نخسر على الأكثر بضع ثوانٍ من الأحداث قيد المعالجة من جدول التجهيز — وتعيد المصادر المنتجة المحاولة، فيعود الجدول الدائم إلى الصحة.

ودعمنا ذلك بطبقة تشغيلية صغيرة لكنها دقيقة: لوحة Grafana لمعدل امتلاء المرحلة 1، وتنبيه حاسم إذا تأخر المفرِّغ، وتدوير الأقسام (partitions) في الجدول الدائم، ودليل تشغيل مكتوب لحالات الإخفاق الثلاث المهمة.

  • جدول تجهيز UNLOGGED بلا فهارس — مصمم خصيصاً لإنتاجية الإدخال الخام
  • استيعاب COPY على دفعات من خدمة Python/FastAPI، وليس INSERT صفاً بصف
  • مفرِّغ Celery ينقل دفعات صغيرة إلى جدول events الدائم المفهرس بالكامل
  • عمليات upsert غير مكررة الأثر (ON CONFLICT DO NOTHING) بحيث تكون إعادة محاولات المصادر المنتجة آمنة
  • تقسيم شهري للجدول الدائم لإبقاء أعمال vacuum والفهارس محدودة
  • لوحات تأخر شاملة وتنبيهات في Grafana / Datadog
  • دليل تشغيل مكتوب يغطي تجاوز الفشل في RDS وتعطل المفرِّغ والضغط العكسي من المصادر المنتجة
  • اعتماد التدقيق لملف المتانة قبل الإطلاق — دون مفاجآت

كيف بنيناه

  1. 01

    تحديد الاختناق الحقيقي

    قبل المساس بالمخطط، أمضينا أسبوعًا مع pg_stat_statements وRDS Performance Insights وأداة مخصصة لتتبع إنتاجية WAL. كانت البيانات حاسمة: شكّلت كتابات WAL وصيانة الفهارس على جدول الأحداث الضخم الواحد 78% من وقت الكتابة. لم تكن المشكلة في المعالج أو الذاكرة، بل في المتانة (durability).

  2. 02

    تصميم عقد الجدولين

    كتبنا وثيقة تصميم قصيرة حدّدت بدقة ما منحتنا إياه UNLOGGED وما كلفتنا وما الضمانات التي يجب أن يوفرها المنتجون في المنبع (تسليم مرة واحدة على الأقل، ومعرّفات أحداث idempotent). جعل العقد ملف الفقد صريحًا حتى يتمكن فريق التدقيق من الموافقة مسبقًا: «قد يُفقد ما يصل إلى N ثانية من الأحداث المرحلية عند تجاوز RDS للفشل؛ أما الجدول الدائم فلا يتأثر.»

  3. 03

    COPY بالدفعات ومفرِّغ بدفعات صغيرة

    استبدلنا عمليات الإدراج صفًا صفًا بخدمة إدخال بـ Python تخزّن الأحداث مؤقتًا في الذاكرة لمدة تصل إلى 250 ميلي ثانية ثم تفرغها عبر PostgreSQL COPY في جدول التجهيز UNLOGGED. ويسحب مُفرِغ Celery دفعات صغيرة من 5,000 صف إلى الجدول الدائم داخل معاملة واحدة مع ON CONFLICT DO NOTHING لضمان idempotency.

  4. 04

    التشغيل والتجزئة ودليل التشغيل

    أضفنا تقسيمًا شهريًا (partitioning) للجدول الدائم (فتبقى عمليات vacuum وإعادة بناء الفهارس رخيصة)، ولوحات Grafana لقياس التأخر من البداية إلى النهاية، وتنبيهات على امتلاء الجدول المرحلي وتأخر المُفرِغ (drainer)، ودليل تشغيل مكتوبًا يغطي أنماط الفشل الثلاثة الفعلية: تعطل المُفرِغ، وتجاوز RDS للفشل، والضغط العكسي من المنتج. وسُلّم إلى فريق المناوبة لدى العميل مع مرافقة.

حزمة التقنيات

  • PostgreSQL 16
  • Python
  • FastAPI
  • Celery
  • Redis
  • AWS RDS
  • AWS S3
  • Grafana
  • Datadog
  • هندسة أداء الأنظمة الخلفية
  • Python وFastAPI
  • حلول السحابة
  • هندسة قواعد البيانات
“توقعنا أن نقضي ربع سنة في الانتقال إلى قاعدة بيانات متخصصة بالسلاسل الزمنية. وبدلاً من ذلك أرانا UnlockLive كيف نجعل Postgres يؤدي المهمة، ووافق مدققونا على التصميم الجديد قبل إطلاقه.”
رئيس هندسة البيانات · منصة تحليلات (الاسم سري)

الأسئلة الشائعة

هل جداول PostgreSQL من نوع UNLOGGED آمنة للاستخدام في الإنتاج؟

نعم، للمهمة المناسبة. تتجاوز الجداول من نوع UNLOGGED الكتابة في WAL، مما يمنحك إنتاجية إدراج خام أعلى بما يعادل 5 إلى 10 أضعاف مقابل ملف خسائر موثّق: تُفقد الصفوف عند تعطل الخادم ولا تُنسخ. وهي مناسبة جداً لبيانات التجهيز قصيرة العمر عندما يستطيع النظام المصدر إعادة بث الأحداث، وغير مناسبة إطلاقاً لأي شيء تحتاج إلى قراءته لاحقاً كمصدر موثوق. ونقرنها دائماً بجدول دائم مسجَّل تقرأ منه المسارات الخاضعة للتدقيق فعلياً.

لماذا لا نستخدم ببساطة قاعدة بيانات سلاسل زمنية متخصصة مثل TimescaleDB أو ClickHouse؟

لأن تكلفة الترحيل والسطح التشغيلي حقيقيان. إذا كان فريقك يشغّل PostgreSQL بكفاءة وكان هدف الإنتاجية لديك في حدود عشرات الآلاف من الأحداث في الثانية، فإن نمط UNLOGGED مع COPY بالدفعات يوصلك إلى ذلك غالبًا في أسابيع بدلًا من أرباع سنوية. ونوصي بقاعدة بيانات سلاسل زمنية (TSDB) مخصصة عندما يحتاج حمل العمل فعلًا إلى تخزين عمودي أو فهارس مقسّمة زمنيًا أو استعلامات نطاق بالمللي ثانية على نطاق واسع.

هل يمكنني إضافة فهارس إلى جدول تجهيز PostgreSQL من نوع UNLOGGED؟

يمكنك ذلك، لكن لا ينبغي تقريباً. الغاية الأساسية من طبقة التجهيز هي سرعة الإدراج الخام، وكل فهرس تضيفه يكلّفك من إنتاجية الاستيعاب. نُبقي جدول التجهيز بلا فهارس، ونضع جميع الفهارس على الجدول اللاحق الدائم الذي تعمل عليه استعلامات التحليلات فعلياً.

كيف تضمنون عدم فقدان البيانات عند استخدام جداول UNLOGGED في خط معالجة؟

قاعدتان. أولاً، يجب أن يوفر المنتجون في المنبع تسليماً مرة واحدة على الأقل مع معرّفات أحداث متماثلة الأثر لتكون إعادة المحاولة آمنة. ثانياً، لا يقرأ الجانب الدائم من خط المعالجة إلا من الجدول اللاحق المسجَّل بالكامل، ولا يقرأ أبداً من جدول التجهيز UNLOGGED. وبذلك يصبح أسوأ سيناريو هو بضع ثوانٍ من أحداث التجهيز القابلة لإعادة البث عند حدوث عطل، لا تقريراً مفقوداً.

ما مقدار إنتاجية الاستيعاب التي يمكن الحصول عليها من مثيل PostgreSQL واحد بهذا النمط؟

يعتمد ذلك على حجم الصف والشبكة وفئة المثيل، لكن على مثيل AWS RDS متواضع من طراز db.r6g.xlarge بهذا النمط ذي الجدولين نشاهد بانتظام ما بين 60 ألفاً و100 ألف حدث في الثانية بشكل مستدام، مع زمن إدراج في المرحلة الأولى عند المئين 95 أقل من 50 مللي ثانية. وبعد ذلك نقسّم جدول التجهيز أو نتوسع أفقياً باستخدام موجّه كتابة.

هل تريد نتيجة كهذه؟

تحدّث إلى الفريق نفسه الذي بنى زيادة سرعة الاستيعاب 10 أضعاف في خط تحليلات PostgreSQL باستخدام جداول UNLOGGED. سنحدد نطاق مشروعك، ونقدّم لك عرضًا بسعر ثابت، ونُريك أقرب مثال من أعمالنا.

احجز مكالمة استراتيجية