مدیریت تغییرات اسکیما پایگاه داده اغلب منبعی از اصطکاک برای تیمهای مهندسی است و معمولاً نیازمند پنجرههای نگهداری است که در دسترس بودن سرویس را مختل میکند. با این حال، در معماریهای توزیعشده مدرن، این اصطکاک غیرقابل قبول است. وقتی دادههای شما در چندین نمونه PostgreSQL پارتیشنبندی (Sharded) شدهاند، چالش به طور قابل توجهی تشدید میشود. یک تغییر اسکیما تنها یک دستور روی یک نود نیست؛ بلکه یک رقص هماهنگ در سراسر دهها پارتیشن است که هر کدام بخشی از دادههای تولیدی شما را در خود جای دادهاند.
این مقاله الگوهای معماری و استراتژیهای عملیاتی مورد نیاز برای اجرای تغییرات اسکیما به صورت آنلاین در محیطهای PostgreSQL پارتیشنبندی شده را بررسی میکند. ما از محدودیتهای مکانیزمهای قفل سنتی فراتر رفته و بررسی میکنیم که چگونه replication منطقی، الگوهای نوشتن دوگانه و مهاجرت تدریجی میتوانند سرویسهای شما را ۱۰۰٪ از زمان در دسترس نگه دارند.
محدودیت پارتیشنبندی
قبل از اجرای مهاجرت، باید محدودیتهای توپولوژی پارتیشنبندی شده را درک کرد. برخلاف یک پایگاه داده تکموتور (Monolithic) که میتوانید به سادگی ALTER TABLE را با یک قفل انحصاری اجرا کنید، پارتیشنبندی پیچیدگیهایی را در مورد یکپارچگی دادهها و هماهنگی ایجاد میکند.
در یک پیکربندی پارتیشنبندی شده، برنامه کوئریها را بر اساس یک کلید پارتیشنبندی (مانند user_id) به پارتیشنهای خاصی هدایت میکند. اگر تغییر اسکیما روی یک پارتیشن انجام شود اما روی سایرین نه، یا اگر تغییر به گونهای انجام شود که قفل طولانیمدت ایجاد کند، برنامه ممکن است با شکستهای جزئی، مشاهده ناسازگار دادهها یا توقف کامل سرویس در طول پنجره مهاجرت مواجه شود. بنابراین، استراتژی مهاجرت باید ایمن، بدون وابستگی به وضعیت (Idempotent) و قابل اجرا بدون نگهداری قفلی باشد که ترافیک استاندارد خواندن/نوشتن را مسدود کند.
استراتژی نوشتن دوگانه
قویترین الگو برای تغییرات اسکیما بدون توقف، رویکردی مرحلهای است که اغلب حول محور نوشتن دوگانه (Dual Writing) میچرخد. این روش به شما اجازه میدهد یک ستون جدید را بدون شکستن منطق فعلی برنامه معرفی کنید.
مرحله ۱: پر کردن مجدد ستون جدید
اول، ستون جدید را به اسکیما روی تمام پارتیشنها اضافه کنید. استفاده از ADD COLUMN بدون مقدار پیشفرض بسیار سریع است، زیرا PostgreSQL تنها سرستون (Tuple Header) را بهروزرسانی میکند، نه دادههای موجود را.
-- اجرا روی تمام پارتیشنها
ALTER TABLE events ADD COLUMN new_tracking_id UUID;
در این مرحله، برنامه به طور عادی به کار خود ادامه میدهد. دادههای قدیم و جدید با هم وجود دارند. با این حال، ستون جدید در حال حاضر برای تمام رکوردهای تاریخی NULL است.
مرحله ۲: پر کردن مجدد و نوشتن دوگانه
در مرحله بعد، یک وظیفه پسزمینه (Background Job) برای پر کردن ستون جدید با دادههای تاریخی اجرا کنید. این فرآیند باید محدود (Throttled) شود تا از فشار آوردن بیش از حد به زیرسیستم I/O جلوگیری کند. همزمان، کد برنامه را بهروزرسانی کنید تا هم به ستون قدیم و هم به ستون جدید بنویسد (نوشتن دوگانه).
-- شبهکد منطق برنامه
INSERT INTO events (event_type, old_tracking_id, new_tracking_id)
VALUES ('purchase', 12345, gen_random_uuid());
با نوشتن در هر دو ستون، اطمینان حاصل میکنید که حتی اگر منطق برنامه به طور موقت به ستون قدیم بازگشت، داده در ساختار جدید حفظ شود. این فاز میتواند به مدت لازم برای اطمینان از یکپارچگی داده در تمام پارتیشنها اجرا شود.
Replication منطقی برای هماهنگی
برای تغییرات اسکیما پیچیده یا زمانی که نیاز دارید ساختار زیربنایی را کاملاً عوض کنید، replication منطقی یک جایگزین قدرتمند ارائه میدهد. میتوانید یک اسلات replication منطقی روی نود اصلی ایجاد کنید و تغییرات را به یک جدول "سایه" یا یک اسکیما جداگانه روی Replicaها جریان دهید.
این رویکرد به ویژه زمانی مفید است که نیاز به تغییر نوع داده یا بازنویسی ساختار جدولها به صورت قابل توجه دارید. فرآیند شامل موارد زیر است:
- ایجاد یک جدول Replica با اسکیمای مطلوب.
- جریاندهی تغییرات از نود اصلی به جدول Replica با استفاده از replication منطقی.
- اجرای یک وظیفه پر کردن مجدد (Backfill) برای همگامسازی مجموعه داده اولیه.
- تغییر خواندنهای برنامه به جدول جدید پس از اینکه دادهها کاملاً همگام شدند.
چون replication منطقی ناهمگام (Asynchronous) است، نوشتنها را روی نود اصلی مسدود نمیکند و در دسترس بودن مداوم را در طول فرآیند همگامسازی تضمین میکند.
اجرای تغییر
پس از تکمیل فاز نوشتن دوگانه و پایان پر کردن مجدد، برای برش نهایی آماده هستید. این فاز اغلب سریعترین بخش است، زیرا دادهها قبلاً در جای خود قرار دارند.
- تغییر به حالت فقط خواندنی: برنامه را بهروزرسانی کنید تا به طور انحصاری از ستون جدید یا ساختار جدول جدید بخواند.
- حذف ستون قدیم: پس از اینکه اطمینان حاصل کردید هیچ دادهای از ساختار قدیم خوانده نمیشود، ستون قدیم را حذف کنید.
- افزودن محدودیتها: در نهایت، محدودیتهای NOT NULL یا ایندکسهای یکتا را روی ستون جدید اعمال کنید تا یکپارچگی داده تضمین شود.
-- فاز ۳: پاکسازی
ALTER TABLE events DROP COLUMN old_tracking_id;
ALTER TABLE events ALTER COLUMN new_tracking_id SET NOT NULL;
نتیجهگیری
اجرای تغییرات اسکیما در خوشههای PostgreSQL پارتیشنبندی شده نیازمند دوری از ذهنیتهای "قفل و حذف" است. با بهرهگیری از الگوهای نوشتن دوگانه، پر کردن مجدد پسزمینه و replication منطقی، مهندسان میتوانند به مهاجرتهای واقعی بدون توقف دست یابند. اگرچه این استراتژیها پیچیدگی اولیهای را در کد برنامه و ابزارهای عملیاتی ایجاد میکنند، اما پاداش آن یک زیرساخت پایگاه داده است که با کسبوکار شما مقیاس میپذیرد بدون اینکه در دسترس بودن را قربانی کند.
به یاد داشته باشید، هدف نه تنها تغییر اسکیما، بلکه انجام آن به گونهای است که برای کاربر نهایی شفاف باشد. با برنامهریزی دقیق و استقرار تدریجی، معماری پارتیشنبندی شده شما میتواند به سرعتی که نیازهای محصول شما ایجاب میکند، تکامل یابد.