در این مقاله قصد دارم براتون داستانی رو تعریف کنم که هر تیم فنی، حتی در کابوسهاش هم دوست نداره بهش فکر کنه: خرابی ناگهانی دیتابیس! ولی این کابوس برای ما توی آمادست (amadast.com) به واقعیت تبدیل شد؛ جایی که دیتابیس MySQL ما به یکباره از کار افتاد و ما با کلی تلاش و سختی تونستیم اونو نجات بدیم. شاید این تجربه به دردتون بخوره و توی شرایط مشابه، کمکتون کنه.
صبح دوشنبه بود، از همون روزایی که همه چی ظاهراً عادیه و تو انتظار داری همه کارها روتین پیش بره. ساعت ۱۲:۱۵:۱۱ بود که یه دفعه کانتینر دیتابیس ما ریستارت شد و با یه خطای عجیب و غریب روبرو شدیم:
Execution of server-side SQL statement 'INSERT IGNORE INTO server_cost(cost_name) VALUES ("row_evaluate_cost"), ("key_compare_cost"), ("memory_temptable_create_cost"), ("memory_temptable_row_cost"), ("disk_temptable_create_cost"), ("disk_temptable_row_cost"); ' failed with error code = 1881, error message = 'Operation not allowed when innodb_force_recovery > 0.'.
اولش فکر کردیم شاید یه ریستارت ساده مشکل رو حل کنه، ولی نه، اوضاع خیلی بدتر از اون چیزی بود که فکرش رو میکردیم. هر لحظه استرس بیشتری به تیم وارد میشد و کاربر ها پشتیبانی رو اورده بودن پایین و ما نمیدونستیم هنوز دقیقا چه بلایی سرمون اومده .
از همون اول، یه تصمیم خیلی مهم گرفتیم : قبل از هرکاری، یه کپی از فایلهای دیتابیس رو روی یه سرور دیگه ذخیره کردیم. اینطوری اگه اوضاع بدتر میشد، حداقل میتونستیم از همون جایی که بودیم دوباره شروع کنیم.
در MySQL و انجین InnoDB برای بازیابی دادهها شش سطح ریکاوری وجود داره. ما از سطح ۱ شروع کردیم و کمکم سطوح بالاتر رو تست کردیم. ولی انگار هر سطح بیشتر مارو به سمت ناکامی میبرد. با خطاهای مختلفی مواجه شدیم که حتی تا حالا ندیده بودیم:
برای اطلاعات بیشتر درباره MySQL recovery modes هم میتونید به مستندات MySQL مراجعه کنید.
از سطح ۱ تا ۵ این ارور :
Execution of server-side SQL statement 'ALTER TABLE tables_priv MODIFY Db char(64) NOT NULL default '', MODIFY User char(32) NOT NULL default '', MODIFY Table_name char(64) NOT NULL default '', CONVERT TO CHARACTER SET utf8mb3 COLLATE utf8mb3_bin; ' failed with error code = 1050, error message = 'Table '#sql2-2e-4' already exists'.
و سطح ۶ این ارور ها :
Tablespace 'innodb_undo_002' Page [page id: space=4294967278, page number=3] log sequence number 117726770646 is in the future! Current system log sequence number 117724575410. -------------------------------------------------------------------- [InnoDB] Your database may be corrupt or you may have copied the InnoDB tablespace but not the InnoDB redo log files. Please refer to http://dev.mysql.com/doc/refman/8.2/en/forcing-innodb-recovery.html for information about forcing recovery.
حالا باید یه راه دیگه پیدا میکردیم. ساعت از ۶ عصر گذشته بود و ما هرجایی رو بگید سرچ زدیم و دنبال راه حل ولی هیچ نتیجهای نگرفتیم .
در اینجا یه نکته بگم از هرکسی میشناختیم و میتونست کمک کنه خواستیم که به یه گروه میت بیاد و داخل اونجا هر کس ایده ای داشت انجام میدادیم یکی دو ساعت بعد تقسیم شدیم به دو گروه هر گروه روی یک سرور با کپی هایی که داشتیم شروع کرد کار کردن تا به نتایج زیر رسیدیم.
.ibd
جزئیات این کار رو تو این مقاله میتونید بخونید .
تصمیم گرفتیم از فایلهای دیتابیس قبلی (همون .ibdها) استفاده کنیم و اونا رو به یه دیتابیس جدید منتقل کنیم. اما دیتابیس جدید این فایلها رو نمیشناخت. ساعت ۲ شب بود و ما هنوز دنبال راه حل بودیم.
تو این ساعتها، بالاخره جستجو توی اینترنت و فرومها برامون یه نور امید پیدا کرد: یه اسکریپت توی GitHub که فایلهای .ibd
رو به SQL تبدیل میکرد. اما اینم کار زیادی داشت. مجبور شدیم تو کدش دست ببریم و بعد از کلی تست، بالاخره دیدیم جواب میده! (https://github.com/anyongjin/mysql_ibd)
این هم در پرانتز بگم ما کد های این اسکریپت رو خوندیم که خطرناک نباشه.
« برای جاهای حساس اگر از اسکریپتی استفاده میکنید حتما بخونید که اتفاق بدی نیفته »
حالا باید تیبلها رو از نو میساختیم و دیتاها رو یکی یکی وارد دیتابیس جدید میکردیم. اما باز هم ارورهای جدیدی منتظرمون بودن:
InnoDB: Failing assertion: page_get_n_recs(page) > 1
توی فرومهای MySQL کلی گشتیم تا اینکه راه حل رو پیدا کردیم: باید ایندکسها رو از فایلهای SQL حذف میکردیم و بعد یکی یکی اضافهشون میکردیم.
بالاخره ساعت ۷ صبح روز بعد، بعد از یه ماراتن ۲۱ ساعته، سایت رو با دسترسی محدود بالا آوردیم. تستها رو انجام دادیم و به ارورهایی مربوط به IDهای تکراری برخوردیم. دلیلش این بود که همه IDها توی دیتابیس جدید از ۱ شروع میشد. برای حل این مشکل یه اسکریپت PHP نوشتیم که اوضاع رو ردیف کرد.
این ماجرا یه سری درسهای خیلی مهم به ما داد:
« در نهایت، میخوام از همه دوستانی که توی این ماجرا به ما کمک کردن تشکر کنم ، این مشکل بدون وجود اون ها حل نمی شد. »
این بود داستان ما، یه ماجرای پر از استرس ولی با پایانی خوش!