بهروزرسانی پایگاهدادهی اصلی ترب – داستان یک مهاجرت
چگونه 1.5 ترابایت دادههای پستگرس 11 را به پستگرس 16 منتقل کردیم
این مقاله توسط حسین علیرضایی در وبلاگ ترب در ویرگول منتشر شده است و وب سایت مهندسی داده با هدف تجمیع مطالب فنی مفید در خصوص زیرساختهای ذخیره و پردازش داده، عینا آنرا در اینجا بازنشر کرده است.
لینک مقاله اصلی :
https://techblog.torob.com/postgresql-upgrade-from-11-to-16-torob-experience-v62efb53gn6h
ما در ترب از PostgreSQL (برای راحتی در نوشتن از این جا به بعد «پستگرس» نوشته خواهد شد) به عنوان پایگاهدادهی اصلی استفاده میکنیم. با توجه به اتمام دورهی پشتیبانی از نسخهی ۱۱ در آبان ماه ۱۴۰۲، تصمیم به بهروزرسانی این پایگاهداده به نسخهی ۱۶ گرفتیم. این بهروزرسانی نه تنها برای اطمینان از دریافت آخرین بهروزرسانیهای امنیتی و رفع باگها ضروری بود، بلکه به ما اجازه میداد تا از ویژگیها و بهبودهای کارایی که در نسخههای جدیدتر اضافه شده، بهرهمند شویم. فرآیند ارتقا نیازمند برنامهریزی دقیق و انجام تستهای گسترده بود تا اطمینان حاصل کنیم که تغییرات هیچ تأثیر منفی روی سرویسهای حیاتی ما نخواهند داشت. در این پست قصد داریم در مورد فرآیندی که برای بهروزرسانی طی کردیم و تجربهها و مشکلاتی که پیش آمد بنویسیم.
روشهای بهروزرسانی PostgresQL
به صورت کلی بهروزرسانی این پایگاهداده به دو دسته تقسیم میشود:
- بهروزرسانی نسخهی Major (برای مثال از ۱۱ به ۱۶)
- بهروزرسانی نسخههای Minor (برای مثال از ۱۱٫۲۰ به ۱۱٫۲۱)
معمولاً بهروزرسانی نسخههای Minor کار سادهای هست. کافی است نسخهی قبلی (برای مثال ۱۱٫۲۰) را متوقف کنیم. سپس یک پایگاهداده با نسخهی جدید (برای مثال ۱۱٫۲۱) اجرا کنیم.
اما با توجه به تغییر روش ذخیرهسازی اطلاعات بین نسخههای major این بهروزرسانیها پیچیدگی بیشتری داشته و نیازمند طی کردن مراحل بیشتری است. که در ادامه تعدادی از روشهای انجام به روزرسانی نسخه Major اشاره شده است.
استفاده از pg_dump
ابزار pg_dump یک ابزار قدرتمند در پستگرس است. این ابزار برای پشتیبانگیری (backup) از پایگاهداده استفاده میشود. با استفاده از این ابزار میتوان یک نسخهی متنی (معمولاً به صورت دستورهای SQL) از پایگاهداده ایجاد کرد. از این خروجی میتوان برای بازگردانی اطلاعات در آینده استفاده کرد. با توجه به این که خروجی این ابزار به صورت یک سری دستور SQL است، در نتیجه تمام نسخههای پستگرس آن را شناسایی میکنند. برای انجام بهروزرسانی به این روش کافی است با استفاده از pg_dump یک نسخهی پشتیبان تهیه کنیم. سپس در یک پایگاهداده با نسخهی بالاتر آن را بازگردانی (restore) کنیم.
قابل اطمینانترین و سادهترین روش بهروزرسانی استفاده از همین ابزار هست. اما در صورتی که حجم دادههای داخل پایگاهداده زیاد باشد فرآیند بهروزرسانی به مراتب کند خواهد بود و در طول فرآیند لازم است پایگاهداده به صورت read-only باشد.
استفاده از pg_upgrade
ابزار pg_upgrade یک ابزار کاربردی در پستگرس است که برای ارتقای نسخهی پایگاهداده از یک نسخهی قدیمیتر به نسخهی جدیدتر استفاده میشود.
این ابزار به ۲ روش فرآیند تغییرات را انجام میدهد. روش اول به صورت in-place است. (به صورت دقیقتر، فایلهای جدید رو به صورت hard link به فایلهای قبلی link میکنه) به عبارت دیگر تغییرات بر روی فایلهای نسخهی قبلی اعمال میشود. به همین جهت اگر به هر دلیلی فرآیند بهروزرسانی با مشکل مواجه شود، پایگاهداده خراب شده و دیگر قابل استفاده نخواهد بود. در روش دوم تمام فایلهای پایگاهداده در محل جدید کپی میشوند. در نتیجه اگر حین بهروزرسانی مشکلی ایجاد شود. نسخهی قبلی را داریم و میتوانیم از آن استفاده کنیم. بدیهی است که استفاده از روش اول به دلیل این که عملاً دادهای کپی نمیشود به مراتب سریعتر خواهد بود.
این روش مشکل زمان زیاد به روزرسانی را حل میکرد ولی با توجه به احتمال خرابی پایگاهداده روش مناسبی به نظر نمیرسید.
استفاده از Logical Replication
از Logical Replication میتوان برای replicate کردن دادهها بین نسخههای مختلف major استفاده کرد. در این روش که براساس معماری publish-subscribe است. پایگاهدادهی مبدا همزمان با تولید WAL record ها، آنها را به یک فرمت عمومیتر (مثلاً SQL) تبدیل میکند. سپس این فرم عمومیتر را برای subscriber ارسال میکند. در نهایت subscriber این تغییرات را در پایگاهداده اعمال میکند.
این روش شبیه به pg_dump است. با این تفاوت که تغییرات به صورت پیوسته (continuously) روی پایگاهدادهی مقصد اعمال میشود.
محدودیتهای این روش:
- دستورات DDL منتقل نمیشوند. در نتیجه در زمان انجام بهروزرسانی نباید هیچگونه تغییراتی که منجر به عوض شدن schema پایگاهداده شود انجام داد.
- در این روش sequence ها منتقل نمیشوند. در نتیجه مقدار تمام sequence ها بر روی پایگاهدادهی مقصد برابر مقدار اولیه است. اگر این مقدار تغییر نکند میتواند منجر به ایجاد conflict در زمان ایجاد یک سطر جدید در پایگاهداده شود. (معمولاً از sequence برای ایجاد ستون id استفاده میشود)
استفاده از Streaming Replication و pg_upgrade
مشکل اصلی استفاده از pg_upgrade این بود که در صورت استفاده از روش in-place و بروز خطا در زمان بهروزرسانی نسخهی قدیمی پایگاهداده دیگر قابل استفاده نخواهد بود. برای حل این مشکل میتوان از Streaming Replication استفاده کرد.
در این روش WAL Record ها بدون تغییر به پایگاهدادهی مقصد منتقل میشوند. پایگاهدادهی مقصد به صورت پیوسته در حالت recovery قرار داشته و به صورت پیوسته این WAL Record ها را apply میکند. در Streaming Replication پایگاهدادهی مقصد باید هم نسخه با پایگاهدادهی مبدا باشد. (بر خلاف Logical Replication که نسخهها میتواند متفاوت باشد)
بعد از Sync شدن کامل پایگاهدادهی مقصد، میتوان بدون مشکل خاص از pg_upgrade استفاده کرد. در صورت ایجاد مشکل، همچنان پایگاهدادهی اصلی (مبدا) بدون تغییر در مدار قرار دارد.
استفاده از Streaming Replication و Logical Replication و pg_upgrade
در روش قبل، قبل از اجرا کردن pg_upgrade باید پایگاهدادهی مبدا از مدار خارج شود (یا میتوان read only کرد) با این کار مطمئن میشویم که اطلاعات هیچ Transaction گم نمیشود. به عبارت دیگر در صورتی که پایگاهدادهی مبدا از مدار خارج شود یا در وضعیت read only قرار گیرد، سطح Application خطا میخورد و کاربر بعد از چند دقیقه دوباره امتحان میکند. ولی در صورتی که از مدار خارج نشود به کاربر اطلاع داده میشود که عملیات موفق بود. در حالی که Transaction به پایگاهدادهی جدید منتقل نشده است. در نتیجه نیاز به یک Down Time چند دقیقهای (مدت زمان اجرای دستور pg_upgrade) دارد.
در صورتی که نتوانیم Down Time چند دقیقهای را تحمل کنیم، میتوانیم از Logical Replication استفاده کنیم. به عبارت دیگر بعد از اجرای دستور pg_upgrade، با استفاده از Logical Replication تغییراتی که بعد از اجرای pg_upgrade روی مبدا اعمال شده است را به پایگاهدادهی مقصد منتقل میکنیم. (بعد از اجرای pg_upgrade نسخهی پایگاهدادهها متفاوت میشود. برای مثال یکی ۱۱ و یکی ۱۶ خواهد بود. در نتیجه باید Logical Replication استفاده کرد) در نهایت هر دو پایگاهداده به صورت پیوسته با همدیگر sync خواهند بود. در این state میتوان به گونهای عمل کرد که برای چند ثانیه write ها pause شوند. (با استفاده از PgBouncer میتوان این کار را انجام داد) پایگاهدادهها عوض شوند و سپس write ها از حالت pause خارج شوند. به این شکل کاربر احساس خرابی نمیکند. (چون دستورها صرفاً pause شدند و خطا نخوردند)
در ترب به چه روشی فرآیند بهروزرسانی رو انجام دادیم؟
برای انتخاب روش باید نکات زیر را در نظر میگرفتیم:
- پایگاهدادهی اصلی ترب حدود ۱٫۵TB حجم دارد.
- میزان Down Time قابل تحمل برای ترب ۱۵ دقیقه در نظر گرفته شد.
- روش مورد استفاده باید تا حد امکان ساده باشد.
- سرعت پیادهسازی روش مورد استفاده تا حد امکان باید بالا باشد.
با توجه به موارد بالا استفاده از pg_dump عملی نبود. چون با توجه به حجم پایگاهدادهی ترب، فرآیند تولید نسخه پشتیبان از آن حدود ۱۲ ساعت طول میکشید.
استفاده از pg_upgrade به شکلی که فایلها به صورت in-place تغییر کنند شدنی نبود. چرا که امکان revert کردن فرآیند در صورت بروز مشکل را از ما میگرفت. همچنین استفاده از روش کپی (به جای تغییر in-place فایلها، آنها رو در مسیر دیگری کپی کند) در pg_upgrade دو مشکل ایجاد میکرد. اول اینکه در این روش دیسک اضافهای مورد نیاز بود که بعداً قابل بازپسگیری نبود. دوم این که برای کپی کردن دادهها به مراتب زمان بیشتری مورد نیاز بود. در نتیجه استفاده از pg_upgrade به تنهایی مورد پذیرش نبود.
استفاده از Logical Replication نیاز به زمان زیادی برای sync شدن داشت. چون ابتدا باید تمام دادهها به فرمت عمومی تبدیل شده و سپس برای مقصد ارسال میشد. این موضوع تاثیر منفی روی کارایی پایگاهدادهی مبدا داشت. همچنین این روش محدودیتهایی داشت که باعث پیچیدگی فرآیند میشد. در نتیجه این روش هم مناسب نبود.
استفاده از Streaming Replication و Logical Replication و pg_upgrade هم بسیار پیچیده بود. به همین دلیل ترجیج دادیم که از این روش استفاده نکنیم.
با این توضیحات استفاده از ترکیب Streaming Replication و pg_upgrade به عنوان راهحلی اولیه، انتخاب شد. بعد از تستی که انجام دادیم، مشخص شد که دستور pg_upgrade زیر ۱ دقیقه پایگاه داده را بهروز میکند. در نتیجه با احتساب سایر موارد تخمین ۵ دقیقه down time به دست آمد. با توجه به این میزان Down Time استفاده ترکیبی از Streaming Replication و pg_upgrade برای ترب مناسب بود.
روش تست کردن فرآیند بهروزرسانی
یکی از پیشنیازهای هر کار کم خطایی، داشتن تسلط نسبی یا کامل به فرآیند انجام کار مورد نظر است. با توجه به اینکه پایگاهدادهی مورد نظر حساسیت بالایی داشت، نیاز بود تا فرآیند بهروزرسانی را بارها و بارها تکرار کنیم تا علاوه بر تسلط به کل فرآیند، مشکلات احتمالی را پیدا کنیم. همچنین این تکرار چند باره باعث شد تا بتونیم یک سری از فرآیندها رو از طریق توسعه script هایی خودکار کنیم تا فرآیند بهروزرسانی سریعتر و با احتمال خطای کمتری انجام شود.
با توجه به حجم ۱٫۵TB پایگاهداده، فرآیند sync شدن پایگاهدادهی مقصد با پایگاهدادهی مبدا حدود ۲ تا ۳ ساعت زمان نیاز داشت. از طرفی بعد از اجرای دستور pg_upgrade فایلها تغییر میکرد و دیگر امکان استفاده از آنها نبود. به همین جهت باید پایگاهداده از اول sync میشد. در نتیجه به صورت معمول هر بار تست کردن کل فرآیند نیازمند صرف ۲ تا ۳ ساعت زمان بود.
برای حل این مشکل از LVM استفاده کردیم. با استفاده از این ابزار یک Logical Volume (LV) برای قرار دادن فایلهای پایگاهدادهی مقصد ساختیم. این پایگاهداده با استفاده از Streaming Replication به صورت پیوسته از روی پایگاهدادهی اصلی sync میشد. هر زمان نیاز به تست کردن داشتیم، یک Snapshot از LV مورد نظر میگرفتیم. سپس با استفاده از این Snapshot تستها رو انجام میدادیم و پس از انجام تست Snapshot رو پاک میکردیم.
این راهکار باعث شد که زمان مورد نیاز برای انجام تستهای مختلف به شدت کاهش پیدا کند. در واقع، به جای اینکه هر بار مجبور به انجام فرآیند زمانبر Sync پایگاهدادهی مقصد با مبدا شویم، میتوانستیم در عرض چند ثانیه یک Snapshot جدید ایجاد کرده و تستهای خود را بدون اختلال و با سرعت بیشتر، بر روی پایگاهدادهای مانند پایگاهدادهی اصلی انجام دهیم. این روش نه تنها در زمان صرفهجویی قابلتوجهی به همراه داشت، بلکه ریسکهای ناشی از خطاهای احتمالی در فرآیند تست و ارتقا را نیز به حداقل رساند. استفاده از LVM و Snapshotهای آن به ما اجازه داد تا با خیال راحت چندین بار فرآیند بهروزرسانی را شبیهسازی کنیم و اسکریپتهای خودکارسازی را با دقت بیشتری توسعه دهیم. همچنین، با این روش توانستیم محیطی شبیه به محیط عملیاتی واقعی ایجاد کنیم که در آن میتوانستیم رفتار پایگاهداده پس از ارتقا را دقیقتر بررسی کنیم. این امر به کاهش ریسکهای ناشی از مشکلات غیرمنتظره در روز نهایی ارتقا کمک کرد و اطمینان داد که فرآیند بهروزرسانی در زمان کوتاهتری و با کمترین اختلال انجام میشود.
انجام بهروزرسانی
با توجه تستهایی که انجام دادیم، تا حدود زیادی به کل فرآیند تسلط پیدا کرده بودیم. منتهی برای کاهش دادن خطای انسانی، باید فرآیند رو تا حد امکان خودکار میکردیم.
برای راحتی کار اجازه بدید یک سری تعریف داشته باشیم
- پایگاهدادهی pg_11_master: پایگاهدادهی اصلی که بهروز نشده و دارای نسخهی قدیمی است.
- پایگاهدادهی pg_11_slave_upgrade: پایگاهدادهای که از روی pg_11_master همگام میشود و برای بهروزرسانی ازش استفاده میشود. این پایگاهداده بعداً به pg_16_master تبدیل میشود.
- پایگاهدادهی pg_16_master: پایگاهدادهی اصلی ترب بعد از بهروزرسانی است.
به صورت کلی فرآیند بهروزرسانی شامل مراحل زیر بود:
- ایجاد یک نسخه از پایگاهداده از روی پایگاهدادهی قدیمی (ایجاد pg_11_slave_upgrade از روی pg_11_master)
- قرار دادن پایگاهدادهی قدیمی (pg_11_master) در وضعیت read only
- صبر برای sync شدن pg_11_slave_upgrade با pg_11_master
- خارج کردن pg_11_slave_upgrade از حالت standby و promote کردن آن (pg_ctl promote)
- اجرا کردن دستور pg_upgrade بر روی pg_11_slave_upgrade
- تعویض connection string ها برای اشارهی application به پایگاهدادهی جدید
مراحل ۱ تا ۵ خودکارسازی شدند. به ازای هر مرحله یک فایل script نوشته شده بود که کارهای مورد نیاز اون مرحله را انجام میداد.
مشکل اعمال نشدن بعضی از Unique Constraint ها بعد از بهروزرسانی
بعد از بهروزرسانی به مرور زمان تعدادی خطا در sentry مشاهده کردیم که نشان میداد در بعضی از ستونهایی که دارای unique constraint بودند، مقادیر تکراری ذخیره شده بود.
بعد از بررسی، متوجه شدیم که Gitlab هم در فرآیند به روزرسانی به مشکل مشابهای برخورد کرده است. به صورت خلاصه مشکل از بهروز شدن glibc در image مربوط به پستگرس بوده است. در نسخهی ۲٫۲۸ از glibc تغییراتی ایجاد شده که باعث تغییر رفتار عملگرهای مقایسه شده است. عملکرد تکه کد زیر در بین دو نسخه متفاوت glibc متفاوت است.
این تغییرات در glibc منجر به خرابی بعضی از unique index ها شده بود. برای حل این مشکل ابتدا مقادیر تکراری را پیدا و اصلاح کردیم. سپس index مورد نظر را دوباره reindex کردیم.
جمعبندی
در فرآیند بهروزرسانی پایگاهداده پستگرس از نسخهی ۱۱ به ۱۶ در ترب، با چالشهای مختلفی روبرو شدیم که نیازمند برنامهریزی دقیق، تستهای مکرر و خودکارسازی فرآیند بود. برای کاهش زمان Downtime و اطمینان از صحت عملکرد، ترکیبی از روشهای Streaming Replication و pg_upgrade انتخاب شد. این روش امکان بهروزرسانی سریع و کمخطر پایگاهداده را فراهم کرد. همچنین، با استفاده از ابزار LVM و Snapshot، توانستیم فرآیند تست را تسریع کنیم و مشکلات احتمالی را پیش از اجرا شناسایی کنیم. در نهایت، با خودکارسازی مراحل مختلف، توانستیم خطاهای انسانی را به حداقل برسانیم و فرآیند بهروزرسانی را با موفقیت به پایان برسانیم.
لینکهای مفید
https://www.youtube.com/watch?v=o08kJggkovg
https://handbook.gitlab.com/handbook/engineering/infrastructure/database/
https://news.ycombinator.com/item?id=38616181
https://www.postgresql.fastware.com/blog/inside-logical-replication-in-postgresql