الجزء 3: قاعدة بيانات أصغر، أرخص، و Highly Available في 26 دقيقة، و لماذا AI حصل على PostgreSQL خاص به
نقلنا عقدة MySQL واحدة كبيرة إلى cluster Standard أرخص مع standby في 26 دقيقة، نقسيم القراءات إلى standby و أعطينا AI PostgreSQL خاص به مع pgvector. مساء امتحان حقيقي أظهر قاعدة البيانات لم تكن أبداً bottleneck.
في 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 GB
4 vCPU / 16 GB لكل عقدة
إذا عقدة فشلت
قاعدة البيانات نزولاً
Standby تستيقظ
التكلفة الشهرية
أعلى
أقل؛ primary + standby حول $389 بسعر القائمة
أرخص و highly available في نفس الوقت نادر، لذا أخذناها. إليك كامل طبقة البيانات كما تبدو اليوم. بقية المقال تمشي عبرها.
الكتابات تذهب primary، القراءات تشارك بين standby و primary، و AI، cache و ملفات كل واحد يعيش على خدمة خاصة.
النقل 26 دقيقة، خطوة بخطوة
النافذة كانت مخطط و موافق عليها مع الأعمال. هذا الترتيب الذي تبعناه في 5 أكتوبر.
22:33. كلا load balancers أشار إلى وسم فارغ. الطلاب رأوا صفحة صيانة.
الطوابير فُرّغت و المجدول توقف، لذا لا job لا يمكن كتابة في وسط النسخ.
الكتابات صفر على قاعدة البيانات القديمة لـ 30 ثانية. كانت هذه البوابة: النسخ بدأت فقط بعد الجانب القديم ذهب هادي.
نسخ مع mysqldump في 4 تيارات متوازية. استغرقت 10 دقائق.
عد فحص: 341 جداول و 18,727,244 صفوف مطابقة بالضبط بين قديم وجديد.
التطبيق كان اختبر ضد قاعدة البيانات الجديدة. بعدها snapshot ذهبي جديد، rollout من كلا البركتين و worker جديد.
22:59. Load balancers خلفي، 26 دقيقة بعد ابتدأنا.
سبع خطوات مخطط داخل النافذة، و مفاجأة واحدة 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 يقول هذا:
القراءات تذهب 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 nodes
php-fpm workers لكل عقدة
أسوأ حالة اتصالات
حد MySQL
2
80
160
1,601
4
80
320
1,601
6
80
480
1,601
10 (أقصى بركة)
80
800
1,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 لهذه الوظيفة.
اثنين 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 waits
0
Valkey memory
12.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.