One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

الجزء 3: قاعدة بيانات أصغر، أرخص، و Highly Available في 26 دقيقة، و لماذا AI حصل على PostgreSQL خاص به

نقلنا عقدة MySQL واحدة كبيرة إلى cluster Standard أرخص مع standby في 26 دقيقة، نقسيم القراءات إلى standby و أعطينا AI PostgreSQL خاص به مع pgvector. مساء امتحان حقيقي أظهر قاعدة البيانات لم تكن أبداً bottleneck.

Author

Anichur Rahaman

منذ يومين12 min read5 views
الجزء 3: قاعدة بيانات أصغر، أرخص، و Highly Available في 26 دقيقة، و لماذا AI حصل على PostgreSQL خاص به

في 22:33 على ليلة 5 أكتوبر، أشرنا كلا load balancers إلى وسم فارغ. أي شخص فتح الموقع رأى صفحة صيانة. خلفها، كل طالب، إجابة ونتيجة كانت تُنسخ إلى قاعدة بيانات جديدة، والنافذة التي اتفقنا معها مع الأعمال كانت 26 دقيقة.

لا أحد شكا أن قاعدة البيانات كانت بطيئة. المشكلة كانت شكلها. كانت عقدة MySQL كبيرة واحدة بدون standby، لذا اليوم ذلك الجهاز فشل كان يكون اليوم المنصة كاملة فشلت. كانت أيضاً أكبر، وأكثر تكلفة، من العمل المطلوب.

هذا المقال حول طبقة البيانات من NovaCommerce، المنصة التي تبيع الدورات وتشغل امتحانات تفاعلية مجدولة. يغطي نقل 26 دقيقة، العميل الذي نسينا، كيفية نقسيم القراءات والكتابات، لماذا AI حصل على PostgreSQL خاص به، وما قالت ليلة امتحان حقيقي من 7 أكتوبر عن كل ذلك. يتبع الجزء 2، حيث طبقة التطبيق تعلمت تتسع.

هذا الجزء 3 من سلسلة خماسية «من خادم واحد إلى جاهز لليلة الامتحان». NovaCommerce اسم خيالي؛ المعمارية والأرقام والأخطاء حقيقية.

النوع الخاطئ من الكبير

قاعدة البيانات القديمة كانت عقدة Managed MySQL «Advanced» واحدة بـ 8 vCPU و 32 GB من RAM. كانت كبيرة الطريقة التي جسر واحد ضخم جداً كبير. مثير إعجاب، والطريقة الوحيدة عبر.

مبكراً في المشروع كتبنا ما High Availability كانت يجب أن تعني لنا. سطر واحد قال: قاعدة البيانات تنجو من فشل عقدة. عقدة واحدة لا تستطيع تحقيق ذلك السطر في أي حجم. حجم وتوفرية أسئلة مختلفة، والإعداد القديم أجاب فقط الأول.

البيانات كانت حول 11 GB. لذا انتقلنا إلى Managed MySQL «Standard» 8.4 مع 4 vCPU و 16 GB لكل عقدة، واثنين عقدة: primary، و standby التي تستيقظ إذا primary فشل. على العنقود الجديد buffer pool هو 7 GB، max connections هو 1,601 و storage autoscale على. Status: Implemented, 5 October.

قبلبعد
الخطةAdvanced، عقدة واحدةStandard 8.4، اثنين عقدة (primary + standby)
الحجم8 vCPU / 32 GB4 vCPU / 16 GB لكل عقدة
إذا عقدة فشلتقاعدة البيانات نزولاًStandby تستيقظ
التكلفة الشهريةأعلىأقل؛ primary + standby حول $389 بسعر القائمة

أرخص و highly available في نفس الوقت نادر، لذا أخذناها. إليك كامل طبقة البيانات كما تبدو اليوم. بقية المقال تمشي عبرها.

خريطة طبقة البيانات: عقد backend تكتب إلى MySQL primary وتنشر reads عبر standby و primary، العامل يستخدم فقط primary، و PostgreSQL مع pgvector، Valkey و Spaces هي خدمات منفصلة
الكتابات تذهب primary، القراءات تشارك بين standby و primary، و AI، cache و ملفات كل واحد يعيش على خدمة خاصة.

النقل 26 دقيقة، خطوة بخطوة

النافذة كانت مخطط و موافق عليها مع الأعمال. هذا الترتيب الذي تبعناه في 5 أكتوبر.

  1. 22:33. كلا load balancers أشار إلى وسم فارغ. الطلاب رأوا صفحة صيانة.
  2. الطوابير فُرّغت و المجدول توقف، لذا لا job لا يمكن كتابة في وسط النسخ.
  3. الكتابات صفر على قاعدة البيانات القديمة لـ 30 ثانية. كانت هذه البوابة: النسخ بدأت فقط بعد الجانب القديم ذهب هادي.
  4. نسخ مع mysqldump في 4 تيارات متوازية. استغرقت 10 دقائق.
  5. عد فحص: 341 جداول و 18,727,244 صفوف مطابقة بالضبط بين قديم وجديد.
  6. التطبيق كان اختبر ضد قاعدة البيانات الجديدة. بعدها snapshot ذهبي جديد، rollout من كلا البركتين و worker جديد.
  7. 22:59. Load balancers خلفي، 26 دقيقة بعد ابتدأنا.
جدول زمني للنقل في 5 أكتوبر: load balancers إلى وسم فارغ في 22:33، طوابير فُرّغت، بوابة هادية 30 ثانية، نسخة 10 دقيقة، عد مطابق، اختبارات، خلف الحي في 22:59، و لوحة قديمة منسية وجدت في 23:24
سبع خطوات مخطط داخل النافذة، و مفاجأة واحدة 25 دقيقة بعد إغلاقه.

كيفية أثبتنا النسخة صحيحة

عد صفوف مطابقة اختبار ضعيف، لأن جداول اثنين يمكن احتفظ بـ نفس عدد الصفوف وحتوى مختلف. لذا فحصنا أكثر من أعداد، والاستمرار في الفحص بعد الموقع عاد.

فحصالنتيجة
الجداول و الصفوف، قديم مقابل جديد341 جداول، 18,727,244 صفوف، مطابقة بالضبط
محتوى كل جدولChecksums محتوى كامل على كل الجداول، بعد النافذة
أكبر جدول نتائجالمقارنة العمود بـ العمود: 2.76 مليون صفوف متطابقة
حقوق مستخدم التطبيقCREATE TABLE تم رفضها، كما يجب أن تكون
أول 4 دقائق حيحول 2,900 طلب، 0 5xx، 110 وظائف إشعار، 0 فشل

العميل الذي نسينا

في 23:24، 25 دقيقة بعد الموقع عاد، وجدنا شيء لم نكن مخطط له. لوحة إدارة قديمة، يعمل على خادم قديم منفصل، لا تزال تكتب إلى قاعدة البيانات القديمة.

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

بـ ذلك الوقت 48 صفوف هبطت في قاعدة البيانات القديمة. دمجناها في قاعدة الجديدة مع upsert شرطي: أدرج الصف، أو حدّث ذلك فقط عندما النسخة القديمة الأحدث. 44 صفوف دُمجت. لـ 4 صفوف قاعدة الجديدة بالفعل كانت إصدار أحدث، وأبقينا تلك. بعدها أشرنا اللوحة القديمة في قاعدة الجديدة وغيّرنا كلمة المرور للـ app القديم cluster، لذا لا شيء يستطيع كتابة ثانية. Status: Implemented.

تغيير كلمة المرور مهم مثل الدمج. عميل منسي ثم يفشل بصراحة بدلاً من هادي ملء قاعدة بيانات لا أحد يقرأها. الـ cluster القديم بقي متجمد لـ 48 ساعة كطريقتنا للخلف، وحذف بعد ذلك.

الدرس عنصر قائمة تحقق، وليس قصة:

  • قبل النقل، اذرع كل عميل خارجي لـ قاعدة البيانات. اقرأ موارد موثوقة الـ cluster القديم، وأسأل لكل مدخل: من يمتلك ذلك؟
  • بدّل كل عميل في نفس النافذة مع التطبيق.
  • بعد البدل، غيّر كلمة المرور القديمة، لذا عميل افتقدت تفشل مرة واحدة.
  • احتفظ بـ الـ cluster القديم متجمد لفترة ما كطريق للخلف.

القراءات من standby: النقسيم read/write

لماذا لا read replica

الإجابة كتاب لـ «القراءات ثقيلة» هي عقدة read-only replica. فكّرنا. على هذه المنصة عقدة read-only يجب أن تكون على الأقل بنفس حجم primary، لذا كلّفت حول بقدر. بينما ندفع بالفعل لـ standby يجلس خامل، ينتظر فشل.

لذا أجريْنا شيء أرخص: standby هو أيضاً reader، وصل عبر replica hostname الخاص به. القراءات تُشارك بين standby و primary، وكل واحد يغطي للآخر. نفس الأجهزة، لا خط جديد على الفاتورة.

الخيارالتكلفة الإضافيةStatusملاحظة
كل شيء على primaryبدونBaselineالأبسط، لكن primary تفعل كل قراءة
عقدة read-only replica مكرسةحول بقدر primary ثانيةConsideredيجب أن تكون على الأقل بنفس حجم primary
Standby يشارك قراءات مع primaryبدونImplemented, 6 Octoberالقراءات قد تتخلف قليلاً؛ sticky reads تغطي ذلك

القواعد، بكلمات عادية

بدلنا النقسيم عند 00:13 في 6 أكتوبر. في صيغة رسم، Laravel database configuration يقول هذا:

read   hosts = [standby, primary]
write  host  = primary
sticky = true
connect timeout = 2 s
  • الكتابات تذهب primary. دائماً.
  • القراءات تذهب standby، و primary مذكور كثاني read host. إذا أحد لا يجيب، الآخر يفعل.
  • Sticky. بعد كتابة، نفس طلب يبقى يقرأ من primary، لذا طالب يرى إجابتهم الخاصة. middleware صغيرة تبقي الطلب التالي على primary أيضاً. فكر عن كتابة ملاحظة على لوحة بيضاء: تعود إلى اللوحة لقراءتها، وليس إلى نسخة قد تزال تطبع.
  • Timeout اتصال 2 ثانية. Standby بطيء يسقط خلف primary بسرعة، و لا طالب ينتظر عليها.
  • العامل أبداً لا يستخدم standby. يبقى على primary، لأن الوظائف تحتاج بيانات حديثة.

هل ذلك حقاً ينقسم؟

فحصنا على عقدة حية بدلاً من الثقة بـ الكود. فتحنا 8 اتصالات طازجة، مرتين. الأول يجري انقسم 6 و 2 بين اثنين الخوادم، الثاني انقسم 4 و 4. Primary أبلغ عن read_only=0 و standby read_only=1، لذا كلاهما كانا يُستخدم، وعرفنا أيهما أي.

Gotcha واحد

الإنتاج يحمل الفئات من خريطة فئة سلطة موثوقة. فئة جديدة تماماً، مثل middleware جديدة، ليست في الخريطة موجود أبداً كما التطبيق قلق، و الطلب يرجع 500. أضفها إلى خريطة الفئة كجزء من deploy.

حجم ثابت، حقوق ضيقة، دخول بـ وسم

إعادة حجم أسبوعية: considered. فكرة واحدة كانت تحجيم قاعدة البيانات قبل أيام امتحان وتنخفض بعده. الأعمال اختارت ثابت 4 vCPU / 16 GB بدلاً من ذلك. أرقام مساء امتحان أسفل دعم ذلك الاختيار.

أقل امتياز: implemented. مستخدم app database لديه SELECT، INSERT، UPDATE و DELETE، و لا DDL. يستطيع تغيير صفوف، الذي يجب عليه، لكن لا يمكنه إنشاء، تعديل أو حذف جدول. اختبرناه أثناء النقل: CREATE TABLE تم رفضها.

دخول بـ وسم: implemented. كل قاعدة بيانات تعترف backend وسم، العامل و مكتب و عناوين قفزة. عقدة backend autoscaled جديدة تتصل مع لا خطوة يدوية.

هذا يترك قطعة من الحساب. كل عقدة backend يشغل 80 php-fpm workers، وكل واحد قد يمسك MySQL connection. المجموع يجب أن يبقى تحت 1,601 المسموح.

Backend nodesphp-fpm workers لكل عقدةأسوأ حالة اتصالاتحد MySQL
2801601,601
4803201,601
6804801,601
10 (أقصى بركة)808001,601

حتى أقصى بركة نستخدم حول نصف الحد، الذي يترك مجال للعامل و جلسات إدارة.

Valkey، في طبقة البيانات

Valkey مُدار واحد، مع standby، يمسك cache، الجلسات و الطوابير، ويشغل مع noeviction policy. الجزء 4 يذهب أعمق. هنا فقط ما ينتمي إلى طبقة البيانات.

في 6 أكتوبر في 02:28، اختبار تحميل backend أرجع 2,854 5xx تطبيق. كل واحد كان «Operation timed out» أثناء الاتصال بـ Valkey، مع timeout اتصال 5 ثانية. كل طلب فتح اتصال TLS جديد، الذي كلّف حول 5.4 ملسة من CPU لكل طلب لـ Valkey، ضد حول 3.3 ملسة لـ MySQL. في 4 GB، Valkey لم تستطيع قبول اتصالات بسرعة كافية.

Implemented: أعدنا حجمها في المكان إلى 8 GB مع 2 عقدة. البيانات احتفظت، الخادم لم يتغير. الاختبار إعادة تشغيل من 25 حتى 500 طلب/س، ابتداء على 2 backend عقدة مع البركة تتسع: 0 أخطاء تطبيق، 0 backend 5xx. على أمسيات امتحان تستخدم حول 12.5% من ذاكرتها. تكلّف أكثر أموالاً، وأزالت فشل حقيقي.

لماذا AI حصل على PostgreSQL خاص به

NovaCommerce لديها ميزات دراسة AI: تعليم متكيف، تحليل ضعف و أسئلة متوقع. تعمل على embeddings، وهي قوائم طويلة من الأرقام التي تصف معنى السؤال. إيجاد الأكثر تشابهاً يُدعى vector similarity search.

نحتفظ بتلك embeddings في Managed PostgreSQL 17 منفصل مع pgvector extension، في 2 vCPU و 4 GB. ليس في MySQL. ثلاث أسباب:

  • شكل استعلام مختلف. Vector search معناه vectors كبيرة، approximate-nearest-neighbour indexes و استعلامات CPU-heavy. حركة امتحان تقرأ قصيرة وكتابات.
  • لا منافسة مع كتابات امتحان. استعلام تشابه ثقيل يجب أبداً لا تأخذ CPU من الطالب يحفظ إجابة. خادمان معناه اثنين ميزانيات.
  • الأداة الصحيحة. MySQL لا أول class vector index لهذه الوظيفة.
المقارنة جنب بـ جنب من MySQL و PostgreSQL مع pgvector عبر workload، شكل استعلام، scaling lever وما يحدث عندما كل واحد يناضل
اثنين workloads مع أشكال مختلفة و تكاليف فشل مختلفة، لذا اثنين قواعد بيانات مع أحجام مختلفة.

التنخفيض نحن رجعنا

عقدة PostgreSQL صغيرة، وحاولنا جعلها أصغر: 1 vCPU و 2 GB. Tested, then reverted. رجعناها إلى 2 vCPU و 4 GB بسبب الكتابات التي تصل أثناء امتحانات. توفير صغير لم يكن يستحق عقدة تناضل في ساعة واحدة تهم. حجم لـ ساعة امتحان، ليس لـ يوم هادي.

للنسخة الشاملة من هذه النصيحة، مثل اختيار نوع index و إبقاء filtered vector search دقيق، انظر دليل حقل لنا The Data Tier Under Load. هذه دراسة حالة التصق إلى ما فعلناه.

Backups و recovery

Standby يحمي ضد جهاز ميت. هو لا يحمي ضد خطأ: إذا شخص حذف جدول، standby يحذفه لحظة لاحقة. ذلك لماذا backups. نستخدم كليهما، وهم يجيبون أسئلة مختلفة.

ما يخطئما يحميناStatus
عقدة MySQL تفشلStandby تستيقظ (provider failover)Implemented
البيانات فقدت أو تالفةDaily backups من قواعد البيانات المُدارة (provider)Implemented
النقل يذهب خاطئCluster القديم احتفظت متجمد لـ 48 ساعة، بعدها حذفImplemented
عقدة ويب فقدت أو rollout يفشلGolden snapshots من عقد الويب؛ آخر 3 صور جيدة احتفظImplemented
العامل فقدSnapshot واحد من العامل؛ هو لا يزال نقطة فشل مفردةSnapshot implemented، standby worker planned

قاعدة البيانات ليست اختناق

لم ننقل قاعدة البيانات لأنها كانت بطيئة. نقلناها لـ توفرية وتكلفة. القياسات قررت ما لا تُنفق: على مساء امتحان حقيقي من 7 أكتوبر، مع امتحانين ظهراً ضهراً، قاعدة البيانات كانت الجزء الهادي من النظام.

قياس، 7 October exam eveningالقيمة
MySQL CPU، الأقصى / المتوسط24.5% / 13.4%
MySQL استعلامات جارية، الأقصى5
MySQL lock waits0
Valkey memory12.5%
Backend peakحول 69 طلب/س
Backend node CPU، متوسط 2 دقيقة، الأقصى47% (عقدة واحدة لمست 86% لـ دقيقة)

الاختناق كان backend CPU لكل عقدة، وقاعدة البيانات كانت حول 6x headroom. نموذج الطاقة يقول MySQL يصبح الحد بالقرب 450 طلب/س، ضد ذروة 69 ذلك المساء. ذلك النموذج جاء من امتحان اختيار متعدد واحد. امتحانات مكتوبة مع تحميل PDF أثقل العامل بكثير ويجب أن يُقاس منفصل.

في شروط المال، ثلاثة data stores كلّف حول $689 شهرياً بسعر القائمة: MySQL حول $389، Valkey حول $240 و PostgreSQL $60. ذلك حول 70% من تقريباً $990 فاتورة شهرية مع backend pool في 2 عقدة، وهذا لماذا ننمو متجر فقط بعد قياسه.

ما تعلمنا

  • عقدة واحدة كبيرة ليست high availability. التوفرية تحتاج عقدة ثانية يمكن أن تستيقظ، و زوج أصغر يمكن يتفوق على جهاز عملاق واحد على السعر.
  • النقل يحتاج بوابة قبل النسخ (كتابات صفر)، عدد أثناء ذلك، وchecksums بعد.
  • اذرع كل عميل لـ قاعدة البيانات القديمة قبل النقل، بعدها غيّر كلمة المرور القديمة لذا عميل افتقد يفشل بصراحة.
  • استخدم standby كقارئ قبل شراء replica. أضف sticky reads و timeout اتصال قصير، و احتفظ بـ وظائف على primary.
  • أعط مستخدم التطبيق الحقوق التي يحتاجها و لا شيء أكثر. لا DDL.
  • أعط AI PostgreSQL الخاص به، و حجم ذلك لـ ساعة امتحان.
  • قِس قبل تُنفق. قاعدة البيانات كانت 6x headroom؛ الشغل كان في backend.

قواعس البيانات الآن هادية، لذا الأسئلة التالية حول كل شيء يجري في الخلفية: العامل الوحيد، الطوابير، السجلات و المراقبة. الجزء 4 يغطي تلك: Valkey, workers, logging and reliability.

About the Author

Anichur Rahaman

Continue Reading